Click here to Skip to main content
Click here to Skip to main content

Extend OpenFileDialog and SaveFileDialog the easy way

By , 22 Mar 2012
 
  • Download source files - 278 KB
  • Download release files - 35 KB
  • Table of Contents

    Screenshot - saveas.jpg

    Introduction

    If you used WinForms, chances are that at some point you wanted to extend the OpenFileDialog or SaveFileDialog, but you gave up because there is no easy way to do it, especially if you wanted to add some new graphical elements. Even in the MFC days, such a task was not as daunting as it is for .NET because these classes are sealed, exposing only 16 properties, 5 methods and 3 events that can be used to customize the dialogs. Martin Parry's article on MSDN can give you an insight into how you can customize the OpenFileDialog using the OFNHookProc function and PInvoke. It would look like we have to roll back the clock to early 1990's way of programming and add quite a bit of PInvoke and marshalling to do anything. This is probably enough to give up or look to alternatives like third party libraries. If you are developing for WPF rather than Windows Forms, I suggest that you jump directly to my WPF article on the same topic. However, CastorTiu's article made life A LOT EASIER on those who chose to customize these dialogs using Forms. Using his great work on this topic, I tried to take it even further and make customizing of these two dialogs even more painless. I will focus only on my refactoring and improvements to his original hard work, so if you need details please check CastorTiu's article. While this article uses only C# snippets, I’ve included the equivalent VB.NET code in the downloadable zip file for the VB folks.

    What's New with this Control

    To make extending as easy as possible, I've added some extra properties and events to the base control as well as some design features. Probably the most appreciated property will be the FileDlgType that would allow selecting between OpenFileDialog and SaveFileDialog at design time. The extra properties and events are displayed below:

    Properties Events

    Improvements at Design Time

    It would be nice to have some visual clue about how the control will look like. I did not want to dig too much design time architecture so I used a simple OnPaint override to draw a red line or a dot where the FileDialog touches the extension. Notice that it is painting only in Design mode.

    protected override void OnPaint(PaintEventArgs e)
    {
        if (DesignMode)
        {
            Graphics gr = e.Graphics;
            {
                HatchBrush hb = null;
                Pen p = null;
                try
                {
                    switch (this.FileDlgStartLocation)
                    {
                        case AddonWindowLocation.Right:
                            hb = new System.Drawing.Drawing2D.HatchBrush
                          (HatchStyle.NarrowHorizontal, Color.Black, Color.Red);
                            p = new Pen(hb, 5);
                            gr.DrawLine(p, 0, 0, 0, this.Height);
                            break;
                        case AddonWindowLocation.Bottom:
                            hb = new System.Drawing.Drawing2D.HatchBrush
                          (HatchStyle.NarrowVertical, Color.Black, Color.Red);
                            p = new Pen(hb, 5);
                            gr.DrawLine(p, 0, 0, this.Width, 0);
                            break;
                        case AddonWindowLocation.BottomRight:
                        default:
                            hb = new System.Drawing.Drawing2D.HatchBrush
                        (HatchStyle.Sphere, Color.Black, Color.Red);
                            p = new Pen(hb, 5);
                            gr.DrawLine(p, 0, 0, 4, 4);
                            break;
                    }
                }
                finally
                {
                    if (p != null)
                        p.Dispose();
                    if (hb != null)
                        hb.Dispose();
                }
            }
        }
        base.OnPaint(e);
    }

    How It Works

    CastorTiu's article describes in detail how the control works. I've made some improvements, but the general idea is the same.

    You need to get the dialog handle before it goes modal, so you can 'glue together' the dialog with your own control.

    Here is the flow of initializing of this composite dialog:

    1. Create the dialog using its constructor. At this point there is still no UI, so no Windows messages to catch
    2. Set the properties you want to change at runtime using the virtual OnPrepareMSDialog() for the FileDialog and the Load event for the control itself
    3. Use the DialogResult return and dispose of the control

    Here is what goes on behind the scenes: You start by creating the helper class DialogWrapper with the right FileDialog type as a generic parameter:

    public DialogResult ShowDialog(IWin32Window owner)
    {
        DialogResult returnDialogResult = DialogResult.Cancel;
        if (this.IsDisposed)
            return returnDialogResult;
        if (owner == null || owner.Handle == IntPtr.Zero)
        {
            WindowWrapper wr = new WindowWrapper
    	(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
            owner = wr;
        }
        OriginalCtrlSize = this.Size;
        _MSdialog = (FileDlgType == 
    	FileDialogType.OpenFileDlg)?new OpenFileDialog()as 
    	FileDialog:new SaveFileDialog() as FileDialog;
        _dlgWrapper = new WholeDialogWrapper(this);
        OnPrepareMSDialog();
        if (!_hasRunInitMSDialog)
            InitMSDialog();
        try
        {
            System.Reflection.PropertyInfo AutoUpgradeInfo = 
    		MSDialog.GetType().GetProperty("AutoUpgradeEnabled");
            if (AutoUpgradeInfo != null)
                AutoUpgradeInfo.SetValue(MSDialog, false, null);
            returnDialogResult = _MSdialog.ShowDialog(owner);
        }
        catch (ObjectDisposedException)
        {
        }
        catch (Exception ex)
        {
            MessageBox.Show("unable to get the modal dialog handle", ex.Message);
        }
        return returnDialogResult;
    }

    When you call ShowDialog public method of your control, the messages start flowing only after you called .NET's Open(Save)FileDialog.ShowDialog(). You have to watch for WM_ACTIVATE, and since an Application filter won't catch it, you have to rely on the parent's window WndProc. Instead of using a dummy form, I found out that a message pump window will do just as fine with less overhead. I created it inside WholeDialogWrapper's constructor calling AssignDummyWindow().

    private void AssignDummyWindow()
    {
        _hDummyWnd = NativeMethods.CreateWindowEx(0, "Message",
            null, WS_VISIBLE, 0, 0, 0, 0,HWND_MESSAGE, NULL, NULL, NULL);
        if (_hDummyWnd == NULL || !NativeMethods.IsWindow(_hDummyWnd))
            throw new ApplicationException("Unable to create a dummy window");
       AssignHandle(_hDummyWnd);
    }

    Since we have this Window that listens to its Children, we will catch the WM_ACTIVATE as below:

    protected override void WndProc(ref Message m)
    {
      switch ((Msg)m.Msg)
      {//.... code omitted
       case Msg.WM_ACTIVATE:
       if (_WatchForActivate && !mIsClosing && m.Msg == (int)Msg.WM_ACTIVATE)
                            //WM_NCACTIVATE works too
       {  //Now the Open/Save Dialog is visible and about to enter the modal loop
          _WatchForActivate = false;
          //Now we save the real dialog window handle
          _FileDialogHandle = m.LParam;
          ReleaseHandle();//release the dummy window
          AssignHandle(_FileDialogHandle);//assign the native open file handle
                                    // to grab the messages
          NativeMethods.GetWindowRect(_FileDialogHandle,
                        ref _CustomControl._DialogWindowRect);
          _CustomControl._FileDialogHandle = _FileDialogHandle;
        }
        break;
       //.... code omitted
      }
      base.WndProc(ref m);
    }

    Once we got the real dialog handle as _FileDialogHandle, we can forget about the dummy Window and start listening to what really matters. Notice how I released the dummy Window handle and assigned the new one. When the same WndProc catches
    the WM_SHOWWINDOW message, we can finally arrange our control and set the parent:

    private void InitControls()
    {
        mInitializated = true;
    
        // Lets get information about the current open dialog
        NativeMethods.GetClientRect(new HandleRef(this,_hFileDialogHandle), 
    				ref _DialogClientRect);
        NativeMethods.GetWindowRect(new HandleRef(this,_hFileDialogHandle), 
    				ref _DialogWindowRect);
    
        // Lets borrow the Handles from the open dialog control
        PopulateWindowsHandlers();
    
        switch (_CustomControl.FileDlgStartLocation)
        {
            case AddonWindowLocation.Right:
                // Now we transfer the control to the open dialog
                _CustomControl.Location = new Point((int)
    		(_DialogClientRect.Width - _CustomControl.Width), 0);
                break;
            case AddonWindowLocation.Bottom:
                // Now we transfer the control to the open dialog
                _CustomControl.Location = new Point(0, 
    		(int)(_DialogClientRect.Height - _CustomControl.Height));
                break;
            case AddonWindowLocation.BottomRight:
                // We don't have to do too much in this case, just the default thing
                _CustomControl.Location = 
    		new Point((int)(_DialogClientRect.Width - _CustomControl.Width), 
    		(int)(_DialogClientRect.Height - _CustomControl.Height));
                break;
        }
        // Everything is ready, now lets change the parent
        NativeMethods.SetParent(new HandleRef(_CustomControl,_CustomControl.Handle), 
    		new HandleRef(this,_hFileDialogHandle));
    
        // Send the control to the back
        // NativeMethods.SetWindowPos(_CustomControl.Handle, 
    	(IntPtr)ZOrderPos.HWND_BOTTOM, 0, 0, 0, 0, UFLAGSZORDER);
        _CustomControl.MSDialog.Disposed += new EventHandler(DialogWrappper_Disposed);
    }

    How the Events are Hooked Up

    You might think that this code is exhaustive relating to the properties and events you can use. Well... not quite! I've added several properties and events to the original work, but you might still want to add more to it.

    Just in the above code snippet, you see how the events from the Open(Save)FileDialog.Dispose are hooked up, but this is an easy one. I'll describe how you can add new events to FileDialogControlBase based on the one I've added as an example.
    There is still another helper class called MSFileDialogWrapper that monitors the Open(Save)FileDialog object through the WndProc as below:

    protected override void WndProc(ref Message m)
    {
        switch ((Msg)m.Msg)
        {
            case Msg.WM_NOTIFY:
                OFNOTIFY ofNotify = (OFNOTIFY)Marshal.PtrToStructure
                        (m.LParam, typeof(OFNOTIFY));
                switch (ofNotify.hdr.code)
                {
                    //.... code omitted
                    case (uint)DialogChangeStatus.CDN_TYPECHANGE:
                        {
                            OPENFILENAME ofn =
                (OPENFILENAME)Marshal.PtrToStructure
                (ofNotify.OpenFileName, typeof(OPENFILENAME));
                            int i = ofn.nFilterIndex;
                            if (_CustomCtrl != null && _filterIndex != i)
                            {
                                _filterIndex = i;
                                _CustomCtrl.OnFilterChanged
                        (this as IWin32Window, i);
                            }
                        }
                        break;
                }
            //.... code omitted
        }
        base.WndProc(ref m);
    }

    Once we marshal the internal pointers into the right structures, we call the OnFilterChanged method of FileDialogControBase object, which is _CustomCtrl in this case. If we dug deeper into this method, we find something as below:

    internal void OnFilterChanged(IWin32Window sender, int index)
    {
        if (EventFilterChanged != null)
            EventFilterChanged(sender, index);
    }

    A quick look at the EventFilterChanged definitions reveals that it is an event.

    public delegate void PathChangedEventHandler(IWin32Window sender,
                                                string filePath);
    public delegate void FilterChangedEventHandler(IWin32Window sender, int index);
    public event PathChangedEventHandler EventFileNameChanged;
    public event PathChangedEventHandler EventFolderNameChanged;
    public event FilterChangedEventHandler EventFilterChanged;
    public event CancelEventHandler EventClosingDialog;

    This makes it very easy to add and remove them the same way you deal with regular WinForms events.

    How to Add New Properties

    To set the properties of the Open(Save)FileDialog object, you have to override the OnPrepareMSDialog() if the design time settings are not right. However changing the appearance of the Open(Save)FileDialog is more elaborate. As an example, I'll show how to change the Text on the Ok button - that's the Save or Open button. We start with
    exposing the property from the FileDialogControlBase and then use PInvoke to set the text as below:

    [DefaultValue("&Open")]
    public string FileDlgOkCaption
    {
        get { return _OKCaption; }
        set { _OKCaption = value; }
    //........................
    
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if (!DesignMode)
        {
            if (MSDialog != null)
            {
                MSDialog.FileOk += new CancelEventHandler
                        (FileDialogControlBase_ClosingDialog);
                MSDialog.Disposed += new EventHandler
                        (FileDialogControlBase_DialogDisposed);
                MSDialog.HelpRequest += new EventHandler
                        (FileDialogControlBase_HelpRequest);
                NativeMethods.SetWindowText(_dlgWrapper.Handle, _Caption);
                //will work only for open dialog, save dialog will not update
                NativeMethods.SetWindowText(_hOKButton, _OKCaption);
            }
        }
    }

    As you see the comment in the code above, realize that we are dealing with a black box and sometimes we can't know the right way to update. This is one of those cases when we can change it on OpenFileDialog but not on SaveFileDialog. If you wonder how we got the _hOKButton or _dlgWrapper.Handle, you would have to look at FileDialogEnumWindowCallBack that is invoked when the WM_SHOWWINDOW is captured in DialogWrapper's WndProc.

    private bool FileDialogEnumWindowCallBack(IntPtr hwnd, int lParam)
    {
        StringBuilder className = new StringBuilder(256);
        NativeMethods.GetClassName(new HandleRef(this,hwnd), className, className.Capacity);
        int controlID = NativeMethods.GetDlgCtrlID(hwnd);
        WINDOWINFO windowInfo;
        NativeMethods.GetWindowInfo(new HandleRef(this,hwnd), out windowInfo);
        // Dialog Window
        if (className.ToString().StartsWith("#32770"))
        {
            _BaseDialogNative = new MSFileDialogWrapper(_CustomControl);
            _BaseDialogNative.AssignHandle(hwnd);
            return true;
        }
        switch ((ControlsId)controlID)
        {
        //.....code omitted
            case ControlsId.ButtonOk:
                _OKButton = hwnd;
                _OKButtonInfo = windowInfo;
                _CustomControl._hOKButton = hwnd;
                break;
        //.....code omitted
        }
    }

    How to Add the Places Bar to the OpenFileDialog and SaveFiledialog on Windows 2000 and XP

    Until .NET 3.5 introduced FileDialogCustomPlacesCollection for Windows Vista and up, there was no API that I know of to allow you to modify the places bar on the dialog. There is a way to do it on Windows 2000 and XP as described by Dino Esposito in his MSDN article. That requires modifying the registry and it will affect all instances of these dialogs as long as the user is logged on. Since this looks more like a native dialog feature, I’ve used extension methods applied to the base class FileDialog. The static class is shown below and it exposes two public methods to set the places and to restore the registry.

    public static class FileDialogPlaces
    {
        private static readonly string TempKeyName = 
    		"TempPredefKey_" + Guid.NewGuid().ToString();
        private const string Key_PlacesBar = 
    	@"Software\Microsoft\Windows\CurrentVersion\Policies\ComDlg32\PlacesBar";
        private static RegistryKey _fakeKey;
        private static IntPtr _overriddenKey;
        private static object[] m_places;
    
        public static void SetPlaces(this FileDialog fd, object[] places)
        {
            if (fd == null)
                return;
            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];
                }
            }
            if (_fakeKey != null)
                ResetPlaces(fd);
            SetupFakeRegistryTree();
            if (fd != null)
                fd.Disposed += (object sender, EventArgs e) => 
    		{ if (m_places != null && fd != null) ResetPlaces(fd); };
        }
    
        static public void ResetPlaces(this FileDialog fd)
        {
            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 static void SetupFakeRegistryTree()
        {
            _fakeKey = Registry.CurrentUser.CreateSubKey(TempKeyName);
            _overriddenKey = InitializeRegistry();
            // 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]);
                }
            }
        }
    
        static readonly UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);
        private static IntPtr InitializeRegistry()
        {
            IntPtr hkMyCU;
            NativeMethods.RegCreateKeyW(HKEY_CURRENT_USER, TempKeyName, out hkMyCU);
            NativeMethods.RegOverridePredefKey(HKEY_CURRENT_USER, hkMyCU);
            return hkMyCU;
        }
    
        static void ResetRegistry(IntPtr hkMyCU)
        {
            NativeMethods.RegOverridePredefKey(HKEY_CURRENT_USER, IntPtr.Zero);
            NativeMethods.RegCloseKey(hkMyCU);
            return;
        }
    }

    SetPlaces takes an argument as an array of up to five objects that can be numbers as special folders or strings as regular folders. The Disposed event set on the FileDialog makes an automatic call to the ResetPlaces that restores the registry. As a convenience, I’ve included the Places helper enumeration for predefined special folders. Be aware that this could fail on Vista or newer Windows OSes due to UAC.
    If you are running your application on Vista or later, ignore this class and use Microsoft’s FileDialogCustomPlacesCollection class instead, or better yet, use some logic to select the method based on the OS version.

    Using the Control

    Now, let's put it to work. To start using it, you can drop the code in your project or just add a reference to the FileDlgExtenders.dll assembly or to FileDlgExtenders project. If you choose the latter, build the solution before you move forward, because you need the base class at design time. To make things as easy as possible, select 'Add User Control' to your project, than pick 'Inherited User Control' and finally select FileDialogControlBase from the list. As an example, I've added a control called MySaveDialogControl that is just converting images to thumbnails of the desired dimensions, orientation and file format. Next you will probably set the properties and events in design mode. There are 2 ways to display the control.

    Display it by calling the regular method ShowDialog

    To set up FileDialog's data at runtime, override the virtual method OnPrepareMSDialog() in your subclass. You should call base.OnPrepareMSDialog() firstly, so your own changes won't be wiped away. Below is an example of how I change FileDialog's properties at runtime in my derived control.

    protected override void OnPrepareMSDialog()
    {
        base.FileDlgInitialDirectory = Environment.GetFolderPath
    				(Environment.SpecialFolder.MyPictures);
        if (Environment.OSVersion.Version.Major < 6)
        	MSDialog.SetPlaces( new object[] { @"c:\", 
    	(int)Places.MyComputer, (int)Places.Favorites, (int)Places.Printers, 
    	(int)Places.Fonts, });
        base.OnPrepareMSDialog();
    }

    A similar precaution is needed if you choose to override OnLoad. Always call base.OnLoad, otherwise the Load event won't get called. As a tip, if Visual Studio can't render the new control, clean the solution and rebuild it again. If it still does not work, you might have to manually purge the bin folders, and check what objects you initialize at Design time.
    Finally, here is the caller displaying it:

    using (MySaveDialogControl saveDialog = new MySaveDialogControl(lblFilePath.Text, this))
    {
        if (saveDialog.ShowDialog(this) == DialogResult.OK)
        {
            lblFilePath.Text = saveDialog.MSDialog.FileName;
        }
    }

    Display it by Calling the Extension Method ShowDialog of Extensions Class

    Some of you might like more the syntactical sugar offered by the extension method ShowDialog:

    public static class Extensions
    {
        public static DialogResult ShowDialog
    	(this FileDialog fdlg, FileDialogControlBase ctrl, IWin32Window owner)
       {
            ctrl.FileDlgType =(fdlg is SaveFileDialog)?
    		FileDialogType.SaveFileDlg: FileDialogType.OpenFileDlg;
            if (ctrl.ShowDialogExt(fdlg, owner) == DialogResult.OK)
                return DialogResult.OK;
            else
                return DialogResult.Ignore;
        }
    }

    You won’t have to care about overriding OnPrepareMSDialog() anymore, but you will have to set the FileDialog members programmatically at runtime in the caller code.

    using (MyOpenFileDialogControl openDialogCtrl = new MyOpenFileDialogControl())
    {
        openDialogCtrl.FileDlgInitialDirectory = 
    	Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
        OpenFileDialog openDialog = new OpenFileDialog();
        openDialog.InitialDirectory = Environment.GetFolderPath
    		(Environment.SpecialFolder.MyPictures);
        openDialog.AddExtension = true;
        openDialog.Filter = "Image Files(*.bmp)|*.bmp 
    	|Image Files(*.JPG)|*.JPG|Image Files(*.jpeg)|*.jpeg
    	|Image Files(*.GIF)|*.GIF|Image Files(*.emf)|*emf.|
    	Image Files(*.ico)|*.ico|Image Files(*.png)|*.png|
    	Image Files(*.tif)|*.tif|Image Files(*.wmf)|*.wmf|Image Files(*.exif)|*.exif";
        openDialog.FilterIndex = 2;
        openDialog.CheckFileExists = true;
        openDialog.DefaultExt = "jpg";
        openDialog.FileName = "Select Picture";
        openDialog.DereferenceLinks = true;
        if (Environment.OSVersion.Version.Major < 6)
        	openDialog.SetPlaces(new object[] { @"c:\", 
    	(int)Places.MyComputer, (int)Places.Favorites, 
    	(int)Places.Printers, (int)Places.Fonts, });
        if (openDialog.ShowDialog(openDialogCtrl, this) == DialogResult.OK)
        {
            lblFilePath.Text = openDialog.FileName;
        }
    }

    History

    • This is version 1.0 if you disregard the work it's based on. For me, it works fine on 32 bit Windows XP SP3. I hope you'll enjoy it.
    • Version 1.1 fixes a bug associated with the destruction of the file list and keeps the handle and the view mode up to date. I've added a new design time property to enable/disable the OK button called 'FileDlgEnableOkBtn'.
      Below is an example of how this property can be used based on the conversion result:
      private void MySaveDialogControl_FilterChanged
                      (IWin32Window sender, int index)
      {
          FileDlgEnableOkBtn = GetFormatFromIndex(index);
      }
    • Version 1.2 added several fixes suggested by viewers who left feedback.
      Here are the most important changes as I see them:
      • John Horigan: set the 'AutoUpgradeEnabled' property for Vista/7 to false
      • LETRESTE Bruno: improved the autosize when showing the dialog
      • The solution has been converted to Visual Studio 2008. The old source code for VS 2005 is available in the customFileDialog_old.zip
      • If you still use VS 2005, just copy the new *.cs files over the old ones and open the solution/project. It should work.
      • I also used an automated tool to create a VB version for the same solution.
      • The VB source code is included too and it seems to work.
    • Version 1.3 has fixed more issues, thanks to viewers like beautyod
    • I’ve also added support for the places bar as a new feature targeted at windows 2000 and XP.
    • There are solution files for VS 2008(.NET 3.5) and 2010(.NET 4.0) for both VB and C#. The previous source code is available in the embedded CustomFileDialog_src_old.zip archive.
    • Version 1.4 is incorporating more fixes related to Windows 7, 64 bit, etc, thanks to the comments provided by Phil Atkin, John Simmons / outlaw programmer, neyerMat, kore_sar and others.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    dmihailescu
    Software Developer (Senior)
    United States United States
    Decebal Mihailescu is a software engineer with interest in .Net, C# and C++.

    Sign Up to vote   Poor Excellent
    Add a reason or comment to your vote: x
    Votes of 3 or less require a comment

    Comments and Discussions

     
    You must Sign In to use this message board.
    Search this forum  
        Spacing  Noise  Layout  Per page   
    QuestionHow to change folder programmaticaly?membervanselm27-Jan-13 22:27 
    Hello,
     
    I wonder how to change to a different folder/path, if the user select one of recent used files from the "File name" listbox. I have already found out how to react on changes in "File Name" Listbox, but do not know what window message to send in order to change folder and update the list view. Can you help me please? Thanks.
     
    Best regards,
     
    Vitali
    AnswerHow to react on Selection Change of FileName listbox?membervanselm25-Jan-13 1:04 
    In general, it is possible to drop down the listBox "file name" and you will see the recent opened files. Therefore, you can chose one of the entries. However, the preview was not updated in that case. You can fix it, if you add the following code in the "WndProc()" method of the "WholeDialogWrapper" class:
     
    case Msg.WM_COMMAND:
                            switch (NativeMethods.GetDlgCtrlID(m.LParam))
                            {
                                case (int)ControlsId.ButtonOk://OK
                                    break;
                                case (int)ControlsId.ButtonCancel://Cancel
                                    break;
                                case (int)ControlsId.ButtonHelp://help
                                    break;
                                case (int)ControlsId.ComboFileName:  // file Name combo box
                                    if (m.WParam == (IntPtr)66684)
                                    {
                                        StringBuilder filePath = new StringBuilder(256);
                                        NativeMethods.SendMessage(
                                            new HandleRef(this, _hFileDialogHandle),
                                            (uint)DialogChangeProperties.CDM_GETFILEPATH,
                                            (IntPtr)256,
                                            filePath);
                                        if (_CustomControl != null)
                                        {
                                            _CustomControl.OnFileNameChanged(this, filePath.ToString());
                                        }
                                    }
                                    break;

    QuestionRe: How to react on Selection Change of FileName listbox?membervanselm27-Jan-13 23:18 
    Unfortunately, the solution above works only, if nothing is selected within the list view. As soon as some element is selected the following code returns the path of selected element, but not the current path of the FileName listbox:
    NativeMethods.SendMessage(
                                            new HandleRef(this, _hFileDialogHandle),
                                            (uint)DialogChangeProperties.CDM_GETFILEPATH,
                                            (IntPtr)256,
                                            filePath);
     
    How to get the real path, then? Thanks.
    QuestionHow to compile it successfully on VS 2005memberMember 378397625-Nov-12 3:58 
    Could you guide me how to make it code-compatible with VS 2005?
     
    Thanks a lot.
    QuestionSplitter bar between file dialog and extensionmemberPhil Jollans4-Oct-12 22:21 
    I have extended the file dialog with additional information on the right hand side. At present, the extended part of the dialog has a fixed width.
    It would be cool if I could add a splitter bar between the original file open dialog and the extended part.
    Is this possible? Does anybody have some sample code?
     
    Phil
    QuestionHow to close the dialog programaticallymemberPhil Jollans4-Oct-12 22:16 
    In my application, the extended part of the dialog shows a list of items in the file.
    I would like to select an item from this list and close the dialog with a double click on the item.
    Is there any way to close the file open dialog from my DoubleClick event?
     
    Phil
    AnswerRe: How to close the dialog programaticallymemberdmihailescu5-Oct-12 15:46 
    Call the OK event.
    GeneralRe: How to close the dialog programaticallymemberPhil Jollans8-Oct-12 21:40 
    Confused | :confused: I'm sorry if this is a stupid question, but I don't understand what you mean.
     
    Exactly which method do I have to call?
     
    Phil
    GeneralRe: How to close the dialog programaticallymemberPhil Jollans7-Nov-12 5:40 
    I just took another - admittedly quick - look and I still find this non obvious. A tip would be gratefully accepted.
     
    Phil
    GeneralRe: How to close the dialog programaticallymemberPhil Jollans8-Nov-12 22:14 
    I have added a method OKClick() to the class FileDialogControlBase. This method simply sends a BM_CLICK message to the OK button, using the handle _hOKButton.
     
    This was an easy solution which solved my problem. I still don't know method Decebal was referring to, but thanks again for a great article.
     
    Phil
    QuestioninterestingmemberCIDev20-Sep-12 7:13 
    A very interesting and useful article.
    Just because the code works, it doesn't mean that it is good code.

    QuestionShow/Hidemembercharga28-Aug-12 12:34 
    Great Work!!
     
    One question, what can I do in order to implement show/hide the extender portion of dialog? If show display, if not show hide (look like standard dialog)
    AnswerRe: Show/Hidememberdmihailescu28-Aug-12 15:48 
    charga wrote:
    One question, what can I do in order to implement show/hide the extender portion
    of dialog? If show display, if not show hide (look like standard dialog)

    in AssignDummyWindow() make the dummy window IntPtr.Zero
    GeneralRe: Show/Hidemembercharga28-Aug-12 19:13 
    Ok, the main idea is to show/hide the extended area of the dialog at runtime, something like change the size or similar.
     
    Maybe I can put the dummy window to IntPtr.Zero, but not at runtime
    QuestionPossible to get this working with AutoUpgradeEnabled = true?memberapex7517-Jul-12 4:27 
    I really like the work you have done, but as it's not possible to use the newer Vista / windows 7 Look & Feel (AutoUpgradeEnabled = true) I can't use this. Is it possible to get this somehow working with the new Dialogs?
     
    Best wishes,
    Robin
    QuestionChange in filename editbox does not cause navigation to new foldermemberlzanoni218-Jun-12 22:39 
    If the user is manually changing the text in the file name field (edit box) and then presses enter, it doesn"t have the same behaviour as the stock OpenFileDialog: it doesn't change the current folder, it just 'beeps'.
     
    Any hint ?
    QuestionError after maximizing the DialogmemberBen Ahlborn13-Jun-12 12:49 
    Has any one come across this bug? It happens in my application, and I verified that it also happens in the CustomOpenFileDialog.exe app.
     
    (1) Launch CustomOpenFileDialog.exe
    (2) Click "Save"
    *Click some images see that it is working.
    (3) Double click in the title bar to maximize the Save dialog
    *Click some images see that it is working.
    (4) Close the save dialog
    (5) Click "Save" to reopen the dialog
    * Notice that the preview controls are absent from the right. Click on the images and see that nothing is happening.
    (6) Double click in the title bar to restore the Save dialog to its windowsed state
    * Notice that the preview controls are absent from the right. Click on the images and see that nothing is happening.
    (7) Close the save dialog
    (8) Click "Save" to reopen the dialog
    *Click some images see that it is working.
     

    It appears that if the dialog is opened in a maximized state, it doesn't work properly. When this occurs the file changed event is not being fired.
    AnswerRe: Error after maximizing the DialogmemberBen Ahlborn14-Jun-12 6:38 
    It appears that WM_SHOWWINDOW is not called when the dialog is maximized. I removed the WM_SHOWWINDOW handling in WholeDialogWrapper.WndProc and changed the WM_PAINT handler to
     
    <pre lang="vb">If (Not mInitializated) Then
        InitControls()
    End If</pre>
     
    
     
    This appears to have resolved the issue.

    AnswerRe: Error after maximizing the Dialogmemberdmihailescu14-Jun-12 17:08 
    what OS do you use when this bug occurs?
    Questionthe form is too largememberqingshanduizuo10-May-12 17:12 
    I just want to add a combobox at the bottom of the savefiledialog,while the height of FileDialogControlBase is almost OpenFileDialog.Height*2.Can i short the height of FileDialogControlBase ?
    AnswerRe: the form is too largememberdmihailescu11-May-12 17:47 
    just drag the margin of the control or set the Height property as you wish.
    QuestionChange filter during runtimememberMember 382855828-Mar-12 4:43 
    First of all I want to thank you for this great work. Therefore voting with 5.
     
    Is there a possibility to add or remove filters programmatically during runtime? I.e., I display the dialog and accordingly to some action I would like to change the list of filters. Setting the FileDlgFilter has no impact...
     
    Thanks in advance,
    Thomas
    AnswerRe: Change filter during runtimememberdmihailescu3-Apr-12 6:41 
    I don't think you can set it except at start-up. That's the behaviour of the underlying FileDialog. :(
    GeneralWorks great on win 7 x86 machines !memberMazen el Senih23-Mar-12 4:34 
    Thanks and here is my Thumbs Up | :thumbsup: Thumbs Up | :thumbsup: Thumbs Up | :thumbsup: Thumbs Up | :thumbsup: Thumbs Up | :thumbsup:
    GeneralMy vote of 5memberProEnggSoft22-Mar-12 7:31 
    Good article
    QuestionChange filename in SaveDialog at runtimememberMember 447014618-Mar-12 5:24 
    I had developed my control based on the same concept. My Control contains 3 radiobuttons and I want on radiobutton_checkchanged event, the filename should get changed in the filename combobox.
    How can I achieve that? Please Help
     
    EzDee
    QuestionHow to dynamically change filenamememberMember 447014615-Mar-12 21:21 
    I had developed my control based on the same concept. My Control contains 3 radiobuttons and I want on radiobutton_checkchanged event, the filename should get changed in the filename combobox.
    How can I achieve that? Please Help
    AnswerRe: How to dynamically change filenamememberdmihailescu21-Mar-12 8:05 
    Member 4470146 wrote:
    I had developed my control based on the same concept. My Control contains 3
    radiobuttons and I want on radiobutton_checkchanged event, the filename should
    get changed in the filename combobox.
    How can I achieve that?

    Use FileDlgFileName and change its value on check events.
    GeneralRe: How to dynamically change filenamememberMember 447014622-Mar-12 0:49 
    I tried doing the same, but the filename is not reflecting in the filename dropdown in the dialog
    GeneralRe: How to dynamically change filename [modified]memberdmihailescu22-Mar-12 5:45 
    In the set property you also have to add a SendMessage call for the _hLabelFileName window with the message WM_SETTEXT and with the new text.
    NativeMethods.SetWindowText might work too.

    modified 23-Mar-12 9:03am.

    AnswerRe: How to dynamically change filenamememberMember 382855828-Mar-12 4:46 
    For me
    NativeMethods.SetDlgItemText(_hFileDialogHandle, ControlsId.ComboFileName, _FileName)
    is working fine...
    QuestionHave you looked in to supporting the AutoUpgradeInfo flag?memberjefflund23-Jan-12 10:20 
    I am looking to do something similar, but I need to use the AutoUpgradeInfo flag. Have you per chance looked in to what you would need to do to support the upgraded dialog?
     
    Thanks,
     
    Jeff
    AnswerRe: Have you looked in to supporting the AutoUpgradeInfo flag?memberdmihailescu24-Jan-12 16:31 
    that's a property on the control.
    GeneralRe: Have you looked in to supporting the AutoUpgradeInfo flag?memberJISE15-Feb-13 6:35 
    I'm having trouble turning this on.
     
    Whereabouts should I set AutoUpgradeEnabled = true?
    GeneralRe: Have you looked in to supporting the AutoUpgradeInfo flag?memberdmihailescu18-Feb-13 13:03 
    It was in the article above:
    System.Reflection.PropertyInfo AutoUpgradeInfo =
    MSDialog.GetType().GetProperty("AutoUpgradeEnabled");
    if (AutoUpgradeInfo != null)
    AutoUpgradeInfo.SetValue(MSDialog, false, null);
    GeneralMy vote of 5memberPhil Atkin6-Dec-11 1:33 
    An extensive piece of work that solves a common and difficult problem. Unlike some others, I don't expect you to be available, indefinitely and at short notice, to fix any issue I might have with it.
    GeneralRe: My vote of 5memberdmihailescu6-Dec-11 15:48 
    Thanks,Rose | [Rose]
    My time is limited and I would have to update both C# and vb.net versions I maintain.
    Add testing for varios environments like XP, vista, win 7, 32 and 64 bits and you'll understand why an apparently simple fix takes so much time. I need to tests in all those environments to make sure that the 'cure' is not worse than the 'disease'.
    QuestionInitial size of dialog [modified]memberPhil Atkin6-Dec-11 1:31 
    I'm running Vista 32. I had the same problem that mariusmeier reported (who was running Windows 7): the initial size of the dialog was very large.
    It seems that Vista (and presumably Windows 7) sends WM_WINDOWPOSCHANGING to the WholeDialogWrapper with SWP_NOSIZE unset several times. The code is not written with this possibility in mind, and adds the (width or height) of the custom control each time.
    I worked around this by adding a bool mResized flag to the WholeDialogWrapper (initial value false). I then test for this flag to be false (instead of bInitialized, which is only set once, in InitControls), and set it true once the window has been resized in WholeDialogWrapper.WndProc:
     
    case Msg.WM_WINDOWPOSCHANGING:
        if (!mIsClosing)
        {
            if (!mResized)
            {
                // Resize OpenDialog to make fit our extra form
                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))
                {
                    switch (_CustomControl.FileDlgStartLocation)
                    {
    ...
                    }
                    mResized = true; // Don't resize again
                }
            }
        }
     
        break;
    
    I haven't tested this except under Vista.
    Phil Atkin
    Cambridge, UK


    modified 9-Dec-11 8:26am.

    QuestionRe: Initial size of dialogmemberbeautyod9-Dec-11 0:37 
    Thanks for your report.
     
    Mistype in your text: Change "bResized" (with B) to "mResized" (with M).
     
    I couldn't figure out how to apply your code.
    In WndProc() of class "NativeFileDialogWrapper" is no "_CustomControl".
    And in WndProc() of class "DialogWrapper" the switch doesn't like the enum "Msg.WM_WINDOWPOSCHANGING".
     
    Also I don't know what to add as replacement for "...".
    It would be nice when you edit your post and add more details.
    AnswerRe: Initial size of dialogmemberPhil Atkin9-Dec-11 2:28 
    You are correct about the bResized/mResized typo: I've corrected this.
    The corrected code is from WholeDialogWrapper.WndProc; I've edited to clarify this. I hope the edit is easier to apply now.
    Phil Atkin
    Cambridge, UK

    GeneralMy vote of 3mvpJohn Simmons / outlaw programmer13-Oct-11 5:47 
    It's reasonably good code, but your lack of desire to update it IMMEDIATELY upon hearing about problems is annoying.
     
    Saying "I'll revist the code with VS2012 comes out" is pretty much unacceptable.
     
    That is the sole reason I gave your article a 3 instead of a 4 or 5.
    QuestionBug Regarding Sizing (with Fix for C#)mvpJohn Simmons / outlaw programmer13-Oct-11 5:32 
    I created a control and implemented it. When I made the form smaller than my control, my control wouldn't be resized until I clicked on the form's border. The fix is here:
     
    File: HelperTypes.cs
    Class: WholeDialogWrapper
    Method: WndProc
     
    case Msg.WM_SIZE:
    {
        NativeMethods.GetClientRect(new HandleRef(this,_hFileDialogHandle), ref currentSize);
        switch (_CustomControl.FileDlgStartLocation)
        {
            case AddonWindowLocation.Bottom:
                if (!mInitializated && FileDialogControlBase.OriginalDlgWidth == 0)
                {
                    FileDialogControlBase.OriginalDlgWidth = currentSize.Width;
                }
                // the following line will only resize the control if the form is LARGER than the control
                //if (currentSize.Width > _CustomControl.Width)
                // new code - resize the control if it's not the same size as the control
                if (currentSize.Width != _CustomControl.Width)
                {
                    _CustomControl.Width = (int)currentSize.Width;
                }
                break;
     
                ...
    }
    break;
    ".45 ACP - because shooting twice is just silly" - JSOP, 2010
    -----
    You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
    -----
    "Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

    SuggestionDetailsView sort by datememberneyerMat12-Oct-11 1:50 
    My users allways come up with questions about the FileDialogs. Now they want to have automatically details view and sorted by date on start of the diaolg. I have found how to change to details view but now how to sort. Here is what i got. it works for me. I simply call this funtion in the OnLoad override:
     
    	public void SortByDate()
    		{
    			//handle of the "defView" --> container of the listView  
    			IntPtr hWndWin = NativeMethods.FindWindowEx(_dlgWrapper.Handle, IntPtr.Zero, "SHELLDLL_DefView", "");
     
    			if (hWndWin != IntPtr.Zero)
    			{
    				//change to details view
    				NativeMethods.SendMessage(new HandleRef(this, hWndWin), (int)Msg.WM_COMMAND, (IntPtr)(int)DefaultViewType.Details, IntPtr.Zero);
     
    				#region  sort by date
    				int HDN_FIRST = (-300);
    				int HDN_ITEMCLICKW = (HDN_FIRST - 22);
     
    				//get the ListView//s hWnd
    				IntPtr hWndLV = NativeMethods.FindWindowEx(hWndWin, IntPtr.Zero, "SysListView32", IntPtr.Zero);
    				//get the ColumnHeaders hWnd
    				IntPtr hWndColHd = NativeMethods.FindWindowEx(hWndLV, IntPtr.Zero, "SysHeader32", IntPtr.Zero);
     
    				//now click on column 3 to sort for date
    				NMHEADER NMH = new NMHEADER();
    				NMH.hdr.hwndFrom = hWndColHd;
    				NMH.hdr.code = HDN_ITEMCLICKW;
    				NMH.iItem = 3;
    				NMH.iButton = 0;
     
    				// Initialize unmanged memory to hold the struct.
    				IntPtr ptrNMH = Marshal.AllocHGlobal(Marshal.SizeOf(NMH));
    				try
    				{
     
    					// Copy the struct to unmanaged memory.
    					Marshal.StructureToPtr(NMH, ptrNMH, false);
     
    					NativeMethods.SendMessage(new HandleRef(this, hWndLV), (uint)Msg.WM_NOTIFY, IntPtr.Zero, ptrNMH);
    					//click again for descending order = newest files first
    					NativeMethods.SendMessage(new HandleRef(this, hWndLV), (uint)Msg.WM_NOTIFY, IntPtr.Zero, ptrNMH);
    				}
    				finally
    				{
    					// Free the unmanaged memory.
    					Marshal.FreeHGlobal(ptrNMH);
    				}
     
    
     
    				////if wanted give the dialog a larger size here
    				//If DialogXSize > 0 And DialogYSize > 0 Then
    				//   SetWindowPos hWndDlg, 0&, 0&, 0&, DialogXSize, DialogYSize, 0&
    				//End If
    				//}
    				#endregion
    			}
    		}
     

    you also need the these structs:
    	[StructLayout(LayoutKind.Sequential)]
    		public struct NMHDR
    		{
    			public IntPtr hwndFrom;
    			public int idFrom;
    			public int code;
    		}
    		[StructLayout(LayoutKind.Sequential)]
    		public struct NMHEADER
    		{
    			public NMHDR hdr;
    			public int iItem;
    			public int iButton;
    			public IntPtr pItem;
    		}
     
    and i added following function imports to NativeMethods:
    		[DllImport("user32.dll", SetLastError = true)]
    		public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
     
    
            internal static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, StringBuilder lParam);
    		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    
     

     
    Maybe the author will implement this someday in that article !? Wink | ;-)
     
    take care
    M.N.
     
    PS: found this on (VB6.0 Dead | X| ): http://63.236.73.79/forum/showthread.php?t=443285[^]
    GeneralRe: DetailsView sort by datememberdmihailescu12-Oct-11 16:48 
    thanks, nice tipRose | [Rose]
    I might update the article when the Visual Studio 2012 comes out.
    GeneralRe: DetailsView sort by datememberAsaba Nahayama29-Jan-12 5:20 
    As kore_sar (11 Apr 2011) noted below, NMHDR's idFrom field must be an IntPtr, or it will not work on 64-bit systems which, face it, are becoming more and more popular.
     
    Repeating this here so that anyone who reads only this thread will also be aware of it, so they don't introduce bugs that aren't visible on their 32-bit development environment, not that I'd have any experience with that...
    ~~a

    GeneralWindows 7 - Dialog size problemmembermariusmeier25-Apr-11 20:49 
    Hello
     
    I run this code with .NET 2.0 under Windows 7 32 Bit. My main problem is that the dialog opens always with a too big dialog size (width and height).
    I haven't any options to control this manner. I tried to implement in the WndProc() method the GET_MINMAXINFO to control the max size but this was not
    the solution because it does not work. How can i set the start width and start height of the whole dialog?
     
    Greetings
    GeneralRe: Windows 7 - Dialog size problemmemberelectrawinds5-Dec-11 5:13 
    Hi,
     
    I am having the same issue.
     
    Seems like the calculations for the dialog size are not correct under win. 7.
     
    Will investigate further,
     
    Kind regards,
     
    Tom Staelens
    GeneralRe: Windows 7 - Dialog size problemmemberelectrawinds5-Dec-11 5:28 
    Hi,
     
    Found a quick hack Smile | :)
     
    Change the following code :
     

    internal static uint OriginalDlgWidth
          {
              get { return FileDialogControlBase._originalDlgWidth; }
              set { FileDialogControlBase._originalDlgWidth = value; }
          }
     
          internalstatic uint OriginalDlgHeight
          {
              get { return FileDialogControlBase._originalDlgHeight; }
              set { FileDialogControlBase._originalDlgHeight = value; }
          }
     
    to
     
    public static uint OriginalDlgWidth
          {
              get { return FileDialogControlBase._originalDlgWidth; }
              set { FileDialogControlBase._originalDlgWidth = value; }
          }
     
          public static uint OriginalDlgHeight
          {
              get { return FileDialogControlBase._originalDlgHeight; }
              set { FileDialogControlBase._originalDlgHeight = value; }
          }
     
    Then you can access these variables from your custom openfiledialog:
     
    public MyOpenFileDialogControl(int width)
           {
               base.Width = width;
               InitializeComponent();
               OriginalDlgWidth = 400;
               OriginalDlgHeight = 1000;
           }
     
    At the moment these variables are declared "static" just change the modifier to public and there ya go, works for me...
     
    Greets,
     
    Tom
    GeneralFolderFolderSelect option FolderSelect option & extended Filename filtermemberk t jackson19-Apr-11 4:31 
    Two features that I would like to see in an extended OpenFile dialog are:
     
    1) FolderSelect option -
     
    FolderSelect would allow the user to open/select folders instead of files, and would free the developer from having to use the horrible FolderBrowser dialog.
     
    a) A useful subfeature would be an "IncludeSubFolders" checkbox
     

    2) Filename filter -
     
    Sometimes it's useful to include/exclude files that appear in the dialog based upon a name filter -- in addition to the extension filter. For example, suppose I only want to show files that begin with "filenamePrefix_" or suppose I want to EXCLUDE files that end with "_filenameSuffix". It would be great be able to filter files, based upon a pattern-matching schema -- preferably regular expressions.
    GeneralFound a bug! 'struct NMHDR' is wrong for 64 bit Windowsmemberkore_sar11-Apr-11 5:32 
    See Win32Types.NMHDR struct. The item 'idFrom' should be of type 'IntPtr' but not 'uint'.
     
    Due to this bug no events are thrown when user selects a file or changes folder on the dialog.

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Permalink | Advertise | Privacy | Mobile
    Web04 | 2.6.130617.1 | Last Updated 22 Mar 2012
    Article Copyright 2007 by dmihailescu
    Everything else Copyright © CodeProject, 1999-2013
    Terms of Use
    Layout: fixed | fluid