【问题标题】:How to use a FolderBrowserDialog from a WPF application with MVVM如何使用带有 MVVM 的 WPF 应用程序中的 FolderBrowserDialog
【发布时间】:2012-08-30 15:00:18
【问题描述】:

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

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

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

问题是这样的:

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

在我的ViewModel 中,this 没有 GetIWin32Window() 方法让我获取 Window 上下文。

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

【问题讨论】:

    标签: c# .net wpf mvvm folderbrowserdialog


    【解决方案1】:

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

    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>
    

    【讨论】:

      【解决方案2】:

      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

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          【解决方案4】:

          如果你确定要使用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 的多个视图,它应该可以正常工作。如果你创建了一个新的 View 但你没有实现 DependencyProperty,它只会使用一个空句柄来操作。

          编辑:

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

          【讨论】:

            【解决方案5】:

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

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

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

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

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

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

            @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 以便测试可以继续。

            【讨论】:

              【解决方案6】:

              MVVM方式:

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

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

              【讨论】:

              • 他仍然需要将 IWin32Window 传递给 FolderBrowserDialog 以使其显示为正确的模式对话框。他想知道如何对 ViewModel 进行编程,以便能够检索其当前视图的 IWin32Window。
              • 是的,这就是实现细节。就像我说的,如果你实现了接口,实现就可以为所欲为。 Viewmodel 不需要知道任何东西。您可以通过 Application.Current.Windows.Where(x => x.IsActive = true) 获取当前视图
              猜你喜欢
              • 2010-09-23
              • 1970-01-01
              • 2011-05-31
              • 1970-01-01
              • 1970-01-01
              • 2013-01-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多