Новый экземпляр окна MVVM нарушает привязки - PullRequest
0 голосов
/ 04 апреля 2020

Это моя первая неделя в WPF с Windows форм, и я уже вынужден go к шаблону MVVM, потому что почти каждый учебник или ответ Stack Overflow, на который я наткнулся, имеет в виду этот тип шаблона.

Поскольку я уже проделал большую работу в своем существующем проекте, я тестирую шаблон MVVM в одном окне, чтобы увидеть его потенциал.

После очистки этого у меня есть Window1, UserControl1 и UserControl2 представления, каждый из которых имеет соответствующий ViewModel.

. Window1 используется для перемещения между UserControl1 и UserControl2, и каждый из пользовательских элементов управления имеет кнопку для переключения на другой пользовательский элемент управления.

Навигация основана на учебном пособии, предоставленном Рэйчел Лим (https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/. Кажется, все работает так, как задумано в первом экземпляре Window1.

Однако, если Я закрываю экземпляр Window1 и открываю другой, кнопки в пользовательских элементах управления больше не изменяют ControlControl, помещенный в Window1.

Кроме того, если оставить первый Window1 открытым, создайте еще один экземпляр Window1 и нажмите кнопку, которая установил бы UserControl2 в качестве элемента управления содержимым во втором экземпляре Window1, это не тот экземпляр, который изменяет свой элемент управления содержимым, а первый.

Мой вывод таков, что каким-то образом все привязки выполняются с первым экземпляром, который он запущен, но я не могу понять, почему.

Вот мой проект.

Некоторая значимая часть кодов XAML и CS:

Способ запуска Window1, который находится в другом окне:

Window1 window = new Window1();
Window1ViewModel context = new Window1ViewModel();
window.DataContext = context;
window.Show();

XAML Window1:

<Window.Resources>
    <DataTemplate DataType="{x:Type local:UserControl1ViewModel}">
        <local:UserControl1 />
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:UserControl2ViewModel}">
        <local:UserControl2 />
    </DataTemplate>
</Window.Resources>
<Grid>
    <ContentControl Content="{Binding CurrentPageViewModel}" />
</Grid>

Window1ViewModel class

class Window1ViewModel : BaseViewModel
    {
        private IPageViewModel _currentPageViewModel;
        private List<IPageViewModel> _pageViewModels;

        public List<IPageViewModel> PageViewModels
        {
            get
            {
                if (_pageViewModels == null)
                    _pageViewModels = new List<IPageViewModel>();

                return _pageViewModels;
            }
        }

        public IPageViewModel CurrentPageViewModel
        {
            get
            {
                return _currentPageViewModel;
            }
            set
            {
                _currentPageViewModel = value;
                OnPropertyChanged("CurrentPageViewModel");
            }
        }

        private void ChangeViewModel(IPageViewModel viewModel)
        {
            if (!PageViewModels.Contains(viewModel))
                PageViewModels.Add(viewModel);

            CurrentPageViewModel = PageViewModels
                .FirstOrDefault(vm => vm == viewModel);
        }

        private void OnGoTo1(object obj)
        {
            ChangeViewModel(PageViewModels[0]);

        }

        private void OnGoTo2(object obj)
        {
            ChangeViewModel(PageViewModels[1]);

        }

        public Window1ViewModel()
        {
            // Add available pages and set page
            PageViewModels.Clear();
            PageViewModels.Add(new UserControl1ViewModel());
            PageViewModels.Add(new UserControl2ViewModel());
            CurrentPageViewModel = PageViewModels[0];

            Mediator.Subscribe("GoTo1", OnGoTo1);
            Mediator.Subscribe("GoTo2", OnGoTo2);

        }
    }

Что мне здесь не хватает? Я не могу понять, почему он сохраняет все привязки с первым экземпляром Window, даже если я создаю новый класс.

Ответы [ 2 ]

2 голосов
/ 04 апреля 2020

Я думаю, что навигационные кнопки не являются частью элемента управления Window1 и привязываются к Window1ViewModel, который является DataContext из Window1. Если это так, вы должны повторно использовать один и тот же экземпляр Window1ViewModel для каждого экземпляра Window1.

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private Window1ViewModel Window1ViewModel { get; set; }

  public MainWindow()
  {
    this.Window1ViewModel = new Window1ViewModel();
  }

  private void ShowWindow1()
  {
    Window1 window = new Window1();
    window.DataContext = this.Window1ViewModel;
    window.Show();
  }
}

При создании Требуется новый экземпляр Window1ViewModel, тогда вам следует изменить дизайн представления и переместить навигационные кнопки к элементу управления Window1.

Замечания

Я попытаюсь объяснить, почему у вас есть повторно использовать начальный экземпляр Window1ViewModel в вашей текущей реализации.

Это вопрос объема и экземпляров или ссылок на экземпляры.

Давайте возьмем вашу первоначальную настройку в качестве контекста: у нас есть первый элемент управления, например, Button, который связывается с DataContext, например, Window1ViewModel второго элемента управления, например, Window1.
. привязки к Window1ViewModel изначально работают, но когда вы закрываете Window1 и открываете новый экземпляр Window1, эти привязки больше не работают.

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

Как правило, информация о привязке (исходный объект и целевой объект привязки данных) сохраняется в экземпляре класса Binding.

Теперь при настройке привязки XAML для свойства первого элемента управления (Button.Command), например, для привязки к команде экземпляра Window1ViewModel, каркас создаст новый экземпляр Binding, где его свойство Binding.Target установлено на Button.Command (свойство текущего экземпляра Button), а свойство Binding.Source установлено на текущий (первый) экземпляр из Window1ViewModel (и к свойству экземпляра, например NextPageCommand).

Вы показываете окно следующим образом:

private void ShowWindow1()
{
  Window1 window = new Window1();
  window.DataContext = new Window1ViewModel();
  window.Show();
}

Когда вы закрываете Window1 и выходите из области действия переменной экземпляра window, вы не можете получить доступ к первому экземпляру Window1ViewModel больше, потому что единственная ссылка на этот экземпляр была сохранена в свойстве DataContext Window1. Но все еще , привязка Button ссылается на первый экземпляр Window1ViewModel.

Затем вы решаете показать новое окно и создать новый (второй) экземпляр Window1 и назначьте ему новый (второй) экземпляр Window1ViewModel. Как теперь привязка к новому экземпляру Window1ViewModel?
Даже когда вы повторно используете первый экземпляр Window1 и просто добавляете новый экземпляр Window1ViewModel к Window1.DataContext, привязка все еще ссылается на первый (начальный) экземпляр Window1ViewModel.

Binding не является производным от DependencyObject и поэтому не реализует его свойства как DependencyProperty. Это означает, что Binding.Source не является DependencyProperty и не может инициировать изменения свойств, и поэтому не будет обновлять ссылку, чтобы указывать на второй экземпляр Window1ViewModel. Вот почему повторное использование начального экземпляра Window1ViewModel решает проблему (Binding.Source все еще ссылается на него).

В качестве альтернативы вы могли бы заменить экземпляр Binding при замене экземпляра binging source. Но для этого потребуется написать более сложный код C#, без помощи конструктора XAML для разрешения текущего DataContext.

Решение

При взгляде на логи c вы пытаясь реализовать, на самом деле не имеет смысла иметь один набор кнопок навигации для навигации по нескольким независимым windows.

Если вы решили иметь несколько Window1 экземпляров, работающих параллельно, то вы должны позволить Window1 обрабатывать навигацию самостоятельно.

Window1.xaml

<window>
  <StackPanel>
    <Button x:Name="LoadPreviousButton" 
            Command="ShowPreviousCommand}" />
    <Button x:Name="LoadNextButton"
            Command="ShowNextCommand}" />

    <ContentPresenter Content="{Binding CurrentPage}" />
  </StackPanel>
</Window>

Теперь вы можете иметь столько экземпляров Window1, сколько вам нужно, причем каждый экземпляр Window1 может иметь выделенный экземпляр Window1ViewModel:

// This will now behave as you expected it to
var window = new Window1() { DataContext = new Window1ViewModel() };
window.Show();
0 голосов
/ 04 апреля 2020

Вы должны сделать два изменения. Во-первых, вы должны отменить подписку посредника в Window1ViewModel.cs

public Window1ViewModel()
{
    Mediator.Unsubscribe("GoTo1", OnGoTo1);
    Mediator.Unsubscribe("GoTo2", OnGoTo2);
    // Add available pages and set page
    PageViewModels.Clear();
    PageViewModels.Add(new UserControl1ViewModel());
    PageViewModels.Add(new UserControl2ViewModel());
    CurrentPageViewModel = PageViewModels[0];

    Mediator.Subscribe("GoTo1", OnGoTo1);
    Mediator.Subscribe("GoTo2", OnGoTo2);
}

Во-вторых, вы должны обновить метод отмены подписки в классе посредника:

public static void Unsubscribe(string token, Action<object> callback)
{
    if (pl_dict.ContainsKey(token)) {
        //pl_dict[token].Remove(callback);
        pl_dict.Remove(token);
    }
}

Ранее этот метод удалял обратный вызов только токен, но на самом деле он должен удалить запись подписки.

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