使用 WPF 扩展 OpenFileDialog 和 SaveFileDialog






4.90/5 (21投票s)
使用 WPF 窗口自定义 OpenFileDialog 和 SaveFileDialog。
目录
引言
我不知道你的想法,但有时我真希望有一种方法可以为OpenFileDialog
和SaveFileDialog
添加额外功能,但现有的框架阻碍了我这样做。如果你使用的是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的OpenFileDialog
和SaveFileDialog
工作方式不同,并且在这种情况下,在它们显示之前捕获消息或抓取句柄不起作用。因此,用于扩展其Windows Forms对应项的技巧不能应用于WPF,我不得不采用一种非常不同的替代方案。我使用了免费的Red Gate Reflector[^]从PresentationFramework.dll中获取相关代码,并在我自己的命名空间中重新创建现有功能。在尝试逆向工程OpenFileDialog
和SaveFileDialog
时,我不得不深入到FileDialog
类两层,因为这些类重写了一个名为RunDialog
的abstract
方法,该方法接受一个OPENFILENAME_I
类型的参数,而这个类型仅在PresentationFramework
程序集中是内部的。
因此,我最终提取了一些额外的辅助类型,供我自己的FileDialog
类型(新OpenFileDialog
和SaveFileDialog
的基类)使用。由于我抓取的是程序集3.5版本的所有代码,请注意,.NET 4.0引入的Microsoft.Win32.FileDialog
类中的一些新功能,如CustomPlaces[^]将不可用。所有内容都打包在WpfCustomFileDialog
程序集中的同一命名空间下。
为了使事情更有趣,并让你体验真实应用程序的感觉,我将OpenFileDialog
与一个基于Lee Brimelow的代码[^]的媒体查看器结合使用,并将SaveFileDialog
与一个媒体编码器设置窗口结合使用,该窗口只是Armoghan Asif的工作[^]的重新设计。为了使后者正常工作,你需要下载并安装已过时的Windows Media Encoder 9[^],如果你想获得全部功能,当然还有.NET 3.5[^]或更高版本的运行时。然而,媒体组件或编码器的使用将不是本文的重点。
工作原理
我创建的新OpenFileDialog
和SaveFileDialog
只能操作旧式窗口句柄,但现有的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
接口的子类型。尝试抽象UserControl
和Window
类型共享的共同行为,并使用继承和泛型约束来强制执行它是有意义的。
下面是接口定义
//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
,它将提供句柄及其消息循环。如果使用UserControl
,HwndSource
将以编程方式创建并与之关联,以便我们可以获得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
辅助枚举。
如何使用控件
要使用这些控件,您需要遵循几个步骤:
- 将
WpfCustomFileDialog
项目添加到您的解决方案并引用它。如果您不喜欢这种方式,作为其他替代方案,您可以只引用此DLL或将代码放入您自己的项目中。 - 创建子窗口或用户控件,并实现
IWindowExt
接口。 - 在您的子窗口中或外部设置
IFileDlgExt
公开的属性和事件。 - 使用构造函数和子窗口类型作为泛型参数创建对话框。
实现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
程序集中创建的现有类。
它们是WindowAddOnBase
和ControlAddOnBase
,继承只是一个普通的类继承:
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开始抱怨奇怪的构建或运行错误,请关闭解决方案,删除所有bin和obj文件夹,然后重试。
历史
我仅在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。