Переключение представления MVVM - новый контекст данных представления не заполняется после InitializeComponent - PullRequest
0 голосов
/ 28 мая 2020

Я разрабатываю свое первое приложение WPF и пытаюсь придерживаться подхода MVVM. Я использую MVVM Light. Это простое приложение, которое просто отображает список вещей под названием «Слияние» и позволяет пользователю выбрать один и отредактировать его.

У меня есть MainView:

<Window x:Class="FileMerger.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FileMerger"
        xmlns:Views="clr-namespace:FileMerger.Views"
        xmlns:ViewModels="clr-namespace:FileMerger.ViewModels"
        mc:Ignorable="d"
        Title="File Merge" Height="450" Width="800">
    <Window.DataContext>
        <ViewModels:MainViewModel></ViewModels:MainViewModel>
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate DataType="{x:Type ViewModels:MergeListViewModel}">
            <Views:MergeList/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type ViewModels:EditMergeViewModel}">
            <Views:EditMergeView/>
        </DataTemplate>
    </Window.Resources>

    <ContentControl Content="{Binding CurrentPageViewModel}"></ContentControl>
</Window> 

и MainViewModel следующим образом:

public class MainViewModel:ViewModelBase
    {
            private ViewModelBase _currentPageViewModel;
            private MergeListViewModel _mergeListViewModel=new MergeListViewModel();
            private EditMergeViewModel _editMergeViewModel = new EditMergeViewModel();

        public MainViewModel()
            {
                // Set starting page
                _mergeListViewModel.MergeSelected += navToMergeEdit;
                CurrentPageViewModel = _mergeListViewModel;
            }

            public ViewModelBase CurrentPageViewModel
            {
                get
                {
                    return _currentPageViewModel;
                }
                set
                {
                    if (_currentPageViewModel != value)
                    {
                        _currentPageViewModel = value;
                        RaisePropertyChanged(nameof(CurrentPageViewModel));
                    }
                }
            }

       public void navToMergeEdit(int mergeId)
        {
            _editMergeViewModel.MergeId = mergeId;
            CurrentPageViewModel = _editMergeViewModel;
        }
     }

В моем первом представлении MergeListView есть это событие в ViewModel (MergeListViewModel)

public event Action<int> MergeSelected = delegate { };

Идея состоит в том, что когда выбрано «Слияние», EditMergeView загружается с ViewModel EditMergeViewModel, передавая mergeId в качестве параметра. LoadedCommand в EditMergeViewModel должен быть запущен, который получает данные для заполнения ViewModel с помощью Merge.

EventTrigger в EditMergeView должен делать следующее:

<i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <command:EventToCommand Command="{Binding LoadedCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
</i:Interaction.Triggers>

Я обнаружил, что LoadedCommand является не вызывается, хотя точки останова показывают, что NavToMergeEdit в MainViewModel вызывается через событие. Я установил точку останова в конструкторе EditMergeView и обнаружил, что до вызова InitializeComponent () this.DataContext имеет значение null, а после InitializeComponent () он заполняется. Я считаю, что событие Loaded запускается в InitializeComponent (), но в этот момент не задан текст данных, что вызывает проблему.

Это поддерживается в следующем окне вывода в VS, что предполагает мне, что EditMergeView не привязан к ViewModel в правильной точке

System.Windows.Data Error: 40 : BindingExpression path error: 'LoadedCommand' property not found on 'object' ''EditMergeViewModel' (HashCode=31475357)'. BindingExpression:Path=LoadedCommand; DataItem='EditMergeViewModel' (HashCode=31475357); target element is 'EventToCommand' (HashCode=41172271); target property is 'Command' (type 'ICommand')
Exception thrown: 'System.NullReferenceException' in FileMerger.exe
Exception thrown: 'System.Reflection.TargetInvocationException' in mscorlib.dll
System.Windows.Data Error: 17 : Cannot get 'MergeName' value (type 'String') from '' (type 'EditMergeViewModel'). BindingExpression:Path=MergeName; DataItem='EditMergeViewModel' (HashCode=31475357); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') TargetInvocationException:'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at FileMerger.ViewModels.EditMergeViewModel.get_MergeName() in C:\Users\JonathanS\source\repos\FileMerger\FileMerger\ViewModels\EditMergeViewModel.cs:line 270
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at MS.Internal.Data.PropertyPathWorker.GetValue(Object item, Int32 level)
   at MS.Internal.Data.PropertyPathWorker.RawValue(Int32 k)'
System.Windows.Data Error: 40 : BindingExpression path error: 'EditMergeCommand' property not found on 'object' ''EditMergeViewModel' (HashCode=31475357)'. BindingExpression:Path=EditMergeCommand; DataItem='EditMergeViewModel' (HashCode=31475357); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

Что я делаю не так? Мне нужно просто вызвать его и создать ViewModel в конструкторе View и установить там DataContext? . Может быть, менее элегантно, но я бы этого не делал.

1 Ответ

0 голосов
/ 29 мая 2020

К счастью, у меня было немного времени, чтобы решить эту проблему. В EditMergeViewModel я не использовал свойства с геттерами и сеттерами для команд. Так что у меня, например, было

public ICommand LoadedCommand;

, а не

public ICommand LoadedCommand { get; set; }

Я думаю, причина, по которой ему это нужно, связана с отражением, но если кто-то хочет объяснить, почему это работает, не стесняйтесь. А пока отмечу вопрос как ответ.

...