WPF MVVM:从 ViewModel 中隔离命令逻辑
WPF MVVM: Isolating command logic from ViewModel
我正在使用 Prism 迈出 WPF 和 MVVM 的第一步。到目前为止,还不错,但有一些我似乎无法实现的设计方法。
在我的 UI 中,我有两种打开文件的方法。有一个可以单击的浏览按钮,将出现一个“打开文件”对话框,提示输入文件并将其打开。您也可以将文件拖放到 UI 顶部,它会打开它。
为了隔离浏览逻辑,我为它创建了一个命令。出现第一个代码味道,我需要 ICommand 接口未公开的结果。
public class BrowseFileCommand: ICommand
{
public string ExecutionResult { get; private set; }
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
var openFileDialog = new OpenFileDialog()
{
Multiselect = false,
Filter = "Event log files (*.evtx)|*.evtx"
};
ExecutionResult = openFileDialog.ShowDialog() == true ? openFileDialog.FileName : null;
}
public event EventHandler CanExecuteChanged;
}
然后,在我的 ViewModel class 中,我可以这样调用它:
public class MainWindowViewModel: BindableBase
{
public DelegateCommand BrowseFileCommand { get; set; }
public MainWindowViewModel()
{
BrowseFileCommand = new DelegateCommand(BrowseAndOpenFile, () => _browseFileCommand.CanExecute(null));
// ...
}
private BrowseFileCommand _browseFileCommand = new BrowseFileCommand();
private void BrowseAndOpenFile()
{
_browseFileCommand.Execute(null);
var fileName = _browseFileCommand.ExecutionResult;
if (!string.IsNullOrWhiteSpace(fileName))
OpenFile(fileName);
}
// ...
}
这里有一些其他的代码味道:
- 我需要在另一个命令中结束我的命令,该命令将在执行后从中读取值
- 我没有正确链接 CanExecuteChanged 事件(我不需要,但如果我要嵌套命令,我似乎应该这样做)
- 使用空参数调用 CanExecute()(据我所知,这是 "common",但对我来说仍然是代码的味道),因为
ICommand
需要它
- 不带参数调用 CanExecute(),因为 Prism 的
DelegateCommand
允许。
- 我仍然在 ViewModel 中保留了 "gluing" 逻辑,这是我一开始就想避免的。
有什么好的方法可以让我将逻辑完全隔离到命令/class?
注意以下设计限制:
- 这些操作 do 属于 UI 逻辑:BrowseCommand 严格依赖于所使用的 UI 技术
- 某些命令可能会在操作之间共享。请注意 OpenFile 是浏览文件后的第二步,但是 Drop 上的第一步(此处未显示)。
ICommand
主要仅用于绑定到 UI。它并不意味着用于分离行为并从普通代码中调用。这就是为什么您的代码有异味。
相反,使用正常的语言特性(方法、继承、组合等)来分离关注点,并仅使用 ICommand
命令将特定操作公开给 UI 以进行绑定。
我正在使用 Prism 迈出 WPF 和 MVVM 的第一步。到目前为止,还不错,但有一些我似乎无法实现的设计方法。
在我的 UI 中,我有两种打开文件的方法。有一个可以单击的浏览按钮,将出现一个“打开文件”对话框,提示输入文件并将其打开。您也可以将文件拖放到 UI 顶部,它会打开它。
为了隔离浏览逻辑,我为它创建了一个命令。出现第一个代码味道,我需要 ICommand 接口未公开的结果。
public class BrowseFileCommand: ICommand
{
public string ExecutionResult { get; private set; }
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
var openFileDialog = new OpenFileDialog()
{
Multiselect = false,
Filter = "Event log files (*.evtx)|*.evtx"
};
ExecutionResult = openFileDialog.ShowDialog() == true ? openFileDialog.FileName : null;
}
public event EventHandler CanExecuteChanged;
}
然后,在我的 ViewModel class 中,我可以这样调用它:
public class MainWindowViewModel: BindableBase
{
public DelegateCommand BrowseFileCommand { get; set; }
public MainWindowViewModel()
{
BrowseFileCommand = new DelegateCommand(BrowseAndOpenFile, () => _browseFileCommand.CanExecute(null));
// ...
}
private BrowseFileCommand _browseFileCommand = new BrowseFileCommand();
private void BrowseAndOpenFile()
{
_browseFileCommand.Execute(null);
var fileName = _browseFileCommand.ExecutionResult;
if (!string.IsNullOrWhiteSpace(fileName))
OpenFile(fileName);
}
// ...
}
这里有一些其他的代码味道:
- 我需要在另一个命令中结束我的命令,该命令将在执行后从中读取值
- 我没有正确链接 CanExecuteChanged 事件(我不需要,但如果我要嵌套命令,我似乎应该这样做)
- 使用空参数调用 CanExecute()(据我所知,这是 "common",但对我来说仍然是代码的味道),因为
ICommand
需要它 - 不带参数调用 CanExecute(),因为 Prism 的
DelegateCommand
允许。 - 我仍然在 ViewModel 中保留了 "gluing" 逻辑,这是我一开始就想避免的。
有什么好的方法可以让我将逻辑完全隔离到命令/class?
注意以下设计限制:
- 这些操作 do 属于 UI 逻辑:BrowseCommand 严格依赖于所使用的 UI 技术
- 某些命令可能会在操作之间共享。请注意 OpenFile 是浏览文件后的第二步,但是 Drop 上的第一步(此处未显示)。
ICommand
主要仅用于绑定到 UI。它并不意味着用于分离行为并从普通代码中调用。这就是为什么您的代码有异味。
相反,使用正常的语言特性(方法、继承、组合等)来分离关注点,并仅使用 ICommand
命令将特定操作公开给 UI 以进行绑定。