首页 > 解决方案 > MVVM Window 的新实例打破了绑定

问题描述

这是我从 Windows 窗体进入 WPF 的第一周,我已经被迫转向 MVVM 模式,因为我偶然发现的几乎每个教程或 Stack Overflow 答案都考虑到了这种模式。

因为我已经在我现有的项目中投入了大量的工作,所以我正在单个窗口上测试 MVVM 模式,以便看到它的潜力。

清除后,我有 Window1、UserControl1 和 UserControl2 视图,每个视图都有一个对应的ViewModel.

Window1 用于在 UserControl1 和 UserControl2 之间导航,每个用户控件都有一个用于切换到另一个用户控件的按钮。

导航基于 Rachel Lim 提供的教程(https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/。在 Window1 的第一个实例中,一切似乎都按预期工作。

但是,如果我关闭 Window1 实例并打开另一个实例,则用户控件中的按钮不再更改放置在 Window1 中的 ControlControl。

此外,如果保持第一个 Window1 处于打开状态,则创建另一个 Window1 实例并单击将 UserControl2 设置为第二个 Window1 实例中的内容控件的按钮,不是该实例更改其内容控件而是第一个实例。

我的结论是,不知何故,所有绑定都是在它启动的第一个实例中进行的,但我不知道为什么。

这是我的项目。

XAML 和 CS 代码的一些相关部分:

Window1的启动方法,位于另一个Window中:

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

Window1 的 XAML:

<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 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 实例的所有绑定。

标签: c#wpfmvvm

解决方案


我猜导航按钮不是控件的一部分,而是Window1绑定到. 如果是这样,您必须为.Window1ViewModelDataContextWindow1Window1ViewModelWindow1

主窗口.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在当前实现中重用初始实例。

这是范围和实例或实例引用的问题。

让我们将您的初始设置作为上下文:我们有第一个控件,例如 a Button,它绑定到第二个控件的 eg,DataContext例如。 绑定最初可以工作,但是当您关闭并打开 的新实例时,这些绑定将不再工作。Window1ViewModelWindow1
Window1ViewModelWindow1Window1

要了解真正发生的事情,您必须记住,您处理的不是类,而是类的实例。您可以拥有同一类的多个实例。

通常,绑定信息(数据绑定的源对象和目标对象)存储在类的实例中Binding

现在,当在第一个控件( )的属性上设置 XAML 绑定时,Button.Command例如绑定到Window1ViewModel实例的命令,框架将创建 的新实例Binding,其中其Binding.Target属性设置为Button.Command(当前实例上的属性of Button) 并且该Binding.Source属性设置为当前(第一个)实例Window1ViewModel以及实例的属性,例如NextPageCommand)。

你显示这样的窗口:

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

当您现在关闭Window1并离开window实例变量的范围时,您将无法再访问第一个Window1ViewModel实例,因为对该实例的唯一引用存储DataContextWindow1. 但是,仍然绑定了Button引用的第一个实例Window1ViewModel

然后,您决定显示一个新窗口并实例化一个新的(第二个)实例Window1并为其分配一个新的(第二个)实例Window1ViewModel。现在关于新实例的绑定如何Window1ViewModel
即使您重用第一个Window1实例并且只是将 的新实例添加Window1ViewModelWindow1.DataContext,绑定仍然引用 的第一个(初始)实例Window1ViewModel

Binding不派生自DependencyObject,因此不将其属性实现为DependencyProperty. 这意味着Binding.Source不是DependencyProperty并且不能触发属性更改,因此不会更新引用以指向Window1ViewModel. 这就是重用初始实例Window1ViewModel解决问题的原因(Binding.Source仍然引用它)。

或者,您可以在Binding替换 binging 源实例时替换该实例。但这需要编写更复杂的 C# 代码,而无需 XAML 设计器帮助解决当前的DataContext.

解决方案

查看您尝试实现的逻辑时,使用一组导航按钮来导航多个独立窗口确实没有意义。

如果您决定让多个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();

推荐阅读