如何在WPF应用程序中使用文件夹浏览对话框

57

我正在尝试从我的WPF应用程序使用FolderBrowserDialog,没什么复杂的。我并不在意它具有Windows Forms外观。

然而,当我调用ShowDialog时,我想传递拥有者窗口,它是一个IWin32Window。如何从我的WPF控件中获取它?

实际上,这重要吗?如果我运行此代码并使用没有参数的ShowDialog重载,则可以正常工作。在什么情况下需要传递所有者窗口?

谢谢,

Craig


2
看看Sven Groot的奇妙 Ookii.Dialogs,它为WinForms和WPF提供了现代的“Vista”样式文件夹和文件对话框。 - lightw8
10个回答

65

这是我的最终版本。

public static class MyWpfExtensions
{
    public static System.Windows.Forms.IWin32Window GetIWin32Window(this System.Windows.Media.Visual visual)
    {
        var source = System.Windows.PresentationSource.FromVisual(visual) as System.Windows.Interop.HwndSource;
        System.Windows.Forms.IWin32Window win = new OldWindow(source.Handle);
        return win;
    }

    private class OldWindow : System.Windows.Forms.IWin32Window
    {
        private readonly System.IntPtr _handle;
        public OldWindow(System.IntPtr handle)
        {
            _handle = handle;
        }

        #region IWin32Window Members
        System.IntPtr System.Windows.Forms.IWin32Window.Handle
        {
            get { return _handle; }
        }
        #endregion
    }
}

然后实际使用它:

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

当我运行这段代码时,每当对话框关闭时都会抛出Win32Exception异常。这似乎不是问题,如果我只是捕获它,一切似乎都可以正常工作。您知道可能是什么原因引起的吗? - Scott Wisniewski
看起来工作正常,但我个人认为没有任何意义,即使使用0个参数调用仍然显示“相同”的模态对话框。 - John
1
对我来说工作得很好,到目前为止没有异常(.Net 3.5)。 必须添加一个System.Windows.Interop的 using 语句。 可以轻松扩展解决方案以与System.Windows.Forms.OpenFileDialog一起使用。 同伴读者们 - 不要忘记检查DialogResult以确保用户没有单击“取消”按钮。 - sfuqua
1
在我的按钮事件中,这个代码是有效的:dlg.ShowDialog(this.GetIWin32Window(this)); - Farrukh Waheed
1
没有Farrukh的建议,甚至无法编译...上面的使用代码是错误的,因为在调用GetIWin32Window( ???? )时没有提供参数。 - Jon

17

如果您指定所有者(Owner),则会在指定的WPF窗口上弹出模态对话框。

要获取与WinForms兼容的Win32窗口,请创建一个实现了IWin32Window的类,如下所示。

 public class OldWindow : System.Windows.Forms.IWin32Window
{
    IntPtr _handle;

    public OldWindow(IntPtr handle)
    {
        _handle = handle;
    }

    #region IWin32Window Members

    IntPtr System.Windows.Forms.IWin32Window.Handle
    {
        get { return _handle; }
    }

    #endregion
}

在您的WinForms中使用此类的实例。

        IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle; // 'this' means WPF Window
        folderBrowserDialog.ShowDialog(new OldWindow(mainWindowPtr));

谢谢你,这几乎是正确的 - 我会在下面发布一个答案。 - Craig Shearer
这正是我需要的,这里唯一有效的方法。System.Windows.PresentationSource.FromVisual(visual) 返回了 null。 - Mike Blandford

4

我知道这是一个老问题,但是这里有一种方法可能会更加优雅(之前可能已经存在),...

using System;
using System.Windows;
using System.Windows.Forms;

// ...

/// <summary>
///     Utilities for easier integration with WinForms.
/// </summary>
public static class WinFormsCompatibility {

    /// <summary>
    ///     Gets a handle of the given <paramref name="window"/> and wraps it into <see cref="IWin32Window"/>,
    ///     so it can be consumed by WinForms code, such as <see cref="FolderBrowserDialog"/>.
    /// </summary>
    /// <param name="window">
    ///     The WPF window whose handle to get.
    /// </param>
    /// <returns>
    ///     The handle of <paramref name="window"/> is returned as <see cref="IWin32Window.Handle"/>.
    /// </returns>
    public static IWin32Window GetIWin32Window(this Window window) {
        return new Win32Window(new System.Windows.Interop.WindowInteropHelper(window).Handle);
    }

    /// <summary>
    ///     Implementation detail of <see cref="GetIWin32Window"/>.
    /// </summary>
    class Win32Window : IWin32Window { // NOTE: This is System.Windows.Forms.IWin32Window, not System.Windows.Interop.IWin32Window!

        public Win32Window(IntPtr handle) {
            Handle = handle; // C# 6 "read-only" automatic property.
        }

        public IntPtr Handle { get; }

    }

}

然后,从您的WPF窗口,您只需简单地...
public partial class MainWindow : Window {

    void Button_Click(object sender, RoutedEventArgs e) {
        using (var dialog = new FolderBrowserDialog()) {
            if (dialog.ShowDialog(this.GetIWin32Window()) == System.Windows.Forms.DialogResult.OK) {
                // Use dialog.SelectedPath.
            }
        }
    }

}

实际上,这有关紧要吗?

我不确定在这种情况下是否重要,但通常情况下,您应该告诉Windows您的窗口层次结构,因此,如果单击父窗口而子窗口是模态的,则Windows可以向用户提供视觉(可能还有听觉)提示。

此外,它确保在存在多个模态窗口时,“正确”的窗口处于顶部(尽管我不主张这样的UI设计)。我曾经见过某个多亿美元公司设计的UI,只因为一个模态对话框被“卡”在另一个对话框下面而挂起,用户甚至都不知道它的存在,更别说如何关闭它了。


1
你使用什么来调用GetIWin32Window()函数? - StuiterSlurf
@StuiterSlurf 我不确定我理解你的问题。你是在问 using 指令吗? - Branko Dimitrijevic
是的,我的 Visual Studio 没有显示我需要获取哪个 using。 - StuiterSlurf
@StuiterSlurf 看一下第一个代码片段 - GetIWin32Window 在那里被定义为扩展方法,因此在第二个代码片段中无需使用 using 就可以自动访问它(假设两个代码片段在同一个项目中)。 - Branko Dimitrijevic

2
//add a reference to System.Windows.Forms.dll

public partial class MainWindow : Window, System.Windows.Forms.IWin32Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void button_Click(object sender, RoutedEventArgs e)
    {
        var fbd = new FolderBrowserDialog();
        fbd.ShowDialog(this);
    }

    IntPtr System.Windows.Forms.IWin32Window.Handle
    {
        get
        {
            return ((HwndSource)PresentationSource.FromVisual(this)).Handle;
        }
    }
}

这个能在WPF中使用吗?我无法让它工作;这段代码难道只能在Windows Forms中使用吗? - Malavos

2

好的,现在我理解了 - 多亏了Jobi的答案,虽然还不太完整。

下面是我能用的WPF应用程序代码:

首先是一个辅助类:

private class OldWindow : System.Windows.Forms.IWin32Window
{    
    IntPtr _handle;    
    public OldWindow(IntPtr handle)
    {
        _handle = handle;
    }   

    #region IWin32Window Members    
    IntPtr System.Windows.Forms.IWin32Window.Handle
    {
        get { return _handle; }
    }    
    #endregion
}

然后,要使用这个功能:
    System.Windows.Forms.FolderBrowserDialog dlg = new FolderBrowserDialog();
    HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
    System.Windows.Forms.IWin32Window win = new OldWindow(source.Handle);
    System.Windows.Forms.DialogResult result = dlg.ShowDialog(win);

我相信我可以更好地概括这个内容,但基本上它是有效的。耶! :-)


尝试使用我代码中的最后两行,而不是这4行,我认为它可以在没有FromVisual调用的情况下为您工作。 - Jobi Joy

1
这是一个简单的方法。
System.Windows.Forms.NativeWindow winForm; 

public MainWindow()
{
    winForm = new System.Windows.Forms.NativeWindow();
    winForm.AssignHandle(new WindowInteropHelper(this).Handle);
    ...
}

public showDialog()
{
   dlgFolderBrowser.ShowDialog(winForm);
}

1

VB.net 翻译

Module MyWpfExtensions

Public Function GetIWin32Window(this As Object, visual As System.Windows.Media.Visual) As System.Windows.Forms.IWin32Window

    Dim source As System.Windows.Interop.HwndSource = System.Windows.PresentationSource.FromVisual(Visual)
    Dim win As System.Windows.Forms.IWin32Window = New OldWindow(source.Handle)
    Return win
End Function

Private Class OldWindow
    Implements System.Windows.Forms.IWin32Window

    Public Sub New(handle As System.IntPtr)
        _handle = handle
    End Sub


    Dim _handle As System.IntPtr
    Public ReadOnly Property Handle As IntPtr Implements Forms.IWin32Window.Handle
        Get

        End Get
    End Property


End Class

End Module

0
传递所有者句柄的优点是FolderBrowserDialog不会对该窗口进行模态操作。这可以防止用户在对话框处于活动状态时与您的主应用程序窗口进行交互。

0

你可以通过使用 PresentationSource.FromVisual 方法并将其结果转换为实现了 IWin32Window 接口的 HwndSource 来获取一个 IWin32Window。

此外,在 这里 的注释中也提到了:


0
为什么不使用内置的WindowInteropHelper类(请参见命名空间System.Windows.Interop)。该类已经实现了IWin32Window接口;)
因此,您可以忘记“OldWindow类”...使用方式保持不变。

也许以前是这样,但在.NET 4中不再如此。 - MoonStom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接