如何在 MVVM 的 WPF 应用程序中使用 FolderBrowserDialog

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12289100/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 05:13:44  来源:igfitidea点击:

How to use a FolderBrowserDialog from a WPF application with MVVM

c#.netwpfmvvmfolderbrowserdialog

提问by David Work

I'm trying to use the FolderBrowserDialogfrom my WPF application - nothing fancy. I don't much care that it has the Windows Forms look to it.

我正在尝试使用FolderBrowserDialog我的 WPF 应用程序中的 - 没什么特别的。我不太关心它是否具有 Windows 窗体外观。

I found a question with a suitable answer (How to use a FolderBrowserDialog from a WPF application), except I'm using MVVM.

我找到了一个有合适答案的问题(How to use a FolderBrowserDialog from a WPF application),但我使用的是 MVVM。

Thiswas the answer I "implemented", except I can't get the window object and I'm just calling ShowDialog()without any parameters.

是我“实现”的答案,除了我无法获取 window 对象并且我只是在ShowDialog()没有任何参数的情况下进行调用。

The problem is this:

问题是这样的:

var dlg = new FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dlg.ShowDialog(this.GetIWin32Window());

In my ViewModelthere the thishas no GetIWin32Window()method for me to get the Window context.

在我ViewModel那里,我this没有GetIWin32Window()办法获取 Window 上下文。

Any ideas on how to make this work?

关于如何使这项工作的任何想法?

采纳答案by wageoghe

First, you could use the ShowDialog signature that does not require a window.

首先,您可以使用不需要窗口的 ShowDialog 签名。

var dlg = new FolderBrowserDialog();
DialogResult result = dlg.ShowDialog();

Second, you could send the main window of the Application as the owning window.

其次,您可以将应用程序的主窗口作为拥有窗口发送。

var dlg = new FolderBrowserDialog();
DialogResult result = dlg.ShowDialog(Application.Current.MainWindow.GetIWin32Window());

The second option might not be considered very MVVMish.

第二种选择可能不被认为是非常 MVVMish。

See the answer by @Dr. ABTin this questionfor a way to inject a pointer to your View into your ViewModel (not sure if this is a good idea or a bad idea, but I'm not going to let that stop me) With this technique, you would have access in your VM to the corresponding View if you really want to make that View be the owner of the FolderBrowserDialog.

请参阅 @ Dr. ABT此问题中的答案,了解将指向您的 View 的指针注入您的 ViewModel 的方法(不确定这是一个好主意还是一个坏主意,但我不会让这阻止我)使用这种技术,如果您真的想让该视图成为 FolderBrowserDialog 的所有者,您将可以在您的 VM 中访问相应的视图。

@ChrisDD is right about defining an interface and wrapping FolderBrowserDialog. That is how we do it:

@ChrisDD 在定义接口和包装 FolderBrowserDialog 方面是正确的。我们就是这样做的:

  public interface IFolderBrowserDialog
  {
    string Description { get; set; }
    Environment.SpecialFolder RootFolder { get; set; }
    string SelectedPath { get; set; }
    bool ShowNewFolderButton { get; set; }
    bool? ShowDialog();
    bool? ShowDialog(Window owner);
  }

  //Decorated for MEF injection
  [Export(typeof(IFolderBrowserDialog))]
  [PartCreationPolicy(CreationPolicy.NonShared)]
  internal class WindowsFormsFolderBrowserDialog : IFolderBrowserDialog
  {
    private string _description;
    private string _selectedPath;

    [ImportingConstructor]
    public WindowsFormsFolderBrowserDialog()
    {
      RootFolder = System.Environment.SpecialFolder.MyComputer;
      ShowNewFolderButton = false;
    }

    #region IFolderBrowserDialog Members

    public string Description
    {
      get { return _description ?? string.Empty; }
      set { _description = value; }
    }

    public System.Environment.SpecialFolder RootFolder { get; set; }

    public string SelectedPath
    {
      get { return _selectedPath ?? string.Empty; }
      set { _selectedPath = value; }
    }

    public bool ShowNewFolderButton { get; set; }

    public bool? ShowDialog()
    {
      using (var dialog = CreateDialog())
      {
        var result = dialog.ShowDialog() == DialogResult.OK;
        if (result) SelectedPath = dialog.SelectedPath;
        return result;
      }
    }

    public bool? ShowDialog(Window owner)
    {
      using (var dialog = CreateDialog())
      {
        var result = dialog.ShowDialog(owner.AsWin32Window()) == DialogResult.OK;
        if (result) SelectedPath = dialog.SelectedPath;
        return result;
      }
    }
    #endregion

    private FolderBrowserDialog CreateDialog()
    {
      var dialog = new FolderBrowserDialog();
      dialog.Description = Description;
      dialog.RootFolder = RootFolder;
      dialog.SelectedPath = SelectedPath;
      dialog.ShowNewFolderButton = ShowNewFolderButton;
      return dialog;
    }
  }

  internal static class WindowExtensions
  {
    public static System.Windows.Forms.IWin32Window AsWin32Window(this Window window)
    {
      return new Wpf32Window(window);
    }
  }

  internal class Wpf32Window : System.Windows.Forms.IWin32Window
  {
    public Wpf32Window(Window window)
    {
      Handle = new WindowInteropHelper(window).Handle;
    }

    #region IWin32Window Members

    public IntPtr Handle { get; private set; }

    #endregion
  }

Then we make the VM/Command where we want to use the FolderBrowser import IFolderBrowserDialog. In application, IFolderBrowserDialog.ShowDialog shows the dialog. In unit test, we mock IFolderBrowserDialog so we can verify that it was called with correct parameters and/or send the selected folder back to the sut so that the test can continue.

然后我们在要使用 FolderBrowser 的地方创建 VM/Command 导入 IFolderBrowserDialog。在应用程序中, IFolderBrowserDialog.ShowDialog 显示对话框。在单元测试中,我们模拟 IFolderBrowserDialog 以便我们可以验证它是否使用正确的参数调用和/或将所选文件夹发送回 sut 以便测试可以继续。

回答by Trevor Elliott

If you're determined to use FolderBrowserDialog, I'd use this kind of design.

如果您决定使用 FolderBrowserDialog,我会使用这种设计。

First, create a DependencyProperty on your View to expose its handle.

首先,在您的视图上创建一个 DependencyProperty 以公开其句柄。

public static readonly DependencyProperty WindowHandleProperty =
    DependencyProperty.Register("WindowHandle", typeof(System.Windows.Forms.IWin32Window), typeof(MainWindow), new PropertyMetadata(null));

// MainWindow.cs
public System.Windows.Forms.IWin32Window WindowHandle
{
    get { return (System.Windows.Forms.IWin32Window)GetValue(WindowHandleProperty); }
    set { SetValue(WindowHandleProperty, value); }
}

Now, when your window loads, you can retrieve the handle using the extensions provided in the question you linked to:

现在,当您的窗口加载时,您可以使用链接到的问题中提供的扩展来检索句柄:

// MainWindow.cs
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    var binding = new Binding();
    binding.Path = new PropertyPath("WindowHandle");
    binding.Mode = BindingMode.OneWayToSource;
    SetBinding(WindowHandleProperty, binding);

    WindowHandle = this.GetIWin32Window();
}

So, you are binding one-way to source using a "WindowHandle" property. So if your ViewModel has a WindowHandle property, it will be kept up to date with the valid IWin32Handle for the related view:

因此,您使用“WindowHandle”属性将一种方式绑定到源。因此,如果您的 ViewModel 具有 WindowHandle 属性,它将与相关视图的有效 IWin32Handle 保持同步:

// ViewModel.cs
private System.Windows.Forms.IWin32Window _windowHandle; 
public System.Windows.Forms.IWin32Window WindowHandle
{
    get
    {
        return _windowHandle;
    }
    set
    {
        if (_windowHandle != value)
        {
            _windowHandle = value;
            RaisePropertyChanged("WindowHandle");
        }
    }
}

This is a good solution because you're not hard-coding one ViewModel to be paired with one specific View. If your use multiple Views with the same ViewModel, it should just work. If you create a new View but you don't implement the DependencyProperty, it will just operate with a null handle.

这是一个很好的解决方案,因为您没有硬编码一个 ViewModel 以与一个特定的 View 配对。如果您使用具有相同 ViewModel 的多个视图,它应该可以正常工作。如果你创建了一个新的 View 但你没有实现 DependencyProperty,它只会使用一个空句柄进行操作。

EDIT:

编辑:

As a side note, have you actually tested just not providing an IWin32Owner parameter? For me, it still automatically opens as a modal dialog for the application and blocks the user from interacting with all of the application's windows. Is there something else you need it to do instead?

作为旁注,您是否实际测试过只是不提供 IWin32Owner 参数?对我来说,它仍然会自动作为应用程序的模式对话框打开,并阻止用户与应用程序的所有窗口进行交互。你还需要它做什么吗?

回答by Oyun

MVVM + WinForms FolderBrowserDialog as behavior

MVVM + WinForms FolderBrowserDialog 作为行为

public class FolderDialogBehavior : Behavior<Button>
{
    public string SetterName { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Click += OnClick;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Click -= OnClick;
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        var dialog = new FolderBrowserDialog();
        var result = dialog.ShowDialog();
        if (result == DialogResult.OK && AssociatedObject.DataContext != null)
        {
            var propertyInfo = AssociatedObject.DataContext.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.CanRead && p.CanWrite)
            .Where(p => p.Name.Equals(SetterName))
            .First();

            propertyInfo.SetValue(AssociatedObject.DataContext, dialog.SelectedPath, null);
        }
    }
}

Usage

用法

     <Button Grid.Column="3" Content="...">
            <Interactivity:Interaction.Behaviors>
                <Behavior:FolderDialogBehavior SetterName="SomeFolderPathPropertyName"/>
            </Interactivity:Interaction.Behaviors>
     </Button>

Blogpost: http://kostylizm.blogspot.ru/2014/03/wpf-mvvm-and-winforms-folder-dialog-how.html

博文:http://kostylizm.blogspot.ru/2014/03/wpf-mvvm-and-winforms-folder-dialog-how.html

回答by Christoffer Eriksson

It's handy to use Behaviors in this case. Add a dependency property, and you can use that to bind the value from the dialog to a property in your viewmodel.

在这种情况下使用行为很方便。添加一个依赖属性,您可以使用它来将对话框中的值绑定到视图模型中的属性。

public class FolderBrowserDialogBehavior : Behavior<System.Windows.Controls.Button>
{
    /// <summary>
    /// Dependency Property for Path
    /// </summary>
    public static readonly DependencyProperty PathProperty =
        DependencyProperty.Register(nameof(Path), typeof(string), typeof(FolderBrowserDialogBehavior));

    /// <summary>
    /// Property wrapper for Path
    /// </summary>
    public string Path
    {
        get => (string) this.GetValue(PathProperty);
        set => this.SetValue(PathProperty, value);
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Click += OnClick;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Click -= OnClick;
    }

    /// <summary>
    /// Triggered when the Button is clicked.
    /// </summary>
    private void OnClick(object sender, RoutedEventArgs e)
    {
        using (var dialog = new FolderBrowserDialog())
        {
            try
            {
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    FilePath = dialog.SelectedPath;
                }
            }
            catch (Exception ex)
            {
                //Do something...
            }
        }
    }
}

In the view;

在视图中;

<Button ...>
    <i:Interaction.Behaviors>
        <behaviors:FolderBrowserDialogBehavior FilePath="{Binding Path=SomePropertyInViewModel, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
</Button>

回答by Batuu

To handle any kind of dialog stuff within the mvvm pattern, you should go with a kind of Dialog-Service. In this postyou will find some hints to go with this approach.

要在 mvvm 模式中处理任何类型的对话内容,您应该使用一种 Dialog-Service。在这篇文章中,您会发现一些使用这种方法的提示。

Putting dialog stuff into a service keeps the mvvm pattern untouched. The service takes care of all the creation of the dialogs and can provide the results. The view-model just calls methods and subscribes events provided by a service.

将对话内容放入服务中可以保持 mvvm 模式不变。该服务负责对话框的所有创建并可以提供结果。视图模型只是调用方法并订阅服务提供的事件。

if you use dependency injection for the service (interface), you get the advantage to keep you solution testable by mocking. Or you could replace the forms folderbrowserdialog if there will be a wpf one.

如果您对服务(接口)使用依赖注入,您将获得通过模拟保持解决方案可测试的优势。或者,如果有 wpf,您可以替换表单文件夹浏览器对话框。

回答by Erti-Chris Eelmaa

MVVM way:

MVVM方式:

define a new interface for FolderBrowserDialog. Create a new class & implement that interface. (Implementing is done with actual FolderBrowserDialog class).

为 FolderBrowserDialog 定义一个新接口。创建一个新类并实现该接口。(实现是用实际的 FolderBrowserDialog 类完成的)。

This way you will not tie MVVM to specific implementation and the actual logic can be later tested.

这样您就不会将 MVVM 与特定实现联系起来,并且可以稍后测试实际逻辑。