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

Extend OpenFileDialog and SaveFileDialog Using WPF

0.00/5 (No votes)
16 Jun 2015 3  
Customize OpenFileDialog and SaveFileDialog using a WPF Window

Release

Table of Contents

Introduction

I don't know about you, but there are times when I wished that there was a way to add extra functionality to the OpenFileDialog and SaveFileDialog , but the existing framework stayed in the way of doing it. If you were using Windows Forms, then probably my previous article Extend OpenFileDialog and SaveFileDialog the easy way[^], could be a solution to this issue. But, if your application is designed to use Windows Presentation Foundation[^] introduced by Microsoft with .NET 3.0, this might not be the path you want to pursue. You can try hosting a Windows Presentation Foundation control in Windows Forms [^], but interoperability might pose some issues[^]. Even with an interop solution available, you might still want a pure WPF solution to avoid using the System.Windows.Forms.dll in order to keep the memory usage low, or just to keep the UI consistency.

Background

Unlike Windows Forms, the WPF OpenFileDialog and SaveFileDialog work differently, and catching messages or grabbing handles before they display does not work in this scenario. So, the trick used for their Windows Forms counterparts to extend them cannot be applied for WPF, and I had to go for a very different alternative. I used the free Red Gate Reflector[^] to get the relevant code from PresentationFramework.dll and recreate the existing functionality in my own namespace. While trying to reverse engineer the OpenFileDialog and SaveFileDialog, I was forced to go two levels deep to the FileDialog class because these classes override an abstract method called RunFileDialog with a parameter of the OPENFILENAME_I type that is internal to the PresentationFramework assembly only.

So, I ended up extracting a few more additional helper types used by my own FileDialog type, the base class for the new OpenFileDialog and SaveFileDialog. Since I grabbed all the code from the version 3.5 of the assembly, be advised that some new features introduced by .NET 4.0 into the Microsoft.Win32.FileDialog class like CustomPlaces [^] will not be available. Everything is packaged into the WpfCustomFileDialog assembly under the same namespace.

To make things more interesting and to give you the feel of a real world application, I have used the OpenFileDialog in conjunction with a media viewer based on Lee Brimelow's code[^], and SaveFileDialog with a media encoder settings window that is merely a redesign of Armoghan Asif's work[^]. In order for the latter to work properly, you would have to download and install the now obsoleted Windows Media Encoder 9[^] if you want to have full functionality, and obviously the runtime for .NET 3.5[^] or later. However, the utilization of the media component or encoder will not be the focus of this article.

How It Works

The new OpenFileDialog and SaveFileDialog I've created can only manipulate old style window handles, but existing WPF controls don't have one. That made the System.Windows.Window a good candidate for a WPF child since it seems to be the only one to have an easily accessible handle represented as an IntPtr type. There is a caveat, as you’ll see in my code snippets below because extra code is needed to deal with different runtime versions.

Fortunately, there is also a way to assign a Windows handle to a UserControl that does not depend on the runtime version and that makes it the preferred design in my opinion. I'll elaborate more about it later in this article. In order to show the similarities of the two approaches, I've duplicated the same functionality on two tabs, one using a Window and the other a UserControl.

My new dialog type has to be aware of the window that will become one of its children and vice versa, so they can update their appearance when needed. To have some strong type checking when developing, I've created two interfaces to make it easier to enforce type safety. The old FileDialog class has become the new FileDialogExt, that looks like below:

public abstract partial class FileDialogExt<T>: Microsoft.Win32.CommonDialog, 
                              IFileDlgExt where T : ContentControl, IWindowExt, new()
{
    //....
}

As you might have guessed, T is the child type that will implement the IWindowExt interface. It makes sense to try to abstract the common behavior that the UserControl and Window types are sharing and use the inheritance and generics constraints to enforce it.

Below, the interfaces are defined:

//implemented by the new child window
public interface IWindowExt
{
    HwndSource Source
    {
        set;
    }
    IFileDlgExt ParentDlg
    {
        set;
    }
}
//implemented by the dialog
public interface IFileDlgExt : IWin32Window
{
    event PathChangedEventHandler EventFileNameChanged;
    event PathChangedEventHandler EventFolderNameChanged;
    event FilterChangedEventHandler EventFilterChanged;
    AddonWindowLocation FileDlgStartLocation
    {
        set;
        get;
    }
    string FileDlgOkCaption
    {
        set;
        get;
    }
    bool FileDlgEnableOkBtn
    {
        set;
        get;
    }
    bool FixedSize
    {
        set;
    }
    NativeMethods.FolderViewMode FileDlgDefaultViewMode
    {
        set;
        get;
    }
}

Once we start the dialog, it enters in a Windows loop as defined below:

protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
    IntPtr hres = IntPtr.Zero;
    switch ((NativeMethods.Msg)msg)
    {
        case NativeMethods.Msg.WM_NOTIFY:
            hres = ProcOnNotify(hwnd, lParam);
            break;
        case NativeMethods.Msg.WM_SHOWWINDOW:
            InitControls();
            ShowChild();
            break;
        case NativeMethods.Msg.WM_INITDIALOG:
            _hwndFileDialog = NativeMethods.GetParent(new HandleRef(this, hwnd));
            _hwndFileDialogEmbedded = hwnd;
            NativeMethods.GetWindowRect(new HandleRef(this, _hwndFileDialog), 
                                        ref _OriginalRect);
            break;

// code omitted for brevity

        default:
            if (msg == (int)MSG_POST_CREATION)
            {
                CustomPostCreation();
            }
            break;
    }//switch ends
    return hres;
}

Here is the order for the relevant events:

  • Firstly, the dialog's handle _hwndFileDialog will be saved to be used subsequently.
  • Secondly, when the dialog is about to be displayed, WM_SHOWWINDOW will trigger ShowChild() that will create the child window.
  • Thirdly, the final adjustments are made to display it properly in CustomPostCreation(), which is triggered by a PostMessage call from the ProcOnNotify() method.

The ShowChild method does most of the magic of establishing the layout between the dialog and its child, as displayed below:

private void ShowChild()
{
    _childWnd = new T();
    try
    {
        _childWnd.ParentDlg = this;
    }
    catch
    {
        return;
    }

    RECT dialogWindowRect = new RECT();
    RECT dialogClientRect = new RECT();

    Size size = new Size(dialogWindowRect.Width, dialogWindowRect.Height);
    NativeMethods.GetClientRect(new HandleRef(this, _hwndFileDialog), 
                                ref dialogClientRect);
    NativeMethods.GetWindowRect(new HandleRef(this, _hwndFileDialog), 
                                ref dialogWindowRect);
    int dy = (int)(dialogWindowRect.Height - dialogClientRect.Height);
    int dx = (int)(dialogWindowRect.Width - dialogClientRect.Width);
    size = new Size(dialogWindowRect.Width, dialogWindowRect.Height);

    if (_childWnd is Window)
    {
        Window wnd = _childWnd as Window;
        wnd.WindowStyle = WindowStyle.None;
        wnd.ResizeMode = ResizeMode.NoResize;//will fix the child window!!
        wnd.ShowInTaskbar = false;
        //won't flash on screen
        wnd.WindowStartupLocation = WindowStartupLocation.Manual;
        wnd.Left = -10000;
        wnd.Top = -10000;
        wnd.SourceInitialized += delegate(object sender, EventArgs e)
      {
          try
          {
              _source = System.Windows.PresentationSource.FromVisual(
                                      _childWnd as Window) as HwndSource;
              _source.AddHook(EmbededWndProc);
              _childWnd.Source = _source;
          }
          catch{}
      };
        wnd.Show();
        long styles = (long)NativeMethods.GetWindowLongPtr(new HandleRef(_childWnd, 
                                         _source.Handle), GWL.GWL_STYLE);
        if (IntPtr.Size == 4)
        {
            styles |= System.Convert.ToInt64(NativeMethods.WindowStyles.WS_CHILD);
            styles ^= System.Convert.ToInt64(NativeMethods.WindowStyles.WS_SYSMENU);
        }
        else
        {
            styles |= (long)NativeMethods.WindowStyles.WS_CHILD;
            styles ^= (long)NativeMethods.WindowStyles.WS_SYSMENU;
        }
        NativeMethods.CriticalSetWindowLong(new HandleRef(this, _source.Handle), 
                                           (int)GWL.GWL_STYLE, new IntPtr(styles));

        // Everything is ready, now lets change the parent
        NativeMethods.SetParent(new HandleRef(_childWnd, _source.Handle), 
                                new HandleRef(this, _hwndFileDialog));
    }
    else
    {// what if the child is not a Window 
        ContentControl ctrl = _childWnd as ContentControl;
        HwndSourceParameters parameters = new HwndSourceParameters("WPFDlgControl", 
                                              (int)ctrl.Width, (int)ctrl.Height);
        parameters.WindowStyle = (int)NativeMethods.WindowStyles.WS_VISIBLE | 
                                 (int)NativeMethods.WindowStyles.WS_CHILD;
        parameters.SetPosition((int)_OriginalRect.Width, (int)_OriginalRect.Height);
        parameters.ParentWindow = _hwndFileDialog;
        parameters.AdjustSizingForNonClientArea = false;
        switch (this.FileDlgStartLocation)
        {
            case AddonWindowLocation.Right:
                parameters.PositionX = (int)_OriginalRect.Width - dx/2;
                parameters.PositionY = 0;
                if (ctrl.Height < _OriginalRect.Height - dy)
                    ctrl.Height = parameters.Height = (int)_OriginalRect.Height - dy;
                break;

            case AddonWindowLocation.Bottom:
                parameters.PositionX = 0;
                parameters.PositionY = (int)(_OriginalRect.Height - dy +dx/2);
                if (ctrl.Width < _OriginalRect.Width - dx)
                    ctrl.Width = parameters.Width = (int)_OriginalRect.Width - dx;
                break;
            case AddonWindowLocation.BottomRight:
                parameters.PositionX = (int)_OriginalRect.Width - dx/2;
                parameters.PositionY = (int)(_OriginalRect.Height - dy +dx/2);
                break;
        }

        _source = new HwndSource(parameters);
        _source.CompositionTarget.BackgroundColor = 
                                  System.Windows.Media.Colors.LightGray;
        _source.RootVisual = _childWnd as System.Windows.Media.Visual;
        _source.AddHook(new HwndSourceHook(EmbededCtrlProc));
    }
    switch (this.FileDlgStartLocation)
    {
        case AddonWindowLocation.Right:
            size.Width = _OriginalRect.Width + _childWnd.Width;
            size.Height = _OriginalRect.Height;
            break;

        case AddonWindowLocation.Bottom:
            size.Width = _OriginalRect.Width;
            size.Height = _OriginalRect.Height + _childWnd.Height;
            break;
        case AddonWindowLocation.BottomRight:
            size.Height = _OriginalRect.Height + _childWnd.Height;
            size.Width = _OriginalRect.Width + _childWnd.Width;
            break;
    }
    NativeMethods.SetWindowPos(new HandleRef(this, _hwndFileDialog), new HandleRef(this, 
           (IntPtr)NativeMethods.ZOrderPos.HWND_BOTTOM),
            0, 0, (int)size.Width, (int)size.Height, 
            NativeMethods.SetWindowPosFlags.SWP_NOZORDER);
}

When using a Window, the above anonymous delegate captures the HwndSource that will provide the handle and the message loop for it. If using a UserControl, the HwndSource is programmatically created and is associated with it, so we can have the Windows handle to work with. Finally, the CustomPostCreation will do the final resizing to make it look like a real child.

private void CustomPostCreation()
{
    _hListViewPtr = NativeMethods.GetDlgItem
	(this._hwndFileDialog, (int)NativeMethods.ControlsId.DefaultView);
    UpdateListView(_hListViewPtr);
    if (_bFixedSize)
        SetFixedSize(_hwndFileDialog);
    RECT dialogWndRect = new RECT();
    NativeMethods.GetWindowRect(new HandleRef(this, this._hwndFileDialog), 
	ref dialogWndRect);
    RECT dialogClientRect = new RECT();
    NativeMethods.GetClientRect(new HandleRef(this, this._hwndFileDialog), 
	ref dialogClientRect);
    uint dx = dialogWndRect.Width - dialogClientRect.Width;
    uint dy = dialogWndRect.Height - dialogClientRect.Height;
    if (_childWnd is Window)
    {
        Window wnd = _childWnd as Window;
        //restore the original size
        switch (FileDlgStartLocation)
        {
            case AddonWindowLocation.Bottom:
                int left = (Environment.Version.Major >= 4) 
			? -(int)dx / 2 : dialogWndRect.left;
                if (wnd.Width >= _OriginalRect.Width - dx)
                {
                    NativeMethods.MoveWindow(new HandleRef(this, this._hwndFileDialog), 
			left, dialogWndRect.top, (int)(wnd.ActualWidth + dx / 2), 
			(int)(_OriginalRect.Height + wnd.ActualHeight), true);
                }
                else
                {
                    NativeMethods.MoveWindow(new HandleRef(this, this._hwndFileDialog), 
			left, dialogWndRect.top, (int)(_OriginalRect.Width), 
			(int)(_OriginalRect.Height + wnd.ActualHeight), true);
                    wnd.Width = _OriginalRect.Width - dx / 2;
                }
                wnd.Left = 0;
                wnd.Top = _OriginalRect.Height - dy + dx / 2;
                break;
            case AddonWindowLocation.Right:
                int top = (Environment.Version.Major >= 4) ? (int)(dx / 2 - dy) : 
			dialogWndRect.top;
                if (wnd.Height >= _OriginalRect.Height - dy)
                    NativeMethods.MoveWindow(new HandleRef(this, _hwndFileDialog), 
			(int)(dialogWndRect.left), top, (int)(_OriginalRect.Width + 
			wnd.ActualWidth), (int)(wnd.ActualHeight + dy - dx / 2), true);
                else
                {
                    NativeMethods.MoveWindow(new HandleRef(this, _hwndFileDialog), 
			(int)(dialogWndRect.left), top, (int)(_OriginalRect.Width + 
			wnd.ActualWidth), (int)(_OriginalRect.Height - dx / 2), true);
                    wnd.Height = _OriginalRect.Height - dy;
                }
                    wnd.Top = 0;
                    wnd.Left = _OriginalRect.Width - dx / 2;
                break;
            case AddonWindowLocation.BottomRight:
                NativeMethods.MoveWindow(new HandleRef(this, _hwndFileDialog), 
		dialogWndRect.left, dialogWndRect.top, (int)(_OriginalRect.Width + 
		wnd.Width), (int)(int)(_OriginalRect.Height + wnd.Height), true);
                wnd.Top = _OriginalRect.Height - dy + dx / 2;
                wnd.Left = _OriginalRect.Width - dx / 2;
                break;
        }
    }
    else
    {
        ContentControl ctrl = _childWnd as ContentControl;
        //restore the original size
        const NativeMethods.SetWindowPosFlags flags = 
	NativeMethods.SetWindowPosFlags.SWP_NOZORDER | 
	NativeMethods.SetWindowPosFlags.SWP_NOMOVE;//| 
		SetWindowPosFlags.SWP_NOREPOSITION | 
		SetWindowPosFlags.SWP_ASYNCWINDOWPOS | 
		SetWindowPosFlags.SWP_SHOWWINDOW | SetWindowPosFlags.SWP_DRAWFRAME;
        switch (FileDlgStartLocation)
        {
            case AddonWindowLocation.Bottom:
                NativeMethods.SetWindowPos(new HandleRef(this, this._hwndFileDialog), 
		new HandleRef(this,(IntPtr)ZOrderPos.HWND_BOTTOM),
                    dialogWndRect.left, dialogWndRect.top, 
			(int)(ctrl.ActualWidth + dx / 2), 
			(int)(_OriginalRect.Height + ctrl.ActualHeight), flags);
                NativeMethods.SetWindowPos(new HandleRef(ctrl, _source.Handle), 
			new HandleRef(_source, (IntPtr)ZOrderPos.HWND_BOTTOM),
                      	0, (int)(_OriginalRect.Height - dy + dx / 2), 
			(int)(ctrl.Width), (int)(ctrl.Height), flags);
                break;
            case AddonWindowLocation.Right:
                NativeMethods.SetWindowPos(new HandleRef(this, this._hwndFileDialog), 
			new HandleRef(this, (IntPtr)ZOrderPos.HWND_BOTTOM),
                    (int)(dialogWndRect.left), dialogWndRect.top, 
			(int)(_OriginalRect.Width + ctrl.ActualWidth - dx / 2), 
			(int)(ctrl.ActualHeight + dy - dx / 2), flags);
                NativeMethods.SetWindowPos(new HandleRef(ctrl, _source.Handle), 
			new HandleRef(_source, (IntPtr)ZOrderPos.HWND_BOTTOM),
                  		(int)(_OriginalRect.Width - dx), (int)(0), 
			(int)(ctrl.Width), (int)(ctrl.Height), flags);
                break;
            case AddonWindowLocation.BottomRight:
                NativeMethods.SetWindowPos(new HandleRef(this, this._hwndFileDialog), 
			new HandleRef(this, (IntPtr)ZOrderPos.HWND_BOTTOM),
                     	dialogWndRect.left, dialogWndRect.top, 
			(int)(_OriginalRect.Width + ctrl.Width), 
			(int)(_OriginalRect.Height + ctrl.Height), flags);
                break;
        }
    }
    CenterDialogToScreen();
    NativeMethods.InvalidateRect(new HandleRef(this, _source.Handle), IntPtr.Zero, true);
}

Fixing Interoperability Issues

As you might expect, mixing WPF with Win32 comes with issues. This is the main reason why we needed the HwndSourceHook delegate from the child window or the User Control. It provided not only a Win32 handle, but also a message loop to be used as a way to fix it:

IntPtr EmbededWndProc(IntPtr hwnd, int msg, IntPtr wParam, 
                      IntPtr lParam, ref bool handled)
{
    IntPtr hres = IntPtr.Zero;
    const int DLGC_WANTALLKEYS = 0x0004;
    switch ((NativeMethods.Msg)msg)
    {
        case NativeMethods.Msg.WM_SYSKEYDOWN:
            SetChildStyle(true);
            break;
        case NativeMethods.Msg.WM_SYSKEYUP:
            SetChildStyle(false);
            break;
        //see http://support.microsoft.com/kb/83302
        case NativeMethods.Msg.WM_GETDLGCODE:
            if (lParam != IntPtr.Zero)
                hres = (IntPtr)DLGC_WANTALLKEYS;
            handled = true;
            break;
    }//switch ends
    return hres;
}

Without the fix shown above, the TextBox will not accept most key strokes, and the Alt key won't work properly.

How to Customize the Places Bar to the OpenFileDialog and SaveFiledialog on Windows 2000 and XP

You will need one of the latest versions of Windows and .NET 4.0 to use Microsoft.Win32.FileDialog.CustomPlaces[^]. Since this is not included because I’ve based my code on the older .NET 3.5, I included my own implementation that can handle older versions too. The code for this feature is similar with the what I’ve done in my previous article Extend OpenFileDialog and SaveFileDialog the easy way[^]. The main difference is the fact that instead of using extension methods, I use plain data members in the FileDialogExt class. The overridden FileDialog.RunDialog calls the methods described below:

private readonly string TempKeyName = "TempPredefKey_" + Guid.NewGuid().ToString();
private const string Key_PlacesBar = 
	@"Software\Microsoft\Windows\CurrentVersion\Policies\ComDlg32\PlacesBar";
private RegistryKey _fakeKey;
private IntPtr _overriddenKey;
private object[] m_places;

public void SetPlaces(object[] places)
{
    if (m_places == null)
        m_places = new object[5];
    else
        m_places.Initialize();
    if (places != null)
    {
        for (int i = 0; i < m_places.GetLength(0); i++)
        {
            m_places[i] = places[i];

        }
    }
}

public void ResetPlaces()
{
    if (_overriddenKey != IntPtr.Zero)
    {
        ResetRegistry(_overriddenKey);
        _overriddenKey = IntPtr.Zero;
    }
    if (_fakeKey != null)
    {
        _fakeKey.Close();
        _fakeKey = null;
    }
    //delete the key tree
    Registry.CurrentUser.DeleteSubKeyTree(TempKeyName);
    m_places = null;
}

private void SetupFakeRegistryTree()
{
    _fakeKey = Registry.CurrentUser.CreateSubKey(TempKeyName);
    _overriddenKey = InitializeRegistry();
    // at this point, m_TempKeyName equals places key
    // write dynamic places here reading from Places
    RegistryKey reg = Registry.CurrentUser.CreateSubKey(Key_PlacesBar);
    for (int i = 0; i < m_places.GetLength(0); i++)
    {
        if (m_places[i] != null)
        {
            reg.SetValue("Place" + i.ToString(), m_places[i]);
        }
    }
}

readonly UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);
private IntPtr InitializeRegistry()
{
    IntPtr hkMyCU;
    NativeMethods.RegCreateKeyW(HKEY_CURRENT_USER, TempKeyName, out hkMyCU);
    NativeMethods.RegOverridePredefKey(HKEY_CURRENT_USER, hkMyCU);
    return hkMyCU;
}

private void ResetRegistry(IntPtr hkMyCU)
{
    NativeMethods.RegOverridePredefKey(HKEY_CURRENT_USER, IntPtr.Zero);
    NativeMethods.RegCloseKey(hkMyCU);
    return;
}

You should only care about calling SetPlaces which takes an argument as an array of up to five objects that can be numbers representing Windows special folders or strings as regular folders. For your convenience, I’ve included the Places helper enumeration for predefined special folders.

How to Use the Controls

In order to use these controls, you need to follow several steps:

  1. Add the WpfCustomFileDialog project to your solution and reference it in your project. If you don't like this way, as other alternatives, you could just reference this DLL or drop the code into your own project.
  2. Create the child window or the user control, and implement the IWindowExt interface.
  3. Set up the properties and events exposed by IFileDlgExt in your child window or externally.
  4. Create the dialog using its constructor and the child window type as a generic parameter.

Implementing IWindowExt

You have two choices to do that. The first choice is to implement the IWindowExt directly in your class. You can implement the IWindowExt interface by copying the code below:

System.Windows.Interop.HwndSource _source;
IFileDlgExt _parentDlg;

public System.Windows.Interop.HwndSource Source
{
    set
    {_source = value;}     
}

public IFileDlgExt ParentDlg
{
    set { _parentDlg = value; }
    get { return _parentDlg; }
}

protected override Size ArrangeOverride(Size arrangeBounds)
{
  
            if (Height > 0 && Width > 0)
            {
                arrangeBounds.Height = this.Height;
                arrangeBounds.Width = this.Width;
            }
            return base.ArrangeOverride(arrangeBounds);
}

While the virtual ArrangeOverride is not part of the interface, it has to be overridden as shown above to avoid sizing issues.

Inheriting from the WindowAddOnBase or the ControlAddOnBase Class

You've probably noticed that the above functionality is a good candidate for a base class, hence the second choice is to inherit from existing classes I've created in the WpfCustomFileDialog assembly.

They are WindowAddOnBase and ControlAddOnBase, and inheriting is just a plain class inheritance:

public partial class SelectWindow : WpfCustomFileDialog.WindowAddOnBase
{
   .....
}

or:

public partial class SelectControl : WpfCustomFileDialog.ControlAddOnBase
{
   .....
}

Unfortunately, WPF window inheritance is not supported by the Visual Studio designer, and you've got some manual typing to do[^] in the XAML file. My inherited class has to set the xmlns:src attribute, and the SelectWindow.xaml file becomes:

<src:WindowAddOnBase x:Class="VideoManager.SelectWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:src="clr-namespace:WpfCustomFileDialog;assembly=WpfCustomFileDialog"

    .......

</src:WindowAddOnBase>

The ControlAddOnBase follows the same rules:

<src:ControlAddOnBase x:Class="VideoManager.SelectControl"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:src="clr-namespace:WpfCustomFileDialog;assembly=WpfCustomFileDialog"

    .........

</src:ControlAddOnBase>

Fortunately, the Designer won't complain about it after you've built the assembly/project.

Creating and Invoking the Control

You can set up the properties and events from the caller, as shown below:

var ofd = new WpfCustomFileDialog.OpenFileDialog<SelectWindow>();
ofd.Filter = "avi files (*.avi)|*.avi|wmv files (*.wmv)|*.wmv|All files (*.*)|*.*";
ofd.Multiselect = false;
ofd.Title = "Select Media file";
ofd.FileDlgStartLocation = AddonWindowLocation.Right;
ofd.FileDlgDefaultViewMode = NativeMethods.FolderViewMode.Tiles;
ofd.FileDlgOkCaption = "&Select";
ofd.FileDlgEnableOkBtn = false;
ofd.SetPlaces(new object[] { @"c:\", (int)Places.MyComputer, 
	(int)Places.Favorites, (int)Places.All_Users_MyVideo, (int)Places.MyVideos });
bool? res = ofd.ShowDialog(this);

if (res.Value == true)
{
    //........
}

Using a User Control is almost identical to using a window, so I won't repeat the very similar code. You can also achieve the same thing from inside of your child window or control you've created.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _parentDlg.FileDlgDefaultViewMode = NativeMethods.FolderViewMode.List;
    var fd = _parentDlg as SaveFileDialog<TargetWindow>;
    fd.FileOk += new System.ComponentModel.CancelEventHandler(CreateEncodeInfo);
}

Since the whole reason of this article was to have interaction between the dialog and the WPF child, especially when using OpenFileDialog, you should make use of the events to update both:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
 ParentDlg.EventFileNameChanged += 
           new PathChangedEventHandler(ParentDlg_EventFileNameChanged);
 ParentDlg.EventFolderNameChanged += 
           new PathChangedEventHandler(ParentDlg_EventFolderNameChanged);
 ParentDlg.EventFilterChanged += 
           new FilterChangedEventHandler(ParentDlg_EventFilterChanged);
 _comboSpeed.IsEnabled = false;
}

Be advised that ParentDlg is null in the constructor and it is initialized for the subsequent events only.

I have included solutions for Visual Studio 2008 (v9) and 2010 (v10). If you choose not to install Windows Media Encoder 9, you will still have some limited runtime functionality but the VS 2010 solution might not build. To fix it, you would have to add the existing Interop.WMEncoderLib.dll assembly as a reference to the VideoManager project.
If Visual Studio starts complaining about weird errors while building or running, close the solution and delete all the bin and obj folders before trying again.

History

I have tested this project only on Windows XP SP3 32 bit and .NET 3.5 and 4.0. You might think that everything was pretty straightforward, but I can tell you that the development involved lots of trial and error phases or compromises. If you find ways to improve it like getting the relevant code from the latest version of the PresentationFramework.dll assembly, or make bug fixes, let me know and I'll try to have them in a future update.

If you are interested in Video encoding, you should probably consider replacing the existing deprecated Windows Media Encoder 9 encoder with the latest Expression Encoder[^]. While the code shown in the article is only C#, for the VB.NET folks, I have included the equivalent Visual Basic .NET solutions for Visual Studio 2008 and 2010 in the download source file.

  • Version 1.0 The original
  • Version 1.1 improves the child layout, suppresses the Window flash, and most importantly, adds support for the UserControl to be used as an extension
  • Version 1.2 adds my own CustomPlaces and fixes some incompatibilities with .NET 4.0
  • Version 1.3 adds VS 2013 project style, an access warning for CustomPlaces and moved the latest source code on codeplex.

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