Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: OpenFolderDialog #7689

Closed
miloush opened this issue Mar 30, 2023 · 29 comments
Closed

[API Proposal]: OpenFolderDialog #7689

miloush opened this issue Mar 30, 2023 · 29 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented

Comments

@miloush
Copy link
Contributor

miloush commented Mar 30, 2023

Background and motivation

I was asked to create an API Proposal issue for the OpenFolderDialog, PR #7244

The purpose of the API is to enable folder selection using the common folder dialogs in WPF. #438 is the top voted issue in the repository.

This proposal involves splitting a hierarchy of classes and removing support for Windows XP dialogs (they fallback to Vista dialogs).

API Proposal

The proposed API is a compromise on perceived compatibility requirements.

Note that users cannot inherit from FileDialog due to internal abstract members, so protected and abstract/virtual changes are non-breaking.

Class diagram in PR. Public members diff:

  namespace Microsoft.Win32;
  
  public abstract partial class CommonDialog
  {
      protected CommonDialog();
      public object Tag { get; set; }
      protected virtual void CheckPermissionsToShowDialog();
      protected virtual IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
      public abstract void Reset();
      protected abstract bool RunDialog(IntPtr hwndOwner);
      public virtual bool? ShowDialog();
      public bool? ShowDialog(Window owner);
  }

+ public abstract partial class CommonItemDialog : CommonDialog
+ {
+     private protected CommonItemDialog();
+
+     // lifted up from FileDialog
+     public IList<FileDialogCustomPlace> CustomPlaces { get; set; }
+     public bool DereferenceLinks { get; set; }
+     public string InitialDirectory { get; set; }
+     public string FileName { get; set; }
+     public string[] FileNames { get; }
+     public bool RestoreDirectory { get; set; }
+     public string SafeFileName { get; }
+     public string[] SafeFileNames { get; }
+     public string Title { get; set; }
+     public bool ValidateNames { get; set; }
+     public event CancelEventHandler FileOk { add; remove; }
+     protected void OnFileOk(CancelEventArgs e);
+     protected override bool RunDialog(IntPtr hwndOwner};
+     public override void Reset();
+     public override string ToString();
+
+     // new members
+     public bool AddToRecent { get; set; }        // = true;  (FOS_DONTADDTORECENT)
+     public bool AllowReadOnlyItems { get; set; } // = false; (FOS_NOREADONLYRETURN)
+     public Guid? ClientGuid { get; set; }        //          (IFileDialog::SetClientGuid)
+     public string DefaultDirectory { get; set; } //          (IFileDialog::SetDefaultFolder)
+     public bool ShowHiddenFiles { get; set; }    // = false; (FOS_FORCESHOWHIDDEN )
+     public string RootDirectory { get; set; }    //          (IFileDialog2::SetNavigationRoot)
+ }
  
- // unless noted otherwise, all removed members lifted up to CommonItemDialog
-
-  public abstract partial class FileDialog : CommonDialog
+  public abstract partial class FileDialog : CommonItemDialog
  {
      protected FileDialog();
      public bool AddExtension { get; set; }
      public virtual bool CheckFileExists { get; set; }
      public bool CheckPathExists { get; set; } // WAS VIRTUAL
-     public IList<FileDialogCustomPlace> CustomPlaces { get; set; }
      public string DefaultExt { get; set; }
-     public bool DereferenceLinks { get; set; }
-     public string InitialDirectory { get; set; }
-     public string FileName { get; set; }
-     public string[] FileNames { get; }
      public string Filter { get; set; }
      public int FilterIndex { get; set; }
-     protected int Options { get; } // REMOVED
-     public bool RestoreDirectory { get; set; }
-     public string SafeFileName { get; }
-     public string[] SafeFileNames { get; }
-     public string Title { get; set; }
-     public bool ValidateNames { get; set; }
-     public event CancelEventHandler FileOk { add; remove; }
-     protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); // OVERRIDE REMOVED
-     protected void OnFileOk(CancelEventArgs e);
      public override void Reset();
      protected override bool RunDialog(IntPtr hwndOwner);
      public override string ToString();
  }

  public sealed partial class OpenFileDialog : FileDialog
  {
      public OpenFileDialog();
+     public bool ForcePreviewPane { get; set; } // = false; (FOS_FORCEPREVIEWPANEON)
      public bool Multiselect { get; set; }
      public bool ReadOnlyChecked { get; set; }
      public bool ShowReadOnly { get; set; }
      protected override void CheckPermissionsToShowDialog();
      public System.IO.Stream OpenFile();
      public System.IO.Stream[] OpenFiles();
      public override void Reset();
  }

+ public sealed partial class OpenFolderDialog : CommonItemDialog
+ {
+     public OpenFolderDialog();
+     public bool Multiselect { get; set; }
+     public override void Reset();
+ }

  public sealed partial class SaveFileDialog : FileDialog
  {
      public SaveFileDialog();
+     public bool CreateTestFile { get; set; } // = true; (FOS_NOTESTFILECREATE)
      public bool CreatePrompt { get; set; }
      public bool OverwritePrompt { get; set; }
      public System.IO.Stream OpenFile();
      public override void Reset();
  }

API Usage

OpenFolderDialog dialog = new OpenFolderDialog();
dialog.Multiselect = true;
dialog.ShowDialog();
string[] folders = dialog.FileNames;

Alternative Designs

There are several ways how the dialog could be introduced.

  1. Pile on the technical debt and just add another flag property on OpenFileDialog. This is PickFolders option for OpenFileDialog (minimal) #6374.
    Pros: Minimal risk, no breaking changes, reflects native API design.
    Cons: When using OpenFileDialog for folders, some of its properties would become meaningless, some would start throwing. Any future enhancements to the API would become more and more difficult.

  2. Inherit directly from CommonDialog, or do not inherit form anything.
    Pros: No backward compatibility requirements, can have clean design.
    Cons: Does not reflect the fact the underlying API is a file dialog. User code that prepares the dialog would have to be duplicated across different types. Vast majority of the code that is currently in FileDialog would have to be duplicated (and any future enhancements to it).

Moving the members around and/or splitting the inheritance chain seems to be the only way how to achieve a reasonably clean design and utilize existing code.

Ideally, the situation would be like this:
OpenFolderDialog : CommonDialog
OpenFileDialog : FileDialog : CommonDialog
where CommonDialog would specifically mean common file dialog (dropping the current design where CommonDialog was expected to be used for other system dialogs like color, font, etc.), and all the members that are shared between file and folder dialogs would be lifted up to CommonDialog. Unfortunately touching CommonDialog is a compatibility concern, because unlike FileDialog, people can inherit directly from CommonDialog and even if it mostly wouldn't be a breaking change, we would pollute it with file-related members even for unrelated inheritors.

Having to keep CommonDialog what it is, we need to introduce another class in the inheritance chain for the shared members for both files and folders.

However, introducing a class in-between, e.g.
OpenFolderDialog : FileDialog : CommonDialog
OpenFileDialog : CommonFileDialog : FileDialog : CommonDialog
would mean some of the members would have to be moved down from FileDialog to a derived class. That is a breaking change for when a variable is declared as FileDialog, it would be missing the moved members.

The remaining options is to introduce a new base class (i.e. this proposal):
OpenFolderDialog : CommonItemDialog : CommonDialog
OpenFileDialog : FileDialog : CommonItemDialog : CommonDialog
and lift the members up from FileDialog to a parent class. This will keep variables declared as FileDialog working like before, the existing members would become inherited.

(There is also always the possibility to throw away the current implementation and create a clean design in a different namespace.)

Risks

No public API removed, a new base class introduced. Current applications using the types will keep working without recompilation. Code using reflection even on public types might break if it is not flattening them.

Applications with Windows XP compatibility flag will still work, however, the dialogs will use the IFileDialog API rather than the legacy dialogs.

@pchaurasia14 pchaurasia14 added the API suggestion Early API idea and discussion, it is NOT ready for implementation label Mar 30, 2023
@dipeshmsft dipeshmsft self-assigned this Mar 30, 2023
@lindexi
Copy link
Contributor

lindexi commented Mar 31, 2023

@miloush Should the OpenFolderDialog use the FileName property?

@miloush
Copy link
Contributor Author

miloush commented Apr 1, 2023

@lindexi We discussed this in the PR, I originally had OpenFolderDialog have its own FolderName and SafeFolderName, however, because the dialog supports multiselect, quite a lot of code would need to be duplicated. Also you can have .lnk files in the results if you disable dereferencing links, so it is more that FileName is a generic reference to a path.

I can try to look into it again, extracting the path handling into separate helper class perhaps, I don't exactly remember what the problems where (it was half a year ago!). I would really like to have the CommonItemDialog to work with shell items only.

@dipeshmsft
Copy link
Member

@miloush, this proposal looks good. Adding a new base class is not a breaking change ( except for reflection, which is not an issue ) and we will like to proceed with this proposal.

Although our target here is to enable folder selection, however IMO we should consider adding the public properties / commonly used options to this proposal that are provided by other options, so that there is no loss of functionality for the developers who want to switch to OpenFolderDialog.

Thoughts?

@miloush
Copy link
Contributor Author

miloush commented Apr 6, 2023

@dipeshmsft can you suggest which properties / options are missing?

@dipeshmsft
Copy link
Member

Here is the list of properties that I think we should include in this proposal

In CommonItemDialog:

  • ClientGuid
  • ShowHiddenFiles
  • ShowPinnedPlaces ( FOS_HIDEPINNEDPLACES )
  • OkRequiresInteraction
  • AddToRecent ( or DontAddToRecent )

In OpenFolderDialog, we can look into Description and UseDesriptionAsTitle ?

@miloush
Copy link
Contributor Author

miloush commented Apr 6, 2023

Oh! I am all up for getting in as much as we can from #7248. I kind of thought we have to have a PR already for the API review. If that is not the case, we can get API surface approved first and then deal with the implementation, then no problem. I will update the issue. Do you want me to include API for the controls? (#7256)

@miloush
Copy link
Contributor Author

miloush commented Apr 6, 2023

In OpenFolderDialog, we can look into Description and UseDescriptionAsTitle?

I am not a big fan of Description (it's just a shortcut for the controls) and I am against UseDescriptionAsTitle - just set it as title. This is in WinForms because of compatibility requirements with SHBrowseForFolderDialog API. We don't support this API and therefore don't have such requirements.

@miloush
Copy link
Contributor Author

miloush commented Apr 6, 2023

FOS_HIDEPINNEDPLACES

I don't have issue with including this one, but note that it does not hide pinned places only, it makes the left pane completely empty. Not sure how useful it is.

@dipeshmsft
Copy link
Member

Oh! I am all up for getting in as much as we can from #7248. I kind of thought we have to have a PR already for the API review. If that is not the case, we can get API surface approved first and then deal with the implementation, then no problem. I will update the issue. Do you want me to include API for the controls? (#7256)

Yeah, I think we should get the API Proposal approved first and then we can later deal with the implementation. I am currently going through all the available flags, and will checkout the API for controls.

I don't have issue with including this one, but note that it does not hide pinned places only, it makes the left pane completely empty. Not sure how useful it is.

Agreed, that there will be fewer developers using this flag. I would like to take other community members' opinion here.
We can leave it for now and in case there is a requirement for this flag later, we can introduce it then.
Also not being able to hide pinned places will not be a case which prevents developers from using WPF's folder dialog.

@dipeshmsft
Copy link
Member

@miloush, after a look at the FOS_* flags and IFileDialog methods, I think we should include these four properties in CommonItemDialog for now ClientGuid, AddToRecent, ShowHiddenFiles, OkRequiresInteraction.

Also, in which scenarios DereferenceLinks will be used with an OpenFolderDialog ?

@miloush
Copy link
Contributor Author

miloush commented Apr 11, 2023

Also, in which scenarios DereferenceLinks will be used with an OpenFolderDialog ?

When you have shortcuts (i.e. lnk files) pointing to folders. Dereferencing them would return the target folder path, not doing so would return a path to the .lnk file.

@dipeshmsft
Copy link
Member

dipeshmsft commented Apr 14, 2023

@miloush, what I meant by that question is, can we enable this property by default for OpenFolderDialog? Because when we set this to False, we cannot use navigation via the .lnk file, and since it is a file, it cannot be selected. Although it is not a non-functioning API for OpenFolderDialog and we can have it in CommonItemDialog. Thoughts, about this ?

/cc @Symbai @ThomasGoulet73 @lindexi @batzen

@Symbai
Copy link
Contributor

Symbai commented Apr 14, 2023

I see it in the PR but I don't see where its used. Anyway I created two new projects, one with Winforms (.NET 7) and the other is WPF which uses the WinAPI Code pack (which almost all WPF developers use if they dont use the winforms folder dialolg) just to see their behavior on shortcuts. Both with all options left on their default value.

WPF code:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var dlg = new CommonOpenFileDialog
    {
        IsFolderPicker = true
    };
    if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
    {
        MessageBox.Show(dlg.FileName);
    }
}

Winforms:

private void NewButton_Click(object? sender, EventArgs e)
{
    var dlg = new FolderBrowserDialog();
    if (dlg.ShowDialog() == DialogResult.OK)
    {
        MessageBox.Show(dlg.SelectedPath);
    }
}

Both are showing shortcuts by default. Selecting the shortcut and clicking on the OK button will print out the TARGET folder path, not the shortcut path. This is something that I personally think is expected and wanted. I dont think that developers would want getting a path to a FILE (shortcut). I also haven't seen there is an option to disable the behavior on these two as well. Either we remove DereferenceLinks and deference links all the time or as @dipeshmsft said we should enable this property by default.

@miloush
Copy link
Contributor Author

miloush commented Apr 14, 2023

@dipeshmsft my point was it is a functioning API, you can see .lnk files in the open folder dialog. This property is already on by deafult, see FileDialog.DereferenceLinks (the native flag is FOS_NODEREFERENCELINKS so if you don't set it, by default you get links dereferenced).

@miloush
Copy link
Contributor Author

miloush commented Apr 14, 2023

I have updated the API with additional new properties that I believe are relatively easy to implement (i.e. just a flag or interface call).

I didn't add:

  • anything that requires the custom controls interface
  • OkButtonRequiresInteraction because I think it would be better suited for FileDialogOkButton in File dialog controls #7256
  • ShowPinnedPlaces unless there is a support from community for it, since it does not do what it says and would confuse both users and developers (there is no folder tree, the left pane becomes all empty).
  • Description in favor of dialog controls.

Please have a look and let me know your thoughts, or if you want me to add the default values to the API diff.

@miloush
Copy link
Contributor Author

miloush commented Apr 14, 2023

I would, however, not want to delay the open folder feature only because of discussion about new unrelated file dialog properties that can be added later. Note that 8.0 is a long-term support release.

@terrajobst terrajobst added api-ready-for-review API is ready for review, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work labels Apr 14, 2023
@terrajobst
Copy link
Member

Marked as ready for review, so we'll take a look next Tuesday 4/18

@terrajobst terrajobst added API suggestion Early API idea and discussion, it is NOT ready for implementation and removed API suggestion Early API idea and discussion, it is NOT ready for implementation labels Apr 14, 2023
@dipeshmsft
Copy link
Member

@miloush, I looked at the updated members and here are my thoughts:

  • I am convinced on skipping ShowPinnedPlaces, Description
  • Regarding DereferenceLinks, I think I did not make my point clear. Yes, it is a functioning API for OpenFolderDialog, but I am not convinced that DereferenceLinks=false will be of use in OpenFolderDialog, and hence we can leave it in FileDialog. Similarly, ForcePreviewPane, shows the preview pane, which is a functioning API for OpenFolderDialog, but the pane doesn't show any preview for folders, and due to this I think, this property should also be present in FileDialog rather than in CommonItemDialog.
  • InitialDirectory is the address in the toolbar. Apart from that what is the function of RootDirectory and DefaultDirectory ?

@miloush
Copy link
Contributor Author

miloush commented Apr 17, 2023

@dipeshmsft

Since DereferenceLinks shows .lnk files in the dialog, there is a distinct functionality that developers might want to use. Not having a scenario why someone would want to do that does not mean we should block people doing that. In fact, I managed to find someone doing it on the first page of GH results:

https://github.com/cabal95/RockDevBooster/blob/709049eb6f4175d3e9c011fedabc92793d07d7b2/RockDevBooster/Views/SetupPluginLinksView.xaml.cs#L75-L78

InitialDirectory - IFileDialog.SetFolder explicitly sets the folder to what developer specifies, overriding any last used folder
DefaultDirectory - IFileDialog.SetDefaultFolder a default folder when this is the first time user is opening the dialog and there is no last used folder to start with. It is like InitialDirectory unless there is a last used folder, in which case the last used folder is used instead.
RootDirectory - IFileDialog2.SetNavigationRoot specifies the top-level location, users cannot navigate above it.

@miloush
Copy link
Contributor Author

miloush commented Apr 17, 2023

@lindexi asked about the folder dialog having a FileName property. The alternative would be to name these properties separately after the dialog and have either a shared helper class or internal properties that would be exposed this way, e.g. the counter suggestion is

FileDialog having FileName, FileNames, SafeFileName and SafeFileNames properties.
OpenFolderDialog having FolderName, FolderNames, SafeFolderName and SafeFolderNames properties.

The downsides of doing that would be:

  • Code that customers use to preprocess or postprocess any dialog interaction would need to be duplicated for folder and file dialogs.
  • The folder name can include a path to a .lnk file
  • It departs from the native API naming.

At this point I prefer shared properties as proposed. @lindexi do you have any arguments why using FolderName rather than FileName would be beneficial?

@bartonjs
Copy link
Member

bartonjs commented Apr 18, 2023

Video

  • Corrected the proposal to show that FileDialog should extend the new CommonItemDialog
  • CommonItemDialog.ForceShowHidden was renamed to ShowHiddenFiles to match WinForms
  • Changed CommonItemDialog..ctor from protected to private protected to indicate the type cannot be derived outside of WPF (due to internal abstract members not shown)
  • We discussed the removal of the protected members on FileDialog, and decided it's OK because the type cannot be derived outside the assembly due to (not listed) internal abstract members.
  • FileDialog..ctor should change in the implementation to be private protected or internal to keep the hierarchy effectively sealed (until/unless there's a decision to change that in the future).
  namespace Microsoft.Win32;
  
  public abstract partial class CommonDialog
  {
      protected CommonDialog();
      public object Tag { get; set; }
      protected virtual void CheckPermissionsToShowDialog();
      protected virtual IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
      public abstract void Reset();
      protected abstract bool RunDialog(IntPtr hwndOwner);
      public virtual bool? ShowDialog();
      public bool? ShowDialog(Window owner);
  }

+ public abstract partial class CommonItemDialog : CommonDialog
+ {
+     private protected CommonItemDialog();
+
+     // lifted up from FileDialog
+     public IList<FileDialogCustomPlace> CustomPlaces { get; set; }
+     public bool DereferenceLinks { get; set; }
+     public string InitialDirectory { get; set; }
+     public string FileName { get; set; }
+     public string[] FileNames { get; }
+     public bool RestoreDirectory { get; set; }
+     public string SafeFileName { get; }
+     public string[] SafeFileNames { get; }
+     public string Title { get; set; }
+     public bool ValidateNames { get; set; }
+     public event CancelEventHandler FileOk { add; remove; }
+     protected void OnFileOk(CancelEventArgs e);
+     protected override bool RunDialog(IntPtr hwndOwner};
+     public override void Reset();
+     public override string ToString();
+
+     // new members
+     public bool AddToRecent { get; set; }        // = true;  (FOS_DONTADDTORECENT)
+     public bool AllowReadOnlyItems { get; set; } // = false; (FOS_NOREADONLYRETURN)
+     public Guid? ClientGuid { get; set; }        //          (IFileDialog::SetClientGuid)
+     public string DefaultDirectory { get; set; } //          (IFileDialog::SetDefaultFolder)
+     public bool ShowHiddenFiles { get; set; }    // = false; (FOS_FORCESHOWHIDDEN )
+     public string RootDirectory { get; set; }    //          (IFileDialog2::SetNavigationRoot)
+ }
  
- // unless noted otherwise, all removed members lifted up to CommonItemDialog
-
- public abstract partial class FileDialog : CommonDialog
+ public abstract partial class FileDialog : CommonItemDialog
  {
-     protected FileDialog();
+     private protected FileDialog();
      public bool AddExtension { get; set; }
      public virtual bool CheckFileExists { get; set; }
      public bool CheckPathExists { get; set; } // WAS VIRTUAL
-     public IList<FileDialogCustomPlace> CustomPlaces { get; set; }
      public string DefaultExt { get; set; }
-     public bool DereferenceLinks { get; set; }
-     public string InitialDirectory { get; set; }
-     public string FileName { get; set; }
-     public string[] FileNames { get; }
      public string Filter { get; set; }
      public int FilterIndex { get; set; }
-     protected int Options { get; } // REMOVED
-     public bool RestoreDirectory { get; set; }
-     public string SafeFileName { get; }
-     public string[] SafeFileNames { get; }
-     public string Title { get; set; }
-     public bool ValidateNames { get; set; }
-     public event CancelEventHandler FileOk { add; remove; }
-     protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); // OVERRIDE REMOVED
-     protected void OnFileOk(CancelEventArgs e);
      public override void Reset();
      protected override bool RunDialog(IntPtr hwndOwner);
      public override string ToString();
  }

  public sealed partial class OpenFileDialog : FileDialog
  {
      public OpenFileDialog();
+     public bool ForcePreviewPane { get; set; } // = false; (FOS_FORCEPREVIEWPANEON)
      public bool Multiselect { get; set; }
      public bool ReadOnlyChecked { get; set; }
      public bool ShowReadOnly { get; set; }
      protected override void CheckPermissionsToShowDialog();
      public System.IO.Stream OpenFile();
      public System.IO.Stream[] OpenFiles();
      public override void Reset();
  }

+ public sealed partial class OpenFolderDialog : CommonItemDialog
+ {
+     public OpenFolderDialog();
+     public bool Multiselect { get; set; }
+     public override void Reset();
+ }

  public sealed partial class SaveFileDialog : FileDialog
  {
      public SaveFileDialog();
+     public bool CreateTestFile { get; set; } // = true; (FOS_NOTESTFILECREATE)
      public bool CreatePrompt { get; set; }
      public bool OverwritePrompt { get; set; }
      public System.IO.Stream OpenFile();
      public override void Reset();
  }

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed API suggestion Early API idea and discussion, it is NOT ready for implementation api-ready-for-review API is ready for review, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work labels Apr 18, 2023
@lindexi
Copy link
Contributor

lindexi commented Apr 19, 2023

@miloush Or PathName or Path?

@miloush
Copy link
Contributor Author

miloush commented Apr 19, 2023

@lindexi PathName or Path would be a breaking change to existing code

@dipeshmsft
Copy link
Member

Added some basic tests for OpenFolderDialog in the test repo - dotnet/wpf-test#130

@dipeshmsft
Copy link
Member

dipeshmsft commented May 29, 2023

On further investigation and reviewing the PR, there are a few things that I want to point out - OpenFolderDialog, does not return the path of the .lnk filled.

When DereferenceLinks is true, the path pointed by the .lnk file is returned. On the other hand, when DereferenceLinks = false, nothing is returned, and the dialog shows a prompt with the message that the folder name is not valid.

Considering this, we should move back FileName and similar properties to FileDialog class and have FolderName for the folder dialog. For this, I think @miloush suggestion of having internal properties in CommonItemDialog is a good approach.

The alternative would be to name these properties separately after the dialog and have either a shared helper class or internal properties that would be exposed this way, e.g., the counter suggestion is
FileDialog having FileName, FileNames, SafeFileName and SafeFileNames properties.
OpenFolderDialog having FolderName, FolderNames, SafeFolderName and SafeFolderNames properties.

@dipeshmsft
Copy link
Member

dipeshmsft commented Jun 8, 2023

/cc @miloush @lindexi
Based on the above discussion, below is the updated API -

  namespace Microsoft.Win32;
  
  public abstract partial class CommonDialog
  {
      protected CommonDialog();
      public object Tag { get; set; }
      protected virtual void CheckPermissionsToShowDialog();
      protected virtual IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
      public abstract void Reset();
      protected abstract bool RunDialog(IntPtr hwndOwner);
      public virtual bool? ShowDialog();
      public bool? ShowDialog(Window owner);
  }

+ public abstract partial class CommonItemDialog : CommonDialog
+ {
+     private protected CommonItemDialog();
+
+     // lifted up from FileDialog
+     public IList<FileDialogCustomPlace> CustomPlaces { get; set; }
+     public bool DereferenceLinks { get; set; }
+     public string InitialDirectory { get; set; }
+     public string Title { get; set; }
+     public bool ValidateNames { get; set; }     
+     protected virtual void OnItemOk(CancelEventArgs e);
+     protected override bool RunDialog(IntPtr hwndOwner);
+     public override void Reset();
+     public override string ToString();
+
+     // new members
+     public bool AddToRecent { get; set; }        // = true;  (FOS_DONTADDTORECENT)
+     public Guid? ClientGuid { get; set; }        //          (IFileDialog::SetClientGuid)
+     public string DefaultDirectory { get; set; } //          (IFileDialog::SetDefaultFolder)
+     public bool ShowHiddenItems { get; set; }    // = false; (FOS_FORCESHOWHIDDEN )
+     public string RootDirectory { get; set; }    //          (IFileDialog2::SetNavigationRoot)
+ }
  
- // unless noted otherwise, all removed members lifted up to CommonItemDialog
-
- public abstract partial class FileDialog : CommonDialog
+ public abstract partial class FileDialog : CommonItemDialog
  {
-     protected FileDialog();
+     private protected FileDialog();
      public bool AddExtension { get; set; }
-     public virtual bool CheckFileExists { get; set; }
+     public bool CheckFileExists { get; set; } // WAS VIRTUAL
      public bool CheckPathExists { get; set; } 
-     public IList<FileDialogCustomPlace> CustomPlaces { get; set; }
      public string DefaultExt { get; set; }
-     public bool DereferenceLinks { get; set; }
-     public string InitialDirectory { get; set; }
      public string FileName { get; set; }
      public string[] FileNames { get; }
      public string Filter { get; set; }
      public int FilterIndex { get; set; }
-     protected int Options { get; } // REMOVED
      public bool RestoreDirectory { get; set; }
      public string SafeFileName { get; }
      public string[] SafeFileNames { get; }
-     public string Title { get; set; }
-     public bool ValidateNames { get; set; }
      public event CancelEventHandler FileOk { add; remove; }
+     protected override void OnItemOk(CancelEventArgs e);
-     protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam); // OVERRIDE REMOVED
-     protected void OnFileOk(CancelEventArgs e);
      public override void Reset();
-     protected override bool RunDialog(IntPtr hwndOwner);
      public override string ToString();
  }

  public sealed partial class OpenFileDialog : FileDialog
  {
      public OpenFileDialog();
+     public bool ForcePreviewPane { get; set; } // = false; (FOS_FORCEPREVIEWPANEON)
      public bool Multiselect { get; set; }
      public bool ReadOnlyChecked { get; set; }
      public bool ShowReadOnly { get; set; }
-     protected override void CheckPermissionsToShowDialog();
      public System.IO.Stream OpenFile();
      public System.IO.Stream[] OpenFiles();
      public override void Reset();
  }

+ public sealed partial class OpenFolderDialog : CommonItemDialog
+ {
+     public OpenFolderDialog();
+     public bool Multiselect { get; set; }
+     public override void Reset();
+     public string FolderName { get; }
+     public string[] FolderNames { get; }
+     public event CancelEventHandler FolderOk { add; remove; }
+     protected override void OnItemOk(CancelEventArgs e);
+     public string SafeFolderName { get; }
+     public string[] SafeFolderNames { get; }
+     public override string ToString();
+ }

  public sealed partial class SaveFileDialog : FileDialog
  {
      public SaveFileDialog();
+     public bool CreateTestFile { get; set; } // = true; (FOS_NOTESTFILECREATE)
      public bool CreatePrompt { get; set; }
      public bool OverwritePrompt { get; set; }
      public System.IO.Stream OpenFile();
      public override void Reset();
  }

The above API has been reviewed and approved internally ( over email ). We will be proceeding with the implementation.

@lindexi
Copy link
Contributor

lindexi commented Jun 8, 2023

Thank you @dipeshmsft

Looks good to me

@singhashish-wpf
Copy link
Member

Closing as completed.

@lindexi
Copy link
Contributor

lindexi commented Aug 28, 2023

Awesome

@ghost ghost locked as resolved and limited conversation to collaborators Sep 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented
Projects
None yet
Development

No branches or pull requests

8 participants