Customizing OpenFileDialog in .NET

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 though "Where do I need to start?" Then, I decided to create a menu with the Open functionality. I though the Open should have a preview screen to see the icon before opening it.

If you've reached this paragraph, it's probably 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 you can't customize the OpenFileDialog in .NET is because the class is declared sealed; this means that 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 can't?

Searching for code on the Internet, I found a couple of places where MFC was used, but I found nothing for .NET. OpenFileDialog is not natively implemented 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 by re-using resources, use the "GetOpenFileName" API, and provide my own template.
  3. Hack on the .NET OpenFileDialog and add the functionality I need for it.

Choice 1 was not an option because it could require a lot of development time when there is a lot more to be done. Later, when the product is finished, I can review this.

Choice 2 required me to provide my own template using calls to Win32 API and resources.

Choice 3 was the more viable option at this time. Don't think of this as a bad hack. Basically, a hack is when you want make the control do some extra functionality and you must obtain this from different thread or process.

So, because I like the 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's it, but I ran with this problem many times from .NET 1.0 and no one so far came 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 would still be capable of adding multiple controls without knowing the details of how it works. It needed to be a standalone control that can be added as any control in the IDE. I created this control and I called "OpenFileDialogEx."

How to Do It

I imaged 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 one abstract class that avoids rendering on the screen.

You could use the OpenFileDialogEx class like it is, but makes 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 controls to it. You could add extra buttons, panels, and groupboxes. Basically, it is a controls container; later, this container will be "appended" to the .NET Open File Dialog object on the fly.

There are three extra properties in this control—three methods and two events—that are different from any UserControl. These are explained in the following sections.

DefaultViewMode

This property lets you choose which view the OpenFileDialog should start. By default, an OpenFileDialog is on the "Details view;" here, you can specify a different default view, such as as "Icons," "List," "Thumbnail," or "Detail."

StartLocation

This property tells whether the control created should be stacked on the right, bottom, or behind the classic OpenFileDialog. Usually, this property will be on the right to expand the OpenFileDialog horizontally. If, instead, you need to add extra controls to the current OpenFileDialog, 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 set up the standards property, such as "InitialDir," "AddExtension," "Filters," and so forth.

OpenFileDialog, by 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: The user control will be resized vertically.
  • Bottom: The user control will be resized horizontally.
  • None: The user control will be resized horizontally and vertically.

Then, basically, when you add your controls—such as buttons, panels, groupbox, and so on—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 resize the OpenFileDialog.

The following 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 resource allocated.

The two events are FileNameChanged and FolderNameChanged. Those events are fired from their respective virtual methods, "OnFileNameChanged" and "OnFolderNameChanged." Instead, use the events I recommend and override the methods because it is cleaner code and it doesn't have another level of indirection.

Customizing OpenFileDialog in .NET

How It Is Done

The first problem is that OpenFileDialog is a Modal dialog. This means, basically, that you can't get the handle of the window because, when you call ShowDialog(), you don't have more control of the program flow as long the OpenFileDialog is open.

One way to get the handle of 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, such as WM_IDLE, WM_ACTIVATE, WM_NC_ACTIVATE, and the like. The entire set of messages will set the lParam parameter 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. When ShowDialog() is called, it creates a dummy form off the screen and hides it. This form will take care of open the OpenFileDialog and take the OpenFileDialog window handle.

At first, it listened to the WM_IDLE message, but the problem is that, when the message is sent, it is already too late and the window is created and shows in the screen. Still, you can change the content but the user will see a small flicker on the screen between the original OpenFileDialog and the customized version. Instead, it takes 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 does it change properties for the OpenFileDialog window?

Here is when the .NET NativeWindow comes in handy. 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 your own WndProc method in NativeWindow instead, and you can cancel, modify, or let them pass through.

In your WndProc, you process the WM_WINDOWPOSCHANGING message. If the opendialog is opening, you will change the original horizontal or vertical size depending on 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, you will process the WM_SHOWWINDOW message. Here, all controls inside the original OpenFileDialog are created and you are going to "append" your control to the open file dialog. This is done calling a Win32 "SetParent" API. This API lets you change the parent window. Then, it "attaches" your control to the original OpenFileDialog in the location it set, depending on the value of the StartLocation property.

The advantage is that you still have complete control over the controls attached to OpenFileDialog window. This means that you can receive events and call methods, and do whatever you want with those controls.

Also, in the initialization, you will get the window handles for every control inside the origianl OpenFileDialog. This allows you to create .NET NativeWindows again to process the messages in every control.

Now that everything is ready, how do you 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 different view, the handler is destroyed and you have to re-create the handler to it. If you analyze all windows inside FileOpenDialog with MS Spy, you can notice another FileDialog window inside the FileOpenDialog, and very probably it is the base window of FileOpenDialog.

Checking the MSDN documentation, I saw 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; 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 got the handle to the base FileWindow and I created a NativeWindow from this handle. This allowed me to process the WM_NOTIFY messages to analize 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 windows is closed. At first, I used the WM_CLOSE message and it worked, but later I discovered that this message is not called when the user double-clicks on a file inside the list view. I watched the messages produced by FileOpenDialog and saw that I could use WM_IME_NOTIFY message. This message is sent with a wParam value of IMN_CLOSESTATUSWINDOW when the FileOpenDialog is been closed. Here is when you forward the call to the OnClosingDialog() method.

Now, how it resizes the UserControl when the user resizes the FileOpenDialog is done again by processing the WM_WINDOWPOSCHANGING messsage. Here, you specify the change of the size of the control in relation to FileOpenDialog's size.

Note: When OpenFileWindow closes, it must restore the original size that it had when you opened it because OpenFileWindow remembers the last position/size. If you don't do that every time OpenFileDialog is open, it will increment the size, making it bigger and bigger.

Conclusion

I tested on Windows XP and it works well. I didn't have a chance to try on different OSes, such as Windows 2000/2003 or Vista, but it should work without any problem. I don't think it will work on Windows 95/98 because I set the structs 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.



About the Author

Gustavo Franco

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, Clipper, Fox, SQL, C/C++, Pro*C, VB3/5/6, Java, and today loving C#. Currently working on VOIP/SIP technology. Passion for most programming languages and my son Aidan.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds