Create a UserControl with a ViewModel

问题描述 投票:0回答:0

我想在 WinUI 3 中创建一个名为 ImageViewer 的用户控件,它将显示、遍历、添加和删除照片。

我的用户控件包含以下属性:

  • ICollectionPhotos 用于访问图片模型
  • DependencyProperty PhotosProperty 用于创建可以从 XAML 绑定的属性,如下所示: 注意:XAML 属性的名称不必与集合属性相同。
  • int CurrentPhotoIndex 用于跟踪显示的当前照片并阻止用户访问错误索引(尝试访问最后一张之后或第一张之前的照片)
  • 字符串当前照片描述 用于跟踪当前照片的路径
  • ImageViewerViewModel ViewModel 包含数据和逻辑

由于 ImageViewer 是一个 UserControl,它继承自类 UserControl。为了在按下 Previous/Next/Add/Delete 按钮时能够更改照片,Photos Collection 和 Current* Properties 必须能够通知 UI 某些内容已更改(即当前照片已被删除,因此显示最后一张一)。 但是,我的理解是,一个类必须继承自 ObservableRecipient,从而对其数据(例如 Photos、CurrentPhotoIndex)使用 OnPropertyChanged 方法。我的想法是 Photos 集合不能在代码隐藏 (ImageViewer.xaml.cs) 中,而是在 ViewModel (ImageViewerViewModel) 中,然后我就可以遍历/添加/删除照片了。

我的问题是在示例中(我从中获得灵感)没有使用 ViewModel,我不知道如何将 DependencyProperty 数据设置为直接来自 ViewModel。

我的问题:

  1. 在 UserControl 中有一个 ViewModel 是个好主意吗?
  2. 如果 ViewModel 的使用是适用的并且是良好的实践/设计,我是否需要在代码隐藏和 ViewModel 中保留模型的集合,在适当的时候同步它们,还是只在 ViewModel 中?
  3. 如果使用 ViewModel 不是一个好主意(很难实现,还有另一种更简单或更好的方法)Photos 集合如何变成 Observable? 注意:我将它初始化为一个 ObservableCollection 但我无法正确看到图片。

我希望我的问题提出得很好。如果有人需要更多信息,我将非常乐意提供。

ImageViewer.xaml

<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->

<UserControl
    x:Class="MyApp.Desktop.UserControls.ImageViewer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp.Desktop.UserControls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:dxe="using:DevExpress.WinUI.Editors"
    mc:Ignorable="d">

    <Grid Padding="12">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal"
                    Grid.Row="0">
            <Grid>
                <Image Source="{x:Bind CurrentPhotoPath, Mode=TwoWay}"
                       Stretch="Uniform"/>

                <Button Click="BtnPrevPhoto_Click"
                VerticalAlignment="Center" HorizontalAlignment="Left"
                Style="{StaticResource PreviousButtonStyle}"/>

                <Button Click="BtnNextPhoto_Click"
                VerticalAlignment="Center" HorizontalAlignment="Right"
                Style="{StaticResource NextButtonStyle}"/>
            </Grid>

            <StackPanel Orientation="Vertical"
                        VerticalAlignment="Center"
                        Margin="{StaticResource SmallLeftMargin}"
                        Grid.Column="1">
                <Button Click="BtnAddPhoto_Click"
                        Margin="{StaticResource XXSmallBottomMargin}">
                    <FontIcon Glyph="&#xe109;" Style="{StaticResource SmallFontIconStyle}"/>
                </Button>
                
                <Button Click="BtnDeletePhoto_Click"
                        Margin="{StaticResource XXSmallTopMargin}">
                    <FontIcon Glyph="&#xe107;" Style="{StaticResource SmallFontIconStyle}"/>
                </Button>
            </StackPanel>
        </StackPanel>

        <dxe:TextEdit x:Uid="/Products/TxtPhotoDescription"
                      Text="{x:Bind CurrentPhotoDescription, Mode=TwoWay}"
                      IsReadOnly="{x:Bind IsReadOnly, Mode=OneWay}"
                      Style="{StaticResource FormSmallTextEditStyle}"
                      Margin="{StaticResource SmallTopMargin}"
                      HorizontalAlignment="Stretch"
                      Grid.Row="1"/>
        
    </Grid>
</UserControl>

ImageViewer.xaml.cs

// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using DevExpress.Mvvm.Native;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using MyApp.Desktop.Helpers;
using MyApp.Desktop.ViewModels.UserControls;
using MyApp.Models.FileStorage;
using MyApp.Models.Products;

namespace ThemelioApp.Desktop.UserControls;

public sealed partial class ImageViewer : UserControl
{
    public static readonly DependencyProperty PhotosProperty = DependencyProperty.Register(
    "Photos", typeof(ICollection<Product_PhotoModel>), typeof(ImageViewer), new PropertyMetadata(null));

    public ICollection<Product_PhotoModel> Photos
    {
        get => (ICollection<Product_PhotoModel>)GetValue(PhotosProperty);
        set => SetValue(PhotosProperty, value);
    }

    public static readonly DependencyProperty PhotosToDeleteProperty = DependencyProperty.Register(
"PhotosToDelete", typeof(ICollection<long>), typeof(ImageViewer), new PropertyMetadata(null));

    public ICollection<long> PhotosToDelete
    {
        get => (ICollection<long>)GetValue(PhotosToDeleteProperty);
        set => SetValue(PhotosToDeleteProperty, value);
    }

    public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
    "IsReadOnlyProperty", typeof(bool), typeof(ImageViewer), new PropertyMetadata(null));

    public bool IsReadOnly
    {
        get => (bool)GetValue(IsReadOnlyProperty);
        set => SetValue(IsReadOnlyProperty, value);
    }

    public int CurrentPhotoIndex
    {
        get; set;
    }

    private readonly string _noPhotoPath;

    public string CurrentPhotoPath
    {
        get; set;
    }

    public string CurrentPhotoDescription
    {
        get; set;
    }

    public ImageViewerViewModel ViewModel
    {
        get;
    }

    public ImageViewer()
    {
        InitializeComponent();

        ViewModel = App.GetService<ImageViewerViewModel>();

        Photos = new ObservableCollection<Product_PhotoModel>();

        PhotosToDelete = new ObservableCollection<long>();

        CurrentPhotoIndex = 0;

        _noPhotoPath = new Uri("ms-appx:///assets/Images/NoImage128x128.png").LocalPath;

        CurrentPhotoPath = Photos.Count == 0 ? _noPhotoPath : Photos.FirstOrDefault().Photo.LocalFilePath;

        CurrentPhotoDescription = "";
    }

    private async void BtnAddPhoto_Click(object sender, RoutedEventArgs e)
    {
        var file = await FileStorageHelper.PickFile(FileStorageHelper.PickFileType.image, true);
        if (file == null)
        {
            return;
        }

        AddPhotoLocal(file.Path);
    }

    private async void BtnDeletePhoto_Click(object sender, RoutedEventArgs e)
    {
        RemovePhotoLocal();
    }

    private void BtnNextPhoto_Click(object sender, RoutedEventArgs e)
    {
        Next();
    }

    private void BtnPrevPhoto_Click(object sender, RoutedEventArgs e)
    {
        Previous();
    }

    public void Next()
    {
        CurrentPhotoIndex++;
        UpdateCurrentPhoto();
    }

    public void Previous()
    {
        CurrentPhotoIndex--;
        UpdateCurrentPhoto();
    }

    public void AddPhotoLocal(string path)
    {
        Photos.Add(
            new Product_PhotoModel()
            {
                Photo = new FileStorageDetailedModel()
                {
                    Description = "",
                    LocalFilePath = path
                }
            });
    }

    public void RemovePhotoLocal()
    {
        PhotosToDelete ??= new List<long>();

        var toDelete = Photos.ElementAt(CurrentPhotoIndex);
        if (toDelete.Id > 0)
        {
            PhotosToDelete.Add(toDelete.Photo.Id);
        }

        Photos.Remove(toDelete);

        UpdateCurrentPhoto();
    }

    public void UpdateCurrentPhoto()
    {
        if (CurrentPhotoIndex > Photos?.Count - 1)
        {
            CurrentPhotoIndex = (int)(Photos?.Count - 1);
        }
        else if (CurrentPhotoIndex < 0)
        {
            CurrentPhotoIndex = 0;
        }

        CurrentPhotoPath = Photos.ElementAt(CurrentPhotoIndex).Photo.LocalFilePath;
        CurrentPhotoDescription = Photos.ElementAt(CurrentPhotoIndex).Photo.Description;
    }
}

ImageViewerViewModel.cs(目前是空的,因为我无法让它工作)

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using MyApp.Desktop.UserControls;
using MyApp.Models.FileStorage;
using MyApp.Models.Products;

namespace MyApp.Desktop.ViewModels.UserControls;

public class ImageViewerViewModel : ObservableRecipient
{

    public ImageViewerViewModel()
    {
    }

}
c# mvvm user-controls winui-3 windows-app-sdk
© www.soinside.com 2019 - 2024. All rights reserved.