c# - 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 实例的所有绑定。
解决方案
我猜导航按钮不是控件的一部分,而是Window1
绑定到. 如果是这样,您必须为.Window1ViewModel
DataContext
Window1
Window1ViewModel
Window1
主窗口.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
例如。
绑定最初可以工作,但是当您关闭并打开 的新实例时,这些绑定将不再工作。Window1ViewModel
Window1
Window1ViewModel
Window1
Window1
要了解真正发生的事情,您必须记住,您处理的不是类,而是类的实例。您可以拥有同一类的多个实例。
通常,绑定信息(数据绑定的源对象和目标对象)存储在类的实例中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
实例,因为对该实例的唯一引用存储DataContext
在Window1
. 但是,仍然绑定了Button
引用的第一个实例Window1ViewModel
。
然后,您决定显示一个新窗口并实例化一个新的(第二个)实例Window1
并为其分配一个新的(第二个)实例Window1ViewModel
。现在关于新实例的绑定如何Window1ViewModel
?
即使您重用第一个Window1
实例并且只是将 的新实例添加Window1ViewModel
到Window1.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();
推荐阅读
- emacs - Emacs 守护进程减慢关机速度
- ios - Firebase(电话身份验证)获取 iOS 错误:注册自定义 URL 方案
- r - 红色刻度条形图
- python - 如何将下面 pi_string 的结果保存到新变量 pi_flow 中?
- java - 线程“主”java.lang.NullPointerException1000 中的异常
- javascript - 将完整日期添加到 Squarespace 博客文章
- python - 如何通过带有for循环的字典(python)设置实例的属性
- javascript - 如何允许输入中除数字外的所有字符
- python - 如何存储 mplleaflet 对象?
- postgresql - Sails Orm Hook 超时/未连接到 AWS EC2 上的 postgres 数据库