Разрешить выбор файлов только для чтения из SaveFileDialog? - PullRequest
0 голосов
/ 13 июня 2010

Я использую Microsoft.Win32.SaveFileDialog в приложении, где все файлы сохраняются только для чтения, но пользователь должен иметь возможность выбирать существующие файлы.Заменяемые существующие файлы переименовываются, например: blah.png становится blah.png-0.bak, blah.png-1.bak и т. Д.

Таким образом, язык для OverwritePrompt неуместен - мы не позволяет им перезаписывать файлы - поэтому я устанавливаю dlog.OverwritePrompt = false;

Исходные имена файлов для диалога генерируются на основе значений документа, поэтому для них это просто - мы переименовываем кандидатазаранее, и если пользователь отменяет или выбирает другое имя, переименуйте его снова.

Когда я предоставил эту функцию, тестеры быстро пожаловались, потому что хотели многократно сохранять файлы с именами , отличными от согласованных.workflow (эти глупые, игривые парни!).

Я не могу придумать, как сделать это с помощью стандартных диалогов, так, чтобы они безопасно работали как на XP (пока), так и на данный момент.Windows 7.

Я надеялся подключиться к событию FileOK , но это называется после Я получаю диалоговое окно с предупреждением:

|-----------------------------------------|
| blah.png                                |
| This file is set to read-only.          |
| Try again with a different file name.   |
|-----------------------------------------|

Ответы [ 3 ]

2 голосов
/ 26 октября 2014

Возникла та же проблема при попытке выбрать файлы из рабочей области Perforce, которые доступны только для чтения, пока не будут извлечены.Решил это путем вызова родного SaveFileDialog.

Вы можете удалить FOS_NOREADONLYRETURN в собственной версии, но она такая же, как в .NET.Windows API просто добавляет флаг автоматически.

Сначала я попытался использовать OpenFileDialog и изменить текст кнопки ОК с помощью встроенного метода SetOkButtonLabel.Но это дает вам проблему с локализацией, и вы также теряете проверку перезаписи.

Я закончил тем, что использовал событие OnSelectionChange (которое не доступно в версии .NET) для временного удаления флага только для чтениявыбранных файлов.Этот прием работает довольно хорошо, за исключением папок, где необходимы повышенные права (например, корневая папка C :).Но я могу жить с этим.

using System.IO;
using System.Runtime.InteropServices;

namespace System.Windows.Forms
{
    /// <summary>
    /// Same as .NETs SaveFileDialog, except that it also allows you to select read-only files.
    /// 
    /// Based on the native Common Item Dialog, which is also used internally by SaveFileDialog. Uses
    /// the OnSelectionChange event to temporarily remove the read-only flag of selected files to
    /// trick the dialog. Unfortunately, this event is not exponsed in the .NET version.
    /// 
    /// Since the Common Item Dialog was not available until Windows Vista, call the static IsSupported()
    /// method first. If it returns false, use the regular SaveFileDialog instead. On XP, the regular 
    /// dialog works also for read-only files.
    /// 
    /// Note that this trick won't work where elevated rights are needed (e.g. in the root folder of C:).
    /// </summary>
    public class SaveFileDialogRO : IDisposable
    {
        private const int S_OK = 0;

        private FileDialogNative.IFileSaveDialog dialog;
        private string defaultExt = string.Empty;
        private FileDialogNative.FOS options;
        private string filter = string.Empty;
        private string initialDirectory = string.Empty;
        private string title = string.Empty;

        /// <summary>
        /// Returns true, if Common Item Dialog is supported, which SaveFileDialogRO uses internally.
        /// If not, just use the regular SaveFileDialog.
        /// </summary>
        public static bool IsSupported()
        {
            return Environment.OSVersion.Version.Major > 5;
        }

        public SaveFileDialogRO()
        {
             dialog = (FileDialogNative.IFileSaveDialog)new FileDialogNative.FileSaveDialogRCW();
             dialog.GetOptions(out options);
        }

        ~SaveFileDialogRO()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            Marshal.ReleaseComObject(dialog);
        }

        /// <summary>
        /// Gets or sets a value indicating whether the dialog box displays a warning if the user specifies a file name 
        /// that does not exist.
        /// </summary>
        public bool CheckFileExists 
        {
            get
            {
                return (options & FileDialogNative.FOS.FOS_FILEMUSTEXIST) != 0;
            }
            set
            {
                if (value)
                    options |= FileDialogNative.FOS.FOS_FILEMUSTEXIST;
                else
                    options &= ~FileDialogNative.FOS.FOS_FILEMUSTEXIST;
                dialog.SetOptions(options);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the dialog box displays a warning if the user specifies a 
        /// path that does not exist.
        /// </summary>
        public bool CheckPathExists
        {
            get
            {
                return (options & FileDialogNative.FOS.FOS_PATHMUSTEXIST) != 0;
            }
            set
            {
                if (value)
                    options |= FileDialogNative.FOS.FOS_PATHMUSTEXIST;
                else
                    options &= ~FileDialogNative.FOS.FOS_PATHMUSTEXIST;
                dialog.SetOptions(options);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the dialog box prompts the user for permission to create a 
        /// file if the user specifies a file that does not exist.
        /// </summary>
        public bool CreatePrompt 
        {
            get
            {
                return (options & FileDialogNative.FOS.FOS_CREATEPROMPT) != 0;
            }
            set
            {
                if (value)
                    options |= FileDialogNative.FOS.FOS_CREATEPROMPT;
                else
                    options &= ~FileDialogNative.FOS.FOS_CREATEPROMPT;
                dialog.SetOptions(options);
            }
        }

        /// <summary>
        /// Gets or sets the default file name extension.
        /// </summary>
        public string DefaultExt
        {
            get
            {
                return defaultExt;
            }
            set
            {
                dialog.SetDefaultExtension(value);
                defaultExt = value;
            }
        }

        /// <summary>
        /// Gets or sets the default file name extension.
        /// </summary>
        public bool DereferenceLinks
        {
            get
            {
                return (options & FileDialogNative.FOS.FOS_NODEREFERENCELINKS) == 0;
            }
            set
            {
                if (!value)
                    options |= FileDialogNative.FOS.FOS_NODEREFERENCELINKS;
                else
                    options &= ~FileDialogNative.FOS.FOS_NODEREFERENCELINKS;
                dialog.SetOptions(options);
            }
        }

        /// <summary>
        /// Gets or sets a string containing the file name selected in the file dialog box.
        /// </summary>
        public string FileName 
        {
            get
            {
                // Get the selected file name (fails if the dialog has been cancelled or not yet been shown)
                string fileName;
                try
                {
                    FileDialogNative.IShellItem item;
                    dialog.GetResult(out item);

                    item.GetDisplayName(FileDialogNative.SIGDN.SIGDN_FILESYSPATH, out fileName);
                }
                catch (Exception)
                {
                    // Return the name that was set via SetFileName (fails if none has been set)
                    try
                    {
                        dialog.GetFileName(out fileName);
                    }
                    catch (Exception)
                    {
                        fileName = string.Empty;
                    }
                }
                return fileName;
            }
            set
            {
                dialog.SetFileName(value);
            }
        }

        /// <summary>
        /// Gets the file names of all selected files in the dialog box.
        /// For the SaveFileDialog, this will always be at most a single file.
        /// </summary>
        public string[] FileNames
        {
            get
            {
                // Get the selected file name (fails if the dialog has been cancelled or not yet been shown)
                try
                {
                    string fileName;
                    FileDialogNative.IShellItem item;
                    dialog.GetResult(out item);

                    item.GetDisplayName(FileDialogNative.SIGDN.SIGDN_FILESYSPATH, out fileName);
                    return new string[] { fileName };
                }
                catch (Exception)
                {
                    return new string[0];
                }
            }
        }

        /// <summary>
        /// Gets or sets the current file name filter string, which determines the choices that appear 
        /// in the "Save as file type" or "Files of type" box in the dialog box.
        /// </summary>
        /// <remarks>
        /// For each filtering option, the filter string contains a description of the filter, followed 
        /// by the vertical bar (|) and the filter pattern. The strings for different filtering options are 
        /// separated by the vertical bar.</br>
        /// The following is an example of a filter string:</br>
        /// Text files (*.txt)|*.txt|All files (*.*)|*.*
        /// </remarks>
        public string Filter 
        {
            get
            {
                return filter;
            }
            set
            {
                // Split at vertical bars
                string[] types = value.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                if (types.Length == 0 || types.Length % 2 != 0)
                    throw new ArgumentException("Invalid filter: " + value);

                // Convert to COMDLG_FILTERSPEC array
                int numTypes = types.Length / 2;
                FileDialogNative.COMDLG_FILTERSPEC[] specs = new FileDialogNative.COMDLG_FILTERSPEC[numTypes];
                for (int i = 0; i < numTypes; ++i)
                {
                    specs[i] = new FileDialogNative.COMDLG_FILTERSPEC
                    {
                        pszName = types[i * 2 + 0],
                        pszSpec = types[i * 2 + 1],
                    };
                }

                // Set new filter
                dialog.SetFileTypes((uint)numTypes, specs);
                filter = value;
            }
        }

        /// <summary>
        /// Gets or sets the index of the filter currently selected in the file dialog box.
        /// Note: The index value of the first filter entry is 1!
        /// </summary>
        public int FilterIndex 
        {
            get
            {
                uint index;
                dialog.GetFileTypeIndex(out index);
                return (int)index;
            }
            set
            {
                dialog.SetFileTypeIndex((uint)value);
            }
        }

        /// <summary>
        /// Gets or sets the initial directory displayed by the file dialog box.
        /// </summary>
        public string InitialDirectory 
        {
            get
            {
                return initialDirectory;
            }
            set
            {
                FileDialogNative.IShellItem item;
                IntPtr idl;
                uint atts = 0;
                if (SHILCreateFromPath(value, out idl, ref atts) == S_OK)
                {
                    if (SHCreateShellItem(IntPtr.Zero, IntPtr.Zero, idl, out item) == S_OK)
                    {
                        dialog.SetFolder(item);
                        initialDirectory = value;
                    }

                    CoTaskMemFree(idl);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Save As dialog box displays a warning if the user 
        /// specifies a file name that already exists.
        /// </summary>
        public bool OverwritePrompt
        {
            get
            {
                return (options & FileDialogNative.FOS.FOS_OVERWRITEPROMPT) != 0;
            }
            set
            {
                if (value)
                    options |= FileDialogNative.FOS.FOS_OVERWRITEPROMPT;
                else
                    options &= ~FileDialogNative.FOS.FOS_OVERWRITEPROMPT;
                dialog.SetOptions(options);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the dialog box restores the current directory before closing.
        /// </summary>
        public bool RestoreDirectory 
        {
            get
            {
                return (options & FileDialogNative.FOS.FOS_NOCHANGEDIR) != 0;
            }
            set
            {
                if (value)
                    options |= FileDialogNative.FOS.FOS_NOCHANGEDIR;
                else
                    options &= ~FileDialogNative.FOS.FOS_NOCHANGEDIR;
                dialog.SetOptions(options);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Help button is displayed in the file dialog box.
        /// </summary>
        public bool ShowHelp
        {
            get
            {
                return true;
            }
            set
            {
                // seems to be always true in case of the Common Item Dialog
            }
        }

        /// <summary>
        /// Gets or sets whether the dialog box supports displaying and saving files that have multiple file name extensions.
        /// </summary>
        public bool SupportMultiDottedExtensions
        {
            get
            {
                return true;
            }
            set
            {
                // seems to be always true in case of the Common Item Dialog
            }
        }

        /// <summary>
        /// Gets or sets the file dialog box title.
        /// </summary>
        public string Title 
        {
            get
            {
                return title;
            }
            set
            {
                dialog.SetTitle(value);
                title = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the dialog box accepts only valid Win32 file names.
        /// </summary>
        public bool ValidateNames
        {
            get
            {
                return true;
            }
            set
            {
                // seems to be always true in case of the Common Item Dialog
            }
        }

        /// <summary>
        /// Runs the dialog box with a default owner.
        /// </summary>
        public DialogResult ShowDialog()
        {
            return ShowDialog(null);
        }

        /// <summary>
        /// Runs the dialog box with the specified owner.
        /// </summary>
        public DialogResult ShowDialog(IWin32Window owner)
        {
            // Set event handler
            SaveFileDialogROEvents events = new SaveFileDialogROEvents();
            uint cookie;
            dialog.Advise(events, out cookie);

            // Show dialog
            int hr = dialog.Show(owner != null ? owner.Handle : IntPtr.Zero);

            // Remove event handler
            dialog.Unadvise(cookie);
            events.RestoreAttribute();      // needed in case of cancel

            // Convert return value to DialogResult
            return hr == S_OK ? DialogResult.OK : DialogResult.Cancel;
        }

        /// <summary>
        /// Event handler, which temporarily removes the read-only flag of selected files.
        /// </summary>
        class SaveFileDialogROEvents : FileDialogNative.IFileDialogEvents
        {
            FileInfo lastReadOnlyFile = null;

            public int OnFileOk(FileDialogNative.IFileDialog pfd)
            {
                // This method is not called in case of cancel
                RestoreAttribute();
                return S_OK;
            }

            public int OnFolderChanging(FileDialogNative.IFileDialog pfd, FileDialogNative.IShellItem psiFolder)
            {
                return S_OK;
            }

            public void OnFolderChange(FileDialogNative.IFileDialog pfd)
            {
                RestoreAttribute();
            }

            public void OnSelectionChange(FileDialogNative.IFileDialog pfd)
            {
                // Get selected file
                string name;
                try
                {
                    FileDialogNative.IShellItem item;
                    pfd.GetCurrentSelection(out item);
                    item.GetDisplayName(FileDialogNative.SIGDN.SIGDN_FILESYSPATH, out name);
                }
                catch (Exception)
                {
                    // No file selected yet
                    return;
                }

                // Has it changed?
                if (lastReadOnlyFile != null && lastReadOnlyFile.FullName == name)
                    return;

                // Restore read-only attribute of the previous file, if necessary
                RestoreAttribute();

                // Remove read-only attribute of the selected file, if necessary
                FileInfo f = new FileInfo(name);
                if (f.Exists && (f.Attributes & FileAttributes.ReadOnly) != 0)
                {
                    try
                    {
                        f.Attributes &= ~FileAttributes.ReadOnly;
                        lastReadOnlyFile = f;
                    }
                    catch (Exception)
                    {
                        // Not enough rights, nothing we can do
                        return;
                    }
                }
            }

            public void OnShareViolation(FileDialogNative.IFileDialog pfd, FileDialogNative.IShellItem psi, out FileDialogNative.FDE_SHAREVIOLATION_RESPONSE pResponse)
            {
                pResponse = FileDialogNative.FDE_SHAREVIOLATION_RESPONSE.FDESVR_DEFAULT;
            }

            public void OnTypeChange(FileDialogNative.IFileDialog pfd)
            {
            }

            public void OnOverwrite(FileDialogNative.IFileDialog pfd, FileDialogNative.IShellItem psi, out FileDialogNative.FDE_OVERWRITE_RESPONSE pResponse)
            {
                // Removing the read-only attribute in here, unfortunately does not work
                pResponse = FileDialogNative.FDE_OVERWRITE_RESPONSE.FDEOR_DEFAULT;
            }

            /// <summary>
            /// Restores the read-only attribute of the previously selected file.
            /// </summary>
            public void RestoreAttribute()
            {
                if (lastReadOnlyFile != null)
                {
                    lastReadOnlyFile.Attributes |= FileAttributes.ReadOnly;
                    lastReadOnlyFile = null;
                }
            }
        }

        [DllImport("shell32.dll")]
        private static extern int SHILCreateFromPath([MarshalAs(UnmanagedType.LPWStr)] string pszPath, out IntPtr ppIdl, ref uint rgflnOut);

        [DllImport("shell32.dll")]
        private static extern int SHCreateShellItem(IntPtr pidlParent, IntPtr psfParent, IntPtr pidl, out FileDialogNative.IShellItem ppsi);

        [DllImport("ole32.dll")]
        public static extern void CoTaskMemFree(IntPtr ptr);
    }
}

Вам также нужны эти привязки:

http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/fx/src/WinForms/Managed/System/WinForms/FileDialog_Vista_Interop@cs/1/FileDialog_Vista_Interop@cs

1 голос
/ 29 июля 2014

Вы можете обойти это, используя 'OpenFileDialog' вместо 'SaveFileDialog':

Если для свойства 'CheckFileExists' установлено значение 'False', оно должно действовать как диалог сохранения без проверки только для чтения.

1 голос
/ 13 июня 2010

Я какое-то время ткнул в это, структура OPENFILENAME имеет флаги для управления поведением только для чтения. Не повезло, они включены только для OpenFileDialog, а не для SaveFileDialog. Проверка только для чтения является сложной, ее нельзя обойти.

Кроме разочарований группы QA, я настоятельно рекомендую защищать содержимое файла с помощью обычных настроек безопасности файлов Windows, а не атрибута файла ReadOnly.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...