65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 WPF 扩展 OpenFileDialog 和 SaveFileDialog

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (21投票s)

2009 年 9 月 1 日

CPOL

9分钟阅读

viewsIcon

196744

downloadIcon

6391

使用 WPF 窗口自定义 OpenFileDialog 和 SaveFileDialog。

Release

目录

引言

我不知道你的想法,但有时我真希望有一种方法可以为OpenFileDialogSaveFileDialog添加额外功能,但现有的框架阻碍了我这样做。如果你使用的是Windows Forms,那么我以前的文章轻松扩展OpenFileDialog和SaveFileDialog[^]或许能解决这个问题。但是,如果你的应用程序设计为使用Microsoft在.NET 3.0中引入的Windows Presentation Foundation[^],这可能不是你想要走的路径。你可以尝试将Windows Presentation Foundation控件托管在Windows Forms中[^],但互操作性可能会带来一些问题[^]。即使有互操作解决方案,你可能仍然想要一个纯WPF解决方案,以避免使用System.Windows.Forms.dll,从而保持内存使用量低,或者保持UI一致性。

背景

与Windows Forms不同,WPF的OpenFileDialogSaveFileDialog工作方式不同,并且在这种情况下,在它们显示之前捕获消息或抓取句柄不起作用。因此,用于扩展其Windows Forms对应项的技巧不能应用于WPF,我不得不采用一种非常不同的替代方案。我使用了免费的Red Gate Reflector[^]从PresentationFramework.dll中获取相关代码,并在我自己的命名空间中重新创建现有功能。在尝试逆向工程OpenFileDialogSaveFileDialog时,我不得不深入到FileDialog类两层,因为这些类重写了一个名为RunDialogabstract方法,该方法接受一个OPENFILENAME_I类型的参数,而这个类型仅在PresentationFramework程序集中是内部的。

因此,我最终提取了一些额外的辅助类型,供我自己的FileDialog类型(新OpenFileDialogSaveFileDialog的基类)使用。由于我抓取的是程序集3.5版本的所有代码,请注意,.NET 4.0引入的Microsoft.Win32.FileDialog类中的一些新功能,如CustomPlaces[^]将不可用。所有内容都打包在WpfCustomFileDialog程序集中的同一命名空间下。

为了使事情更有趣,并让你体验真实应用程序的感觉,我将OpenFileDialog与一个基于Lee Brimelow的代码[^]的媒体查看器结合使用,并将SaveFileDialog与一个媒体编码器设置窗口结合使用,该窗口只是Armoghan Asif的工作[^]的重新设计。为了使后者正常工作,你需要下载并安装已过时的Windows Media Encoder 9[^],如果你想获得全部功能,当然还有.NET 3.5[^]或更高版本的运行时。然而,媒体组件或编码器的使用将不是本文的重点。

工作原理

我创建的新OpenFileDialogSaveFileDialog只能操作旧式窗口句柄,但现有的WPF控件没有句柄。这使得System.Windows.Window成为WPF子窗口的一个好选择,因为它是唯一一个具有易于访问的IntPtr类型句柄的控件。有一个缺点,正如你在下面的代码片段中看到的,需要额外的代码来处理不同的运行时版本。

幸运的是,还有一种方法可以将Windows句柄分配给一个不依赖于运行时版本的UserControl,在我看来,这使得它成为首选设计。我将在本文后面详细介绍。为了展示这两种方法的相似性,我在两个选项卡上复制了相同的功能,一个使用Window,另一个使用UserControl

我的新对话框类型必须知道将成为其子窗口的窗口,反之亦然,以便它们在需要时更新外观。为了在开发时进行强类型检查,我创建了两个接口,以便于强制执行类型安全性。旧的FileDialog类已成为新的FileDialogExt,如下所示:

public abstract partial class FileDialogExt<T>: Microsoft.Win32.CommonDialog, 
                              IFileDlgExt where T : ContentControl, IWindowExt, new()
{
    //....
}

你可能已经猜到,T是实现IWindowExt接口的子类型。尝试抽象UserControlWindow类型共享的共同行为,并使用继承和泛型约束来强制执行它是有意义的。

下面是接口定义

//implemented by the new child window
public interface IWindowExt
{
    HwndSource Source
    {
        set;
    }
    IFileDlgExt ParentDlg
    {
        set;
    }
}
//implemented by the dialog
public interface IFileDlgExt : IWin32Window
{
    event PathChangedEventHandler EventFileNameChanged;
    event PathChangedEventHandler EventFolderNameChanged;
    event FilterChangedEventHandler EventFilterChanged;
    AddonWindowLocation FileDlgStartLocation
    {
        set;
        get;
    }
    string FileDlgOkCaption
    {
        set;
        get;
    }
    bool FileDlgEnableOkBtn
    {
        set;
        get;
    }
    bool FixedSize
    {
        set;
    }
    NativeMethods.FolderViewMode FileDlgDefaultViewMode
    {
        set;
        get;
    }
}

一旦我们启动对话框,它就会进入如下定义的Windows循环

protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
    IntPtr hres = IntPtr.Zero;
    switch ((NativeMethods.Msg)msg)
    {
        case NativeMethods.Msg.WM_NOTIFY:
            hres = ProcOnNotify(hwnd, lParam);
            break;
        case NativeMethods.Msg.WM_SHOWWINDOW:
            InitControls();
            ShowChild();
            break;
        case NativeMethods.Msg.WM_INITDIALOG:
            _hwndFileDialog = NativeMethods.GetParent(new HandleRef(this, hwnd));
            _hwndFileDialogEmbedded = hwnd;
            NativeMethods.GetWindowRect(new HandleRef(this, _hwndFileDialog), 
                                        ref _OriginalRect);
            break;

// code omitted for brevity

        default:
            if (msg == (int)MSG_POST_CREATION)
            {
                CustomPostCreation();
            }
            break;
    }//switch ends
    return hres;
}

相关事件的顺序如下

  • 首先,保存对话框的句柄_hwndFileDialog以供后续使用。
  • 其次,当对话框即将显示时,WM_SHOWWINDOW会触发ShowChild(),从而创建子窗口。
  • 第三,在CustomPostCreation()中进行最终调整以正确显示它,该函数由ProcOnNotify()方法中的PostMessage调用触发。

ShowChild方法完成了大部分工作,建立了对话框与其子窗口之间的布局,如下所示:

private void ShowChild()
{
    _childWnd = new T();
    try
    {
        _childWnd.ParentDlg = this;
    }
    catch
    {
        return;
    }

    RECT dialogWindowRect = new RECT();
    RECT dialogClientRect = new RECT();

    Size size = new Size(dialogWindowRect.Width, dialogWindowRect.Height);
    NativeMethods.GetClientRect(new HandleRef(this, _hwndFileDialog), 
                                ref dialogClientRect);
    NativeMethods.GetWindowRect(new HandleRef(this, _hwndFileDialog), 
                                ref dialogWindowRect);
    int dy = (int)(dialogWindowRect.Height - dialogClientRect.Height);
    int dx = (int)(dialogWindowRect.Width - dialogClientRect.Width);
    size = new Size(dialogWindowRect.Width, dialogWindowRect.Height);

    if (_childWnd is Window)
    {
        Window wnd = _childWnd as Window;
        wnd.WindowStyle = WindowStyle.None;
        wnd.ResizeMode = ResizeMode.NoResize;//will fix the child window!!
        wnd.ShowInTaskbar = false;
        //won't flash on screen
        wnd.WindowStartupLocation = WindowStartupLocation.Manual;
        wnd.Left = -10000;
        wnd.Top = -10000;
        wnd.SourceInitialized += delegate(object sender, EventArgs e)
      {
          try
          {
              _source = System.Windows.PresentationSource.FromVisual(
                                      _childWnd as Window) as HwndSource;
              _source.AddHook(EmbededWndProc);
              _childWnd.Source = _source;
          }
          catch{}
      };
        wnd.Show();
        long styles = (long)NativeMethods.GetWindowLongPtr(new HandleRef(_childWnd, 
                                         _source.Handle), GWL.GWL_STYLE);
        if (IntPtr.Size == 4)
        {
            styles |= System.Convert.ToInt64(NativeMethods.WindowStyles.WS_CHILD);
            styles ^= System.Convert.ToInt64(NativeMethods.WindowStyles.WS_SYSMENU);
        }
        else
        {
            styles |= (long)NativeMethods.WindowStyles.WS_CHILD;
            styles ^= (long)NativeMethods.WindowStyles.WS_SYSMENU;
        }
        NativeMethods.CriticalSetWindowLong(new HandleRef(this, _source.Handle), 
                                           (int)GWL.GWL_STYLE, new IntPtr(styles));

        // Everything is ready, now lets change the parent
        NativeMethods.SetParent(new HandleRef(_childWnd, _source.Handle), 
                                new HandleRef(this, _hwndFileDialog));
    }
    else
    {// what if the child is not a Window 
        ContentControl ctrl = _childWnd as ContentControl;
        HwndSourceParameters parameters = new HwndSourceParameters("WPFDlgControl", 
                                              (int)ctrl.Width, (int)ctrl.Height);
        parameters.WindowStyle = (int)NativeMethods.WindowStyles.WS_VISIBLE | 
                                 (int)NativeMethods.WindowStyles.WS_CHILD;
        parameters.SetPosition((int)_OriginalRect.Width, (int)_OriginalRect.Height);
        parameters.ParentWindow = _hwndFileDialog;
        parameters.AdjustSizingForNonClientArea = false;
        switch (this.FileDlgStartLocation)
        {
            case AddonWindowLocation.Right:
                parameters.PositionX = (int)_OriginalRect.Width - dx/2;
                parameters.PositionY = 0;
                if (ctrl.Height < _OriginalRect.Height - dy)
                    ctrl.Height = parameters.Height = (int)_OriginalRect.Height - dy;
                break;

            case AddonWindowLocation.Bottom:
                parameters.PositionX = 0;
                parameters.PositionY = (int)(_OriginalRect.Height - dy +dx/2);
                if (ctrl.Width < _OriginalRect.Width - dx)
                    ctrl.Width = parameters.Width = (int)_OriginalRect.Width - dx;
                break;
            case AddonWindowLocation.BottomRight:
                parameters.PositionX = (int)_OriginalRect.Width - dx/2;
                parameters.PositionY = (int)(_OriginalRect.Height - dy +dx/2);
                break;
        }

        _source = new HwndSource(parameters);
        _source.CompositionTarget.BackgroundColor = 
                                  System.Windows.Media.Colors.LightGray;
        _source.RootVisual = _childWnd as System.Windows.Media.Visual;
        _source.AddHook(new HwndSourceHook(EmbededCtrlProc));
    }
    switch (this.FileDlgStartLocation)
    {
        case AddonWindowLocation.Right:
            size.Width = _OriginalRect.Width + _childWnd.Width;
            size.Height = _OriginalRect.Height;
            break;

        case AddonWindowLocation.Bottom:
            size.Width = _OriginalRect.Width;
            size.Height = _OriginalRect.Height + _childWnd.Height;
            break;
        case AddonWindowLocation.BottomRight:
            size.Height = _OriginalRect.Height + _childWnd.Height;
            size.Width = _OriginalRect.Width + _childWnd.Width;
            break;
    }
    NativeMethods.SetWindowPos(new HandleRef(this, _hwndFileDialog), new HandleRef(this, 
           (IntPtr)NativeMethods.ZOrderPos.HWND_BOTTOM),
            0, 0, (int)size.Width, (int)size.Height, 
            NativeMethods.SetWindowPosFlags.SWP_NOZORDER);
}

当使用Window时,上面的匿名委托捕获了HwndSource,它将提供句柄及其消息循环。如果使用UserControlHwndSource将以编程方式创建并与之关联,以便我们可以获得Windows句柄来处理。最后,CustomPostCreation将进行最终的重排,使其看起来像一个真正的子窗口。

private void CustomPostCreation()
{
    _hListViewPtr = NativeMethods.GetDlgItem
	(this._hwndFileDialog, (int)NativeMethods.ControlsId.DefaultView);
    UpdateListView(_hListViewPtr);
    if (_bFixedSize)
        SetFixedSize(_hwndFileDialog);
    RECT dialogWndRect = new RECT();
    NativeMethods.GetWindowRect(new HandleRef(this, this._hwndFileDialog), 
	ref dialogWndRect);
    RECT dialogClientRect = new RECT();
    NativeMethods.GetClientRect(new HandleRef(this, this._hwndFileDialog), 
	ref dialogClientRect);
    uint dx = dialogWndRect.Width - dialogClientRect.Width;
    uint dy = dialogWndRect.Height - dialogClientRect.Height;
    if (_childWnd is Window)
    {
        Window wnd = _childWnd as Window;
        //restore the original size
        switch (FileDlgStartLocation)
        {
            case AddonWindowLocation.Bottom:
                int left = (Environment.Version.Major >= 4) 
			? -(int)dx / 2 : dialogWndRect.left;
                if (wnd.Width >= _OriginalRect.Width - dx)
                {
                    NativeMethods.MoveWindow(new HandleRef(this, this._hwndFileDialog), 
			left, dialogWndRect.top, (int)(wnd.ActualWidth + dx / 2), 
			(int)(_OriginalRect.Height + wnd.ActualHeight), true);
                }
                else
                {
                    NativeMethods.MoveWindow(new HandleRef(this, this._hwndFileDialog), 
			left, dialogWndRect.top, (int)(_OriginalRect.Width), 
			(int)(_OriginalRect.Height + wnd.ActualHeight), true);
                    wnd.Width = _OriginalRect.Width - dx / 2;
                }
                wnd.Left = 0;
                wnd.Top = _OriginalRect.Height - dy + dx / 2;
                break;
            case AddonWindowLocation.Right:
                int top = (Environment.Version.Major >= 4) ? (int)(dx / 2 - dy) : 
			dialogWndRect.top;
                if (wnd.Height >= _OriginalRect.Height - dy)
                    NativeMethods.MoveWindow(new HandleRef(this, _hwndFileDialog), 
			(int)(dialogWndRect.left), top, (int)(_OriginalRect.Width + 
			wnd.ActualWidth), (int)(wnd.ActualHeight + dy - dx / 2), true);
                else
                {
                    NativeMethods.MoveWindow(new HandleRef(this, _hwndFileDialog), 
			(int)(dialogWndRect.left), top, (int)(_OriginalRect.Width + 
			wnd.ActualWidth), (int)(_OriginalRect.Height - dx / 2), true);
                    wnd.Height = _OriginalRect.Height - dy;
                }
                    wnd.Top = 0;
                    wnd.Left = _OriginalRect.Width - dx / 2;
                break;
            case AddonWindowLocation.BottomRight:
                NativeMethods.MoveWindow(new HandleRef(this, _hwndFileDialog), 
		dialogWndRect.left, dialogWndRect.top, (int)(_OriginalRect.Width + 
		wnd.Width), (int)(int)(_OriginalRect.Height + wnd.Height), true);
                wnd.Top = _OriginalRect.Height - dy + dx / 2;
                wnd.Left = _OriginalRect.Width - dx / 2;
                break;
        }
    }
    else
    {
        ContentControl ctrl = _childWnd as ContentControl;
        //restore the original size
        const NativeMethods.SetWindowPosFlags flags = 
	NativeMethods.SetWindowPosFlags.SWP_NOZORDER | 
	NativeMethods.SetWindowPosFlags.SWP_NOMOVE;//| 
		SetWindowPosFlags.SWP_NOREPOSITION | 
		SetWindowPosFlags.SWP_ASYNCWINDOWPOS | 
		SetWindowPosFlags.SWP_SHOWWINDOW | SetWindowPosFlags.SWP_DRAWFRAME;
        switch (FileDlgStartLocation)
        {
            case AddonWindowLocation.Bottom:
                NativeMethods.SetWindowPos(new HandleRef(this, this._hwndFileDialog), 
		new HandleRef(this,(IntPtr)ZOrderPos.HWND_BOTTOM),
                    dialogWndRect.left, dialogWndRect.top, 
			(int)(ctrl.ActualWidth + dx / 2), 
			(int)(_OriginalRect.Height + ctrl.ActualHeight), flags);
                NativeMethods.SetWindowPos(new HandleRef(ctrl, _source.Handle), 
			new HandleRef(_source, (IntPtr)ZOrderPos.HWND_BOTTOM),
                      	0, (int)(_OriginalRect.Height - dy + dx / 2), 
			(int)(ctrl.Width), (int)(ctrl.Height), flags);
                break;
            case AddonWindowLocation.Right:
                NativeMethods.SetWindowPos(new HandleRef(this, this._hwndFileDialog), 
			new HandleRef(this, (IntPtr)ZOrderPos.HWND_BOTTOM),
                    (int)(dialogWndRect.left), dialogWndRect.top, 
			(int)(_OriginalRect.Width + ctrl.ActualWidth - dx / 2), 
			(int)(ctrl.ActualHeight + dy - dx / 2), flags);
                NativeMethods.SetWindowPos(new HandleRef(ctrl, _source.Handle), 
			new HandleRef(_source, (IntPtr)ZOrderPos.HWND_BOTTOM),
                  		(int)(_OriginalRect.Width - dx), (int)(0), 
			(int)(ctrl.Width), (int)(ctrl.Height), flags);
                break;
            case AddonWindowLocation.BottomRight:
                NativeMethods.SetWindowPos(new HandleRef(this, this._hwndFileDialog), 
			new HandleRef(this, (IntPtr)ZOrderPos.HWND_BOTTOM),
                     	dialogWndRect.left, dialogWndRect.top, 
			(int)(_OriginalRect.Width + ctrl.Width), 
			(int)(_OriginalRect.Height + ctrl.Height), flags);
                break;
        }
    }
    CenterDialogToScreen();
    NativeMethods.InvalidateRect(new HandleRef(this, _source.Handle), IntPtr.Zero, true);
}

修复互操作性问题

正如你可能预料到的那样,将WPF与Win32混合使用会带来问题。这就是为什么我们需要来自子窗口或User Control的HwndSourceHook委托。它不仅提供了Win32句柄,还提供了一个消息循环来解决这个问题。

IntPtr EmbededWndProc(IntPtr hwnd, int msg, IntPtr wParam, 
                      IntPtr lParam, ref bool handled)
{
    IntPtr hres = IntPtr.Zero;
    const int DLGC_WANTALLKEYS = 0x0004;
    switch ((NativeMethods.Msg)msg)
    {
        case NativeMethods.Msg.WM_SYSKEYDOWN:
            SetChildStyle(true);
            break;
        case NativeMethods.Msg.WM_SYSKEYUP:
            SetChildStyle(false);
            break;
        //see http://support.microsoft.com/kb/83302
        case NativeMethods.Msg.WM_GETDLGCODE:
            if (lParam != IntPtr.Zero)
                hres = (IntPtr)DLGC_WANTALLKEYS;
            handled = true;
            break;
    }//switch ends
    return hres;
}

如果没有上面显示的修复,TextBox将无法接受大多数按键,并且Alt键将无法正常工作。

如何自定义Windows 2000和XP上OpenFileDialog和SaveFileDialog的“位置”栏

您需要最新版本的Windows和.NET 4.0才能使用Microsoft.Win32.FileDialog.CustomPlaces[^]。由于这不包含在内,因为我的代码是基于旧的.NET 3.5,所以我包含了我自己的实现,该实现也可以处理旧版本。此功能的代码与我在上一篇文章轻松扩展OpenFileDialog和SaveFileDialog[^]中所做的类似。主要区别在于,我使用的是FileDialogExt类中的普通数据成员,而不是扩展方法。重写的FileDialog.RunDialog调用下面描述的方法:

private readonly string TempKeyName = "TempPredefKey_" + Guid.NewGuid().ToString();
private const string Key_PlacesBar = 
	@"Software\Microsoft\Windows\CurrentVersion\Policies\ComDlg32\PlacesBar";
private RegistryKey _fakeKey;
private IntPtr _overriddenKey;
private object[] m_places;

public void SetPlaces(object[] places)
{
    if (m_places == null)
        m_places = new object[5];
    else
        m_places.Initialize();
    if (places != null)
    {
        for (int i = 0; i < m_places.GetLength(0); i++)
        {
            m_places[i] = places[i];

        }
    }
}

public void ResetPlaces()
{
    if (_overriddenKey != IntPtr.Zero)
    {
        ResetRegistry(_overriddenKey);
        _overriddenKey = IntPtr.Zero;
    }
    if (_fakeKey != null)
    {
        _fakeKey.Close();
        _fakeKey = null;
    }
    //delete the key tree
    Registry.CurrentUser.DeleteSubKeyTree(TempKeyName);
    m_places = null;
}

private void SetupFakeRegistryTree()
{
    _fakeKey = Registry.CurrentUser.CreateSubKey(TempKeyName);
    _overriddenKey = InitializeRegistry();
    // at this point, m_TempKeyName equals places key
    // write dynamic places here reading from Places
    RegistryKey reg = Registry.CurrentUser.CreateSubKey(Key_PlacesBar);
    for (int i = 0; i < m_places.GetLength(0); i++)
    {
        if (m_places[i] != null)
        {
            reg.SetValue("Place" + i.ToString(), m_places[i]);
        }
    }
}

readonly UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);
private IntPtr InitializeRegistry()
{
    IntPtr hkMyCU;
    NativeMethods.RegCreateKeyW(HKEY_CURRENT_USER, TempKeyName, out hkMyCU);
    NativeMethods.RegOverridePredefKey(HKEY_CURRENT_USER, hkMyCU);
    return hkMyCU;
}

private void ResetRegistry(IntPtr hkMyCU)
{
    NativeMethods.RegOverridePredefKey(HKEY_CURRENT_USER, IntPtr.Zero);
    NativeMethods.RegCloseKey(hkMyCU);
    return;
}

您只需要关注调用SetPlaces,它接受最多五个对象的数组,这些对象可以是表示Windows特殊文件夹的数字,也可以是常规文件夹的字符串。为了方便起见,我包含了用于预定义特殊文件夹的Places辅助枚举。

如何使用控件

要使用这些控件,您需要遵循几个步骤:

  1. WpfCustomFileDialog项目添加到您的解决方案并引用它。如果您不喜欢这种方式,作为其他替代方案,您可以只引用此DLL或将代码放入您自己的项目中。
  2. 创建子窗口或用户控件,并实现IWindowExt接口。
  3. 在您的子窗口中或外部设置IFileDlgExt公开的属性和事件。
  4. 使用构造函数和子窗口类型作为泛型参数创建对话框。

实现IWindowExt

您有两种方法可以做到这一点。第一种方法是直接在类中实现IWindowExt。您可以通过复制下面的代码来实现IWindowExt接口:

System.Windows.Interop.HwndSource _source;
IFileDlgExt _parentDlg;

public System.Windows.Interop.HwndSource Source
{
    set
    {_source = value;}     
}

public IFileDlgExt ParentDlg
{
    set { _parentDlg = value; }
    get { return _parentDlg; }
}

protected override Size ArrangeOverride(Size arrangeBounds)
{
  
            if (Height > 0 && Width > 0)
            {
                arrangeBounds.Height = this.Height;
                arrangeBounds.Width = this.Width;
            }
            return base.ArrangeOverride(arrangeBounds);
}

虽然虚拟的ArrangeOverride不是接口的一部分,但它必须像上面那样被重写,以避免尺寸问题。

继承自WindowAddOnBase或ControlAddOnBase类

您可能已经注意到,上述功能非常适合作为基类,因此第二种选择是继承我已在WpfCustomFileDialog程序集中创建的现有类。

它们是WindowAddOnBaseControlAddOnBase,继承只是一个普通的类继承:

public partial class SelectWindow : WpfCustomFileDialog.WindowAddOnBase
{
   .....
}

public partial class SelectControl : WpfCustomFileDialog.ControlAddOnBase
{
   .....
}

不幸的是,WPF窗口继承不受Visual Studio设计器支持,您需要在XAML文件中进行一些手动输入[^]。我的继承类必须设置xmlns:src属性,SelectWindow.xaml文件将变成:

<src:WindowAddOnBase x:Class="VideoManager.SelectWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src="clr-namespace:WpfCustomFileDialog;assembly=WpfCustomFileDialog"
    .......
</src:WindowAddOnBase>

ControlAddOnBase遵循相同的规则:

<src:ControlAddOnBase x:Class="VideoManager.SelectControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src="clr-namespace:WpfCustomFileDialog;assembly=WpfCustomFileDialog"
    .........
</src:ControlAddOnBase>

幸运的是,在您构建程序集/项目后,设计器将不会对此进行投诉。

创建和调用控件

您可以从调用方设置属性和事件,如下所示:

var ofd = new WpfCustomFileDialog.OpenFileDialog<SelectWindow>();
ofd.Filter = "avi files (*.avi)|*.avi|wmv files (*.wmv)|*.wmv|All files (*.*)|*.*";
ofd.Multiselect = false;
ofd.Title = "Select Media file";
ofd.FileDlgStartLocation = AddonWindowLocation.Right;
ofd.FileDlgDefaultViewMode = NativeMethods.FolderViewMode.Tiles;
ofd.FileDlgOkCaption = "&Select";
ofd.FileDlgEnableOkBtn = false;
ofd.SetPlaces(new object[] { @"c:\", (int)Places.MyComputer, 
	(int)Places.Favorites, (int)Places.All_Users_MyVideo, (int)Places.MyVideos });
bool? res = ofd.ShowDialog(this);

if (res.Value == true)
{
    //........
}

使用User Control几乎与使用Window相同,所以我不会重复非常相似的代码。您也可以在您创建的子窗口或控件内部实现相同的功能。

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _parentDlg.FileDlgDefaultViewMode = NativeMethods.FolderViewMode.List;
    var fd = _parentDlg as SaveFileDialog<TargetWindow>;
    fd.FileOk += new System.ComponentModel.CancelEventHandler(CreateEncodeInfo);
}

由于本文的全部目的是实现对话框与WPF子窗口之间的交互,尤其是在使用OpenFileDialog时,您应该利用事件来更新两者。

private void Window_Loaded(object sender, RoutedEventArgs e)
{
 ParentDlg.EventFileNameChanged += 
           new PathChangedEventHandler(ParentDlg_EventFileNameChanged);
 ParentDlg.EventFolderNameChanged += 
           new PathChangedEventHandler(ParentDlg_EventFolderNameChanged);
 ParentDlg.EventFilterChanged += 
           new FilterChangedEventHandler(ParentDlg_EventFilterChanged);
 _comboSpeed.IsEnabled = false;
}

请注意,ParentDlg在构造函数中为null,并且仅在后续事件中初始化。

我提供了Visual Studio 2008 (v9)和2010 (v10)的解决方案。如果您选择不安装Windows Media Encoder 9,您仍然会获得一些有限的运行时功能,但VS 2010解决方案可能无法构建。要解决此问题,您需要将现有的Interop.WMEncoderLib.dll程序集添加为VideoManager项目的引用。
如果Visual Studio开始抱怨奇怪的构建或运行错误,请关闭解决方案,删除所有binobj文件夹,然后重试。

历史

我仅在Windows XP SP3 32位和.NET 3.5及4.0上测试了此项目。您可能认为一切都很简单,但我可以告诉您,开发过程涉及大量的试错阶段或妥协。如果您找到了改进它的方法,例如从最新版本的PresentationFramework.dll程序集中获取相关代码,或进行错误修复,请告诉我,我将在未来的更新中尝试包含它们。

如果您对视频编码感兴趣,您应该考虑将现有的已弃用的Windows Media Encoder 9编码器替换为最新的Expression Encoder[^]。虽然本文所示代码仅为C#,但对于VB.NET用户,我在下载源文件中包含了Visual Basic .NET的等效解决方案,适用于Visual Studio 2008和2010。

  • 版本1.0 原始版本
  • 版本1.1 改进了子窗口布局,抑制了窗口闪烁,最重要的是,增加了对UserControl作为扩展的支持。
  • 版本1.2 添加了我自己的CustomPlaces,并修复了与.NET 4.0的一些不兼容性。
  • 版本1.3 添加了VS 2013项目样式,对CustomPlaces的访问警告,并将最新源代码移至codeplex
© . All rights reserved.