dotnet / winforms

Windows Forms is a .NET UI framework for building Windows desktop applications.
MIT License
4.41k stars 984 forks source link

WinForm class OpenFileDialog and SaveFileDialog don't support OFN_DONTADDTORECENT flag #5405

Closed ghost closed 2 years ago

ghost commented 3 years ago

If the users don't want to save the opened file history to the Windows system, the Windows API provides the OFN_DONTADDTORECENT flag to achieve this, but it is currently not available in WinForm class OpenFileDialog and SaveFileDialog.

Even if the reflection method OpenFileDialog.SetOption is called and OFN_DONTADDTORECENT is passed in, it also does not work.

Tested .NET Version:

.NET Framework 4.8 .NET 5.0

willibrandon commented 3 years ago

ShareAware

Controls whether the application is called back for guidance in the case of a sharing violation.

https://user-images.githubusercontent.com/5017479/137435623-d94562f7-7c57-4e88-9464-c2b3ecfebd0a.mp4

willibrandon commented 3 years ago

I forgot to demonstrate that the full path of the file involved in the sharing violation is also passed back to the application.

willibrandon commented 3 years ago

An example of the full path passed back to the application. The application can respond to the sharing violation by handling the ShareViolation event and specifying a response of Default, Accept, or Refuse.

https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-fde_shareviolation_response

ShareAware

willibrandon commented 3 years ago

This is where things really got interesting and I realized that some of the file dialog events have not yet been implemented. This might be a good time to explore the FolderChange, FolderChanging, Overwrite, SelectionChange, ShareViolation, and TypeChange events.

https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifiledialogevents

willibrandon commented 3 years ago

Before I forget I would like to take a moment and say thank you to the Windows Forms team for making this project available on GitHub. Regardless of what happens with this proposal, this has been a wonderful learning experience that I will never forget.

willibrandon commented 3 years ago

FolderChanging

Occurs just before the user navigates to a new folder.

Allows the application to prevent navigation to a particular location and provides an option to redirect to an alternate folder.

https://user-images.githubusercontent.com/5017479/137570458-d0261f75-aac7-4804-b374-cb44a74f7b00.mp4

willibrandon commented 3 years ago

FolderChanging sample usage.


private void OpenFileDialog1_FolderChanging(object? sender, FolderChangingEventArgs e)
{
    if (new DirectoryInfo(e.Folder).Name == "ForbiddenFolder")
    {
        // Prevent navigation to ForbiddenFolder.
        e.Prevent = true;

        // Redirect to recent.
        e.Folder = Environment.GetFolderPath(Environment.SpecialFolder.Recent);

        // Explain the restriction because the docs say to do so.
        MessageBox.Show(
            "This is a forbidden folder.",
            "Hello WinForms", MessageBoxButtons.OK,
            MessageBoxIcon.Stop);
    }
}
willibrandon commented 3 years ago

Overwrite

Called from the save dialog when the user chooses to overwrite a file.

Allows the application to create custom overwrite prompts.

https://user-images.githubusercontent.com/5017479/137576966-66aaa263-d061-4a06-aec3-53d4e20f5bae.mp4

willibrandon commented 3 years ago

Overwrite sample usage.


private void SaveFileDialog1_Overwrite(object? sender, OverwriteEventArgs e)
{
    // Show the overwrite prompt
    DialogResult overwriteResult =
    MessageBox.Show(
        Path.GetFileName(e.FileName) + " already exists." +
        Environment.NewLine +
        "Would you like to replace it?",
        "Hello WinForms", MessageBoxButtons.YesNo,
        MessageBoxIcon.Exclamation);

    // Set the overwrite response
    e.Response = overwriteResult switch
    {
        DialogResult.Yes => OverwriteResponse.ACCEPT,
        DialogResult.No => OverwriteResponse.REFUSE,
        _ => OverwriteResponse.DEFAULT
    };
}
willibrandon commented 3 years ago

I didn't know custom overwrite prompts were even possible.

willibrandon commented 3 years ago

I have noticed more file dialog features that have not been implemented yet. IFileDialogCustomize exposes methods that allows the application to add controls to the dialogs and IFileDialogControlEvents allows the application to be notified of the events related to the controls that have been added. Interesting.

I'm not ready to investigate this one yet but noting it here so I don't forget. Customized file dialogs.

willibrandon commented 3 years ago

I've just discovered there is more to the ShareAware/ShareViolation feature. There might be an opportunity to pass more ShareViolationEventArgs back to the application and enable even more functionality like switching to the application that has the file open and closing the file in use from the File In Use dialog. I need to take a closer look at IFileIsInUse.

RussKie commented 3 years ago

This is where things really got interesting and I realized that some of the file dialog events have not yet been implemented. This might be a good time to explore the FolderChange, FolderChanging, Overwrite, SelectionChange, ShareViolation, and TypeChange events.

docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifiledialogevents

This is awesome! If I may suggest though, let's try to concentrate on OFN_* flags here. Otherwise we risk expanding the scope beyond reason. Please open a new proposal for the events, and others (if desired/necessary) for other customisations. This way it would be easier to review and discuss each aspect individually.

willibrandon commented 3 years ago

Will do and thank you for clarifying that for me. If the focus of this proposal is primarily the missing file dialog options, then I'm ready to update the proposal now. I'll need a little time to sort some of it out but I'll try to be quick about it.

willibrandon commented 3 years ago

I've just updated the proposal above. If you need anything or have any questions please let me know. Thanks for the opportunity to take a look at this. Its been really fun.

RussKie commented 3 years ago

Can you please add xml-docs for each new property explaining the purpose and denoting default values?

willibrandon commented 3 years ago

Good idea, done. I hope the summaries with the videos are good enough to understand.

RussKie commented 3 years ago

Thank you. The team is currently super busy wrapping up .NET 6.0 and VS 2022 releases, and it will take us sometime to review the proposal (a cursory look tells me we'd want to tweak the proposed names). Please bear with us.

dreddy-work commented 3 years ago

@willibrandon, This is still on our radar. Team is still wrapping few things in .NET 6.0 and VS releases and should be able to take this further by end of this month.

willibrandon commented 3 years ago

@dreddy-work, thanks for the update, looking forward to it and good luck.

RussKie commented 2 years ago

I've reviewed the proposal, and compared it against the existing API. I'm proposing few tweaks:

namespace System.Windows.Forms
{
    public partial class FileDialog
    {
        /// <summary>
-       ///  Controls whether to add the file being opened or saved to the recent list.
+       ///  Gets or sets a value indicating whether the dialog box adds the file being opened or saved to the recent list.
        /// </summary>
        [DefaultValue(true)]
-       public bool AddToRecent { get; set; }
+       public bool AddToRecent { get; set; } = true;

        /// <summary>
-       ///  Controls whether to include hidden and system items.
+       ///  Gets or sets a value indicating whether the dialog box displays hidden and system files.
        /// </summary>
        [DefaultValue(false)]
-       public bool ForceShowHiden { get; set; }
+       public bool ShowHiddenFiles { get; set; }

        /// <summary>
-       ///  Controls whether the items shown by default in the view's navigation pane are hidden.
+       ///  Gets or sets a value indicating whether the items shown by default in the view's navigation pane are hidden.
        /// </summary>
-       [DefaultValue(false)]
-       public bool HidePinnedPlaces { get; set; }
+       [DefaultValue(true)]
+       public bool ShowPinnedPlaces { get; set; } = true;

        /// <summary>
-       ///  Controls whether the OK button needs interaction before it is enabled.
+       ///  Gets or sets a value indicating whether the OK button of the dialog box is disabled until the user navigates the view or edits the filename (if applicable).
        /// </summary>
+       /// <remarks>
+       ///  Note: Disabling of the OK button does not prevent the dialog from being submitted by the Enter key.
+       /// </remarks>
        [DefaultValue(false)]
-       public bool OkButtonNeedsInteraction { get; set; }
+       public bool OkRequiresInteraction { get; set; }
    }
}
namespace System.Windows.Forms
{
    public partial class OpenFileDialog
    {
        /// <summary>
-       ///  Controls whether to always display the Open dialog box preview pane.
+       ///  Gets or sets a value indicating whether the dialog box shows a preview for selected files.
        /// </summary>
        [DefaultValue(false)]
-       public bool ForcePreviewPaneOn { get; set; }
+       public bool ShowPreview { get; set; }

        /// <summary>
-       /// Controls whether read-only items are returned.
+       ///  Gets or sets a value indicating whether the dialog box allows to select read-only files.
        /// </summary>
        [DefaultValue(true)]
-       public bool ReadOnlyReturn { get; set; }
+       public bool IgnoreReadOnly { get; set; } = true;
    }
}
namespace System.Windows.Forms
{
    public partial class SaveFileDialog
    {
        /// <summary>
-       ///  Controls whether to always open the Save As dialog box in expanded mode.
+       ///  Gets or sets a value indicating whether the dialog box is always opened in the expanded mode.
        /// </summary>
        [DefaultValue(true)]
-       public bool ExpandedMode { get; set; }
+       public bool ExpandedMode { get; set; } = true;

        /// <summary>
-       ///  Controls whether to create a test file.
+       ///  Gets or sets a value indicating whether the dialog box verifies whether creation of the specified file will be successful. 
+       ///  If this flag is not set, the calling application must handle errors, such as denial of access, discovered when the item is created.
        /// </summary>
        [DefaultValue(true)]    <-- what's the default behaviour now?
-       public bool TestFileCreate { get; set; } 
+       public bool CheckWriteAccess { get; set; }
    }
}
namespace System.Windows.Forms
{
    public partial class FolderBrowserDialog
    {
        /// <summary>
-       ///  Controls whether to add the folder being selected to the recent list.
+       ///  Gets or sets a value indicating whether the dialog box adds the folder being selected to the recent list.
        /// </summary>
        [DefaultValue(true)]
-       public bool AddToRecent { get; set; }
+       public bool AddToRecent { get; set; } = true;

        /// <summary>
-       ///  Controls whether to include hidden and system items.
+       ///  Gets or sets a value indicating whether the dialog box displays hidden and system files.
        /// </summary>
        [DefaultValue(false)]
-       public bool ForceShowHiden { get; set; }
+       public bool ShowHiddenFiles { get; set; }

        /// <summary>
-       ///  Controls whether the items shown by default in the view's navigation pane are hidden.
+       ///  Gets or sets a value indicating whether the items shown by default in the view's navigation pane are hidden.
        /// </summary>
-       [DefaultValue(false)]
-       public bool HidePinnedPlaces { get; set; }
+       [DefaultValue(true)]
+       public bool ShowPinnedPlaces { get; set; } = true;

        /// <summary>
-       ///  Controls whether the OK button needs interaction before it is enabled.
+       ///  Gets or sets a value indicating whether the OK button of the dialog box is disabled until the user navigates the view or edits the filename (if applicable).
        /// </summary>
+       /// <remarks>
+       ///  Note: Disabling of the OK button does not prevent the dialog from being submitted by the Enter key.
+       /// </remarks>
        [DefaultValue(false)]
-       public bool OkButtonNeedsInteraction { get; set; }
+       public bool OkRequiresInteraction { get; set; }
    }
}

I'm a little ensure about the proposed default value for the TestFileCreate - what's the current implementation's behaviour?

[DefaultValue(true)] 
public bool TestFileCreate { get; set; } 

Please let me know if you have any questions or objections. If not, please update your proposal, and then I think we'll be ready to pass it on to the API Review Board. Thanks

willibrandon commented 2 years ago

Those tweaks look good to me, thanks for taking a look. The proposed default value for TestFileCreate, or CheckWriteAccess should be true which is the current default behavior. I'll update the proposal with your suggestions and if you have any other thoughts on CheckWriteAccess please let me know.

willibrandon commented 2 years ago

The proposal has been updated and thank you for the suggestions @RussKie.

RussKie commented 2 years ago

Thank you. Marking as ready for review by the API Review Board. The review may take awhile, once the proposal is reviewed a member of the ARB will post here.

terrajobst commented 2 years ago

Video

namespace System.Windows.Forms
{
    public partial class FileDialog
    {
        [DefaultValue(true)]
        public bool AddToRecent { get; set; } = true;

        [DefaultValue(false)]
        public bool ShowHiddenFiles { get; set; }

        [DefaultValue(true)]
        public bool ShowPinnedPlaces { get; set; } = true;

        [DefaultValue(false)]
        public bool OkRequiresInteraction { get; set; }
    }
    public partial class OpenFileDialog
    {
        [DefaultValue(false)]
        public bool ShowPreview { get; set; }

        [DefaultValue(false)]
        public bool SelectReadOnly { get; set; }
    }
    public partial class SaveFileDialog
    {
        [DefaultValue(true)]
        public bool ExpandedMode { get; set; } = true;

        [DefaultValue(true)]
        public bool CheckWriteAccess { get; set; } = true;
    }
    public partial class FolderBrowserDialog
    {
        [DefaultValue(true)]
        public bool AddToRecent { get; set; } = true;

        [DefaultValue(false)]
        public bool ShowHiddenFiles { get; set; }

        [DefaultValue(true)]
        public bool ShowPinnedPlaces { get; set; } = true;

        [DefaultValue(false)]
        public bool OkRequiresInteraction { get; set; }
    }
}
RussKie commented 2 years ago

Thank you @terrajobst. @willibrandon would you like to submit a PR with an implementation?

willibrandon commented 2 years ago

Yes!

willibrandon commented 2 years ago

I can investigate replicating these in WPF.

RussKie commented 2 years ago

Great! Though I suggest to raise an issue in https://github.com/dotnet/wpf with a proposal and wait for a formal ack from the @dotnet/wpf-developers before investing too much time in this.

willibrandon commented 2 years ago

@roland5572 - Thank you for the good idea.

ghost commented 2 years ago

@willibrandon Thank you. I also found a possibility to increase the speed of WPF controls and save WPF system resources. I have updated the test results in this case.

https://github.com/dotnet/wpf/discussions/5582