/*================================================================================ File: OpenFileDialog.cs Summary: This is part of a sample showing how to place Windows Forms controls inside one of the common file dialogs. ---------------------------------------------------------------------------------- Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or on-line documentation. See these other materials for detailed information regarding Microsoft code samples. This sample is not intended for production use. Code and policy for a production application must be developed to meet the specific data and security requirements of the application. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. ================================================================================*/ using System; using System.Text; using System.Runtime.InteropServices; using System.Drawing; using System.IO; namespace ExtensibleDialogs { /// /// The extensible OpenFileDialog /// public class OpenFileDialog : IDisposable { // The maximum number of characters permitted in a path private const int _MAX_PATH = 260; // The "control ID" of the content window inside the OpenFileDialog // See the accompanying article to learn how I discovered it private const int _CONTENT_PANEL_ID = 0x0461; // A constant that determines the spacing between panels inside the OpenFileDialog private const int _PANEL_GAP_FACTOR = 3; /// /// Clients can implement handlers of this type to catch "selection changed" events /// public delegate void SelectionChangedHandler( string path ); /// /// This event is fired whenever the user selects an item in the dialog /// public event SelectionChangedHandler SelectionChanged; public delegate void FolderChangedHandler( string path ); public event FolderChangedHandler FolderChanged; // unmanaged memory buffers to hold the file name (with and without full path) private IntPtr _fileNameBuffer; private IntPtr _fileTitleBuffer; private IntPtr _initialDirBuffer; // user-supplied control that gets placed inside the OpenFileDialog private System.Windows.Forms.Control _userControl; // unmanaged memory buffer that holds the Win32 dialog template private IntPtr _ipTemplate; private string _filter; private string _fileName; private string _defaultExtension; private int _filterIndex; private short _fileOffset; private short _fileExtension; private string _initialDir; public string InitialDirectory { get { return _initialDir; } set { _initialDir = value; if ( !Path.IsPathRooted( _initialDir ) ) { if ( !Directory.Exists( _initialDir ) ) { _initialDir = Path.GetDirectoryName( _initialDir ); } } _fileName = ""; UnicodeEncoding ue = new UnicodeEncoding(); byte[] zero = new byte[2 * _MAX_PATH]; for ( int i = 0; i < 2 * _MAX_PATH; i++ ) { zero[i] = 0; } Marshal.Copy( zero, 0, _initialDirBuffer, 2 * _MAX_PATH ); Marshal.Copy( zero, 0, _fileNameBuffer, 2 * _MAX_PATH ); if ( _initialDir.Length > 0 ) { byte[] initial_dir_buffer = ue.GetBytes( _initialDir ); Marshal.Copy( initial_dir_buffer, 0, _initialDirBuffer, initial_dir_buffer.Length ); } } } public int FilterIndex { get { return _filterIndex; } set { _filterIndex = value; } } public OpenFileDialog( System.Windows.Forms.Control userControl ) : this( "", "", "", userControl ) { } /// /// Sets up the data structures necessary to display the OpenFileDialog /// /// The file extension to use if the user doesn't specify one (no "." required) /// You can specify a filename to appear in the dialog, although the user can change it /// See the documentation for the OPENFILENAME structure for a description of filter strings /// Any Windows Forms control, it will be placed inside the OpenFileDialog private OpenFileDialog( string defaultExtension, string fileName, string filter, System.Windows.Forms.Control userControl ) { _filter = filter; _fileName = fileName; _defaultExtension = defaultExtension; // LipSync Character Config(*.lsc,content.xml)|*.lsc;content.xml|All Files(*.*)|*.* // ↁE // LipSync Character Config(*.lsc,content.xml)\0*.lsc;content.xml\0All Files(*.*)\0*.*\0\0 filter = filter.Replace( "|", "\0" ) + "\0\0"; // Need two buffers in unmanaged memory to hold the filename // Note: the multiplication by 2 is to allow for Unicode (16-bit) characters _fileNameBuffer = Marshal.AllocCoTaskMem( 2 * _MAX_PATH ); _fileTitleBuffer = Marshal.AllocCoTaskMem( 2 * _MAX_PATH ); _initialDirBuffer = Marshal.AllocCoTaskMem( 2 * _MAX_PATH ); // Zero these two buffers byte[] zeroBuffer = new byte[2 * (_MAX_PATH + 1)]; for ( int i = 0; i < 2 * (_MAX_PATH + 1); i++ ) { zeroBuffer[i] = 0; } Marshal.Copy( zeroBuffer, 0, _fileNameBuffer, 2 * _MAX_PATH ); Marshal.Copy( zeroBuffer, 0, _fileTitleBuffer, 2 * _MAX_PATH ); Marshal.Copy( zeroBuffer, 0, _initialDirBuffer, 2 * _MAX_PATH ); _filterIndex = 0; _fileOffset = 0; _fileExtension = 0; // keep a reference to the user-supplied control _userControl = userControl; } public string FileName { get { return _fileName; } set { if ( value == null ) { return; } _fileName = value; string folder; if ( Path.IsPathRooted( _fileName ) ) { folder = _fileName; _fileName = ""; } else { if ( Directory.Exists( _fileName ) ) { folder = _fileName; _fileName = ""; } else { if ( _fileName != "" ) { folder = Path.GetDirectoryName( _fileName ); } else { folder = ""; } } } #if DEBUG LipSync.Common.DebugWriteLine( "FileName.set(); folder=" + folder ); LipSync.Common.DebugWriteLine( "FileName.set(); _fileName=" + _fileName ); #endif byte[] zero = new byte[2 * _MAX_PATH]; for ( int i = 0; i < 2 * _MAX_PATH; i++ ) { zero[i] = 0; } Marshal.Copy( zero, 0, _fileNameBuffer, 2 * _MAX_PATH ); Marshal.Copy( zero, 0, _initialDirBuffer, 2 * _MAX_PATH ); UnicodeEncoding ue = new UnicodeEncoding(); if ( _fileName.Length > 0 ) { byte[] file_name_bytes = ue.GetBytes( _fileName ); Marshal.Copy( file_name_bytes, 0, _fileNameBuffer, file_name_bytes.Length ); } if ( folder.Length > 0 ) { byte[] initial_dir_bytes = ue.GetBytes( folder ); Marshal.Copy( initial_dir_bytes, 0, _initialDirBuffer, initial_dir_bytes.Length ); } #if DEBUG LipSync.Common.DebugWriteLine( "FileName.set(); _fileNameBuffer=" + Marshal.PtrToStringUni( _fileNameBuffer ) ); LipSync.Common.DebugWriteLine( "FileName.set(); _initialDir=" + Marshal.PtrToStringUni( _initialDirBuffer ) ); #endif } } public string Filter { get { return _filter; } set { _filter = value; } } public string DefaultExt { get { return _defaultExtension; } set { _defaultExtension = value; } } /// /// The finalizer will release the unmanaged memory, if I should forget to call Dispose /// ~OpenFileDialog() { Dispose( false ); } /// /// Display the OpenFileDialog and allow user interaction /// /// true if the user clicked OK, false if they clicked cancel (or close) public System.Windows.Forms.DialogResult ShowDialog() { // Create an in-memory Win32 dialog template; this will be a "child" window inside the FileOpenDialog // We have no use for this child window, except that its presence allows us to capture events when // the user interacts with the FileOpenDialog _ipTemplate = BuildDialogTemplate(); // Populate the OPENFILENAME structure // The flags specified are the minimal set to get the appearance and behaviour we need OpenFileName ofn = new OpenFileName(); ofn.lStructSize = Marshal.SizeOf( ofn ); ofn.lpstrFile = _fileNameBuffer; ofn.nMaxFile = _MAX_PATH; ofn.lpstrDefExt = Marshal.StringToCoTaskMemUni( _defaultExtension ); ofn.lpstrFileTitle = _fileTitleBuffer; ofn.nMaxFileTitle = _MAX_PATH; string filter = _filter.Replace( "|", "\0" ) + "\0\0"; ofn.lpstrFilter = Marshal.StringToCoTaskMemUni( filter ); ofn.Flags = OpenFileNameFlags.EnableHook | OpenFileNameFlags.EnableTemplateHandle | OpenFileNameFlags.EnableSizing | OpenFileNameFlags.Explorer; ofn.hInstance = _ipTemplate; ofn.lpfnHook = new OfnHookProc( MyHookProc ); ofn.lpstrInitialDir = _initialDirBuffer; ofn.nFilterIndex = _filterIndex; ofn.nFileOffset = _fileOffset; ofn.nFileExtension = _fileExtension; // copy initial file name into unmanaged memory buffer UnicodeEncoding ue = new UnicodeEncoding(); byte[] fileNameBytes = ue.GetBytes( _fileName ); Marshal.Copy( fileNameBytes, 0, _fileNameBuffer, fileNameBytes.Length ); Marshal.Copy( fileNameBytes, 0, _initialDirBuffer, fileNameBytes.Length ); if ( NativeMethods.GetOpenFileName( ref ofn ) ) { _fileName = Marshal.PtrToStringUni( _fileNameBuffer ); _filterIndex = ofn.nFilterIndex; byte[] file_name_buffer = ue.GetBytes( _fileName ); Marshal.Copy( file_name_buffer, 0, _initialDirBuffer, file_name_buffer.Length ); _fileOffset = ofn.nFileOffset; _fileExtension = ofn.nFileExtension; return System.Windows.Forms.DialogResult.OK; } else { return System.Windows.Forms.DialogResult.Cancel; } } /// /// Builds an in-memory Win32 dialog template. See documentation for DLGTEMPLATE. /// /// a pointer to an unmanaged memory buffer containing the dialog template private IntPtr BuildDialogTemplate() { // We must place this child window inside the standard FileOpenDialog in order to get any // notifications sent to our hook procedure. Also, this child window must contain at least // one control. We make no direct use of the child window, or its control. // Set up the contents of the DLGTEMPLATE DlgTemplate template = new DlgTemplate(); // Allocate some unmanaged memory for the template structure, and copy it in IntPtr ipTemplate = Marshal.AllocCoTaskMem( Marshal.SizeOf( template ) ); Marshal.StructureToPtr( template, ipTemplate, true ); return ipTemplate; } /// /// The hook procedure for window messages generated by the FileOpenDialog /// /// the handle of the window at which this message is targeted /// the message identifier /// message-specific parameter data /// mess-specific parameter data /// public IntPtr MyHookProc( IntPtr hWnd, UInt16 msg, Int32 wParam, Int32 lParam ) { if ( hWnd == IntPtr.Zero ) return IntPtr.Zero; // Behaviour is dependant on the message received switch ( msg ) { // We're not interested in every possible message; just return a NULL for those we don't care about default: { return IntPtr.Zero; } // WM_INITDIALOG - at this point the OpenFileDialog exists, so we pull the user-supplied control // into the FileOpenDialog now, using the SetParent API. case WindowMessage.InitDialog: { if( _userControl != null ){ IntPtr hWndParent = NativeMethods.GetParent( hWnd ); NativeMethods.SetParent( _userControl.Handle, hWndParent ); } return IntPtr.Zero; } // WM_SIZE - the OpenFileDialog has been resized, so we'll resize the content and user-supplied // panel to fit nicely case WindowMessage.Size: { FindAndResizePanels( hWnd ); return IntPtr.Zero; } // WM_NOTIFY - we're only interested in the CDN_SELCHANGE notification message: // we grab the currently-selected filename and fire our event case WindowMessage.Notify: { IntPtr ipNotify = new IntPtr( lParam ); OfNotify ofNot = (OfNotify)Marshal.PtrToStructure( ipNotify, typeof( OfNotify ) ); UInt16 code = ofNot.hdr.code; if ( code == CommonDlgNotification.SelChange ) { // This is the first time we can rely on the presence of the content panel // Resize the content and user-supplied panels to fit nicely FindAndResizePanels( hWnd ); // get the newly-selected path IntPtr hWndParent = NativeMethods.GetParent( hWnd ); StringBuilder pathBuffer = new StringBuilder( _MAX_PATH ); UInt32 ret = NativeMethods.SendMessage( hWndParent, CommonDlgMessage.GetFilePath, _MAX_PATH, pathBuffer ); string path = pathBuffer.ToString(); // copy the string into the path buffer byte[] zero = new byte[2 * _MAX_PATH]; for ( int i = 0; i < 2 * _MAX_PATH; i++ ) { zero[i] = 0; } Marshal.Copy( zero, 0, _fileNameBuffer, 2 * _MAX_PATH ); UnicodeEncoding ue = new UnicodeEncoding(); byte[] pathBytes = ue.GetBytes( path ); Marshal.Copy( pathBytes, 0, _fileNameBuffer, pathBytes.Length ); _fileName = path; #if DEBUG LipSync.Common.DebugWriteLine( "ExtensibleDialog.OpenFiledialog.MyHookProc; SelChange; _fileName=" + path ); #endif // fire selection-changed event if ( SelectionChanged != null ) { SelectionChanged( path ); } } else if ( code == CommonDlgNotification.FolderChange ) { // This is the first time we can rely on the presence of the content panel // Resize the content and user-supplied panels to fit nicely FindAndResizePanels( hWnd ); // get the newly-selected path IntPtr hWndParent = NativeMethods.GetParent( hWnd ); StringBuilder pathBuffer = new StringBuilder( _MAX_PATH ); UInt32 ret = NativeMethods.SendMessage( hWndParent, CommonDlgMessage.GetFolderPath, _MAX_PATH, pathBuffer ); string path = pathBuffer.ToString(); // copy the string into the path buffer byte[] zero = new byte[2 * _MAX_PATH]; for ( int i = 0; i < 2 * _MAX_PATH; i++ ) { zero[i] = 0; } Marshal.Copy( zero, 0, _initialDirBuffer, 2 * _MAX_PATH ); Marshal.Copy( zero, 0, _fileNameBuffer, 2 * _MAX_PATH ); UnicodeEncoding ue = new UnicodeEncoding(); byte[] pathBytes = ue.GetBytes( path ); Marshal.Copy( pathBytes, 0, _initialDirBuffer, pathBytes.Length ); // fire selection-changed event if ( FolderChanged != null ) { FolderChanged( path ); } } return IntPtr.Zero; } } } /// /// Layout the content of the OpenFileDialog, according to the overall size of the dialog /// /// handle of window that received the WM_SIZE message private void FindAndResizePanels( IntPtr hWnd ) { // The FileOpenDialog is actually of the parent of the specified window IntPtr hWndParent = NativeMethods.GetParent( hWnd ); // The "content" window is the one that displays the filenames, tiles, etc. // The _CONTENT_PANEL_ID is a magic number - see the accompanying text to learn // how I discovered it. IntPtr hWndContent = NativeMethods.GetDlgItem( hWndParent, _CONTENT_PANEL_ID ); Rectangle rcClient = new Rectangle( 0, 0, 0, 0 ); Rectangle rcContent = new Rectangle( 0, 0, 0, 0 ); // Get client rectangle of dialog RECT rcTemp = new RECT(); NativeMethods.GetClientRect( hWndParent, ref rcTemp ); rcClient.X = rcTemp.left; rcClient.Y = rcTemp.top; rcClient.Width = rcTemp.right - rcTemp.left; rcClient.Height = rcTemp.bottom - rcTemp.top; // The content window may not be present when the dialog first appears if ( hWndContent != IntPtr.Zero ) { // Find the dimensions of the content panel RECT rc = new RECT(); NativeMethods.GetWindowRect( hWndContent, ref rc ); // Translate these dimensions into the dialog's coordinate system POINT topLeft; topLeft.X = rc.left; topLeft.Y = rc.top; NativeMethods.ScreenToClient( hWndParent, ref topLeft ); POINT bottomRight; bottomRight.X = rc.right; bottomRight.Y = rc.bottom; NativeMethods.ScreenToClient( hWndParent, ref bottomRight ); rcContent.X = topLeft.X; rcContent.Width = bottomRight.X - topLeft.X; rcContent.Y = topLeft.Y; rcContent.Height = bottomRight.Y - topLeft.Y; // Shrink content panel's width int width = rcClient.Right - rcContent.Left; if ( _userControl != null ) { rcContent.Width = (width / 2) + _PANEL_GAP_FACTOR; } else { rcContent.Width = width + _PANEL_GAP_FACTOR; } NativeMethods.MoveWindow( hWndContent, rcContent.Left, rcContent.Top, rcContent.Width, rcContent.Height, true ); } if( _userControl != null ){ // Position the user-supplied control alongside the content panel Rectangle rcUser = new Rectangle( rcContent.Right + (2 * _PANEL_GAP_FACTOR), rcContent.Top, rcClient.Right - rcContent.Right - (3 * _PANEL_GAP_FACTOR), rcContent.Bottom - rcContent.Top ); NativeMethods.MoveWindow( _userControl.Handle, rcUser.X, rcUser.Y, rcUser.Width, rcUser.Height, true ); } } /// /// returns the path currently selected by the user inside the OpenFileDialog /// public string SelectedPath { get { return Marshal.PtrToStringUni( _fileNameBuffer ); } } #region IDisposable Members public void Dispose() { Dispose( true ); } /// /// Free any unamanged memory used by this instance of OpenFileDialog /// /// true if called by Dispose, false otherwise public void Dispose( bool disposing ) { if ( disposing ) { GC.SuppressFinalize( this ); } Marshal.FreeCoTaskMem( _fileNameBuffer ); Marshal.FreeCoTaskMem( _fileTitleBuffer ); Marshal.FreeCoTaskMem( _initialDirBuffer ); Marshal.FreeCoTaskMem( _ipTemplate ); } #endregion } }