Sunday, April 19, 2009

OpenFileDialog in .NET on Vista

I'm working on a WPF based application and as quite some applications do, I need to display an OpenFileDialog as well. Trying to be compliant with what .NET 3.5 SP 1 is offering I tried Microsoft.Win32.OpenFileDialog first. I ran into two issues. The first issue I noted was that the dialog box positioned itself just about anywhere but not centered (or at least on the same screen) as the owner window, even if I set the owner window. I did some research and found that despite .NET having been around for years (I started using it in 2001) there is still no OpenFileDialog box available for .NET that inherits from Window (or Forms previously) and can be easily customized. And therefore - as an example - there is no WindowStartupLocation property. Oh, well ... Even with the latest WPF version running on Vista the old XP style dialog box is displayed: In addition even when I passed the owner as a parameter to ShowDialog() it wouldn't center relative to that owner window. Then I tried the previous System.Windows.Forms.OpenFileDialog(). Although it is not recommended to use that namespace within WPF I thought it would still be worth a try. And voila! Here is the result under Vista: Not only did it display the correct dialogbox under Vista but in addition it's location centered on it's owner is correct as well. To achieve this you have to fiddle a bit with the namespaces as there are a few classes that have the same name in both Microsoft.Win32 and in System.Windows.Forms. Since I wanted the client code to look as simple as possible I created two very small wrapper classes. The first wrapper class is basically a replication of the Microsoft.Win32.OpenFileDialog interface (I left out most of the code since it is simply forwarding):
using SWF = System.Windows.Forms;

namespace FooApp.Presentation {
   internal class OpenFileDialog {
      public OpenFileDialog() {
         _dialog = new SWF.OpenFileDialog();
      }

      public bool? ShowDialog(System.Windows.Window window) {
         return _dialog.ShowDialog(new WindowWrapper(window)) == SWF.DialogResult.OK;
      }

      public string DefaultExt {
         get {
            return _dialog.DefaultExt;
         }
         set {
            _dialog.DefaultExt = value;
         }
      }
      public string FileName {
         get {
            return _dialog.FileName;
         }
         set { _dialog.FileName = value; 
         }
      }
      public string Filter {
         get {
            return _dialog.Filter;
         }
         set {
            _dialog.Filter = value;
         }
      }
      public string Title {
         get {
            return _dialog.Title;
         }
         set {
            _dialog.Title = value;
         }
      }

      private readonly SWF.OpenFileDialog _dialog;
   }
}
The only really interesting piece is the ShowDialog() method which takes a WPF window. System.Windows.Forms.DialogBox.ShowDialog() requires a IWin32Window instead (for easy access to the window handle), so we need a second wrapper class that looks as follows:
using System;
using System.Windows;
using System.Windows.Interop;

namespace FooApp.Presentation {
   /// <summary>WindowWrapper is an IWin32Window wrapper around a WPF window.
   /// </summary>
   /// <remarks>Add System.Windows.Forms to the references of your project to
   /// use this class in a WPF application. You don't need this class in a
   /// Forms based application.</remarks>
   internal class WindowWrapper : System.Windows.Forms.IWin32Window {
      /// <summary>
      /// Construct a new wrapper taking a WPF window.
      /// </summary>
      /// <param name="window">The WPF window to wrap.</param>
      public WindowWrapper(Window window) {
         _hWnd = new WindowInteropHelper(window).Handle;
      }

      /// <summary>Gets the handle to the window represented by the implementer.
      /// </summary>
      /// <returns>A handle to the window represented by the implementer.
      /// </returns>
      public IntPtr Handle {
         get { return _hWnd; }
      }

      private readonly IntPtr _hWnd;
   }
}
With this machinery in place the client code in a WPF based application looks very familiar and simple:
var dialog = new OpenFileDialog {
   DefaultExt = ".dll",
   Filter = "Assemblies (*.dll; *.exe)|*.dll;*.exe",
   Title = "Select Assembly"
};
if( dialog.ShowDialog(this) == true ) {
   // File is selected, do something useful...
}
As a result you have it all, simple client code, the correct OpenFileDialog on Vista and the dialog centered properly on its owner. The only thing that I still can't get my head around: After 8 years of .NET we still don't have a class available that is based on Window (or Form) and can be customized. It seems as if this is a puzzle that the guys in Redmond haven't figured out yet...

2 comments:

  1. Anonymous2:09 AM

    Thank you! Great post! I'll be looking at this method for a current application.

    ReplyDelete
  2. Anonymous9:08 PM

    Micro$oft thanks you for doing their work for them.

    ReplyDelete

All comments, questions and other feedback is much appreciated. Thank you!