Я хочу создать UserControl в WinUI 3 под названием ImageViewer, который будет показывать, просматривать, добавлять и удалять фотографии.
Мой UserControl содержит следующие свойства:
Поскольку ImageViewer является UserControl, он наследуется от класса UserControl. Чтобы фотографии могли изменяться при нажатии кнопок «Предыдущая/Далее/Добавить/Удалить», коллекция фотографий и свойства «Текущие*» должны иметь возможность уведомлять пользовательский интерфейс о том, что что-то изменилось (т. е. текущая фотография была удалена, поэтому отображалась последняя один). Однако я понимаю, что класс должен наследоваться от ObservableRecipient и, таким образом, использовать метод OnPropertyChanged для своих данных (например, Photos, CurrentPhotoIndex). Я думаю, что коллекция фотографий должна быть не в коде программной части (ImageViewer.xaml.cs), а в ViewModel (ImageViewerViewModel), и тогда я смогу просматривать/добавлять/удалять фотографии.
Моя проблема в том, что в примере (из которого я черпал вдохновение) не использовалась ViewModel, и я не знаю, как установить данные DependencyProperty непосредственно из ViewModel.
Мои вопросы:
Я надеюсь, что мой вопрос правильно поставлен. И если кому-то нужна дополнительная информация, я буду более чем счастлив предоставить ее.
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()
{
}
}
Внутри вашего UserControl
вы можете просто x:Name
свой элемент управления Image
, чтобы вы могли изменить, какое изображение показывать из кода программной части.
И, на мой взгляд, UserControl
должен быть только его *.xaml и *.xaml.cs. И должен быть многоразовым и доступным для ViewModels.
Вам также необходимо учитывать, за что отвечает ваш элемент управления ImageViewer.
Например:
Просто просмотрщик изображений
Предоставьте CurrentImage, PreviousCommand, NextCommand, AddCommand, RemoveCommand и т. д. как DependencyProperties
и позвольте ViewModel выполнять всю работу.
Просто навигация по изображениям
Выставляйте ItemsSource и выполняйте навигацию в коде программной части, а также выставляйте AddCommand, RemoveCommand и позволяйте ViewModel добавлять/удалять из коллекции ItemsSource в ViewModel.
Навигация по изображениям и добавление/удаление
Выставляйте каталог как DependencyProperty
и делайте все (включая загрузку изображений) в коде программной части.