我正在尝试使用我的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()
没有{{1}}方法来获取Window上下文。
关于如何使这项工作的任何想法?
答案 0 :(得分: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中查看@ this question的答案,了解如何将View指针注入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
}
然后我们在VM / Command中使用FolderBrowser导入IFolderBrowserDialog。在应用程序中,IFolderBrowserDialog.ShowDialog显示对话框。在单元测试中,我们模拟IFolderBrowserDialog,以便我们可以验证它是否使用正确的参数调用和/或将选定的文件夹发送回sut,以便测试可以继续。
答案 1 :(得分:2)
如果您决定使用FolderBrowserDialog,我会使用这种设计。
首先,在View上创建一个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,它只会使用null句柄操作。
修改强>
作为旁注,您实际测试过没有提供IWin32Owner参数吗?对我来说,它仍然会自动打开作为应用程序的模式对话框,并阻止用户与所有应用程序的窗口进行交互。是否还有其他事情要做呢?
答案 2 :(得分: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>
Blogpost:http://kostylizm.blogspot.ru/2014/03/wpf-mvvm-and-winforms-folder-dialog-how.html
答案 3 :(得分:0)
MVVM方式:
为FolderBrowserDialog定义一个新接口。创建一个新的类&amp;实现该接口。 (使用实际的FolderBrowserDialog类完成实现。)
这样您就不会将MVVM与特定实现联系起来,以后可以对实际逻辑进行测试。
答案 4 :(得分:0)
要处理mvvm模式中的任何类型的对话框内容,您应该使用一种Dialog-Service。在this post中,您会发现这种方法的一些提示。
将对话框内容放入服务可保持mvvm模式不变。该服务负责所有对话框的创建,并可提供结果。视图模型只调用方法并订阅服务提供的事件。
如果对服务(接口)使用依赖注入,则可以通过模拟使您的解决方案具有可测试性。或者你可以替换表格folderbrowserdialog,如果有一个wpf。
答案 5 :(得分: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>