最近想實現這么一個功能:通過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,可以直接拖放到窗體上。
最后的運行效果如下圖所示:
源代碼如下:

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事件。具體代碼如下:

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)