5

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

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

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

问题是这样的:

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

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

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

4

6 回答 6

4

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

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

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

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

第二个选项可能不被认为是非常 MVVMish。

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

@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
  }

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

于 2012-09-05T20:46:50.920 回答
2

如果你确定要使用FolderBrowserDialog,我会使用这种设计。

首先,在您的视图上创建一个 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); }
}

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

// 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();
}

因此,您使用“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");
        }
    }
}

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

编辑:

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

于 2012-09-05T21:18:21.627 回答
1

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);
        }
    }
}

用法

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

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

于 2014-04-09T10:37:21.923 回答
0

MVVM方式:

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

这样您就不会将 MVVM 绑定到特定的实现,并且可以稍后测试实际的逻辑。

于 2012-09-05T20:50:30.810 回答
0

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

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

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

于 2012-09-05T23:10:16.670 回答
0

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

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...
            }
        }
    }
}

在视图中;

<Button ...>
    <i:Interaction.Behaviors>
        <behaviors:FolderBrowserDialogBehavior FilePath="{Binding Path=SomePropertyInViewModel, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
</Button>
于 2019-02-27T14:41:55.463 回答