MVVM:代码隐藏是邪恶的还是实用主义的?
MVVM: Is code-behind evil or just pragmatic?
假设您想要一个 Save & Close
和一个 Cancel & Close
按钮在您喜欢的 WPF MVVM 上 window?
你会怎么做? MVVM 指示您将按钮绑定到 ICommand
,而控制反转指示您的 View
可能知道您的 ViewModel
,但反之则不然。
在网上搜索我发现了一个解决方案,它有一个 ViewModel
关闭事件,View
像这样订阅:
private void OnLoaded(Object sender
, RoutedEventArgs e)
{
IFilterViewModel viewModel = (IFilterViewModel)DataContext;
viewModel.Closing += OnViewModelClosing;
}
private void OnViewModelClosing(Object sender
, EventArgs<Result> e)
{
IFilterViewModel viewModel = (IFilterViewModel)DataContext;
viewModel.Closing -= OnViewModelClosing;
DialogResult = (e.Value == Result.OK) ? true : false;
Close();
}
但这是代码隐藏与我目前设计得非常好的 MVVM 的混合。
另一个问题是在显示主 window 时显示许可问题消息框。我可以再次像上面那样使用 Window
.Loaded
事件,但这也破坏了 MVVM,不是吗?
在这些情况下,是否有一种干净的方法或者应该务实而不是迂腐?
看看一些工具包,例如MVVMLight 具有 EventToCommand,它允许您将命令绑定到事件。我通常会尽量限制 View 中的逻辑,因为它很难测试。
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
...
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding YourCommandInVM}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
使用代码隐藏没有错或对,这主要是基于意见,取决于您的喜好。
此 example 展示了如何使用无代码隐藏的 MVVM 设计模式关闭 window。
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>
<!-- the CommandParameter should bind to your window, either by name or relative or what way you choose, this will allow you to hold the window object and call window.Close() -->
基本上您将 window 作为参数传递给命令。 IMO 你的视图模型不应该知道控件,所以这个版本不是那么好。我会将 Func<object>
/ 一些接口传递给视图模型,以使用依赖注入关闭 window。
有时我会使用变通方法。
假设你有一个 view
"MainWindow" 和一个 viewmodel
"MainWindowVM".
public class MainWindowVM
{
private MainWindow mainWindow;
public delegate void EventWithoudArg();
public event EventWithoudArg Closed;
public MainWindowVM()
{
mainWindow = new MainWindow();
mainWindow.Closed += MainWindow_Closed;
mainWindow.DataContext = this;
mainWindow.Loaded += MainWindow_Loaded;
mainWindow.Closing += MainWindow_Closing;
mainWindow.Show();
}
private void MainWindow_Loaded()
{
//your code
}
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//your code
}
private void MainWindow_Closed()
{
Closed?.Invoke();
}
}
这里我将我的视图存储在一个私有变量中,以便您在需要时可以访问它。它打破了一点 MVVM。
在我的视图模型中,我创建了一个新的 view
并显示它。
在这里,我还捕获了 view
的关闭事件并将其传递给自己的事件。
您还可以向 view
.
的 .Loaded
和 .Closing
事件添加方法
在 App.xaml.cs 中,您只需创建一个新的 viewmodel
对象。
public partial class App : Application
{
public App()
{
MainWindowVM mainWindowVM = new MainWindowVM();
mainWindowVM.Closed += Mwvm_Close;
}
private void Mwvm_Close()
{
this.Shutdown();
}
}
我创建了一个新的 viewmodel
对象并捕获它自己的关闭事件并将其绑定到 App 的关闭方法。
您的描述表明视图模型是某种文档视图。如果那是正确的,那么我会让 Save, Close, etc.
由文档容器处理,例如应用程序或主要 window,因为这些命令位于文档之上的级别,就像 copy/paste 位于应用程序级别一样。事实上 ApplicationCommands
已经为保存和关闭预定义了命令,这表明框架作者采用了某种方法。
首先,创建一个只包含Close
方法的接口:
interface IClosable
{
void Close();
}
接下来,让您的 window 实现 IClosable
:
class MyWindow : Window, IClosable
{
public MyWindow()
{
InitializeComponent();
}
}
然后让视图将自身作为 IClosable
作为命令参数传递给视图模型:
<Button Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
最后,命令调用 Close
:
CloseCommand = new DelegateCommand<IClosable>( view => view.Close() );
我们现在有什么?
- 我们有一个按钮可以关闭 window
- 我们在代码隐藏中没有代码 except
, IClosable
- 视图模型对视图一无所知,它只是获取一个可以关闭的任意对象
- 命令可以很容易地进行单元测试
假设您想要一个 Save & Close
和一个 Cancel & Close
按钮在您喜欢的 WPF MVVM 上 window?
你会怎么做? MVVM 指示您将按钮绑定到 ICommand
,而控制反转指示您的 View
可能知道您的 ViewModel
,但反之则不然。
在网上搜索我发现了一个解决方案,它有一个 ViewModel
关闭事件,View
像这样订阅:
private void OnLoaded(Object sender
, RoutedEventArgs e)
{
IFilterViewModel viewModel = (IFilterViewModel)DataContext;
viewModel.Closing += OnViewModelClosing;
}
private void OnViewModelClosing(Object sender
, EventArgs<Result> e)
{
IFilterViewModel viewModel = (IFilterViewModel)DataContext;
viewModel.Closing -= OnViewModelClosing;
DialogResult = (e.Value == Result.OK) ? true : false;
Close();
}
但这是代码隐藏与我目前设计得非常好的 MVVM 的混合。
另一个问题是在显示主 window 时显示许可问题消息框。我可以再次像上面那样使用 Window
.Loaded
事件,但这也破坏了 MVVM,不是吗?
在这些情况下,是否有一种干净的方法或者应该务实而不是迂腐?
看看一些工具包,例如MVVMLight 具有 EventToCommand,它允许您将命令绑定到事件。我通常会尽量限制 View 中的逻辑,因为它很难测试。
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
...
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<command:EventToCommand Command="{Binding YourCommandInVM}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
使用代码隐藏没有错或对,这主要是基于意见,取决于您的喜好。
此 example 展示了如何使用无代码隐藏的 MVVM 设计模式关闭 window。
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>
<!-- the CommandParameter should bind to your window, either by name or relative or what way you choose, this will allow you to hold the window object and call window.Close() -->
基本上您将 window 作为参数传递给命令。 IMO 你的视图模型不应该知道控件,所以这个版本不是那么好。我会将 Func<object>
/ 一些接口传递给视图模型,以使用依赖注入关闭 window。
有时我会使用变通方法。
假设你有一个 view
"MainWindow" 和一个 viewmodel
"MainWindowVM".
public class MainWindowVM
{
private MainWindow mainWindow;
public delegate void EventWithoudArg();
public event EventWithoudArg Closed;
public MainWindowVM()
{
mainWindow = new MainWindow();
mainWindow.Closed += MainWindow_Closed;
mainWindow.DataContext = this;
mainWindow.Loaded += MainWindow_Loaded;
mainWindow.Closing += MainWindow_Closing;
mainWindow.Show();
}
private void MainWindow_Loaded()
{
//your code
}
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//your code
}
private void MainWindow_Closed()
{
Closed?.Invoke();
}
}
这里我将我的视图存储在一个私有变量中,以便您在需要时可以访问它。它打破了一点 MVVM。
在我的视图模型中,我创建了一个新的 view
并显示它。
在这里,我还捕获了 view
的关闭事件并将其传递给自己的事件。
您还可以向 view
.
.Loaded
和 .Closing
事件添加方法
在 App.xaml.cs 中,您只需创建一个新的 viewmodel
对象。
public partial class App : Application
{
public App()
{
MainWindowVM mainWindowVM = new MainWindowVM();
mainWindowVM.Closed += Mwvm_Close;
}
private void Mwvm_Close()
{
this.Shutdown();
}
}
我创建了一个新的 viewmodel
对象并捕获它自己的关闭事件并将其绑定到 App 的关闭方法。
您的描述表明视图模型是某种文档视图。如果那是正确的,那么我会让 Save, Close, etc.
由文档容器处理,例如应用程序或主要 window,因为这些命令位于文档之上的级别,就像 copy/paste 位于应用程序级别一样。事实上 ApplicationCommands
已经为保存和关闭预定义了命令,这表明框架作者采用了某种方法。
首先,创建一个只包含Close
方法的接口:
interface IClosable
{
void Close();
}
接下来,让您的 window 实现 IClosable
:
class MyWindow : Window, IClosable
{
public MyWindow()
{
InitializeComponent();
}
}
然后让视图将自身作为 IClosable
作为命令参数传递给视图模型:
<Button Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
最后,命令调用 Close
:
CloseCommand = new DelegateCommand<IClosable>( view => view.Close() );
我们现在有什么?
- 我们有一个按钮可以关闭 window
- 我们在代码隐藏中没有代码 except
, IClosable
- 视图模型对视图一无所知,它只是获取一个可以关闭的任意对象
- 命令可以很容易地进行单元测试