A few days ago, I wanted to start creating an Icon Editor application to make use of my IconLib library.
I created my main form, and I thought “where do I need to start”. Then, I decided to create a menu with the Open functionality. I thought the Open feature should have a preview screen to see the icon before opening it.
If you are reading this page, probably it is because you know that .NET has an
OpenFileDialog class, but it cannot be customized. The objective of this control is to allow you to add some functionality to the
OpenFileDialog .NET class. The main reason why you can’t customize the
OpenFileDialog in .NET is because the class is declared
sealed which means you can’t inherit from it. If you go to the base class
FileDialog, it will allow you to inherit from it, but is has an
internal abstract method “
RunFileDialog”. Because it is
abstract, it only allows inheriting from it inside the same assembly.
How many times have you wanted to put some extra control in the
OpenFileDialog control and you couldn’t…
Searching for code for .NET, I found a couple places where they used MFC, but nothing for .NET. OpenFileDialog is not a native implementation in .NET, instead it makes use of a Win32 API “
At this point, I had three choices:
- Create my own OpenFileDialog from scratch.
- Create my own OpenFileDialog reusing resources, (using the API “GetOpenFileName” and providing my own template).
- Hack the .NET
OpenFileDialog and add the functionality I need for it.
Option (a) was not an option for me because it could require a lot of development time when I have a lot more stuff to be done. Later when the product is finished, I could review it. The next option required me to provide my own template using calls to Win32 API and resources. The option (c) was the more viable option at this time; don’t think of this as a bad hack, basically a hack is when you want to make the control do some extra functionality and you must do it from a different thread or process.
So because I like challenges, I decided to “hack” the
OpenFileDialog class to create my own customizable control.
What it can do for you
I could have hacked the control to do what I needed and that would be it, but I ran into this problem many times right from .NET 1.0, and no one so far had come with a solution for it, so I decided to create an interface to this control where it can be used in different applications.
Also, I wanted to create something that didn’t require changing or adding code to the current project, and be capable of adding multiple controls without knowing the details of how they work; it needed to be a standalone control that can be added just as any other control in the IDE.
I created this control and I called it “
How do I do it?
OpenFileDialogEx as an abstract class: the only reason I didn’t make this class abstract is because the VS IDE can’t create an instance of an abstract class which avoids the rendering in the screen.
You could use the
OpenFileDialogEx class like it is but make no sense, because it contains no extra functionality, just an empty
So you must inherit OpenFileDialogEx to create your own customized version of the Open File Dialog.
After you inherit
OpenFileDialogEx, you have created a custom control where you can add any control, you could add extra buttons, panels, or group boxes. Basically, it is a controls container; later this container will be “appended” to the .NET
OpenFileDialog object on the fly.
There are three extra properties, three methods, and two events in this control that are different from any
This property lets you choose which view the
OpenFileDialog should start in; by default, it opens using the “Details view”. Here you can specify a different default view like Icons, List, Thumbnail, Detail, etc.
This property tells if the control created should be stacked on the right, bottom, or behind the classic
OpenFileDialog. Usually, this property will be the one used to expand the
OpenFileDialog horizontally. If instead, you need to add extra controls to the current
OpenFileDialog, then you can specify “
None” and the controls inside
OpenFileDialogEx will share the same client area with the original
This property is the embedded
OpenFileDialog inside the control. Here you can setup the standards property as InitialDir, AddExtension, Filters, etc.
OpenFileDialog for default is resizable,
OpenFileDialogEx will help you with that automatically; the user control “
OpenFileDialogEx” will be resized automatically. When the user expands or shrinks the window, it will behave differently depending on the
Right: user control will be resized vertically.
Bottom: user control will be resized horizontally.
None: user control will be resized horizontally and vertically.
Basically, when you add your controls as buttons, panels, group boxes etc., you have to set the
Anchor property of every control, then you can control where your control will be when the user resizes the
For example, to have an image preview, you could set the start location at the right, add a
PictureBox to your inherited
OpenFileDialogEx and set the
Anchor property for the
PictureBox to be
Left, Top, Right, Bottom; this will resize the picture box dynamically when the user resizes the
The methods are virtual methods that you will override to interact with the original
This method is called every time the user clicks on any file inside the view.
This method is called every time the user changes a folder from any control inside the
This method is called when the
OpenFileDialog is closing, this is useful to release any resources allocated.
The two events are
FolderNameChanged, those events are fired from their respective virtual methods “
OnFileNameChanged” and “
OnFolderNameChanged”. Instead of using the events, I recommend overriding the methods because it is cleaner code and also it doesn’t have another level of indirection.
How is it done?
The first problem is that
OpenFileDialog is a modal dialog. This means that basically you can’t get the handle of the window because when you call
ShowDialog(), you don’t have the control of the program flow as long the
OpenFileDialog is open.
One way to get the handle of the
OpenFileDialog is to override the
WndProc method on your form and watch for the messages. When
OpenFileDialog is created, the owner form will receive some messages like
WM_NC_ACTIVATE, etc. Those entire set of messages will set the parameter
lParam with the handle to the
As you see, this requires overriding the
WndProc methods. Some developers even don’t know that
WndProc exists, so I wanted to avoid that. Also. I noticed some problems with MDI windows opening an
Then what I did, basically, was when
ShowDialog() is called, it creates a dummy form off the screen and hides it, this form will take care of opening the
OpenFileDialog and taking the
OpenFileDialog window handle.
At first, it listened on the
WM_IDLE message, but the problem is when the message is send, it is already too late and the window is created and shown in the screen. Still, you can change the contents, but the user will see a small flicker on the screen between the original
OpenFileDialog and the customized version.
Instead, we could take the message
WM_ACTIVATE that happens before the
OpenDialog is show on the screen.
So far it gets the handle and it is ready to be shown, now what?
How will it change the properties for the
Here is when the handy .NET
NativeWindow comes into the picture, a
NativeWindow is a window wrapper where it processes the messages sent by the handle associated to it. It creates a
NativeWindow and associates the
OpenFileWindow handle to it. From this point, every message sent to
OpenFileWindow will be redirected to our own
WndProc method in the
NativeWindow instead, and we can cancel, modify, or let them pass through.
WndProc, we process the message
WM_WINDOWPOSCHANGING. If the open dialog is opening, then we will change the original horizontal or vertical size depending of the
StartLocation set by the user. It will increment the size of the window to be created. This happens only once when the control is opened.
Also, we will process the message
WM_SHOWWINDOW. Here, all controls inside the original
OpenFileDialog are created, and we are going to “append” our control to the open file dialog. This is done by calling a Win32 API “
SetParent”. This API lets you change the parent window. Then, basically what it does is “attach” our control to the original
OpenFileDialog in the location it set, depending on the value of the
The advantage of it is that we still have complete control over the controls attached to the
OpenFileDialog window. This means we can receive events, call methods, and do whatever we want with those controls.
Also, in the initialization, we will get the window handles for every control inside the original
OpenFileDialog. This allows again to create .NET
NativeWindows to process the messages in every control.
Now everything is ready, how do we watch for the messages when the user clicks on the
At first, I tried to process the messages from the
ListView itself, creating a
NativeWindow to it, but the problem is that every time the user changes the folder or clicks on a different view, the handler is destroyed and we have to recreate the handler to it.
Analyzing all windows inside
FileOpenDialog with MS Spy, we can notice another
FileDialog window inside the
FileOpenDialog, and very probably it is the base window of
FileOpenDialog. Checking the MSDN documentation, we see that every action made on the
FileOpenDialog fires a
WM_NOTIFY message filling a
OFNOTIFY struct; this struct contains a code of the action made, and two of those actions are
They are called when the user interacts with the folder combo box or the list view. Then, first I get the handle to the base
FileWindow and I create a
NativeWindow from this handle. This allows to process the messages
WM_NOTIFY to analyze the
OFNOTIFY struct and process
CDN_FOLDERCHANGE. When this window processes those messages, they are forwarded to the
OpenFileDialogEx control to the methods
Another method was to intercept when the
FileOpenDialog window is closed. At first, I used the message
WM_CLOSE and it worked, but later I discovered that this message is not been called when the user double clicks on a file inside the list view. Watching the messages produced by
FileOpenDialog, I saw that I could use the
WM_IME_NOTIFY message. This message is sent with a
wParam value of
IMN_CLOSESTATUSWINDOW when the
FileOpenDialog is being closed; here is when we forward the call to the method
Now, how to resize the
UserControl when the user resizes the
FileOpenDialog; this is done by processing the message
WM_WINDOWPOSCHANGING; here, we specify to change the size of the control relative to the
As an important detail, when the
OpenFileWindow is closing, it must restore the original size as it was when we opened it, this is possible because
OpenFileWindow remembers the last position/size. If we don’t do that, every time the
OpenFileDialog is open, it will increment the size making it bigger and bigger.
I tested this on Windows XP and it works well, I didn’t have a chance to try on different OSs as Windows 2000/2003 or Vista, but it should work OK without problems. I don’t think it will work on Windows 95/98, because I’m setting the struct sizes only to match the WinNT OS. If you have any comments or discover a bug, let me know and I’ll update the control.
- Release 1.0.1 (11/14/2006)
- Forwards the
DialogResult status to the caller.
- Code optimization.
- Uses the
SetWindowsPos API with special flags to resize the control instead of direct size assignment to reduce flickering.
- Control resizing is now done in
WM_SIZING; this fixes a bug when the control is not resized for the last update until the mouse button is released.
- Initial release 1.0.0 (07/14/2006)
I started with programming about 19 years ago as a teenager, from my old Commodore moving to PC/Server environment Windows/UNIX SQLServer/Oracle doing gwBasic, QBasic, Turbo Pascal, Assembler, Turbo C, BC, Summer87, Clipper, Fox, SQL, C/C++, Pro*C, VB3/5/6, Java, and today loving C#.
Currently working as SDE on Failover Clustering team for Microsoft.
Passion for most programming languages and my kids Aidan&Nadia.