@Deflarten
C# программист

Как правильно реализовать информирование ViewModel'и по MVVM?

Есть класс Search, отвечающий за поиск файлов по указанным критериям и есть ViewModel, которая хранит экземпляр этого класса. Мне нужно, чтобы VM знала о том, сколько файлов проверено, сколько совпадений и на какой папке сейчас ведётся поиск. Как мне стоит реализовать это, чтобы можно было осуществить привязку? В данный момент у меня есть отдельный класс модели, который наследуется от моего ViewModelBase. Подозреваю, что это далеко не лучшее решение, особенно учитывая, что я передаю экземпляр этого класса (SearchInfo) в конструктор класса модели Search.
  • Вопрос задан
  • 245 просмотров
Решения вопроса 1
Casper-SC
@Casper-SC
Программист (.NET)
Читай комментарии в коде, в том числе зачем там lock. Вдруг ты будешь использовать такой поиск с отменой не в UI потоке (не именно этот код, а саму логику).

Models/SearchAlgorithm.cs
using System;
using System.Threading;
using System.Threading.Tasks;

namespace EventsInModel.Models
{
    public class SearchAlgorithm
    {
        public string CurrentFolder { get; private set; }

        public event EventHandler ProgressChanged;

        public async Task Search(CancellationToken cancellationToken)
        {
            for (int i = 0; i < 5; i++)
            {
                await Task.Delay(1200, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();

                // Можно прогресс передавать и в качестве аргумента события,
                // но в данном случае, вряд ли это оправдано. Обработав событие можно получить
                // доступ к отправителю события и прочитать его свойства.
                CurrentFolder = i.ToString();
                ProgressChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }
}


ViewModels/MainViewModel.cs
using System;
using System.Threading;
using System.Windows.Input;
using EventsInModel.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;

namespace EventsInModel.ViewModels
{
    // ViewModelBase из библиотеки MvvmLight
    public class MainViewModel : ViewModelBase
    {
        //private readonly object _sync = new object();
        private readonly SearchAlgorithm _search;

        private string _currentFolder;
        // Логику с отменой можно вынести в отдельный класс, чтобы не писать простыню
        // с отменой в каждом таком месте с операцией, которая может быть отменена, а 
        // в UI приложениях такое сплошь и рядом.
        private volatile CancellationTokenSource _lastCancellationTokenSource;

        public string CurrentFolder
        {
            get { return _currentFolder; }
            private set { Set(ref _currentFolder, value); }
        }

        public ICommand SearchCommand { get; }

        public MainViewModel(SearchAlgorithm search)
        {
            _search = search;
            _search.ProgressChanged += OnSearchProgressChanged;
            SearchCommand = new RelayCommand(Search);
        }

        public override void Cleanup()
        {
            //lock (_sync)
            {
                _lastCancellationTokenSource?.Cancel();
            }

            _search.ProgressChanged -= OnSearchProgressChanged;
            base.Cleanup();
        }

        /// <summary>
        /// Прерывает прошлый поиск и запускает новый.
        /// </summary>
        private async void Search()
        {
            CancellationTokenSource currentTokenSource;
            // В случае, если такой метод вызывать не из UI потока, то lock здесь нужен
            // Если использовать только из UI потока как здесь, то lock можно удалить.
            // Ещё бы я вынес логику в отдельный класс и использовал в других проектах в том числе.
            //lock (_sync)
            {
                _lastCancellationTokenSource?.Cancel();
                currentTokenSource = new CancellationTokenSource();
                _lastCancellationTokenSource = currentTokenSource;
            }

            try
            {
                await _search.Search(currentTokenSource.Token);
            }
            catch (OperationCanceledException)
            {
                // Ignored.
            }
            finally
            {
                //lock (_sync)
                {
                    currentTokenSource.Dispose();
                    if (ReferenceEquals(_lastCancellationTokenSource, currentTokenSource))
                    {
                        _lastCancellationTokenSource = null;
                    }
                }
            }
        }

        private void OnSearchProgressChanged(object sender, EventArgs e)
        {
            var search = (SearchAlgorithm)sender;
            CurrentFolder = search.CurrentFolder;
        }
    }
}


Затестил я по быстрому так. Это тебе не особо нужно:

MainWindow.xaml.cs
using System.Windows;
using EventsInModel.Models;
using EventsInModel.ViewModels;

namespace EventsInModel
{
    public partial class MainWindow : Window
    {
        private readonly MainViewModel _viewModel;

        public MainWindow()
        {
            InitializeComponent();
            _viewModel = new MainViewModel(new SearchAlgorithm());
            DataContext = _viewModel;

            Loaded += OnLoaded;
            Closing += OnClosing;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            _viewModel.SearchCommand.Execute(null);
            _viewModel.SearchCommand.Execute(null);
        }

        private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            _viewModel.Cleanup();
        }
    }
}


Проект с зависимостями
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MvvmLightLibsStd10" Version="5.4.1.1" />
  </ItemGroup>

</Project>
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы