1 回答
TA贡献1785条经验 获得超4个赞
在我看来,你目前的尝试是在正确的轨道上。您发布的代码的主要问题是CreateAccountView.Button_Click()
处理程序无权访问DataContext
它应该设置的属性:
private void Button_Click(object sender, RoutedEventArgs e) { DataContext = new MainWindowViewModel(); }
该DataContext
属性属于CreateAccountView
用户控件。但是,这不是所显示内容的控制上下文。因此更改该属性的值DataContext
不会产生任何有用的效果。(事实上,用户控件DataContext
根本不应该设置自己的属性,因为这样做会丢弃使用该用户控件的客户端代码设置的任何上下文。)
没有足够的上下文来确切地知道执行此操作的最佳方法是什么。我认为在 Stack Overflow 上不可能提供足够的上下文。整体架构将取决于程序的太多小细节。但是,我认为解决这个问题的一种很好的方法是:
创建一个“主”视图模型来管理应用程序的整体行为
创建与 UI 的不同状态相关的单独视图模型
让主视图模型配置各个视图模型,根据用户输入(例如单击按钮)适当切换当前视图模型
将其翻译成代码,看起来像这样......
首先,视图模型:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
当然,这些视图模型只包含处理 UI 切换所需的实现细节。在您的程序中,每个视图状态还包含您需要的特定于每个视图状态的内容。
在我的小示例中,“主页”视图包含几个按钮,用于选择可用的各个子视图:
<UserControl x:Class="WpfApp1.HomeView"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
子视图仅包含返回主视图所需的按钮:
<UserControl x:Class="WpfApp1.Sub1View"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
最后,主窗口设置主视图模型,并声明用于每个特定子视图的模板:
<Window x:Class="WpfApp1.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:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
重要的是,您将看到所有视图对象都不包含任何隐藏代码。当您以这种方式处理问题时,没有必要这样做,至少不是为了控制代码中的基本行为。(您可能仍然会遇到视图对象的代码隐藏,但这通常只是为了实现该视图对象特有的特定用户界面行为,而不是为了处理视图模型状态。)
使用这种方法,您可以让 WPF 完成尽可能多的繁重工作。它还将所有视图模型对象相互解耦。有一个清晰的层次结构:只有顶级“主”视图模型才知道其他视图模型。这允许子视图模型(“home”、“sub1”和“sub2”)根据需要在其他场景中重用,而无需在其中进行任何修改或特殊情况处理。
这是我上面使用的辅助类:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}
- 1 回答
- 0 关注
- 86 浏览
添加回答
举报