最近想实现这么一个功能:通过OpenFileDialog对话框选择文件时,每选中一个文件,能够预览该文件的内容。正好园子里有位朋友分享了这样的代码:http://www.cnblogs.com/xiaozhi_5638/archive/2012/12/21/2828376.html
这位朋友确实厉害啊,谢谢他的分享!
我对他的程序进行了一些完善,主要改进如下(文章最后有源代码下载):
1、OpenFileDialog的窗口宽度会非常大,有1600多像素,我限制了一下宽度。
2、响应WM_ACTIVATE消息时,NativeWindow会重复创建多次,因为主窗体的消息也进入这个方法了,我把多余的窗口Handle排除了。否则,关闭OpenFileDialog之后,每次点击主窗口的边框,窗口宽度都会发生变化。
3、关闭OpenFileDialog之后,主窗口会被其它窗口盖住。
4、封装成了一个支持文件预览的通用对话框,并继承Component,可以直接拖放到窗体上。
最后的运行效果如下图所示:
源代码如下:
View Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.ComponentModel; using System.Runtime.InteropServices; using System.Drawing; namespace WindowsFormsApplication1 { /// <summary> /// 扩展文件打开对话框。不可继承该类。 /// 支持自定义的文件预览功能。 /// </summary> [DefaultEvent("FileSelecting")] public sealed class OpenFileDialogEx : Component { #region 字段区域 private string m_fileName = string.Empty; private string m_filer = string.Empty; private Control m_previewControl; #endregion #region 属性区域 /// <summary> /// 获取或设置当前选择的文件名。 /// </summary> [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string FileName { get { return m_fileName; } set { m_fileName = value ?? string.Empty; } } /// <summary> /// 获取或设置文件筛选条件。 /// </summary> [Description("文件筛选条件。")] public string Filer { get { return m_filer; } set { m_filer = value ?? string.Empty; } } /// <summary> /// 获取或设置文件预览控件。 /// </summary> [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Control PreviewControl { get { return m_previewControl; } set { m_previewControl = value; } } #endregion #region 方法区域 /// <summary> /// 显示模式对话框。 /// </summary> /// <returns></returns> public DialogResult ShowDialog() { return ShowDialog(null); } /// <summary> /// 显示模式对话框。 /// </summary> /// <param name="owner">宿主控件。</param> /// <returns></returns> public DialogResult ShowDialog(IWin32Window owner) { using (OpenFileDialog dialog = new OpenFileDialog() { FileName = m_fileName, Filter = m_filer }) { //在Vista、WIN7、WIN8上按XP风格显示对话框 dialog.AutoUpgradeEnabled = false; OpenFileDialogHostForm hostForm = new OpenFileDialogHostForm(this, dialog); if (owner != null) hostForm.Show(owner); else hostForm.Show(Application.OpenForms[0]); //隐藏中间窗体 Win32.SetWindowPos(hostForm.Handle, IntPtr.Zero, 0, 0, 0, 0, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOOWNERZORDER | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_HIDEWINDOW); //将median作为openfileDialog的owner DialogResult result = dialog.ShowDialog(hostForm); if (result == DialogResult.OK) { m_fileName = dialog.FileName; } hostForm.Close(); hostForm.Dispose(); return result; } } #endregion #region 事件委托 /// <summary> /// 选择文件时引发该事件。 /// </summary> public event EventHandler<OpenFileDialogExPathEventArgs> FileSelecting; /// <summary> /// 打开路径时引发该事件。 /// </summary> public event EventHandler<OpenFileDialogExPathEventArgs> PathOpened; /// <summary> /// 选择文件时调用该方法。 /// </summary> /// <param name="fileName"></param> public void OnFileSelecting(string fileName) { if (FileSelecting != null && !string.IsNullOrEmpty(fileName) && !string.IsNullOrEmpty(System.IO.Path.GetExtension(fileName))) { FileSelecting(this, new OpenFileDialogExPathEventArgs(fileName)); } } /// <summary> /// 打开路径时调用该方法。 /// </summary> /// <param name="path"></param> public void OnPathOpened(string path) { if (PathOpened != null && !string.IsNullOrEmpty(path)) { PathOpened(this, new OpenFileDialogExPathEventArgs(path)); } } #endregion #region 内部类型 /// <summary> /// OpenFileDialog宿主窗体。 /// </summary> class OpenFileDialogHostForm : Form { #region 构造区域 /// <summary> /// 构造函数。 /// </summary> /// <param name="dialogEx"></param> /// <param name="dialog"></param> public OpenFileDialogHostForm(OpenFileDialogEx dialogEx, OpenFileDialog dialog) { m_dialogEx = dialogEx; m_dialog = dialog; this.StartPosition = FormStartPosition.Manual; this.Location = new System.Drawing.Point(-1000, -1000); //隐藏窗口,避免界面闪烁 } #endregion #region 字段区域 private OpenFileDialogEx m_dialogEx; private OpenFileDialog m_dialog = null; private DialogNativeWindow m_nativeWindow; #endregion #region 方法区域 /// <summary> /// 窗口关闭前。 /// </summary> /// <param name="e"></param> protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { if (m_nativeWindow != null) m_nativeWindow.Dispose(); base.OnClosing(e); } /// <summary> /// 处理窗口消息。 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { //m.LParam为要打开的窗口句柄,开始监听OpenFileDialog的Windows消息 if (m.Msg == (int)Msg.WM_ACTIVATE) { //跳过不需要监听的窗口 bool needInitNative = true; if (Application.OpenForms != null && Application.OpenForms.Count > 0) { foreach (Form frm in Application.OpenForms) { if (m.LParam == frm.Handle && frm.Handle != this.Handle) needInitNative = false; } } if (m_nativeWindow == null && needInitNative) m_nativeWindow = new DialogNativeWindow(m_dialogEx, m.LParam, m_dialog); } base.WndProc(ref m); } #endregion } /// <summary> /// OpenFileDialog钩子窗口。 /// </summary> class DialogNativeWindow : NativeWindow, IDisposable { #region 构造区域 /// <summary> /// 构造函数。 /// </summary> /// <param name="dialogEx"></param> /// <param name="handle">要监视的窗口句柄。</param> /// <param name="dialog">打开文件的对话框。</param> public DialogNativeWindow(OpenFileDialogEx dialogEx, IntPtr handle, OpenFileDialog dialog) { m_dialogEx = dialogEx; m_dialog = dialog; AssignHandle(handle); } #endregion #region 字段区域 private OpenFileDialogEx m_dialogEx; private OpenFileDialog m_dialog; //待扩展OpenFileDialog private ChildControlNativeWindow m_childNative; private bool m_isInited;//自定义控件是否已初始化 private bool m_isDisposed; #endregion #region 属性区域 /// <summary> /// 获取一个值,该值指示当前资源是否已被释放。 /// </summary> public bool IsDisposed { get { return m_isDisposed; } } #endregion #region 方法区域 /// <summary> /// 处理窗口消息。 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { switch (m.Msg) { case (int)Msg.WM_SHOWWINDOW: InitChildNative(); InitCustomControl(); break; case (int)Msg.WM_SIZING: UpdateSize(); break; case (int)Msg.WM_WINDOWPOSCHANGING: UpdateLocation(m); break; } base.WndProc(ref m); } /// <summary> /// 初始化子控件的NativeWindow。 /// </summary> private void InitChildNative() { //查找openfileDialog中的子控件 Win32.EnumChildWindows(this.Handle, new Win32.EnumWindowsCallBack((IntPtr handle, int lparam) => { StringBuilder sb = new StringBuilder(256); Win32.GetClassName(handle, sb, sb.Capacity);//获取控件类名 if (sb.ToString().StartsWith("#32770")) //找到目标控件 { m_childNative = new ChildControlNativeWindow(handle); m_childNative.SelectFileChanged += new ChildControlNativeWindow.SelectFileChangedEventHandler(childNative_SelectFileChanged); m_childNative.SelectPathChanged += new ChildControlNativeWindow.SelectPathChangedEventHandler(childNative_SelectPathChanged); return true; } return true; }), 0); } /// <summary> /// 初始化自定义控件。 /// </summary> private void InitCustomControl() { if (m_dialogEx.PreviewControl != null && !m_dialogEx.PreviewControl.IsDisposed) { //添加控件到OpenFileDialog界面 Win32.SetParent(m_dialogEx.PreviewControl.Handle, this.Handle); //调整对话框的宽度 WINDOWINFO info = new WINDOWINFO(); Win32.GetWindowInfo(this.Handle, out info); Win32.SetWindowPos(this.Handle, IntPtr.Zero, (int)info.rcWindow.left, (int)info.rcWindow.top, 500, (int)info.rcWindow.Height, SetWindowPosFlags.SWP_SHOWWINDOW); //计算自定义控件的位置和尺寸 RECT rc = new RECT(); Win32.GetClientRect(this.Handle, ref rc); m_dialogEx.PreviewControl.Height = (int)rc.Height; m_dialogEx.PreviewControl.Location = new Point((int)(rc.Width - m_dialogEx.PreviewControl.Width), 0); } m_isInited = true; } /// <summary> /// 更新自定义控件的位置。 /// </summary> /// <param name="m"></param> private void UpdateLocation(Message m) { if (m_dialogEx.PreviewControl != null && !m_dialogEx.PreviewControl.IsDisposed) { if (!m_isInited && !this.IsDisposed) { WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS)); if (pos.flags != 0 && ((pos.flags & (int)SWP_Flags.SWP_NOSIZE) != (int)SWP_Flags.SWP_NOSIZE)) { pos.cx += m_dialogEx.PreviewControl.Width; //修改OpenfileDialog的宽度 Marshal.StructureToPtr(pos, m.LParam, true); RECT rc = new RECT(); Win32.GetClientRect(this.Handle, ref rc); m_dialogEx.PreviewControl.Height = (int)rc.Height; } } } } /// <summary> /// 更新自定义控件的尺寸。 /// </summary> private void UpdateSize() { if (m_dialogEx.PreviewControl != null && !m_dialogEx.PreviewControl.IsDisposed) { if (!this.IsDisposed) { //新添加的控件与openfileDialog大小一致 RECT rc = new RECT(); Win32.GetClientRect(this.Handle, ref rc); Win32.SetWindowPos(m_dialogEx.PreviewControl.Handle, (IntPtr)ZOrderPos.HWND_BOTTOM, 0, 0, (int)m_dialogEx.PreviewControl.Width, (int)rc.Height, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOOWNERZORDER | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_ASYNCWINDOWPOS | SetWindowPosFlags.SWP_DEFERERASE); } } } /// <summary> /// 释放资源。 /// </summary> public void Dispose() { ReleaseHandle(); if (m_childNative != null) { m_childNative.SelectFileChanged -= new ChildControlNativeWindow.SelectFileChangedEventHandler(childNative_SelectFileChanged); m_childNative.SelectPathChanged -= new ChildControlNativeWindow.SelectPathChangedEventHandler(childNative_SelectPathChanged); m_childNative.Dispose(); } m_isDisposed = true; } /// <summary> /// 选择目录发生变化。 /// </summary> /// <param name="path"></param> void childNative_SelectPathChanged(string path) { m_dialogEx.OnPathOpened(path); } /// <summary> /// 选择文件发生变化。 /// </summary> /// <param name="fileName"></param> void childNative_SelectFileChanged(string fileName) { m_dialogEx.OnFileSelecting(fileName); } #endregion } /// <summary> /// 子控件钩子窗口。 /// </summary> class ChildControlNativeWindow : NativeWindow, IDisposable { #region 构造区域 /// <summary> /// 构造函数。 /// </summary> /// <param name="handle"></param> public ChildControlNativeWindow(IntPtr handle) { AssignHandle(handle); } #endregion #region 方法区域 /// <summary> /// 处理窗口消息。 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { switch (m.Msg) { case (int)Msg.WM_NOTIFY: OFNOTIFY ofNotify = (OFNOTIFY)Marshal.PtrToStructure(m.LParam, typeof(OFNOTIFY)); if (ofNotify.hdr.code == (uint)DialogChangeStatus.CDN_SELCHANGE) //openfileDialog选择文件发生变化 { StringBuilder sb = new StringBuilder(256); Win32.SendMessage(Win32.GetParent(this.Handle), (int)DialogChangeProperties.CDM_GETFILEPATH, (int)256, sb); if (SelectFileChanged != null) SelectFileChanged(sb.ToString()); //通知注册者 } else if (ofNotify.hdr.code == (uint)DialogChangeStatus.CDN_FOLDERCHANGE) //openfileDialog选择目录发生变化 { StringBuilder sb = new StringBuilder(256); Win32.SendMessage(Win32.GetParent(this.Handle), (int)DialogChangeProperties.CDM_GETFOLDERPATH, (int)256, sb); if (SelectPathChanged != null) SelectPathChanged(sb.ToString()); //通知注册者 } break; } base.WndProc(ref m); } /// <summary> /// 释放资源。 /// </summary> public void Dispose() { ReleaseHandle(); } #endregion #region 事件委托 //当openfileDialog的选择文件发生变化时发生 public delegate void SelectFileChangedEventHandler(string fileName); public event SelectFileChangedEventHandler SelectFileChanged; //当openfileDialog的选择目录发生变化时发生 public delegate void SelectPathChangedEventHandler(string path); public event SelectPathChangedEventHandler SelectPathChanged; #endregion } #endregion } /// <summary> /// 路径事件参数。 /// </summary> [Serializable] public class OpenFileDialogExPathEventArgs : EventArgs { #region 构造区域 /// <summary> /// 构造函数。 /// </summary> /// <param name="path">与事件相关的路径名(文件名或文件夹名)。</param> public OpenFileDialogExPathEventArgs(string path) { m_path = path; } #endregion #region 字段区域 private string m_path = string.Empty; #endregion #region 属性区域 /// <summary> /// 获取与事件相关的路径名(文件名或文件夹名)。 /// </summary> public string Path { get { return m_path; } } #endregion } }
使用范例:
往窗体上拖放一个OpenFileDialogEx组件,双击该组件,注册FileSelecting事件。具体代码如下:
View Code
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); pictureBox1 = new PictureBox(); pictureBox1.Width = 300; pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; pictureBox1.BorderStyle = BorderStyle.FixedSingle; openFileDialogEx1.PreviewControl = pictureBox1; } private PictureBox pictureBox1; private void button1_Click(object sender, EventArgs e) { if (openFileDialogEx1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { } } private void openFileDialogEx1_FileSelecting(object sender, OpenFileDialogExPathEventArgs e) { //预览图片 pictureBox1.ImageLocation = e.Path; } } }
下载完整的源代码(解压密码:cnblogs)