Создайте UserControl с ViewModel

Я хочу создать UserControl в WinUI 3 под названием ImageViewer, который будет показывать, просматривать, добавлять и удалять фотографии.

Мой UserControl содержит следующие свойства:

  • ICollection<Product_PhotoModel Фотографии used for accessing the picture models
  • DependencyProperty PhotosProperty used for creating a Property that can be binded from XAML like this: Note: The name of the XAML property does not have to be the same as the collection property.
  • int ТекущийФотоИндекс for keeping track of the current photo shown and stopping the user from accessing bad indexes (trying to access a photo after the last one, or before the first one)
  • строка CurrentPhotoDescription for keeping track which of the path of the current photo
  • ImageViewerViewModel To contain the data and logic

Поскольку ImageViewer является UserControl, он наследуется от класса UserControl. Чтобы фотографии могли изменяться при нажатии кнопок «Предыдущая/Далее/Добавить/Удалить», коллекция фотографий и свойства «Текущие*» должны иметь возможность уведомлять пользовательский интерфейс о том, что что-то изменилось (т. е. текущая фотография была удалена, поэтому отображалась последняя один). Однако я понимаю, что класс должен наследоваться от ObservableRecipient и, таким образом, использовать метод OnPropertyChanged для своих данных (например, Photos, CurrentPhotoIndex). Я думаю, что коллекция фотографий должна быть не в коде программной части (ImageViewer.xaml.cs), а в ViewModel (ImageViewerViewModel), и тогда я смогу просматривать/добавлять/удалять фотографии.

Моя проблема в том, что в примере (из которого я черпал вдохновение) не использовалась ViewModel, и я не знаю, как установить данные DependencyProperty непосредственно из ViewModel.

Мои вопросы:

  1. Стоит ли иметь ViewModel в UserControl?
  2. Если использование ViewModel применимо и является хорошей практикой/дизайном, нужно ли мне хранить коллекцию моделей в отделенном коде и ViewModel, синхронизируя их при необходимости или только в ViewModel?
  3. Если использование ViewModel не является хорошей идеей (сложно реализовать, есть другой более простой или лучший способ), как коллекция Photos становится наблюдаемой? Note: that I initialize it as a ObservableCollection<Product_PhotoModel> but I can't see the pictures correctly.

Я надеюсь, что мой вопрос правильно поставлен. И если кому-то нужна дополнительная информация, я буду более чем счастлив предоставить ее.

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()
    {
    }

}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Внутри вашего UserControl вы можете просто x:Name свой элемент управления Image, чтобы вы могли изменить, какое изображение показывать из кода программной части.

И, на мой взгляд, UserControl должен быть только его *.xaml и *.xaml.cs. И должен быть многоразовым и доступным для ViewModels.

Вам также необходимо учитывать, за что отвечает ваш элемент управления ImageViewer.

Например:

  • Просто просмотрщик изображений
    Предоставьте CurrentImage, PreviousCommand, NextCommand, AddCommand, RemoveCommand и т. д. как DependencyProperties и позвольте ViewModel выполнять всю работу.

  • Просто навигация по изображениям
    Выставляйте ItemsSource и выполняйте навигацию в коде программной части, а также выставляйте AddCommand, RemoveCommand и позволяйте ViewModel добавлять/удалять из коллекции ItemsSource в ViewModel.

  • Навигация по изображениям и добавление/удаление
    Выставляйте каталог как DependencyProperty и делайте все (включая загрузку изображений) в коде программной части.

Другие вопросы по теме