WinUI 3 ImageViewer用户控件ViewModel设计与数据绑定疑问
WinUI 3 ImageViewer UserControl 实现疑问
我需要在WinUI 3中实现一个名为ImageViewer的UserControl,具备照片显示、遍历、添加和删除功能。该控件需要包含以下属性:
ICollection<Product_PhotoModel> Photos:用于访问图片模型集合DependencyProperty PhotosProperty:创建可在XAML中绑定的属性(XAML属性名称无需与集合属性名一致)int CurrentPhotoIndex:跟踪当前显示照片的索引,避免无效索引访问string CurrentPhotoDescription:跟踪当前照片的描述ImageViewerViewModel ViewModel:用于存放数据与业务逻辑
由于ImageViewer继承自UserControl,点击上一张/下一张/添加/删除按钮时,Photos集合及Current*系列属性需要能通知UI更新(例如删除当前照片后自动显示最后一张)。我了解到需要继承ObservableRecipient并使用OnPropertyChanged方法实现数据通知,因此考虑将Photos集合放在ViewModel而非代码后置文件(ImageViewer.xaml.cs)中,来实现照片的遍历、增删操作。但参考的示例并未使用ViewModel,我不清楚如何将DependencyProperty的数据直接与ViewModel关联。
我的疑问如下:
- 在UserControl中使用ViewModel是否是合理的做法?
- 如果使用ViewModel是合适的最佳实践,是否需要在代码后置文件和ViewModel中分别维护模型集合并同步,还是仅在ViewModel中维护?
- 如果使用ViewModel并非好方案(实现困难,存在更简便的方式),如何让Photos集合具备Observable特性?(注:我已将其初始化为
ObservableCollection<Product_PhotoModel>,但无法正常显示图片)
相关代码
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="" Style="{StaticResource SmallFontIconStyle}"/> </Button> <Button Click="BtnDeletePhoto_Click" Margin="{StaticResource XXSmallTopMargin}"> <FontIcon Glyph="" 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() { } }
内容的提问来源于stack exchange,提问作者panos
相关产品推荐
相关产品推荐

