Search code examples
c#wpfwindows-api-code-pack

SaveFileDialog Navigate to Folder on Type Change


I am trying to replicate some behaviour from Microsoft Word around the Save File Dialog. In Word (Office 365 Latest Version) if you go to Save As and bring up the browse dialog, the default directory is My Documents, if you then change the Save as type to "Word Template (*.dotx)" this automatically takes you to the "Custom Office Templates" folder. This is the behaviour I want to replicate.

As far as I am aware this is not possible out of the box in WPF using the Microsoft.Win32.SaveFileDialog, please correct me if I am wrong.

So I have tried to use the CommonSaveFileDialog from the Microsoft.WindowsAPICodePack.Shell Nuget package. I believe I am very close, but I can't seem to get it to actually do the navigation part when a type is changed. Please see the code below, any advice is greatly appreciated. I need this for a WPF desktop application.

CommonSaveFileDialog saveDialog;
private void Button_Click(object sender, RoutedEventArgs e)
{
    saveDialog = new CommonSaveFileDialog();
    saveDialog.Filters.Add(new CommonFileDialogFilter("My File", ".mf"));
    saveDialog.Filters.Add(new CommonFileDialogFilter("My Template File", ".mft"));

    saveDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

    saveDialog.FileTypeChanged += saveDialog_FileTypeChanged;

    saveDialog.ShowDialog();
}

private void saveDialog_FileTypeChanged(object sender, EventArgs e)
{
    var selectedSaveAsType = saveDialog.Filters[saveDialog.SelectedFileTypeIndex - 1];

    if (selectedSaveAsType.DisplayName == "My Template File (*.mft)")
    {   
        //These don't cause a navigation... How can I achieve this?
        //saveDialog.InitialDirectory = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "My Application Templates");
        //saveDialog.DefaultDirectory = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "My Application Templates");
    } 
    else
    {
        //saveDialog.DefaultDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
    }
}

If you need any further information, let me know. Thank you in advance for any advice. :)

There does not appear to be any .CurrentDirectory or .Path property. Or any sort of Navigate method.


Solution

  • After searching for some sort method / property to change folder using .GetType().GetMethods() there was still nothing. So I decompiled the DLL using dnSpy, and this led to me finding a SetFolder method in the IFileDialog.

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);
    

    The only way I could achieve my goal was to clone the GitHub repo for the Nuget package and add to it myself. I added the following public method to the CommonFileDialog class.

    public void SetFolder(string folderPath)
    {
        // Create a native shellitem from our path
        // Add some proper error handling to ensure directory exists....
        Guid guid = new Guid(ShellIIDGuid.IShellItem2);
        IShellItem2 defaultDirectoryShellItem;
        ShellNativeMethods.SHCreateItemFromParsingName(folderPath, IntPtr.Zero, ref guid, out defaultDirectoryShellItem);
        nativeDialog.SetFolder(defaultDirectoryShellItem);
    }
    

    Then I can use this in my application simply by calling

    dialog.SetFolder(@"C:\");