Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Customizing OpenFileDialog in .NET

0.00/5 (No votes)
14 Nov 2006 4  
An extended control to add extra funcionality to the standard OpenFileDialog in .NET.

Sample Image - OpenFileDialogEx.png

Introduction

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 internal and 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 �GetOpenFileName�.

At this point, I had three choices:

  1. Create my own OpenFileDialog from scratch.
  2. Create my own OpenFileDialog reusing resources, (using the API �GetOpenFileName� and providing my own template).
  3. 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 �OpenFileDialogEx�.

How do I do it?

Imagine 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 UserControl.

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 UserControl.

DefaultViewMode:

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.

StartLocation:

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 OpenFileDialog.

OpenDialog:

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 StartLocation property.

StartLocation

  • 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 OpenFileDialog window.

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 OpenFileDialog.

The methods are virtual methods that you will override to interact with the original OpenFileDialog.

OnFileNameChanged()

This method is called every time the user clicks on any file inside the view.

OnFolderNameChanged()

This method is called every time the user changes a folder from any control inside the OpenFileDialog.

OnClosing()

This method is called when the OpenFileDialog is closing, this is useful to release any resources allocated.

The two events are FileNameChanged and 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_IDLE, WM_ACTIVATE, WM_NC_ACTIVATE, etc. Those entire set of messages will set the parameter lParam with the handle to the OpenFileDialog window.

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 OpenFileDialog.

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 OpenFileDialog window?

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.

In our 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 StartLocation property.

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 ListView?

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 CDN_SELCHANGE and CDN_FOLDERCHANGE.

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_SELCHANGE and CDN_FOLDERCHANGE. When this window processes those messages, they are forwarded to the OpenFileDialogEx control to the methods OnFileNameChanged and OnFolderNameChanged.

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 OnClosingDialog().

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 FileOpenDialog size.

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.

Conclusion

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.

History

  • 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)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here