| File: System\Windows\Forms\Dialogs\CommonDialogs\FileDialog.Vista.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using Windows.Win32.UI.Controls.Dialogs; using static Windows.Win32.UI.Controls.Dialogs.OPEN_FILENAME_FLAGS; using static Windows.Win32.UI.Shell.FILEOPENDIALOGOPTIONS; namespace System.Windows.Forms; public partial class FileDialog { private protected virtual bool SettingsSupportVistaDialog => !ShowHelp && Application.VisualStyleState.HasFlag(VisualStyles.VisualStyleState.ClientAreaEnabled); internal bool UseVistaDialogInternal => AutoUpgradeEnabled && SettingsSupportVistaDialog && SystemInformation.BootMode == BootMode.Normal; private protected abstract unsafe ComScope<IFileDialog> CreateVistaDialog(); private unsafe bool TryRunDialogVista(HWND hWndOwner, out bool returnValue) { using ComScope<IFileDialog> dialog = CreateVistaDialog(); if (dialog.IsNull) { // Creating the Vista dialog can fail on Windows Server Core, even if the // Server Core App Compatibility FOD is installed. returnValue = false; return false; } OnBeforeVistaDialog(dialog); using var events = ComHelpers.GetComScope<IFileDialogEvents>(new VistaDialogEvents(this)); dialog.Value->Advise(events, out uint eventCookie); try { returnValue = dialog.Value->Show(hWndOwner) == HRESULT.S_OK; return true; } finally { dialog.Value->Unadvise(eventCookie); } } private unsafe void OnBeforeVistaDialog(IFileDialog* dialog) { if (ClientGuid is { } clientGuid) { // IFileDialog::SetClientGuid should be called immediately after creation of the dialog object. // https://learn.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setclientguid#remarks dialog->SetClientGuid(in clientGuid); } dialog->SetDefaultExtension(DefaultExt); dialog->SetFileName(FileName); if (!string.IsNullOrEmpty(InitialDirectory)) { using ComScope<IShellItem> initialDirectory = new(PInvoke.SHCreateShellItem(InitialDirectory)); if (!initialDirectory.IsNull) { dialog->SetDefaultFolder(initialDirectory); dialog->SetFolder(initialDirectory); } } dialog->SetTitle(Title); dialog->SetOptions(GetOptions()); SetFileTypes(dialog); _customPlaces.Apply(dialog); } private FILEOPENDIALOGOPTIONS GetOptions() { const FILEOPENDIALOGOPTIONS BlittableOptions = FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | FOS_NOVALIDATE | FOS_ALLOWMULTISELECT | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_CREATEPROMPT | FOS_NODEREFERENCELINKS | FOS_DONTADDTORECENT | FOS_NOREADONLYRETURN | FOS_NOTESTFILECREATE | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE | FOS_OKBUTTONNEEDSINTERACTION | FOS_HIDEPINNEDPLACES | FOS_FORCEPREVIEWPANEON; #if DEBUG const OPEN_FILENAME_FLAGS UnexpectedOptions = OFN_SHOWHELP // If ShowHelp is true, we don't use the Vista Dialog | OFN_ENABLEHOOK // These shouldn't be set in options (only set in the flags for the legacy dialog) | OFN_ENABLESIZING // These shouldn't be set in options (only set in the flags for the legacy dialog) | OFN_EXPLORER; // These shouldn't be set in options (only set in the flags for the legacy dialog) Debug.Assert((UnexpectedOptions & _fileNameFlags) == 0, "Unexpected FileDialog options"); #endif FILEOPENDIALOGOPTIONS result = (FILEOPENDIALOGOPTIONS)_fileNameFlags & BlittableOptions; // Make sure that the Open dialog allows the user to specify // non-file system locations. This flag will cause the dialog to copy the resource // to a local cache (Temporary Internet Files), and return that path instead. This // also affects the Save dialog by disallowing navigation to these areas. // An example of a non-file system location is a URL (http://), or a file stored on // a digital camera that is not mapped to a drive letter. // This reproduces the behavior of the "classic" Open and Save dialogs. result |= FOS_FORCEFILESYSTEM; return result; } private protected abstract unsafe string[] ProcessVistaFiles(IFileDialog* dialog); private unsafe bool HandleVistaFileOk(IFileDialog* dialog) { OPEN_FILENAME_FLAGS saveOptions = _fileNameFlags; int saveFilterIndex = FilterIndex; string[]? saveFileNames = _fileNames; bool ok = false; try { Thread.MemoryBarrier(); dialog->GetFileTypeIndex(out uint filterIndexTemp); FilterIndex = unchecked((int)filterIndexTemp); _fileNames = ProcessVistaFiles(dialog); if (ProcessFileNames(_fileNames)) { CancelEventArgs ceevent = new(); if (NativeWindow.WndProcShouldBeDebuggable) { OnFileOk(ceevent); ok = !ceevent.Cancel; } else { try { OnFileOk(ceevent); ok = !ceevent.Cancel; } catch (Exception e) { Application.OnThreadException(e); } } } } finally { if (!ok) { Thread.MemoryBarrier(); _fileNames = saveFileNames; _fileNameFlags = saveOptions; FilterIndex = saveFilterIndex; } else { if (_fileNameFlags.HasFlag(OFN_HIDEREADONLY)) { // When the dialog is dismissed OK, the Readonly bit can't be left on if ShowReadOnly was false. // Downlevel this happens automatically, on Vista mode, we need to watch out for it. _fileNameFlags &= ~OFN_READONLY; } } } return ok; } private unsafe void SetFileTypes(IFileDialog* dialog) { COMDLG_FILTERSPEC[] filterItems = GetFilterItems(_filter); if (filterItems.Length > 0) { fixed (COMDLG_FILTERSPEC* fi = filterItems) { dialog->SetFileTypes((uint)filterItems.Length, fi); } dialog->SetFileTypeIndex(unchecked((uint)FilterIndex)); } } private static unsafe COMDLG_FILTERSPEC[] GetFilterItems(string? filter) { if (string.IsNullOrEmpty(filter)) { return []; } // Expected input types: // // "Text files (*.txt)|*.txt|All files (*.*)|*.*" // "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*" string[] tokens = filter.Split('|'); if (tokens.Length % 2 != 0) { return []; } var extensions = new COMDLG_FILTERSPEC[tokens.Length / 2]; // All even numbered tokens should be labels // Odd numbered tokens are the associated extensions for (int i = 1; i < tokens.Length; i += 2) { fixed (char* tokenName = tokens[i - 1]) fixed (char* tokenSpec = tokens[i]) { COMDLG_FILTERSPEC extension = new() { pszName = tokenName, // This may be a semicolon delimited list of extensions (that's ok) pszSpec = tokenSpec }; extensions[(i - 1) / 2] = extension; } } return extensions; } private protected static unsafe string GetFilePathFromShellItem(IShellItem* item) { item->GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out PWSTR ppszName); return ppszName.ToStringAndCoTaskMemFree()!; } private readonly FileDialogCustomPlacesCollection _customPlaces = []; [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public FileDialogCustomPlacesCollection CustomPlaces => _customPlaces; /// <summary> /// Gets or sets whether the dialog will be automatically upgraded to enable new features. /// </summary> [DefaultValue(true)] public bool AutoUpgradeEnabled { get; set; } = true; /// <summary> /// 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> /// <para> /// Note: Disabling of the OK button does not prevent the dialog from being submitted by the Enter key. /// </para> /// </remarks> [SRCategory(nameof(SR.CatBehavior))] [DefaultValue(false)] [SRDescription(nameof(SR.FileDialogOkRequiresInteractionDescr))] public bool OkRequiresInteraction { get => _dialogOptions.HasFlag(FOS_OKBUTTONNEEDSINTERACTION); set => _dialogOptions.ChangeFlags(FOS_OKBUTTONNEEDSINTERACTION, value); } /// <summary> /// Gets or sets a value indicating whether the items shown by default in the view's /// navigation pane are shown. /// </summary> [SRCategory(nameof(SR.CatBehavior))] [DefaultValue(true)] [SRDescription(nameof(SR.FileDialogShowPinnedPlacesDescr))] public bool ShowPinnedPlaces { get => !_dialogOptions.HasFlag(FOS_HIDEPINNEDPLACES); set => _dialogOptions.ChangeFlags(FOS_HIDEPINNEDPLACES, !value); } }