【问题标题】:Showing Dialogs in MVVM & Setting dialog options在 MVVM 中显示对话框和设置对话框选项
【发布时间】:2010-10-14 12:20:30
【问题描述】:

我只是想知道这是在 MVVM 中显示对话框的方式吗?

public ICommand OpenFileCommand
{
    get
    {
        if (_openFileCommand == null) {
            _openFileCommand = new RelayCommand(delegate
            {
                var strArr = DialogsViewModel.GetOpenFileDialog("Open a file ...", "Text files|*.txt | All Files|*.*");
                foreach (string s in strArr) {
                    // do something with file
                }
            });
        }
        return _openFileCommand;
    }
}

public class DialogsViewModel {
    public static string[] GetOpenFileDialog(string title, string filter)
    {
        var dialog = new OpenFileDialog();
        dialog.Title = title;
        dialog.Filter = filter;
        dialog.CheckFileExists = true;
        dialog.CheckPathExists = true;
        dialog.Multiselect = true;
        if ((bool)dialog.ShowDialog()) {
            return dialog.SafeFileNames;
        }
        return new string[0];
    }
}

如果是这样,我应该如何让自己说修改我正在显示的对话框中的选项。例如,我希望另一个对话框具有不同的对话框选项dialog.something = something_else,而无需向我的方法添加大量参数

【问题讨论】:

    标签: c# wpf mvvm mvvm-foundation


    【解决方案1】:

    不应在 ViewModel 中显示对话框本身。我通常提供一个对话服务接口 IDialogServices,带有适当的 Dialog 方法调用。然后我有一个 View 类(通常是 MainWindow)实现这个接口并执行实际的 Show 逻辑。这将您的 ViewModel 逻辑与特定 View 隔离开来,例如,允许您对想要打开对话框的代码进行单元测试。

    接下来的主要任务是将服务接口注入到需要它的 ViewModel 中。如果您有依赖注入框架,这是最简单的,但您也可以使用服务定位器(一个可以注册接口实现的静态类)或通过其构造函数将接口传递给 ViewModel(取决于您的 ViewModel被构造。)

    【讨论】:

    • 嗯,也许我应该将DialogsViewModel 命名为IDialogService 之类的名称,但我的想法是对话框的显示与我的主视图模型是分开的。如果我重命名它会有所不同吗?我也会尝试阅读 DI。此外,如果我的对话框只是常见的文件对话框,例如。打开/保存文件,那么我将只有一个服务,没有实现接口的视图,对吗?你能告诉我IDialogService 的样子吗?
    • 也许我只是懒惰,或者我想保持简单和精简。实现IDialogServiceDialogService 公开GetOpenFileNames 并返回string[] 要打开的文件是否足够好? (没有服务定位器)。单元测试时,我可以只提供一个FakeDialogService,它给出了一堆文件名,对吗?我不明白为什么需要定位器
    • @jiewmeng,定位器只是让 ViewModel 更容易找到服务。您也可以在实例化 ViewModel 时传入服务,并且效果很好。
    • 我骗了这个主意。一个小问题,实现服务接口的视图怎么命名?我在视图前面加上 View 并将它们放在视图文件夹中,但是当视图不再只是一个简单视图时似乎有点错误
    • @Karsten,我通常使用作为 MainWindow 的 View 来实现接口,因为在我看来,主窗口也将负责“全局”UI 服务,如打开文件对话框,这似乎是合乎逻辑的。对于更多特定于应用程序的对话框(例如,特定屏幕的设置窗口),我实际上可能会将其公开为与该特定对话框相关的视图上的接口。
    【解决方案2】:

    我认为使用 DialogService 是一种重量级的方法。我喜欢使用 Actions/Lambdas 来处理这个问题。

    您的视图模型可能有这样的声明:

    public Func<string, string, dynamic> OpenFileDialog { get; set; }
    

    然后调用者会像这样创建您的视图模型:

    var myViewModel = new MyViewModel();
    myViewModel.OpenFileDialog = (title, filter) =>
    {
        var dialog = new OpenFileDialog();
        dialog.Filter = filter;
        dialog.Title = title;
    
        dynamic result = new ExpandoObject();
        if (dialog.ShowDialog() == DialogResult.Ok) {
            result.Success = true;
            result.Files = dialog.SafeFileNames;
        }
        else {
            result.Success = false;
            result.Files = new string[0];
        }
    
        return result;
    };
    

    你可以这样称呼它:

    dynamic res = myViewModel.OpenFileDialog("Select a file", "All files (*.*)|*.*");
    var wasSuccess = res.Success;
    

    这种方法确实为测试带来了回报。因为您的测试可以将视图模型的返回值定义为他们喜欢的任何值:

     myViewModelToTest.OpenFileDialog = (title, filter) =>
    {
        dynamic result = new ExpandoObject();
        result.Success = true;
        result.Files = new string[1];
        result.Files[0] = "myexpectedfile.txt";
    
        return result;
    };
    

    就我个人而言,我认为这种方法是最简单的。我会喜欢别人的想法。

    【讨论】:

    • 嗯,但我猜这会有问题单元测试?我读到分离对话框显示的重点主要是关注点和单元测试的分离。所以我猜你的方法会有这个问题?
    • 不,一点也不。这就是我的意思是回答并展示如何在单元测试中使用它。我认为这是单元测试的最佳方式,因为您的单元测试不必处理对话框,您可以提前定义函数的结果。请参阅我最后的 sn-p 代码。
    • @JP,接口和委托本质上是相同的接线,虽然我认为 ViewModel 上的 Func 有点神秘(从 ViewModel 调用 Func 时没有 Intellisense 知道参数,并且您必须将 Func 分别注入每个 ViewModel。)接口与依赖注入很好地配合,并且可以轻松地模拟它们以进行单元测试(使用任何体面的模拟框架,您可以同样灵活地替换行为而无需静态创建专门用于测试的类。)
    猜你喜欢
    • 1970-01-01
    • 2012-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 2012-06-02
    相关资源
    最近更新 更多