9

I have been using this example to customize the save dialog,

http://www.codeproject.com/Articles/19566/Extend-OpenFileDialog-and-SaveFileDialog-the-easy

This works well and I could customize the dialog too. However, I see that the customized dialog does not follow the underlying windows style. For example, If I am in Windows 7 the dialog would look like this,

enter image description here

This is a save dialog from word and it does have few options like tags and stuff. But the look and feel is same as OS save dialog. However, the custom save dialog with the above mentioned link would look like this,

enter image description here

Why would it not follow what OS offers? Is there any way to handle this?


Ok, I researched a bit and got to the point where I can use CommonSaveFileDialog from Microsoft.WindowsAPICodePack.Dialogs and create the underlying Save dialog ( which does match with Windows 7 style. ). I installed the WindowsAPI shell package and used the CommonSaveFileDialog control to create something like this,

enter image description here

The controls marked in red are actually CommonFileDialogLabel / CommonFileDialogTextBox / CommonFileDialogComboBox etc which are provided in those API. But now my question is how do I add a user control / custom control to this? I need full control over what I add so it could be a user control. Any idea.. Please help Thanks.

user1821499
  • 301
  • 4
  • 22
  • That is the Windows XP common dialog box style. If you want to use the new Visma/7/etc. style then you have to use the new common item dialog https://msdn.microsoft.com/en-us/library/windows/desktop/bb776913(v=vs.85).aspx#customizing. Also see this article http://www.codeproject.com/Articles/16678/Vista-Goodies-in-C-Using-the-New-Vista-File-Dialog. – Marius Bancila Jun 24 '15 at 21:14
  • Yes this looks like it. It is changed post vista. But the link you provided have C++ / VC++ code and I am looking for C#.net is there any other link? Thanks! – user1821499 Jun 24 '15 at 22:12
  • Can someone please help? I tried to get to IFileDialogCustomize example C# but I am not able to use any of those. A sample or example would be really helpful! – user1821499 Jun 25 '15 at 21:52

1 Answers1

6

The suggested solution works as described:

The Save As file dialog (used in this example) is associated to a User Control, called CustomSaveFileDialog. It has the advantage that it is present in the Toolbox, and that it implements automatically the IDisposable interface. However, it could have also been a simple C# class.

This control has a constructor accepting an arbitrary application specific User Control hosting all the elements which are to show up in the File Dialog. When I got the question right, that is what is required.

The CustomSaveFileDialog has the following properties:

  • Accepting arbitrary User Controls Docked to the bottom of the File Dialog, i.e. they follow the resizing of the File Dialog
  • No special behaviour for the additional elements (buttons, images, checkboxes etc) is necessary. They act quite normally, as in other windows.

This is the code of the described class:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace customSaveFileDialog
{
    public partial class CustomSaveFileDialog : UserControl
    {
        //https://stackoverflow.com/questions/9665579/setting-up-hook-on-windows-messages
        delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
            IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

        const uint WINEVENT_OUTOFCONTEXT = 0;

        [DllImport("user32.dll")]
        private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
           hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
           uint idThread, uint dwFlags);

        [DllImport("user32.dll")]
        private static extern bool UnhookWinEvent(IntPtr hWinEventHook);

        [DllImport("user32.dll")]
        private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool repaint);
        private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }

        [DllImport("user32.dll")]
        private static extern bool GetClientRect(IntPtr hWnd, out RECT rc);

        [DllImport("kernel32.dll")]
        private static extern uint GetLastError();

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetParent(IntPtr hwndChild, IntPtr hwndNewParent);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr GetParent(IntPtr hWnd);

        private IntPtr hDlg;        // Save As dialog handle
        private IntPtr hHook;       // Event hook
        private IntPtr hCtrl;       // App. specific user control handle

        UserControl ctrl;           // App. specific user control

        //Static variable containing the instance object
        private static CustomSaveFileDialog customSaveFileDialog;

        //public property for the user
        //theSaveFileDialog has been added to the control in the designer from the Toolbox
        public SaveFileDialog Dlg { get { return theSaveFileDialog; } }

        //Event hook delegate
        private static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc);

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="ctrl">The User Control to be displayed in the file dialog</param>
        public CustomSaveFileDialog(UserControl ctrl)
        {
            InitializeComponent();

            customSaveFileDialog = this;
            this.ctrl = ctrl;
            hCtrl = ctrl.Handle;

            //Setup Hook; for simplicity, hook all possible events from the current process
            hHook = SetWinEventHook(1, 0x7fffffff, IntPtr.Zero,
                    procDelegate, (uint)Process.GetCurrentProcess().Id, 0, WINEVENT_OUTOFCONTEXT);
        }


        // Hook function
        static void WinEventProc(IntPtr hWinEventHook, uint eventType,
            IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
        {
            CustomSaveFileDialog csfdg = customSaveFileDialog;
            if (csfdg.hDlg == IntPtr.Zero)
                csfdg.hDlg = FindWindowEx(IntPtr.Zero, IntPtr.Zero, "#32770", "Save As");

            if (hwnd == csfdg.hDlg) 
            {
                IntPtr hParent = GetParent(csfdg.hCtrl);

                //this is done only once
                if (!(hParent == csfdg.hDlg))
                    SetParent(csfdg.hCtrl, csfdg.hDlg);   //Bind the user control to the Common Dialog

                RECT cliRect;
                GetClientRect(csfdg.hDlg, out cliRect);

                //Position the button in the file dialog
                MoveWindow(csfdg.hCtrl, cliRect.Left + 130, cliRect.Bottom - 55, 500, 60, true);
            }
        }
    }
}

The essential part is the hooking of the windows events. This has been taken from that post.

It may be noted that the "FindWindowEx" function (in the WinEventProc) finds all Common Dialogs (and probably more) with a title of "Save As". If this should be a problem, more filtering would be necessary, e.g by searching in the current thread only. Such a search function may be found here.

Additionally (not shown in the above code) the "Dispose" method in CustormSaveFileDialog.desinger.cs contains the Unhook function with the hHook handle as the parameter.

The software has been tested in Windows7 in Debug mode. As a test, a simple Forms window with a button has been implemented:

        //Test for the customized "Save As" dialog
        private void button1_Click(object sender, EventArgs e)
        {
            //Arbitrary User Control
            myUserControl ctrl = new myUserControl();

            using (CustomSaveFileDialog csfdg = new CustomSaveFileDialog(ctrl))
            {
                csfdg.Dlg.FileName = "test";

                //Show the Save As dialog associated to the CustomFileDialog control
                DialogResult res = csfdg.Dlg.ShowDialog();
                if (res == System.Windows.Forms.DialogResult.OK)
                    MessageBox.Show("Save Dialog Finished");
            }
        }

And - as well as a test - the applicatioin specific user control handles the following events:

using System;
using System.Windows.Forms;

namespace CustomFile
{
    public partial class myUserControl : UserControl
    {
        public myUserControl()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Button Clicked");
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Image Clicked");

        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (!checkBox1.Checked)
                pictureBox1.Visible = false;
            else
                pictureBox1.Visible = true;
        }
    }
}

The following output is produced:

enter image description here

The next picture shows another screenshot, File Dialog resized, and the checkbox to display the image is unchecked.

enter image description here

Community
  • 1
  • 1
josh
  • 671
  • 5
  • 10
  • This works but the user control can't be bigger than what you have shown here in the picture. There is very little space between "Save as type" and "Browse / hide folders" link. My user control may be bigger and I need extra space in between. Is this possible? – user1821499 Jun 29 '15 at 19:49
  • Yes, you are right. The presented solution works only for minor additions to the common dialog within the space present. You may consult http://www.codeproject.com/Articles/16276/Customizing-OpenFileDialog-in-NET, being the base of the solution cited by yourself in your initiating post, and being a lot more complex than the one presented here. In a nutshell, IMO it is ridiculous by MS to have us to tinker with all kinds of pinvoke to customize such a dialog. – josh Jun 29 '15 at 20:15
  • Well said.. Thanks Josh. I will see what is the best solution for my needs. – user1821499 Jun 29 '15 at 20:44
  • Is there any way to handle the resize event for this window? When we resize the save dialog, it goes over my user control.. I want the user control to stay at one place ( may be with a scroll ). I tried normal dock / anchor properties but it does not work. – user1821499 Jul 09 '15 at 04:56
  • Is there a way I can change the dialog background color? And that of the favourite..desktop.. list as well? – mrid Jan 24 '19 at 02:17