Возникла та же проблема при попытке выбрать файлы из рабочей области 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