вызов метода из ViewModel при изменении DataContext - PullRequest
0 голосов
/ 17 апреля 2020

Ситуация: у меня есть небольшое приложение, которое работает с классами фэнтези. В приведенном ниже примере я сварил его до костей. В ComboBox, расположенном в главном окне, пользователь выбирает класс фэнтези (warrior, rogue, mage et c.) Из списка, загруженного из БД. Эта информация передается в UserControl, находящийся в главном окне, который предоставляет подробную информацию о классе с использованием MVVM и привязки данных. Пока все это работает.

DB имеет значение (в данном случае Gear), сохраненное как int, которое в данный момент отображается как int на экране. Приложение должно проанализировать это в строку.

Итак, вопрос в следующем: Как мне подключить метод в ViewModel UserControl для запуска всякий раз, когда его связанный View имеет DataContext ( выбранный CharacterClass) изменить?

Главное окно:

<Window x:Class="ExampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ExampleApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ComboBox Height="22" MinWidth="70" 
                  ItemsSource="{Binding Classes}" 
                  DisplayMemberPath="Name" 
                  SelectedItem="{Binding SelectedClass}"/>
        <local:DetailsView Grid.Column="1" DataContext="{Binding SelectedClass}"/>
    </Grid>
</Window>

Главное окно ViewModel:


namespace ExampleApp
{
    class MainWindowViewModel : Observable
    {
        private ObservableCollection<CharacterClass> _Classes;
        private CharacterClass _SelectedClass;

        public ObservableCollection<CharacterClass> Classes
        {
            get { return _Classes; }
            set { SetProperty(ref _Classes, value); }
        }
        public CharacterClass SelectedClass
        {
            get { return _SelectedClass; }
            set { SetProperty(ref _SelectedClass, value); }
        }

        public MainWindowViewModel()
        {
            LoadCharacterClasses();
        }

        private void LoadCharacterClasses()
        {
            //simulated data retrieval from a DB.
            //hardcoded for demo purposes
            Classes = new ObservableCollection<CharacterClass>
            {
                //behold: Gear is saved as an int.
                new CharacterClass { Name = "Mage", Gear = 0, Stats = "3,2,1" },
                new CharacterClass { Name = "Rogue", Gear = 1, Stats = "2,2,2" },
                new CharacterClass { Name = "Warrior", Gear = 2, Stats = "1,2,3" }
            };
        }
    }
}

Определение моего CharacterClass. Наследование от Observable, которое инкапсулирует INotifyPropertyChanged

namespace ExampleApp
{
    public class CharacterClass : Observable
    {
        private string _Name;
        private int _Gear;
        private string _Stats;

        public string Name
        {
            get { return _Name; }
            set { SetProperty(ref _Name, value); }
        }
        public int Gear
        {
            get { return _Gear; }
            set { SetProperty(ref _Gear, value); }
        }
        public string Stats
        {
            get { return _Stats; }
            set { SetProperty(ref _Stats, value); }
        }
    }
}

Подробная информация о базовом классе Observable:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ExampleApp
{
    public class Observable : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
        {
            if (object.Equals(member, val)) return;

            member = val;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Пользовательский контроль DetailsView:

<UserControl x:Class="ExampleApp.DetailsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:ExampleApp"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">

    <UserControl.Resources>
        <DataTemplate DataType="{x:Type local:DetailsViewModel}">
            <local:DetailsView/>
        </DataTemplate>
    </UserControl.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <StackPanel>
            <Label Content="Name:"/>
            <Label Content="Base Stats"/>
            <Label Content="Starting Gear"/>
        </StackPanel>
        <StackPanel Grid.Column="1">
            <Label Content="{Binding Name}"/>
            <Label Content="{Binding Stats}"/>
            <Label Content="{Binding gearToString}"/>
        </StackPanel>
    </Grid>
</UserControl>

и, наконец: DetailsViewModel:

public class DetailsViewModel : Observable
    {
        public string GearToString;

        //The method I would like to have called whenever the selected
        //CharacterClass (DetailsView.DataContext, so to speak) changes.
        private void OnCharacterClassChanged(int gearNumber)
        {
            switch (gearNumber)
            {
                case 0:
                    GearToString = "Cloth";
                    break;
                case 1:
                    GearToString = "Leather";
                    break;
                case 2:
                    GearToString = "Plate";
                    break;
                default:
                    GearToString = "*Error*";
                    break;
            }
        }
    }

Я возился с попыткой запустить команду при обновлении метки DetailsView. Сделана неудачная попытка преобразовать DetailsViewModel.GearToString в свойство зависимости. Я попытался переопределить SetProperty Observable внутри DetailsViewModel.

Я не знаю, какие из этих попыток были бы жизнеспособны, если бы мне удалось реализовать их правильно (я только кодировал для через несколько месяцев:))

Я мог бы заставить его работать, используя кодовый фрагмент DetailsView, но это не MVVM'ы.

1 Ответ

0 голосов
/ 19 апреля 2020

Поскольку вы изменяете свой DataContext DetailViews через поле со списком, вы можете получить доступ к «текущему» DetailDataContext до того, как поле со списком изменит SelectedItem. Вы можете сделать это прямо здесь:

public CharacterClass SelectedClass
{
   get { return _SelectedClass; }
   set { 
          _SelectedClass.DoWhatever();
          SetProperty(ref _SelectedClass, value); 
   }
}

Или вы можете обработать событие ComboBoxes SelectionChanged с помощью команды. Ваше старое значение находится в e.RemovedItem.

private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.RemovedItems.Count > 0)
        (e.RemovedItems[0] as CharacterClass).DoSomething();
}

Я предпочитаю этот подход, так как он может быстро запутаться, если вы добавите слишком много логики c в сеттеры. Это приводит к цепным реакциям, которые довольно трудно отслеживать и отлаживать.

В общем, модели представления общаются друг с другом посредством событий. В более сложных / отключенных ситуациях с помощью EventAggregator, MessageBus или чего-то подобного.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...