Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / Microsoft / Win32 / FileDialog.cs / 1 / FileDialog.cs
//---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // FileDialog is an abstract class derived from CommonDialog // that implements shared functionality common to both File // Open and File Save common dialogs. It provides a hook // procedure that handles messages received while the dialog // is visible and numerous properties to control the appearance // and behavior of the dialog. // // The actual call to display the dialog to GetOpenFileName() // or GetSaveFileName() (both functions defined in commdlg.dll) // is implemented in a derived class's RunFileDialog method. // // // History: // t-benja 7/7/2005 Created // //--------------------------------------------------------------------------- namespace Microsoft.Win32 { using MS.Internal; using MS.Internal.PresentationFramework; using MS.Win32; using System; using System.ComponentModel; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Runtime.Remoting; using System.Windows; using CharBuffer = MS.Win32.NativeMethods.CharBuffer; ////// Provides a common base class for wrappers around both the /// File Open and File Save common dialog boxes. Derives from /// CommonDialog. /// /// This class is not intended to be derived from except by /// the OpenFileDialog and SaveFileDialog classes. /// public abstract class FileDialog : CommonDialog { //--------------------------------------------------- // // Constructors // //--------------------------------------------------- #region Constructors ////// In an inherited class, initializes a new instance of /// the System.Windows.FileDialog class. /// ////// Critical: Sets Dialog options, which are critical for set. /// TreatAsSafe: It is okay to set the options to their defaults. The /// ctor does not show the dialog. /// [SecurityCritical, SecurityTreatAsSafe] protected FileDialog() { // Call Initialize to set defaults for fields // and to set defaults for some option flags. // Initialize() is also called from the virtual // Reset() function to restore defaults. Initialize(); } #endregion Constructors //---------------------------------------------------- // // Public Methods // //--------------------------------------------------- #region Public Methods ////// Resets all properties to their default values. /// Classes derived from FileDialog are expected to /// call Base.Reset() at the beginning of their /// implementation of Reset() if they choose to /// override this function. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Sets Dialog options, which are critical for set. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// [SecurityCritical] public override void Reset() { SecurityHelper.DemandUnrestrictedFileIOPermission(); Initialize(); } ////// Returns a string representation of the file dialog with key information /// for debugging purposes. /// // We overload ToString() so that we can provide a useful representation of // this object for users' debugging purposes. It provides the full pathname for // any files selected. public override string ToString() { StringBuilder sb = new StringBuilder(base.ToString() + ": Title: " + Title + ", FileName: "); sb.Append(FileName); return sb.ToString(); // /* catch (System.Security.SecurityException e) { sb.Append("<"); // Coding guidelines require ToString to not throw sb.Append(e.GetType().FullName); // an exception; we catch SecurityException so we sb.Append(">"); // can show filenames in full trust and not fail in // reduced permissions scenarios. } */ } #endregion Public Methods //---------------------------------------------------- // // Public Properties // //---------------------------------------------------- #region Public Properties // // The behavior governed by this property depends // on whether CheckFileExists is set and whether the // filter contains a valid extension to use. For // details, see the ProcessFileNames function. // // It's worth noting that unlike most of these // properties, AddExtension is a custom flag that // is unique to our implementation. As such, it is // a constant value in our class, not stored in // NativeMethods like the other flags. ////// Gets or sets a value indicating whether the /// dialog box automatically adds an extension to a /// file name if the user omits the extension. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool AddExtension { get { return GetOption(OPTION_ADDEXTENSION); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(OPTION_ADDEXTENSION, value); } } // // OFN_FILEMUSTEXIST is only used for Open dialog // boxes, according to MSDN. It implies // OFN_PATHMUSTEXIST and "cannot be used" with a // Save As dialog box... in practice, it seems // to be ignored when used with Save As boxes ////// Gets or sets a value indicating whether /// the dialog box displays a warning if the /// user specifies a file name that does not exist. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public virtual bool CheckFileExists { get { return GetOption(NativeMethods.OFN_FILEMUSTEXIST); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_FILEMUSTEXIST, value); } } ////// Specifies that the user can type only valid paths and file names. If this flag is /// used and the user types an invalid path and file name in the File Name entry field, /// a warning is displayed in a message box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool CheckPathExists { get { return GetOption(NativeMethods.OFN_PATHMUSTEXIST); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_PATHMUSTEXIST, value); } } ////// The AddExtension property attempts to determine the appropriate extension /// by using the selected filter. The DefaultExt property serves as a fallback - /// if the extension cannot be determined from the filter, DefaultExt will /// be used instead. /// public string DefaultExt { get { // For string properties, it's important to not return null, as an empty // string tends to make more sense to beginning developers. return _defaultExtension == null ? String.Empty : _defaultExtension; } set { if (value != null) { // Use Ordinal here as per FxCop CA1307 if (value.StartsWith(".", StringComparison.Ordinal)) // Allow calling code to provide // extensions like ".ext" - { value = value.Substring(1); // but strip out the period to leave only "ext" } else if (value.Length == 0) // Normalize empty strings to null. { value = null; } } _defaultExtension = value; } } // The actual flag is OFN_NODEREFERENCELINKS (set = do not dereference, unset = deref) - // while we have true = dereference and false=do not dereference. Because we expose // the opposite of the Windows flag as a property to be clearer, we need to negate // the value in both the getter and the setter here. ////// Gets or sets a value indicating whether the dialog box returns the location /// of the file referenced by the shortcut or whether it returns the location /// of the shortcut (.lnk). /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool DereferenceLinks { get { return !GetOption(NativeMethods.OFN_NODEREFERENCELINKS); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_NODEREFERENCELINKS, !value); } } ////// Gets a string containing the filename component of the /// file selected in the dialog box. /// /// Example: if FileName = "c:\windows\explorer.exe" , /// SafeFileName = "explorer.exe" /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Scrubs paths from the file name. /// public string SafeFileName { [SecurityCritical] get { // Use the FileName property to avoid directly accessing // the _fileNames field, then call Path.GetFileName // to do the actual work of stripping out the file name // from the path. string safeFN = Path.GetFileName(CriticalFileName); // Check to make sure Path.GetFileName does not return null. // If it does, set safeFN to String.Empty instead to accomodate // programmers that fail to check for null when reading strings. if (safeFN == null) { safeFN = String.Empty; } return safeFN; } } ////// Gets a string array containing the filename of each file selected /// in the dialog box. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Scrubs paths from the file names. /// public string[] SafeFileNames { [SecurityCritical] get { // Retrieve the existing filenames into an array, then make // another array of the same length to hold the safe version. string[] unsafeFileNames = FileNamesInternal; string[] safeFileNames = new string[unsafeFileNames.Length]; for (int i = 0; i < unsafeFileNames.Length; i++) { // Call Path.GetFileName to retrieve only the filename // component of the current full path. safeFileNames[i] = Path.GetFileName(unsafeFileNames[i]); // Check to make sure Path.GetFileName does not return null. // If it does, set this filename to String.Empty instead to accomodate // programmers that fail to check for null when reading strings. if (safeFileNames[i] == null) { safeFileNames[i] = String.Empty; } } return safeFileNames; } } // If multiple files are selected, we only return the first filename. ////// Gets or sets a string containing the full path of the file selected in /// the file dialog box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string FileName { [SecurityCritical] get { SecurityHelper.DemandUnrestrictedFileIOPermission(); return CriticalFileName; } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); // Allow users to set a filename to stored in _fileNames. // If null is passed in, we clear the entire list. // If we get a string, we clear the entire list and make a new one-element // array with the new string. if (value == null) { _fileNames = null; } else { // _fileNames = new string[] { value }; } } } ////// Gets the file names of all selected files in the dialog box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string[] FileNames { [SecurityCritical] get { SecurityHelper.DemandUnrestrictedFileIOPermission(); // FileNamesInternal is a property we use to clone // the string array before returning it. string[] files = FileNamesInternal; return files; } } // The filter string also controls how the AddExtension feature behaves. For // details, see the ProcessFileNames method. ////// 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 at the bottom of the dialog box. /// /// This is an example filter string: /// Filter = "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*" /// ////// Thrown in the setter if the new filter string does not have an even number of tokens /// separated by the vertical bar character '|' (that is, the new filter string is invalid.) /// ////// If DereferenceLinks is true and the filter string is null, a blank /// filter string (equivalent to "|*.*") will be automatically substituted to work /// around the issue documented in Knowledge Base article 831559 /// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// public string Filter { get { // For string properties, it's important to not return null, as an empty // string tends to make more sense to beginning developers. return _filter == null ? String.Empty : _filter; } set { if (String.CompareOrdinal(value,_filter) != 0) // different filter than what we have stored already { string updatedFilter = value; if (!String.IsNullOrEmpty(updatedFilter)) { // Require the number of segments of the filter string to be even - // in other words, there must only be matched pairs of description and // file extensions. // // This implicitly requires there to be at least one vertical bar in // the filter string - or else formats.Length will be 1, resulting in an // ArgumentException. string[] formats = updatedFilter.Split('|'); if (formats.Length % 2 != 0) { throw new ArgumentException(SR.Get(SRID.FileDialogInvalidFilter)); } } else { // catch cases like null or "" where the filter string is not invalid but // also not substantive. We set value to null so that the assignment // below picks up null as the new value of _filter. updatedFilter = null; } _filter = updatedFilter; } } } // Using 1 as the index of the first filter entry is counterintuitive for C#/C++ // developers, but is a side effect of a Win32 feature that allows you to add a template // filter string that is filled in when the user selects a file for future uses of the dialog. // We don't support that feature, so only values >1 are valid. // // For details, see MSDN docs for OPENFILENAME Structure, nFilterIndex ////// Gets or sets the index of the filter currently selected in the file dialog box. /// /// NOTE: The index of the first filter entry is 1, not 0. /// public int FilterIndex { get { return _filterIndex; } set { _filterIndex = value; } } ////// Gets or sets the initial directory displayed by the file dialog box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Don't want to allow setting of the initial directory in Partial Trust. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string InitialDirectory { get { // Avoid returning a null string - return String.Empty instead. return _initialDirectory.Value == null ? String.Empty : _initialDirectory.Value; } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); _initialDirectory.Value = value; } } ////// Restores the current directory to its original value if the user /// changed the directory while searching for files. /// /// This property is only valid for SaveFileDialog; it has no effect /// when set on an OpenFileDialog. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool RestoreDirectory { get { return GetOption(NativeMethods.OFN_NOCHANGEDIR); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_NOCHANGEDIR, value); } } ////// Gets or sets a string shown in the title bar of the file dialog. /// If this property is null, a localized default from the operating /// system itself will be used (typically something like "Save As" or "Open") /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Do not want to allow setting the FileDialog title from a Partial Trust application. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string Title { get { // Avoid returning a null string - return String.Empty instead. return _title.Value == null ? String.Empty : _title.Value; } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); _title.Value = value; } } // If false, the file dialog boxes will allow invalid characters in the returned file name. // We are actually responsible for dealing with this flag - it determines whether all of the // processing in ProcessFileNames (which includes things such as the AddExtension feature) // occurs. ////// Gets or sets a value indicating whether the dialog box accepts only valid /// Win32 file names. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool ValidateNames { get { return !GetOption(NativeMethods.OFN_NOVALIDATE); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_NOVALIDATE, !value); } } #endregion Public Properties //--------------------------------------------------- // // Public Events // //---------------------------------------------------- #region Public Events ////// Occurs when the user clicks on the Open or Save button on a file dialog /// box. /// // We fire this event from DoFileOk. public event CancelEventHandler FileOk; #endregion Public Events //--------------------------------------------------- // // Protected Methods // //--------------------------------------------------- #region Protected Methods ////// Defines the common dialog box hook procedure that is overridden to add /// specific functionality to the file dialog box. /// ////// Critical: due to calls to GetParent and PtrToStructure in UnsafeNativeMethods /// as well as a call to DoFileOk, which is SecurityCritical. /// [SecurityCritical] protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) { // Assume we are successful unless we encounter a problem. IntPtr returnValue = IntPtr.Zero; // Our File Dialogs are Explorer-style dialogs with hook procedure enabled // (OFN_ENABLEHOOK | OFN_EXPLORER). As such, we will get the following // messages: (as per MSDN) // // WM_INITDIALOG // WM_NOTIFY (indicating actions taken by the user or other dialog box events) // Messages for any additional controls defined by specifying a child dialog template // // We only need to handle WM_NOTIFY in this hook procedure. if (msg == NativeMethods.WM_NOTIFY) { // Our hookproc is actually the hook procedure for a child template hosted // inside the actual file dialog box. We want the hwnd of the actual dialog, // so we call GetParent on the hwnd passed to the hookproc. _hwndFileDialog = UnsafeNativeMethods.GetParent(new HandleRef(this, hwnd)); // When we receive WM_NOTIFY, lParam is a pointer to an OFNOTIFY // structure that defines the action. OFNOTIFY is a structure // specific to file open and save dialogs with three members: // (defined in Commdlg.h - see MSDN for more details) // // struct _OFNOTIFY { // NMHDR hdr; // this is a by-value structure; // // the implementation in UnsafeNativeMethods breaks it into // // hdr_hwndFrom (HWND, handle to control sending message), // // hdr_idFrom (UINT, ID of control sending message) and // // hdr_code (UINT, one of the CDN_??? notification constants) // // LPOPENFILENAME lpOFN; // pointer to the OPENFILENAME structure we created in // // RunFileDialog when showing this dialog box. // // LPTSTR pszFile; // if a network sharing violation has occurred, this // // is the name of the file affected. Only valid with // // hdr_code = CDN_SHAREVIOLATION. // } // // Convert the pointer to our OFNOTIFY stored in lparam to an object using PtrToStructure. NativeMethods.OFNOTIFY notify = (NativeMethods.OFNOTIFY)UnsafeNativeMethods.PtrToStructure(lParam, typeof(NativeMethods.OFNOTIFY)); // WM_NOTIFY indicates that the dialog is sending us a notification message. // notify.hdr_code is an int defining which notification is being received. // These codes are integer constants defined originally in commdlg.h. switch (notify.hdr_code) { case NativeMethods.CDN_INITDONE: // CDN_INITDONE is sent by Explorer-style file dialogs when the // system has finished arranging the controls in the dialog box. // // We use this opportunity to move the dialog box to the center // of the appropriate monitor. // // As an aside, this only seems to work the first time we show // a dialog - after that, Windows remembers the position of the // dialog. But that's the Winforms behavior too, so it's fine. MoveToScreenCenter(new HandleRef(this, _hwndFileDialog), new HandleRef(this, OwnerWindowHandle)); break; case NativeMethods.CDN_SELCHANGE: // CDN_SELCHANGE is sent by Explorer-style file dialogs when the // selection changes in the list box that displays the contents // of the currently opened folder or directory. // // When we get this message, we check to make sure our character // buffer is big enough to hold all of the filenames that have // been selected. If it isn't, we create a new, bigger buffer // and substitute it in the OPENFILENAME structure. // Retrieve the OPENFILENAME structure from the OFNOTIFY structure // so we can access the CharBuffer inside it. NativeMethods.OPENFILENAME_I ofn = (NativeMethods.OPENFILENAME_I) UnsafeNativeMethods.PtrToStructure(notify.lpOFN, typeof(NativeMethods.OPENFILENAME_I)); // Get the buffer size required to store the selected file names. // We would like to accomplish this by sending a CDM_GETFILEPATH message // - to which the file dialog responds with the number of unicode // characters needed to store the file names and paths. // // Windows Forms used CDM_GETSPEC here, but that only retrieves the length // of the filenames - not of the complete path. So in cases with network // shortcuts and dereference links enabled, we end up with not enough buffer // and an FNERR_BUFFERTOOSMALL error. // // Unfortunately, CDM_GETFILEPATH returns -1 when a bunch of files are // selected, so changing to it actually makes things worse with very large // cases. So we'll stick with CDM_GETSPEC plus extra buffer space. // int sizeNeeded = (int)UnsafeNativeMethods.UnsafeSendMessage(_hwndFileDialog, // hWnd of window to receive message NativeMethods.CDM_GETSPEC, // Msg (message to send) IntPtr.Zero, // wParam (additional info) IntPtr.Zero); // lParam (additional info) if (sizeNeeded > ofn.nMaxFile) { // A bigger buffer is required, so we'll allocate a new // CharBuffer and substitute it for the existing one. //try //{ // Make the new buffer equal to the size the dialog told us we needed // plus a reasonable growth factor. int newBufferSize = sizeNeeded + (FILEBUFSIZE / 4); // Allocate a new CharBuffer in the appropriate size. CharBuffer charBufferTmp = CharBuffer.CreateBuffer(newBufferSize); // Allocate unmanaged memory for the buffer and store the pointer. IntPtr newBuffer = charBufferTmp.AllocCoTaskMem(); // Free the old, smaller buffer stored in ofn.lpstrFile Marshal.FreeCoTaskMem(ofn.lpstrFile); // Substitute buffer and update the buffer maximum size in // the dialog. ofn.lpstrFile = newBuffer; ofn.nMaxFile = newBufferSize; // Store the reference to the character buffer inside our // class so we can free it when we're done. this._charBuffer = charBufferTmp; // Marshal the OPENFILENAME structure back into the // OFNOTIFY structure, then marshal the OFNOTIFY structure // back into lparam to update the dialog. Marshal.StructureToPtr(ofn, notify.lpOFN, true); Marshal.StructureToPtr(notify, lParam, true); // } // Windows Forms had a catch-all exception handler here // but no justification for why it existed. If exceptions // are thrown when we grow the buffer, re-add this catch // and perform handling specific to the exception you are seeing. // // I don't see anywhere an exception would be thrown that // we would want to simply discard in this try block, so // we'll remove this catch and let any exceptions through. // // catch (Exception) // { // intentionally not throwing here. // } } break; case NativeMethods.CDN_SHAREVIOLATION: // CDN_SHAREVIOLATION is sent by Explorer-style boxes when OK is clicked // and a network sharing violation occurs for the selected file. // Network sharing violation is a bit misleading of a term - it could // also mean the user doesn't have permissions for the file, or it could mean // the file is already opened by another process on the same machine. // // We process this message because of some odd behavior seen when a file // is locked for writing. (for details, see VS Whidbey 95342) // // We get this notification followed by *two* CDN_FILEOK notifications... but only // if the path is entered in the textbox and not selected from the folder view. // // If we get a CDN_SHAREVIOLATION, we'll set a flag and a counter so we can track // which CDN_FILEOK notification we're on to avoid showing two message boxes. this._ignoreSecondFileOkNotification = true; // We want to ignore the second CDN_FILEOK this._fileOkNotificationCount = 0; // to avoid a second prompt by PromptFileOverwrite. break; case NativeMethods.CDN_FILEOK: // CDN_FILEOK is sent when the user specifies a filename and clicks OK. // We need to process the files selected and make sure everything's acceptable. // If it's all OK, we don't need to do anything. // // To tell the dialogs to stay open after we receive a CDN_FILEOK, we must both // return a non-zero value from this hook procedure and call SetWindowLong to // set a nonzero value for DWL_MSGRESULT. // --- Begin VS Whidbey 95342 Workaround --- // See the CDN_SHAREVIOLATION case above for background info about this issue. if (this._ignoreSecondFileOkNotification) { // We got a CDN_SHAREVIOLATION notification and want to ignore the second CDN_FILEOK notification. // We'll allow the first one through and block the second. // Recall that we initialize _fileOkNotificationCount to 0 when we get the CDN_SHAREVIOLATION. if (this._fileOkNotificationCount == 0) { // This is the first CDN_FILEOK, record that we received // it and then allow DoFileOk to be called. this._fileOkNotificationCount = 1; } else { // This is the second CDN_FILEOK, so we want to ignore it. this._ignoreSecondFileOkNotification = false; // Call SetWindowLong to set the DWL_MSGRESULT value of the file dialog window // to a non-zero number to tell the dialog to stay open. // NativeMethods.InvalidIntPtr is defined as -1. UnsafeNativeMethods.CriticalSetWindowLong(new HandleRef(this, hwnd), // hWnd (which window are we affecting) NativeMethods.DWL_MSGRESULT, // nIndex (which value are we setting) NativeMethods.InvalidIntPtr); // dwNewLong (what is the new value) // We also need to return a non-zero value to tell the dialog to stay open. returnValue = NativeMethods.InvalidIntPtr; break; } } // --- End VS Whidbey 95342 Workaround --- // Call DoFileOk to check if the files that have been selected // are acceptable. (See DoFileOk for details.) // // If it returns false, we must notify the dialog box that it // needs to stay open for further input. if (!DoFileOk(notify.lpOFN)) { // Call SetWindowLong to set the DWL_MSGRESULT value of the file dialog window // to a non-zero number to tell the dialog to stay open. // NativeMethods.InvalidIntPtr is defined as -1. UnsafeNativeMethods.CriticalSetWindowLong(new HandleRef(this, hwnd), // hWnd (which window are we affecting) NativeMethods.DWL_MSGRESULT, // nIndex (which value are we setting) NativeMethods.InvalidIntPtr); // dwNewLong (what is the new value) // We also need to return a non-zero value to tell the dialog to stay open. returnValue = NativeMethods.InvalidIntPtr; break; } break; } } // Return IntPtr.Zero to indicate success, unless we have // adjusted the return value elsewhere in the function. return returnValue; } ////// Raises the System.Windows.FileDialog.FileOk event. /// protected void OnFileOk(CancelEventArgs e) { if (FileOk != null) { FileOk(this, e); } } // Because this class, FileDialog, is the parent class for both OpenFileDialog // and SaveFileDialog, this function will perform the common setup tasks // shared between Open and Save, and will then call RunFileDialog, which is // overridden in both of the derived classes to show the correct dialog. // ////// Performs initialization work in preparation for calling RunFileDialog /// to show a file open or save dialog box. /// ////// Critical: Calls UnsafeNativeMethods.SetWindowPos() accesses SecurityCritical data /// _charBuffer. /// [SecurityCritical] protected override bool RunDialog(IntPtr hwndOwner) { // Once we run the dialog, all of our communication with it is handled // by processing WM_NOTIFY messages in our hook procedure, this.HookProc. // NativeMethods.WndProc is a delegate with the appropriate signature // needed for a Win32 window hook procedure. NativeMethods.WndProc hookProcPtr = new NativeMethods.WndProc(this.HookProc); // Create a new OPENFILENAME structure. OPENFILENAME is a structure defined // in Win32's commdlg.h that contains most of the information needed to // successfully display a file dialog box. // NOTE: Despite the name, OPENFILENAME is the proper structure for both // file open and file save dialogs. NativeMethods.OPENFILENAME_I ofn = new NativeMethods.OPENFILENAME_I(); // do everything in a try block, so we always free memory in the finalizer try { // Create an appropriately sized buffer to hold the filenames. // The buffer's initial size is controlled by the FILEBUFSIZE constant, // an arbitrary value chosen so that we will rarely have to grow the buffer. _charBuffer = CharBuffer.CreateBuffer(FILEBUFSIZE); // If we have a filename stored in our internal array _fileNames, // place it in the buffer as a default filename. if (_fileNames != null) { _charBuffer.PutString(_fileNames[0]); } // --- Set up the OPENFILENAME structure --- // lStructSize // Specifies the length, in bytes, of the structure. ofn.lStructSize = Marshal.SizeOf(typeof(NativeMethods.OPENFILENAME_I)); // hwndOwner // Handle to the window that owns the dialog box. This member can be any // valid window handle, or it can be NULL if the dialog box has no owner. ofn.hwndOwner = hwndOwner; // hInstance // This property is ignored unless OFN_ENABLETEMPLATEHANDLE or // OFN_ENABLETEMPLATE are set. Since we do not set either, // hInstance is ignored, so we can set it to zero. ofn.hInstance = IntPtr.Zero; // lpstrFilter // Pointer to a buffer containing pairs of null-terminated filter strings. // The last string in the buffer must be terminated by two NULL characters. // Since our filter strings are stored terminated by vertical bar '|' chars, // we call MakeFilterString to reformat and validate the filter string. ofn.lpstrFilter = MakeFilterString(_filter, this.DereferenceLinks); // nFilterIndex // Specifies the index of the currently selected filter in the File Types // control. Note that since 0 is reserved for a custom filter (which we // do not support), our valid filter indexes begin at 1. ofn.nFilterIndex = _filterIndex; // lpstrFile // Pointer to a buffer used to store filenames. When initializing the // dialog, this name is used as an initial value in the File Name edit // control. When files are selected and the function returns, the buffer // contains the full path to every file selected. ofn.lpstrFile = _charBuffer.AllocCoTaskMem(); // nMaxFile // Size of the lpstrFile buffer in number of Unicode characters. ofn.nMaxFile = _charBuffer.Length; // lpstrInitialDir // Pointer to a null terminated string that can specify the initial directory. // A relatively complex algorithm is used to determine which directory is // actually used as the initial directory - for details, see MSDN for the // OPENFILENAME structure. ofn.lpstrInitialDir = _initialDirectory.Value; // lpstrTitle // Pointer to a string to be placed in the title bar of the dialog box. // NULL causes the title bar to display the operating system default string. ofn.lpstrTitle = _title.Value; // Flags // A set of bit flags you can use to initialize the dialog box. // Most of these will be set through public properties that then call // GetOption or SetOption. We retrieve the flags using the Options property // and then add three additional flags here: // // OFN_EXPLORER // display an Explorer-style box (newer style) // OFN_ENABLEHOOK // enable the hook procedure (important for much of our functionality) // OFN_ENABLESIZING // allow the user to resize the dialog box // ofn.Flags = Options | (NativeMethods.OFN_EXPLORER | NativeMethods.OFN_ENABLEHOOK | NativeMethods.OFN_ENABLESIZING); // lpfnHook // Pointer to the hook procedure. // Ignored unless OFN_ENABLEHOOK is set in Flags. ofn.lpfnHook = hookProcPtr; // FlagsEx // Can be either zero or OFN_EX_NOPLACESBAR, depending on whether // the Places Bar (My Computer/Favorites/etc) should be shown on the // left side of the file dialog. ofn.FlagsEx = NativeMethods.OFN_USESHELLITEM; // lpstrDefExt // Pointer to a buffer that contains the default extension; it will // be appended to filenames if the user does not type an extension. // Only the first three characters are appended by Windows. If this // is NULL, no extension is appended. if (_defaultExtension != null && AddExtension) { ofn.lpstrDefExt = _defaultExtension; } // Call into either OpenFileDialog or SaveFileDialog to show the // actual dialog box. This call blocks until the dialog is closed; // while dialog is open, all interaction is through HookProc. return RunFileDialog(ofn); } finally { // Explicitly set the character buffer to null. _charBuffer = null; // If there is still a pointer to a memory location in // ofn.lpstrFile, we explicitly free that memory here. if (ofn.lpstrFile != IntPtr.Zero) { Marshal.FreeCoTaskMem(ofn.lpstrFile); } } } #endregion Protected Methods //--------------------------------------------------- // // Internal Methods // //---------------------------------------------------- #region Internal Methods ////// Returns the state of the given options flag. /// internal bool GetOption(int option) { return (_dialogOptions.Value & option) != 0; } ////// Sets the given option to the given boolean value. /// ////// Critical: Setting a SecurityCriticalDataForSet member (_dialogOptions). /// [SecurityCritical] internal void SetOption(int option, bool value) { if (value) { // if value is true, bitwise OR the option with _dialogOptions _dialogOptions.Value |= option; } else { // if value is false, AND the bitwise complement of the // option with _dialogOptions _dialogOptions.Value &= ~option; } } ////// Prompts the user with a System.Windows.MessageBox /// with the given parameters. It also ensures that /// the focus is set back on the window that had /// the focus to begin with (before we displayed /// the MessageBox). /// /// Returns the choice the user made in the message box /// (true if MessageBoxResult.Yes, /// false if OK or MessageBoxResult.No) /// /// We have to do this instead of just calling MessageBox because /// of an issue where keyboard navigation would fail after showing /// a message box. See http://bugcheck/default.asp?URL=/Bugs/URT/84016.asp /// (WinForms ASURT 80262) /// ////// Critical: We call GetFocus() and SetFocus() in /// UnsafeNativeMethods, which are marked SupressUnmanagedCodeSecurity. /// [SecurityCritical] internal bool MessageBoxWithFocusRestore(string message, MessageBoxButton buttons, MessageBoxImage image) { bool ret = false; // Get the window that currently has focus and temporarily cache a handle to it IntPtr focusHandle = UnsafeNativeMethods.GetFocus(); try { // Show the message box and compare the return value to MessageBoxResult.Yes to get the // actual return value. ret = (MessageBox.Show(message, DialogCaption, buttons, image, MessageBoxResult.OK /*default button is OK*/, 0) == MessageBoxResult.Yes); } finally { // Return focus to the window that had focus before we showed the messagebox. // SetFocus can handle improper hwnd values, including null. UnsafeNativeMethods.SetFocus(new HandleRef(this, focusHandle)); } return ret; } ////// PromptUserIfAppropriate is a virtual function that shows any prompt /// message boxes (like "Do you want to overwrite this file") necessary after /// the Open button is pressed in a file dialog. /// /// Return value is false if we showed a dialog box and true if we did not. /// (in other words, true if it's OK to continue with the open process and /// false if we need to return the user to the dialog to make another selection.) /// ////// SaveFileDialog overrides this method to add additional message boxes for /// its unique properties. /// /// For FileDialog: /// If OFN_FILEMUSTEXIST is set, we check to be sure the path passed in on the /// fileName parameter exists as an actual file on the hard disk. If so, we /// call PromptFileNotFound to inform the user that they must select an actual /// file that already exists. /// ////// Critical: due to call to PromptFileNotFound, which displays a message box with focus restore. /// Asserts FileIOPermission in order to determine whether the file exists. /// [SecurityCritical] internal virtual bool PromptUserIfAppropriate(string fileName) { bool fileExists = true; // The only option we deal with in this implementation of // PromptUserIfAppropriate is OFN_FILEMUSTEXIST. if (GetOption(NativeMethods.OFN_FILEMUSTEXIST)) { // File.Exists requires a full path, so we call GetFullPath on // the filename before checking if it exists. (new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, fileName)).Assert(); try { string tempPath = Path.GetFullPath(fileName); fileExists = File.Exists(tempPath); } finally { CodeAccessPermission.RevertAssert(); } if (!fileExists) { // file does not exist, we can't continue // and must display an error // Display the message box PromptFileNotFound(fileName); } } return fileExists; } ////// Implements the actual call to GetOpenFileName or GetSaveFileName. /// internal abstract bool RunFileDialog(NativeMethods.OPENFILENAME_I ofn); #endregion Internal Methods //--------------------------------------------------- // // Internal Properties // //---------------------------------------------------- #region Internal Properties ////// In cases where we need to return an array of strings, we return /// a clone of the array. We also need to make sure we return a /// string[0] instead of a null if we don't have any filenames. /// ////// Critical: Accesses _fileNames, which is SecurityCritical. /// internal string[] FileNamesInternal { [SecurityCritical] get { if (_fileNames == null) { return new string[0]; } else { return (string[])_fileNames.Clone(); } } } #endregion Internal Properties //---------------------------------------------------- // // Internal Events // //--------------------------------------------------- //#region Internal Events //#endregion Internal Events //---------------------------------------------------- // // Private Methods // //--------------------------------------------------- #region Private Methods ////// Processes the CDN_FILEOK notification, which is sent by an /// Explorer-style Open or Save As dialog box when the user specifies /// a file name and clicks the OK button. /// ////// true if the dialog can close, or false if we need to return to /// the dialog for additional input. /// ////// Critical due to call access to _charBuffer, _fileNames and _dialogOptions. /// [SecurityCritical] private bool DoFileOk(IntPtr lpOFN) { NativeMethods.OPENFILENAME_I ofn = (NativeMethods.OPENFILENAME_I)UnsafeNativeMethods.PtrToStructure(lpOFN, typeof(NativeMethods.OPENFILENAME_I)); // While processing the results we get from the OPENFILENAME struct, // we will adjust several properties of our own class to reflect the // new data. In case we discover we need to send the user back to // the dialog for further input, we need to be able to revert these // changes - so we backup _dialogOptions, _filterIndex and _fileNames. // // We only assign brand new string arrays to _FileNames, so it's OK // to back up by reference here. int saveOptions = _dialogOptions.Value; int saveFilterIndex = _filterIndex; string[] saveFileNames = _fileNames; // ok is a flag to determine whether we need to show the dialog // again (false) or if we're satisfied with the results we received (true). bool ok = false; try { // Replace the ReadOnly flag in DialogOptions with the ReadOnly flag // from the OPENFILEDIALOG structure - that is, store the user's // choice from the Read Only checkbox so our property is up to date. _dialogOptions.Value = _dialogOptions.Value & ~NativeMethods.OFN_READONLY | ofn.Flags & NativeMethods.OFN_READONLY; // Similarly, update the filterIndex to reflect the selected filter. _filterIndex = ofn.nFilterIndex; // Ask the character buffer to copy the memory from the location // referenced by lpstrFile into our internal character buffer. _charBuffer.PutCoTaskMem(ofn.lpstrFile); if (!GetOption(NativeMethods.OFN_ALLOWMULTISELECT)) { // Since we're selecting a single file, make a string // array with a single element containing the entire contents // of the character buffer. _fileNames = new string[] { _charBuffer.GetString() }; } else { // Multiselect is a bit more complex - call GetMultiselectFiles // to handle that case. _fileNames = GetMultiselectFiles(_charBuffer); } // Call ProcessFileNames() to do validation and post-processing // tasks (see that function for details; it checks if files exist, // prompts users with message boxes if invalid selections are made, etc.) if (ProcessFileNames()) { // ProcessFileNames returned true, so it's OK to fire the // OnFileOk event. CancelEventArgs ceevent = new CancelEventArgs(); OnFileOk(ceevent); // We allow our calling code to do even more post-processing // through the OnFileOk event - and therefore offer them the // opportunity to redisplay the dialog for additional input // using the event arguments if their validation failed. // // If OnFileOk is not handled, ceevent.Cancel will be false. ok = !ceevent.Cancel; } } finally { // No matter what happened, we need to restore dialog state // if the result was not ok=true. if (!ok) { _dialogOptions.Value = saveOptions; _filterIndex = saveFilterIndex; _fileNames = saveFileNames; } } return ok; } ////// Extracts the filename(s) returned by the file dialog. /// /// Marked static for perf reasons because this function doesn't /// actually access any instance data as per FxCop CA1822. private static string[] GetMultiselectFiles(CharBuffer charBuffer) { // Iff OFN_ALLOWMULTISELECT is set for an Explorer-style dialog box // and the user selects multiple files, lpstrFile points to a string // containing the current directory, followed by a NULL, followed by // two or more filenames that are NULL separated, with an extra NULL // character after the last filename. // // We'll use the GetString() function of the character buffer to get // two of these null-terminated chunks at a time, one into directory // and one into filename. string directory = charBuffer.GetString(); string fileName = charBuffer.GetString(); // If OFN_ALLOWMULTISELECT is enabled but the user selects only // one file, we get the filename and path concatenated together without // a null separator. This will cause our directory variable to // contain the full path and fileName to be empty, so make a new // string array with the contents of directory as its single element. // if (fileName.Length == 0) { return new string[] { directory }; } // If the directory was provided without a directory separator // character (typically '\' on Windows) at the end, we add it. if (!directory.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { directory = directory + Path.DirectorySeparatorChar; } // Create a generic list of strings to hold the names. Listnames = new List (); do { // With DereferenceLinks enabled, we can sometimes end // up with full paths provided as filenames. We need // to check for two cases here - the case where the // filename begins with '\', indicating a UNC share path, // or the case where we have a full hard disk path // (e.g. C:\file.txt), where [1] will be the : volume // separator and [2] will be the \ directory separator. bool isUncPath = (fileName[0] == Path.DirectorySeparatorChar && fileName[1] == Path.DirectorySeparatorChar); bool isFullPath = (fileName.Length > 3 && fileName[1] == Path.VolumeSeparatorChar && fileName[2] == Path.DirectorySeparatorChar ); if (!(isUncPath || isFullPath)) { // filename is not a full path, so we need to // add on the directory fileName = directory + fileName; } names.Add(fileName); // Get the next filename fileName = charBuffer.GetString(); } while (!String.IsNullOrEmpty(fileName)); return names.ToArray(); } // Provides the actual implementation of initialization tasks. // Initialize() is called from both the constructor and the // public Reset() function to set default values for member // variables and for the options bitmask. /// /// Critical: Sets Dialog options, which are critical for set. /// [SecurityCritical] private void Initialize() { // // Initialize Options Flags // _dialogOptions.Value = 0; // _dialogOptions is an int containing a set of // bit flags used to initialize the dialog box. // It is placed directly into the OPENFILEDIALOG // struct used to instantiate the file dialog box. // Within our code, we only use GetOption and SetOption // (change from Windows Forms, which sometimes directly // modified _dialogOptions). As such, we initialize to 0 // here and then call SetOption to get _dialogOptions // into the default state. // // Set some default options // // - Hide the Read Only check box. SetOption(NativeMethods.OFN_HIDEREADONLY, true); // - Specifies that the user can type only valid paths and file names. If this flag is // used and the user types an invalid path and file name in the File Name entry field, // we will display a warning in a message box. SetOption(NativeMethods.OFN_PATHMUSTEXIST, true); // - This is our own flag, not a standard one defined in OPENFILEDIALOG. We use this to // indicate to ourselves that we should add the default extension automatically if the // user does not enter it in themselves in ProcessFileNames. (See that function for // details.) SetOption(OPTION_ADDEXTENSION, true); // // Initialize additional properties // _title.Value = null; _initialDirectory.Value = null; _defaultExtension = null; _fileNames = null; _filter = null; _filterIndex = 1; // The index of the first filter entry is 1, not 0. // 0 is reserved for the custom filter functionality // provided by Windows, which we do not expose to the user. // Variables used for bug workaround: // When the selected file is locked for writing, we get a sharing violation notification // followed by *two* CDN_FILEOK notifications. These flags are used to track the multiple // notifications so we only show one error message box to the user. // For a more complete explanation and PS bug information, see HookProc. _ignoreSecondFileOkNotification = false; _fileOkNotificationCount = 0; } ////// Converts the given filter string to the format required in an OPENFILENAME_I /// structure. /// private static string MakeFilterString(string s, bool dereferenceLinks) { if (String.IsNullOrEmpty(s)) { // Workaround for VSWhidbey bug #95338 (carried over from Winforms implementation) // Apparently, when filter is null, the common dialogs in Windows XP will not dereference // links properly. The work around is to provide a default filter; " |*.*" is used to // avoid localization issues from description text. // // This behavior is now documented in MSDN on the OPENFILENAME structure, so I don't // expect it to change anytime soon. if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5) { s = " |*.*"; } else { // Even if we don't need the bug workaround, change empty // strings into null strings. return null; } } StringBuilder nullSeparatedFilter = new StringBuilder(s); // Replace the vertical bar with a null to conform to the Windows // filter string format requirements nullSeparatedFilter.Replace('|', '\0'); // Append two nulls at the end nullSeparatedFilter.Append('\0'); nullSeparatedFilter.Append('\0'); // Return the results as a string. return nullSeparatedFilter.ToString(); } ////// Handle the AddExtension property on newly acquired filenames, then /// call PromptUserIfAppropriate to display any necessary message boxes. /// /// Returns false if we need to redisplay the dialog and true otherwise. /// ////// Critical: due to call to PromptUserIfAppropriate, which displays /// message boxes with focus restore. /// TreatAsSafe: This method does not take external input for the call for /// PromptUserIfAppropriate. /// [SecurityCritical, SecurityTreatAsSafe] private bool ProcessFileNames() { // Only process the filenames if OFN_NOVALIDATE is not set. if (!GetOption(NativeMethods.OFN_NOVALIDATE)) { // Call the FilterExtensions private property to get // a list of valid extensions from the filter(s). // The first extension from FilterExtensions is the // default extension. string[] extensions = GetFilterExtensions(); // For each filename: // - Process AddExtension // - Call PromptUserIfAppropriate to display necessary dialog boxes. for (int i = 0; i < _fileNames.Length; i++) { string fileName = _fileNames[i]; // If AddExtension is enabled and we do not already have an extension: if (AddExtension && !Path.HasExtension(fileName)) { // Loop through all extensions, starting with the default extension for (int j = 0; j < extensions.Length; j++) { // Assert for a valid extension Invariant.Assert(!extensions[j].StartsWith(".", StringComparison.Ordinal), "FileDialog.GetFilterExtensions should not return things starting with '.'"); string currentExtension = Path.GetExtension(fileName); // Assert to make sure Path.GetExtension behaves as we think it should, returning // "" if the string is empty and something beginnign with . otherwise. // Use StringComparison.Ordinal as per FxCop CA1307 and CA130. Invariant.Assert(currentExtension.Length == 0 || currentExtension.StartsWith(".", StringComparison.Ordinal), "Path.GetExtension should return something that starts with '.'"); // Because we check Path.HasExtension above, files should // theoretically not have extensions at this stage - but // we'll go ahead and remove an existing extension if it // somehow slipped through. // // Strip out any extension that may be remaining and place the rest // of the filename in s. // // Changed to use StringBuilder for perf reasons as per FxCop CA1818 StringBuilder s = new StringBuilder(fileName.Substring(0, fileName.Length - currentExtension.Length)); // we don't want to append the extension if it contains wild cards if (extensions[j].IndexOfAny(new char[] { '*', '?' }) == -1) { // No wildcards, so go ahead and append s.Append("."); s.Append(extensions[j]); } // If OFN_FILEMUSTEXIST is not set, or if it is set but the filename we generated // does in fact exist, we update fileName and stop trying new extensions. if (!GetOption(NativeMethods.OFN_FILEMUSTEXIST) || File.Exists(s.ToString())) { fileName = s.ToString(); break; } } // Store this filename back in the _fileNames array. _fileNames[i] = fileName; } // Call PromptUserIfAppropriate to show necessary dialog boxes. if (!PromptUserIfAppropriate(fileName)) { // We don't want to display a bunch of message boxes // if one has already determined we need to return to // the file dialog, so we will return false to short // circuit additional processing. return false; } } } return true; } ////// Prompts the user with a System.Windows.MessageBox /// when a file does not exist. /// ////// Security Critical due to a call to MessageBoxWithFocusRestore. /// [SecurityCritical] private void PromptFileNotFound(string fileName) { MessageBoxWithFocusRestore(SR.Get(SRID.FileDialogFileNotFound, fileName), System.Windows.MessageBoxButton.OK, MessageBoxImage.Warning); } #endregion Private Methods //--------------------------------------------------- // // Private Properties // //--------------------------------------------------- #region Private Properties // If multiple files are selected, we only return the first filename. ////// Gets a string containing the full path of the file selected in /// the file dialog box. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// private string CriticalFileName { [SecurityCritical] get { if (_fileNames == null) // No filename stored internally... { return String.Empty; // So we return String.Empty } else { // Return the first filename in the array if it is non-empty. if (_fileNames[0].Length > 0) { return _fileNames[0]; } else { return String.Empty; } } } } ////// Gets a string containing the title of the file dialog. /// ////// Critical: due to calls to GetWindowTextLength and GetWindowText. /// // When showing message boxes onscreen, we want them to have the // same title bar as the file open or save dialog itself. We can't // just use the Title property, because if it's null the operating // system substitutes a standard localized title. // // The solution is this private property, which returns the title of the // file dialog (using the stored handle of the dialog _hwndFileDialog to // call GetWindowText). // // It is designed to only be called by MessageBoxWithFocusRestore. private string DialogCaption { [SecurityCritical] get { if (!UnsafeNativeMethods.IsWindow(new HandleRef(this, _hwndFileDialog))) { return String.Empty; } // Determine the length of the text we want to retrieve... int textLen = UnsafeNativeMethods.GetWindowTextLength(new HandleRef(this, _hwndFileDialog)); // then make a StringBuilder... StringBuilder sb = new StringBuilder(textLen + 1); // and call GetWindowText to fill it up... UnsafeNativeMethods.GetWindowText(new HandleRef(this, _hwndFileDialog), sb /*target string*/, sb.Capacity /* max # of chars to copy before truncation occurs */ ); // then return the results. return sb.ToString(); } } ////// Extracts the file extensions specified by the current file filter into /// an array of strings. None of the extensions contain .'s, and the /// default extension is first. /// ////// Thrown if the filter string stored in the dialog is invalid. /// private string[] GetFilterExtensions() { string filter = this._filter; Listextensions = new List (); // Always make the default extension the first in the list, // because other functions process files in order accepting the first // valid extension they find. It's a little strange if DefaultExt // is not in the filters list, but I guess it's legal. if (_defaultExtension != null) { extensions.Add(_defaultExtension); } // If we have filters, extract the extensions from the currently selected // filter and add them to the extensions list. if (filter != null) { // Filter strings are '|' delimited, so we split on them string[] tokens = filter.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); // Calculate the index of the token containing extension(s) selected // by the FilterIndex property. Remember FilterIndex is one based. // Multiply by 2 because each filter consists of 2 strings. // Now subtract one to get to the filter component. // // example: Text|*.txt|Pictures|*.jpg|Web Pages|*.htm // tokens[]: 0 1 2 3 4 5 // FilterIndex = 2 selects Pictures; (2*2)-1 = 3 points to *.jpg in tokens // int indexOfExtension = (_filterIndex * 2) - 1; // Check to be sure our filter index is not out of bounds (that is, // greater than the number of filters we actually have). // We multiply by 2 here because each filter consists of two strings, // description and extensions, both separated by | characters.. so // tokens.length is actually twice the number of filters we have. if (indexOfExtension >= tokens.Length) { throw new InvalidOperationException(SR.Get(SRID.FileDialogInvalidFilterIndex)); } // If our filter index is valid (0 is reserved by Windows for custom // filter functionality we don't expose, so filters must be 1 or greater) if (_filterIndex > 0) { // Find our filter in the tokens list, then split it on the // ';' character (which is the filter extension delimiter) string[] exts = tokens[indexOfExtension].Split(';'); foreach (string ext in exts) { // Filter extensions should be in the form *.txt or .txt, // so we strip out everything before and including the '.' // before adding the extension to our list. // If the extension has no '.', we just ignore it as invalid. int i = ext.LastIndexOf('.'); if (i >= 0) { // start the substring one beyond the location of the '.' // (i) and continue to the end of the string extensions.Add(ext.Substring(i + 1, ext.Length - (i + 1))); } } } } return extensions.ToArray(); } /// /// Gets an integer representing the Win32 common Open File Dialog OFN_* option flags /// used to display a dialog with the current set of property values. /// // // We bitwise AND _dialogOptions with all of the options we consider valid // before returning the resulting bitmask to avoid accidentally setting a // flag we don't intend to. Note that this list doesn't include a few of the // flags we set right before showing the dialog in RunDialog (like // NativeMethods.OFN_EXPLORER), since those are only added when creating // the OPENFILENAME structure. // // Also note that our private flags are not included in this list (like // OPTION_ADDEXTENSION) protected int Options { get { return _dialogOptions.Value & (NativeMethods.OFN_READONLY | NativeMethods.OFN_HIDEREADONLY | NativeMethods.OFN_NOCHANGEDIR | NativeMethods.OFN_NOVALIDATE | NativeMethods.OFN_ALLOWMULTISELECT | NativeMethods.OFN_PATHMUSTEXIST | NativeMethods.OFN_NODEREFERENCELINKS); } } #endregion Private Properties //---------------------------------------------------- // // Private Fields // //--------------------------------------------------- #region Private Fields // _dialogOptions is a set of bit flags used to control the behavior // of the Win32 dialog box. private SecurityCriticalDataForSet_dialogOptions; // These two flags are related to a fix for an issue where Windows // sends two FileOK notifications back to back after a sharing // violation occurs. See CDN_SHAREVIOLATION in HookProc for details. private bool _ignoreSecondFileOkNotification; private int _fileOkNotificationCount; // These private variables store data for the various public properties // that control the appearance of the file dialog box. private SecurityCriticalDataForSet _title; // Title bar of the message box private SecurityCriticalDataForSet _initialDirectory; // Starting directory private string _defaultExtension; // Extension appended first if AddExtension // is enabled private string _filter; // The file extension filters that display // in the "Files of Type" box in the dialog private int _filterIndex; // The index of the currently selected // filter (a default filter index before // the dialog is called, and the filter // the user selected afterwards.) This // index is 1-based, not 0-based. // Since we have to interop with native code to show the file dialogs, // we use the CharBuffer class to help with the marshalling of // unmanaged memory that stores the user-selected file names. /// /// Critical: This is a buffer that is operated on by unmanaged /// [SecurityCritical] private CharBuffer _charBuffer; // We store the handle of the file dialog inside our class // for a variety of purposes (like getting the title of the dialog // box when we need to show a message box with the same title bar caption) ////// Critical: The hWnd of the dialog is critical data. /// [SecurityCritical] private IntPtr _hwndFileDialog; // This is the array that stores the filename(s) the user selected in the // dialog box. If Multiselect is not enabled, only the first element // of this array will be used. ////// Critical: The full file paths are critical data. /// [SecurityCritical] private string[] _fileNames; // Constant to control the initial size of the character buffer; // 8192 is an arbitrary but reasonable size that should minimize the // number of times we need to grow the buffer. private const int FILEBUFSIZE = 8192; // OPTION_ADDEXTENSION is our own bit flag that we use to control our // own automatic extension appending feature. private const int OPTION_ADDEXTENSION = unchecked(unchecked((int)0x80000000)); #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // FileDialog is an abstract class derived from CommonDialog // that implements shared functionality common to both File // Open and File Save common dialogs. It provides a hook // procedure that handles messages received while the dialog // is visible and numerous properties to control the appearance // and behavior of the dialog. // // The actual call to display the dialog to GetOpenFileName() // or GetSaveFileName() (both functions defined in commdlg.dll) // is implemented in a derived class's RunFileDialog method. // // // History: // t-benja 7/7/2005 Created // //--------------------------------------------------------------------------- namespace Microsoft.Win32 { using MS.Internal; using MS.Internal.PresentationFramework; using MS.Win32; using System; using System.ComponentModel; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Runtime.Remoting; using System.Windows; using CharBuffer = MS.Win32.NativeMethods.CharBuffer; ////// Provides a common base class for wrappers around both the /// File Open and File Save common dialog boxes. Derives from /// CommonDialog. /// /// This class is not intended to be derived from except by /// the OpenFileDialog and SaveFileDialog classes. /// public abstract class FileDialog : CommonDialog { //--------------------------------------------------- // // Constructors // //--------------------------------------------------- #region Constructors ////// In an inherited class, initializes a new instance of /// the System.Windows.FileDialog class. /// ////// Critical: Sets Dialog options, which are critical for set. /// TreatAsSafe: It is okay to set the options to their defaults. The /// ctor does not show the dialog. /// [SecurityCritical, SecurityTreatAsSafe] protected FileDialog() { // Call Initialize to set defaults for fields // and to set defaults for some option flags. // Initialize() is also called from the virtual // Reset() function to restore defaults. Initialize(); } #endregion Constructors //---------------------------------------------------- // // Public Methods // //--------------------------------------------------- #region Public Methods ////// Resets all properties to their default values. /// Classes derived from FileDialog are expected to /// call Base.Reset() at the beginning of their /// implementation of Reset() if they choose to /// override this function. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Sets Dialog options, which are critical for set. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// [SecurityCritical] public override void Reset() { SecurityHelper.DemandUnrestrictedFileIOPermission(); Initialize(); } ////// Returns a string representation of the file dialog with key information /// for debugging purposes. /// // We overload ToString() so that we can provide a useful representation of // this object for users' debugging purposes. It provides the full pathname for // any files selected. public override string ToString() { StringBuilder sb = new StringBuilder(base.ToString() + ": Title: " + Title + ", FileName: "); sb.Append(FileName); return sb.ToString(); // /* catch (System.Security.SecurityException e) { sb.Append("<"); // Coding guidelines require ToString to not throw sb.Append(e.GetType().FullName); // an exception; we catch SecurityException so we sb.Append(">"); // can show filenames in full trust and not fail in // reduced permissions scenarios. } */ } #endregion Public Methods //---------------------------------------------------- // // Public Properties // //---------------------------------------------------- #region Public Properties // // The behavior governed by this property depends // on whether CheckFileExists is set and whether the // filter contains a valid extension to use. For // details, see the ProcessFileNames function. // // It's worth noting that unlike most of these // properties, AddExtension is a custom flag that // is unique to our implementation. As such, it is // a constant value in our class, not stored in // NativeMethods like the other flags. ////// Gets or sets a value indicating whether the /// dialog box automatically adds an extension to a /// file name if the user omits the extension. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool AddExtension { get { return GetOption(OPTION_ADDEXTENSION); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(OPTION_ADDEXTENSION, value); } } // // OFN_FILEMUSTEXIST is only used for Open dialog // boxes, according to MSDN. It implies // OFN_PATHMUSTEXIST and "cannot be used" with a // Save As dialog box... in practice, it seems // to be ignored when used with Save As boxes ////// Gets or sets a value indicating whether /// the dialog box displays a warning if the /// user specifies a file name that does not exist. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public virtual bool CheckFileExists { get { return GetOption(NativeMethods.OFN_FILEMUSTEXIST); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_FILEMUSTEXIST, value); } } ////// Specifies that the user can type only valid paths and file names. If this flag is /// used and the user types an invalid path and file name in the File Name entry field, /// a warning is displayed in a message box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool CheckPathExists { get { return GetOption(NativeMethods.OFN_PATHMUSTEXIST); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_PATHMUSTEXIST, value); } } ////// The AddExtension property attempts to determine the appropriate extension /// by using the selected filter. The DefaultExt property serves as a fallback - /// if the extension cannot be determined from the filter, DefaultExt will /// be used instead. /// public string DefaultExt { get { // For string properties, it's important to not return null, as an empty // string tends to make more sense to beginning developers. return _defaultExtension == null ? String.Empty : _defaultExtension; } set { if (value != null) { // Use Ordinal here as per FxCop CA1307 if (value.StartsWith(".", StringComparison.Ordinal)) // Allow calling code to provide // extensions like ".ext" - { value = value.Substring(1); // but strip out the period to leave only "ext" } else if (value.Length == 0) // Normalize empty strings to null. { value = null; } } _defaultExtension = value; } } // The actual flag is OFN_NODEREFERENCELINKS (set = do not dereference, unset = deref) - // while we have true = dereference and false=do not dereference. Because we expose // the opposite of the Windows flag as a property to be clearer, we need to negate // the value in both the getter and the setter here. ////// Gets or sets a value indicating whether the dialog box returns the location /// of the file referenced by the shortcut or whether it returns the location /// of the shortcut (.lnk). /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool DereferenceLinks { get { return !GetOption(NativeMethods.OFN_NODEREFERENCELINKS); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_NODEREFERENCELINKS, !value); } } ////// Gets a string containing the filename component of the /// file selected in the dialog box. /// /// Example: if FileName = "c:\windows\explorer.exe" , /// SafeFileName = "explorer.exe" /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Scrubs paths from the file name. /// public string SafeFileName { [SecurityCritical] get { // Use the FileName property to avoid directly accessing // the _fileNames field, then call Path.GetFileName // to do the actual work of stripping out the file name // from the path. string safeFN = Path.GetFileName(CriticalFileName); // Check to make sure Path.GetFileName does not return null. // If it does, set safeFN to String.Empty instead to accomodate // programmers that fail to check for null when reading strings. if (safeFN == null) { safeFN = String.Empty; } return safeFN; } } ////// Gets a string array containing the filename of each file selected /// in the dialog box. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Scrubs paths from the file names. /// public string[] SafeFileNames { [SecurityCritical] get { // Retrieve the existing filenames into an array, then make // another array of the same length to hold the safe version. string[] unsafeFileNames = FileNamesInternal; string[] safeFileNames = new string[unsafeFileNames.Length]; for (int i = 0; i < unsafeFileNames.Length; i++) { // Call Path.GetFileName to retrieve only the filename // component of the current full path. safeFileNames[i] = Path.GetFileName(unsafeFileNames[i]); // Check to make sure Path.GetFileName does not return null. // If it does, set this filename to String.Empty instead to accomodate // programmers that fail to check for null when reading strings. if (safeFileNames[i] == null) { safeFileNames[i] = String.Empty; } } return safeFileNames; } } // If multiple files are selected, we only return the first filename. ////// Gets or sets a string containing the full path of the file selected in /// the file dialog box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string FileName { [SecurityCritical] get { SecurityHelper.DemandUnrestrictedFileIOPermission(); return CriticalFileName; } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); // Allow users to set a filename to stored in _fileNames. // If null is passed in, we clear the entire list. // If we get a string, we clear the entire list and make a new one-element // array with the new string. if (value == null) { _fileNames = null; } else { // _fileNames = new string[] { value }; } } } ////// Gets the file names of all selected files in the dialog box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string[] FileNames { [SecurityCritical] get { SecurityHelper.DemandUnrestrictedFileIOPermission(); // FileNamesInternal is a property we use to clone // the string array before returning it. string[] files = FileNamesInternal; return files; } } // The filter string also controls how the AddExtension feature behaves. For // details, see the ProcessFileNames method. ////// 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 at the bottom of the dialog box. /// /// This is an example filter string: /// Filter = "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*" /// ////// Thrown in the setter if the new filter string does not have an even number of tokens /// separated by the vertical bar character '|' (that is, the new filter string is invalid.) /// ////// If DereferenceLinks is true and the filter string is null, a blank /// filter string (equivalent to "|*.*") will be automatically substituted to work /// around the issue documented in Knowledge Base article 831559 /// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// public string Filter { get { // For string properties, it's important to not return null, as an empty // string tends to make more sense to beginning developers. return _filter == null ? String.Empty : _filter; } set { if (String.CompareOrdinal(value,_filter) != 0) // different filter than what we have stored already { string updatedFilter = value; if (!String.IsNullOrEmpty(updatedFilter)) { // Require the number of segments of the filter string to be even - // in other words, there must only be matched pairs of description and // file extensions. // // This implicitly requires there to be at least one vertical bar in // the filter string - or else formats.Length will be 1, resulting in an // ArgumentException. string[] formats = updatedFilter.Split('|'); if (formats.Length % 2 != 0) { throw new ArgumentException(SR.Get(SRID.FileDialogInvalidFilter)); } } else { // catch cases like null or "" where the filter string is not invalid but // also not substantive. We set value to null so that the assignment // below picks up null as the new value of _filter. updatedFilter = null; } _filter = updatedFilter; } } } // Using 1 as the index of the first filter entry is counterintuitive for C#/C++ // developers, but is a side effect of a Win32 feature that allows you to add a template // filter string that is filled in when the user selects a file for future uses of the dialog. // We don't support that feature, so only values >1 are valid. // // For details, see MSDN docs for OPENFILENAME Structure, nFilterIndex ////// Gets or sets the index of the filter currently selected in the file dialog box. /// /// NOTE: The index of the first filter entry is 1, not 0. /// public int FilterIndex { get { return _filterIndex; } set { _filterIndex = value; } } ////// Gets or sets the initial directory displayed by the file dialog box. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Don't want to allow setting of the initial directory in Partial Trust. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string InitialDirectory { get { // Avoid returning a null string - return String.Empty instead. return _initialDirectory.Value == null ? String.Empty : _initialDirectory.Value; } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); _initialDirectory.Value = value; } } ////// Restores the current directory to its original value if the user /// changed the directory while searching for files. /// /// This property is only valid for SaveFileDialog; it has no effect /// when set on an OpenFileDialog. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool RestoreDirectory { get { return GetOption(NativeMethods.OFN_NOCHANGEDIR); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_NOCHANGEDIR, value); } } ////// Gets or sets a string shown in the title bar of the file dialog. /// If this property is null, a localized default from the operating /// system itself will be used (typically something like "Save As" or "Open") /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Do not want to allow setting the FileDialog title from a Partial Trust application. /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public string Title { get { // Avoid returning a null string - return String.Empty instead. return _title.Value == null ? String.Empty : _title.Value; } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); _title.Value = value; } } // If false, the file dialog boxes will allow invalid characters in the returned file name. // We are actually responsible for dealing with this flag - it determines whether all of the // processing in ProcessFileNames (which includes things such as the AddExtension feature) // occurs. ////// Gets or sets a value indicating whether the dialog box accepts only valid /// Win32 file names. /// ////// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API. /// ////// Critical: Dialog options are critical for set. (Only critical for set /// because setting options affects the behavior of the FileDialog) /// PublicOk: Demands FileIOPermission (PermissionState.Unrestricted) /// public bool ValidateNames { get { return !GetOption(NativeMethods.OFN_NOVALIDATE); } [SecurityCritical] set { SecurityHelper.DemandUnrestrictedFileIOPermission(); SetOption(NativeMethods.OFN_NOVALIDATE, !value); } } #endregion Public Properties //--------------------------------------------------- // // Public Events // //---------------------------------------------------- #region Public Events ////// Occurs when the user clicks on the Open or Save button on a file dialog /// box. /// // We fire this event from DoFileOk. public event CancelEventHandler FileOk; #endregion Public Events //--------------------------------------------------- // // Protected Methods // //--------------------------------------------------- #region Protected Methods ////// Defines the common dialog box hook procedure that is overridden to add /// specific functionality to the file dialog box. /// ////// Critical: due to calls to GetParent and PtrToStructure in UnsafeNativeMethods /// as well as a call to DoFileOk, which is SecurityCritical. /// [SecurityCritical] protected override IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) { // Assume we are successful unless we encounter a problem. IntPtr returnValue = IntPtr.Zero; // Our File Dialogs are Explorer-style dialogs with hook procedure enabled // (OFN_ENABLEHOOK | OFN_EXPLORER). As such, we will get the following // messages: (as per MSDN) // // WM_INITDIALOG // WM_NOTIFY (indicating actions taken by the user or other dialog box events) // Messages for any additional controls defined by specifying a child dialog template // // We only need to handle WM_NOTIFY in this hook procedure. if (msg == NativeMethods.WM_NOTIFY) { // Our hookproc is actually the hook procedure for a child template hosted // inside the actual file dialog box. We want the hwnd of the actual dialog, // so we call GetParent on the hwnd passed to the hookproc. _hwndFileDialog = UnsafeNativeMethods.GetParent(new HandleRef(this, hwnd)); // When we receive WM_NOTIFY, lParam is a pointer to an OFNOTIFY // structure that defines the action. OFNOTIFY is a structure // specific to file open and save dialogs with three members: // (defined in Commdlg.h - see MSDN for more details) // // struct _OFNOTIFY { // NMHDR hdr; // this is a by-value structure; // // the implementation in UnsafeNativeMethods breaks it into // // hdr_hwndFrom (HWND, handle to control sending message), // // hdr_idFrom (UINT, ID of control sending message) and // // hdr_code (UINT, one of the CDN_??? notification constants) // // LPOPENFILENAME lpOFN; // pointer to the OPENFILENAME structure we created in // // RunFileDialog when showing this dialog box. // // LPTSTR pszFile; // if a network sharing violation has occurred, this // // is the name of the file affected. Only valid with // // hdr_code = CDN_SHAREVIOLATION. // } // // Convert the pointer to our OFNOTIFY stored in lparam to an object using PtrToStructure. NativeMethods.OFNOTIFY notify = (NativeMethods.OFNOTIFY)UnsafeNativeMethods.PtrToStructure(lParam, typeof(NativeMethods.OFNOTIFY)); // WM_NOTIFY indicates that the dialog is sending us a notification message. // notify.hdr_code is an int defining which notification is being received. // These codes are integer constants defined originally in commdlg.h. switch (notify.hdr_code) { case NativeMethods.CDN_INITDONE: // CDN_INITDONE is sent by Explorer-style file dialogs when the // system has finished arranging the controls in the dialog box. // // We use this opportunity to move the dialog box to the center // of the appropriate monitor. // // As an aside, this only seems to work the first time we show // a dialog - after that, Windows remembers the position of the // dialog. But that's the Winforms behavior too, so it's fine. MoveToScreenCenter(new HandleRef(this, _hwndFileDialog), new HandleRef(this, OwnerWindowHandle)); break; case NativeMethods.CDN_SELCHANGE: // CDN_SELCHANGE is sent by Explorer-style file dialogs when the // selection changes in the list box that displays the contents // of the currently opened folder or directory. // // When we get this message, we check to make sure our character // buffer is big enough to hold all of the filenames that have // been selected. If it isn't, we create a new, bigger buffer // and substitute it in the OPENFILENAME structure. // Retrieve the OPENFILENAME structure from the OFNOTIFY structure // so we can access the CharBuffer inside it. NativeMethods.OPENFILENAME_I ofn = (NativeMethods.OPENFILENAME_I) UnsafeNativeMethods.PtrToStructure(notify.lpOFN, typeof(NativeMethods.OPENFILENAME_I)); // Get the buffer size required to store the selected file names. // We would like to accomplish this by sending a CDM_GETFILEPATH message // - to which the file dialog responds with the number of unicode // characters needed to store the file names and paths. // // Windows Forms used CDM_GETSPEC here, but that only retrieves the length // of the filenames - not of the complete path. So in cases with network // shortcuts and dereference links enabled, we end up with not enough buffer // and an FNERR_BUFFERTOOSMALL error. // // Unfortunately, CDM_GETFILEPATH returns -1 when a bunch of files are // selected, so changing to it actually makes things worse with very large // cases. So we'll stick with CDM_GETSPEC plus extra buffer space. // int sizeNeeded = (int)UnsafeNativeMethods.UnsafeSendMessage(_hwndFileDialog, // hWnd of window to receive message NativeMethods.CDM_GETSPEC, // Msg (message to send) IntPtr.Zero, // wParam (additional info) IntPtr.Zero); // lParam (additional info) if (sizeNeeded > ofn.nMaxFile) { // A bigger buffer is required, so we'll allocate a new // CharBuffer and substitute it for the existing one. //try //{ // Make the new buffer equal to the size the dialog told us we needed // plus a reasonable growth factor. int newBufferSize = sizeNeeded + (FILEBUFSIZE / 4); // Allocate a new CharBuffer in the appropriate size. CharBuffer charBufferTmp = CharBuffer.CreateBuffer(newBufferSize); // Allocate unmanaged memory for the buffer and store the pointer. IntPtr newBuffer = charBufferTmp.AllocCoTaskMem(); // Free the old, smaller buffer stored in ofn.lpstrFile Marshal.FreeCoTaskMem(ofn.lpstrFile); // Substitute buffer and update the buffer maximum size in // the dialog. ofn.lpstrFile = newBuffer; ofn.nMaxFile = newBufferSize; // Store the reference to the character buffer inside our // class so we can free it when we're done. this._charBuffer = charBufferTmp; // Marshal the OPENFILENAME structure back into the // OFNOTIFY structure, then marshal the OFNOTIFY structure // back into lparam to update the dialog. Marshal.StructureToPtr(ofn, notify.lpOFN, true); Marshal.StructureToPtr(notify, lParam, true); // } // Windows Forms had a catch-all exception handler here // but no justification for why it existed. If exceptions // are thrown when we grow the buffer, re-add this catch // and perform handling specific to the exception you are seeing. // // I don't see anywhere an exception would be thrown that // we would want to simply discard in this try block, so // we'll remove this catch and let any exceptions through. // // catch (Exception) // { // intentionally not throwing here. // } } break; case NativeMethods.CDN_SHAREVIOLATION: // CDN_SHAREVIOLATION is sent by Explorer-style boxes when OK is clicked // and a network sharing violation occurs for the selected file. // Network sharing violation is a bit misleading of a term - it could // also mean the user doesn't have permissions for the file, or it could mean // the file is already opened by another process on the same machine. // // We process this message because of some odd behavior seen when a file // is locked for writing. (for details, see VS Whidbey 95342) // // We get this notification followed by *two* CDN_FILEOK notifications... but only // if the path is entered in the textbox and not selected from the folder view. // // If we get a CDN_SHAREVIOLATION, we'll set a flag and a counter so we can track // which CDN_FILEOK notification we're on to avoid showing two message boxes. this._ignoreSecondFileOkNotification = true; // We want to ignore the second CDN_FILEOK this._fileOkNotificationCount = 0; // to avoid a second prompt by PromptFileOverwrite. break; case NativeMethods.CDN_FILEOK: // CDN_FILEOK is sent when the user specifies a filename and clicks OK. // We need to process the files selected and make sure everything's acceptable. // If it's all OK, we don't need to do anything. // // To tell the dialogs to stay open after we receive a CDN_FILEOK, we must both // return a non-zero value from this hook procedure and call SetWindowLong to // set a nonzero value for DWL_MSGRESULT. // --- Begin VS Whidbey 95342 Workaround --- // See the CDN_SHAREVIOLATION case above for background info about this issue. if (this._ignoreSecondFileOkNotification) { // We got a CDN_SHAREVIOLATION notification and want to ignore the second CDN_FILEOK notification. // We'll allow the first one through and block the second. // Recall that we initialize _fileOkNotificationCount to 0 when we get the CDN_SHAREVIOLATION. if (this._fileOkNotificationCount == 0) { // This is the first CDN_FILEOK, record that we received // it and then allow DoFileOk to be called. this._fileOkNotificationCount = 1; } else { // This is the second CDN_FILEOK, so we want to ignore it. this._ignoreSecondFileOkNotification = false; // Call SetWindowLong to set the DWL_MSGRESULT value of the file dialog window // to a non-zero number to tell the dialog to stay open. // NativeMethods.InvalidIntPtr is defined as -1. UnsafeNativeMethods.CriticalSetWindowLong(new HandleRef(this, hwnd), // hWnd (which window are we affecting) NativeMethods.DWL_MSGRESULT, // nIndex (which value are we setting) NativeMethods.InvalidIntPtr); // dwNewLong (what is the new value) // We also need to return a non-zero value to tell the dialog to stay open. returnValue = NativeMethods.InvalidIntPtr; break; } } // --- End VS Whidbey 95342 Workaround --- // Call DoFileOk to check if the files that have been selected // are acceptable. (See DoFileOk for details.) // // If it returns false, we must notify the dialog box that it // needs to stay open for further input. if (!DoFileOk(notify.lpOFN)) { // Call SetWindowLong to set the DWL_MSGRESULT value of the file dialog window // to a non-zero number to tell the dialog to stay open. // NativeMethods.InvalidIntPtr is defined as -1. UnsafeNativeMethods.CriticalSetWindowLong(new HandleRef(this, hwnd), // hWnd (which window are we affecting) NativeMethods.DWL_MSGRESULT, // nIndex (which value are we setting) NativeMethods.InvalidIntPtr); // dwNewLong (what is the new value) // We also need to return a non-zero value to tell the dialog to stay open. returnValue = NativeMethods.InvalidIntPtr; break; } break; } } // Return IntPtr.Zero to indicate success, unless we have // adjusted the return value elsewhere in the function. return returnValue; } ////// Raises the System.Windows.FileDialog.FileOk event. /// protected void OnFileOk(CancelEventArgs e) { if (FileOk != null) { FileOk(this, e); } } // Because this class, FileDialog, is the parent class for both OpenFileDialog // and SaveFileDialog, this function will perform the common setup tasks // shared between Open and Save, and will then call RunFileDialog, which is // overridden in both of the derived classes to show the correct dialog. // ////// Performs initialization work in preparation for calling RunFileDialog /// to show a file open or save dialog box. /// ////// Critical: Calls UnsafeNativeMethods.SetWindowPos() accesses SecurityCritical data /// _charBuffer. /// [SecurityCritical] protected override bool RunDialog(IntPtr hwndOwner) { // Once we run the dialog, all of our communication with it is handled // by processing WM_NOTIFY messages in our hook procedure, this.HookProc. // NativeMethods.WndProc is a delegate with the appropriate signature // needed for a Win32 window hook procedure. NativeMethods.WndProc hookProcPtr = new NativeMethods.WndProc(this.HookProc); // Create a new OPENFILENAME structure. OPENFILENAME is a structure defined // in Win32's commdlg.h that contains most of the information needed to // successfully display a file dialog box. // NOTE: Despite the name, OPENFILENAME is the proper structure for both // file open and file save dialogs. NativeMethods.OPENFILENAME_I ofn = new NativeMethods.OPENFILENAME_I(); // do everything in a try block, so we always free memory in the finalizer try { // Create an appropriately sized buffer to hold the filenames. // The buffer's initial size is controlled by the FILEBUFSIZE constant, // an arbitrary value chosen so that we will rarely have to grow the buffer. _charBuffer = CharBuffer.CreateBuffer(FILEBUFSIZE); // If we have a filename stored in our internal array _fileNames, // place it in the buffer as a default filename. if (_fileNames != null) { _charBuffer.PutString(_fileNames[0]); } // --- Set up the OPENFILENAME structure --- // lStructSize // Specifies the length, in bytes, of the structure. ofn.lStructSize = Marshal.SizeOf(typeof(NativeMethods.OPENFILENAME_I)); // hwndOwner // Handle to the window that owns the dialog box. This member can be any // valid window handle, or it can be NULL if the dialog box has no owner. ofn.hwndOwner = hwndOwner; // hInstance // This property is ignored unless OFN_ENABLETEMPLATEHANDLE or // OFN_ENABLETEMPLATE are set. Since we do not set either, // hInstance is ignored, so we can set it to zero. ofn.hInstance = IntPtr.Zero; // lpstrFilter // Pointer to a buffer containing pairs of null-terminated filter strings. // The last string in the buffer must be terminated by two NULL characters. // Since our filter strings are stored terminated by vertical bar '|' chars, // we call MakeFilterString to reformat and validate the filter string. ofn.lpstrFilter = MakeFilterString(_filter, this.DereferenceLinks); // nFilterIndex // Specifies the index of the currently selected filter in the File Types // control. Note that since 0 is reserved for a custom filter (which we // do not support), our valid filter indexes begin at 1. ofn.nFilterIndex = _filterIndex; // lpstrFile // Pointer to a buffer used to store filenames. When initializing the // dialog, this name is used as an initial value in the File Name edit // control. When files are selected and the function returns, the buffer // contains the full path to every file selected. ofn.lpstrFile = _charBuffer.AllocCoTaskMem(); // nMaxFile // Size of the lpstrFile buffer in number of Unicode characters. ofn.nMaxFile = _charBuffer.Length; // lpstrInitialDir // Pointer to a null terminated string that can specify the initial directory. // A relatively complex algorithm is used to determine which directory is // actually used as the initial directory - for details, see MSDN for the // OPENFILENAME structure. ofn.lpstrInitialDir = _initialDirectory.Value; // lpstrTitle // Pointer to a string to be placed in the title bar of the dialog box. // NULL causes the title bar to display the operating system default string. ofn.lpstrTitle = _title.Value; // Flags // A set of bit flags you can use to initialize the dialog box. // Most of these will be set through public properties that then call // GetOption or SetOption. We retrieve the flags using the Options property // and then add three additional flags here: // // OFN_EXPLORER // display an Explorer-style box (newer style) // OFN_ENABLEHOOK // enable the hook procedure (important for much of our functionality) // OFN_ENABLESIZING // allow the user to resize the dialog box // ofn.Flags = Options | (NativeMethods.OFN_EXPLORER | NativeMethods.OFN_ENABLEHOOK | NativeMethods.OFN_ENABLESIZING); // lpfnHook // Pointer to the hook procedure. // Ignored unless OFN_ENABLEHOOK is set in Flags. ofn.lpfnHook = hookProcPtr; // FlagsEx // Can be either zero or OFN_EX_NOPLACESBAR, depending on whether // the Places Bar (My Computer/Favorites/etc) should be shown on the // left side of the file dialog. ofn.FlagsEx = NativeMethods.OFN_USESHELLITEM; // lpstrDefExt // Pointer to a buffer that contains the default extension; it will // be appended to filenames if the user does not type an extension. // Only the first three characters are appended by Windows. If this // is NULL, no extension is appended. if (_defaultExtension != null && AddExtension) { ofn.lpstrDefExt = _defaultExtension; } // Call into either OpenFileDialog or SaveFileDialog to show the // actual dialog box. This call blocks until the dialog is closed; // while dialog is open, all interaction is through HookProc. return RunFileDialog(ofn); } finally { // Explicitly set the character buffer to null. _charBuffer = null; // If there is still a pointer to a memory location in // ofn.lpstrFile, we explicitly free that memory here. if (ofn.lpstrFile != IntPtr.Zero) { Marshal.FreeCoTaskMem(ofn.lpstrFile); } } } #endregion Protected Methods //--------------------------------------------------- // // Internal Methods // //---------------------------------------------------- #region Internal Methods ////// Returns the state of the given options flag. /// internal bool GetOption(int option) { return (_dialogOptions.Value & option) != 0; } ////// Sets the given option to the given boolean value. /// ////// Critical: Setting a SecurityCriticalDataForSet member (_dialogOptions). /// [SecurityCritical] internal void SetOption(int option, bool value) { if (value) { // if value is true, bitwise OR the option with _dialogOptions _dialogOptions.Value |= option; } else { // if value is false, AND the bitwise complement of the // option with _dialogOptions _dialogOptions.Value &= ~option; } } ////// Prompts the user with a System.Windows.MessageBox /// with the given parameters. It also ensures that /// the focus is set back on the window that had /// the focus to begin with (before we displayed /// the MessageBox). /// /// Returns the choice the user made in the message box /// (true if MessageBoxResult.Yes, /// false if OK or MessageBoxResult.No) /// /// We have to do this instead of just calling MessageBox because /// of an issue where keyboard navigation would fail after showing /// a message box. See http://bugcheck/default.asp?URL=/Bugs/URT/84016.asp /// (WinForms ASURT 80262) /// ////// Critical: We call GetFocus() and SetFocus() in /// UnsafeNativeMethods, which are marked SupressUnmanagedCodeSecurity. /// [SecurityCritical] internal bool MessageBoxWithFocusRestore(string message, MessageBoxButton buttons, MessageBoxImage image) { bool ret = false; // Get the window that currently has focus and temporarily cache a handle to it IntPtr focusHandle = UnsafeNativeMethods.GetFocus(); try { // Show the message box and compare the return value to MessageBoxResult.Yes to get the // actual return value. ret = (MessageBox.Show(message, DialogCaption, buttons, image, MessageBoxResult.OK /*default button is OK*/, 0) == MessageBoxResult.Yes); } finally { // Return focus to the window that had focus before we showed the messagebox. // SetFocus can handle improper hwnd values, including null. UnsafeNativeMethods.SetFocus(new HandleRef(this, focusHandle)); } return ret; } ////// PromptUserIfAppropriate is a virtual function that shows any prompt /// message boxes (like "Do you want to overwrite this file") necessary after /// the Open button is pressed in a file dialog. /// /// Return value is false if we showed a dialog box and true if we did not. /// (in other words, true if it's OK to continue with the open process and /// false if we need to return the user to the dialog to make another selection.) /// ////// SaveFileDialog overrides this method to add additional message boxes for /// its unique properties. /// /// For FileDialog: /// If OFN_FILEMUSTEXIST is set, we check to be sure the path passed in on the /// fileName parameter exists as an actual file on the hard disk. If so, we /// call PromptFileNotFound to inform the user that they must select an actual /// file that already exists. /// ////// Critical: due to call to PromptFileNotFound, which displays a message box with focus restore. /// Asserts FileIOPermission in order to determine whether the file exists. /// [SecurityCritical] internal virtual bool PromptUserIfAppropriate(string fileName) { bool fileExists = true; // The only option we deal with in this implementation of // PromptUserIfAppropriate is OFN_FILEMUSTEXIST. if (GetOption(NativeMethods.OFN_FILEMUSTEXIST)) { // File.Exists requires a full path, so we call GetFullPath on // the filename before checking if it exists. (new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, fileName)).Assert(); try { string tempPath = Path.GetFullPath(fileName); fileExists = File.Exists(tempPath); } finally { CodeAccessPermission.RevertAssert(); } if (!fileExists) { // file does not exist, we can't continue // and must display an error // Display the message box PromptFileNotFound(fileName); } } return fileExists; } ////// Implements the actual call to GetOpenFileName or GetSaveFileName. /// internal abstract bool RunFileDialog(NativeMethods.OPENFILENAME_I ofn); #endregion Internal Methods //--------------------------------------------------- // // Internal Properties // //---------------------------------------------------- #region Internal Properties ////// In cases where we need to return an array of strings, we return /// a clone of the array. We also need to make sure we return a /// string[0] instead of a null if we don't have any filenames. /// ////// Critical: Accesses _fileNames, which is SecurityCritical. /// internal string[] FileNamesInternal { [SecurityCritical] get { if (_fileNames == null) { return new string[0]; } else { return (string[])_fileNames.Clone(); } } } #endregion Internal Properties //---------------------------------------------------- // // Internal Events // //--------------------------------------------------- //#region Internal Events //#endregion Internal Events //---------------------------------------------------- // // Private Methods // //--------------------------------------------------- #region Private Methods ////// Processes the CDN_FILEOK notification, which is sent by an /// Explorer-style Open or Save As dialog box when the user specifies /// a file name and clicks the OK button. /// ////// true if the dialog can close, or false if we need to return to /// the dialog for additional input. /// ////// Critical due to call access to _charBuffer, _fileNames and _dialogOptions. /// [SecurityCritical] private bool DoFileOk(IntPtr lpOFN) { NativeMethods.OPENFILENAME_I ofn = (NativeMethods.OPENFILENAME_I)UnsafeNativeMethods.PtrToStructure(lpOFN, typeof(NativeMethods.OPENFILENAME_I)); // While processing the results we get from the OPENFILENAME struct, // we will adjust several properties of our own class to reflect the // new data. In case we discover we need to send the user back to // the dialog for further input, we need to be able to revert these // changes - so we backup _dialogOptions, _filterIndex and _fileNames. // // We only assign brand new string arrays to _FileNames, so it's OK // to back up by reference here. int saveOptions = _dialogOptions.Value; int saveFilterIndex = _filterIndex; string[] saveFileNames = _fileNames; // ok is a flag to determine whether we need to show the dialog // again (false) or if we're satisfied with the results we received (true). bool ok = false; try { // Replace the ReadOnly flag in DialogOptions with the ReadOnly flag // from the OPENFILEDIALOG structure - that is, store the user's // choice from the Read Only checkbox so our property is up to date. _dialogOptions.Value = _dialogOptions.Value & ~NativeMethods.OFN_READONLY | ofn.Flags & NativeMethods.OFN_READONLY; // Similarly, update the filterIndex to reflect the selected filter. _filterIndex = ofn.nFilterIndex; // Ask the character buffer to copy the memory from the location // referenced by lpstrFile into our internal character buffer. _charBuffer.PutCoTaskMem(ofn.lpstrFile); if (!GetOption(NativeMethods.OFN_ALLOWMULTISELECT)) { // Since we're selecting a single file, make a string // array with a single element containing the entire contents // of the character buffer. _fileNames = new string[] { _charBuffer.GetString() }; } else { // Multiselect is a bit more complex - call GetMultiselectFiles // to handle that case. _fileNames = GetMultiselectFiles(_charBuffer); } // Call ProcessFileNames() to do validation and post-processing // tasks (see that function for details; it checks if files exist, // prompts users with message boxes if invalid selections are made, etc.) if (ProcessFileNames()) { // ProcessFileNames returned true, so it's OK to fire the // OnFileOk event. CancelEventArgs ceevent = new CancelEventArgs(); OnFileOk(ceevent); // We allow our calling code to do even more post-processing // through the OnFileOk event - and therefore offer them the // opportunity to redisplay the dialog for additional input // using the event arguments if their validation failed. // // If OnFileOk is not handled, ceevent.Cancel will be false. ok = !ceevent.Cancel; } } finally { // No matter what happened, we need to restore dialog state // if the result was not ok=true. if (!ok) { _dialogOptions.Value = saveOptions; _filterIndex = saveFilterIndex; _fileNames = saveFileNames; } } return ok; } ////// Extracts the filename(s) returned by the file dialog. /// /// Marked static for perf reasons because this function doesn't /// actually access any instance data as per FxCop CA1822. private static string[] GetMultiselectFiles(CharBuffer charBuffer) { // Iff OFN_ALLOWMULTISELECT is set for an Explorer-style dialog box // and the user selects multiple files, lpstrFile points to a string // containing the current directory, followed by a NULL, followed by // two or more filenames that are NULL separated, with an extra NULL // character after the last filename. // // We'll use the GetString() function of the character buffer to get // two of these null-terminated chunks at a time, one into directory // and one into filename. string directory = charBuffer.GetString(); string fileName = charBuffer.GetString(); // If OFN_ALLOWMULTISELECT is enabled but the user selects only // one file, we get the filename and path concatenated together without // a null separator. This will cause our directory variable to // contain the full path and fileName to be empty, so make a new // string array with the contents of directory as its single element. // if (fileName.Length == 0) { return new string[] { directory }; } // If the directory was provided without a directory separator // character (typically '\' on Windows) at the end, we add it. if (!directory.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { directory = directory + Path.DirectorySeparatorChar; } // Create a generic list of strings to hold the names. Listnames = new List (); do { // With DereferenceLinks enabled, we can sometimes end // up with full paths provided as filenames. We need // to check for two cases here - the case where the // filename begins with '\', indicating a UNC share path, // or the case where we have a full hard disk path // (e.g. C:\file.txt), where [1] will be the : volume // separator and [2] will be the \ directory separator. bool isUncPath = (fileName[0] == Path.DirectorySeparatorChar && fileName[1] == Path.DirectorySeparatorChar); bool isFullPath = (fileName.Length > 3 && fileName[1] == Path.VolumeSeparatorChar && fileName[2] == Path.DirectorySeparatorChar ); if (!(isUncPath || isFullPath)) { // filename is not a full path, so we need to // add on the directory fileName = directory + fileName; } names.Add(fileName); // Get the next filename fileName = charBuffer.GetString(); } while (!String.IsNullOrEmpty(fileName)); return names.ToArray(); } // Provides the actual implementation of initialization tasks. // Initialize() is called from both the constructor and the // public Reset() function to set default values for member // variables and for the options bitmask. /// /// Critical: Sets Dialog options, which are critical for set. /// [SecurityCritical] private void Initialize() { // // Initialize Options Flags // _dialogOptions.Value = 0; // _dialogOptions is an int containing a set of // bit flags used to initialize the dialog box. // It is placed directly into the OPENFILEDIALOG // struct used to instantiate the file dialog box. // Within our code, we only use GetOption and SetOption // (change from Windows Forms, which sometimes directly // modified _dialogOptions). As such, we initialize to 0 // here and then call SetOption to get _dialogOptions // into the default state. // // Set some default options // // - Hide the Read Only check box. SetOption(NativeMethods.OFN_HIDEREADONLY, true); // - Specifies that the user can type only valid paths and file names. If this flag is // used and the user types an invalid path and file name in the File Name entry field, // we will display a warning in a message box. SetOption(NativeMethods.OFN_PATHMUSTEXIST, true); // - This is our own flag, not a standard one defined in OPENFILEDIALOG. We use this to // indicate to ourselves that we should add the default extension automatically if the // user does not enter it in themselves in ProcessFileNames. (See that function for // details.) SetOption(OPTION_ADDEXTENSION, true); // // Initialize additional properties // _title.Value = null; _initialDirectory.Value = null; _defaultExtension = null; _fileNames = null; _filter = null; _filterIndex = 1; // The index of the first filter entry is 1, not 0. // 0 is reserved for the custom filter functionality // provided by Windows, which we do not expose to the user. // Variables used for bug workaround: // When the selected file is locked for writing, we get a sharing violation notification // followed by *two* CDN_FILEOK notifications. These flags are used to track the multiple // notifications so we only show one error message box to the user. // For a more complete explanation and PS bug information, see HookProc. _ignoreSecondFileOkNotification = false; _fileOkNotificationCount = 0; } ////// Converts the given filter string to the format required in an OPENFILENAME_I /// structure. /// private static string MakeFilterString(string s, bool dereferenceLinks) { if (String.IsNullOrEmpty(s)) { // Workaround for VSWhidbey bug #95338 (carried over from Winforms implementation) // Apparently, when filter is null, the common dialogs in Windows XP will not dereference // links properly. The work around is to provide a default filter; " |*.*" is used to // avoid localization issues from description text. // // This behavior is now documented in MSDN on the OPENFILENAME structure, so I don't // expect it to change anytime soon. if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5) { s = " |*.*"; } else { // Even if we don't need the bug workaround, change empty // strings into null strings. return null; } } StringBuilder nullSeparatedFilter = new StringBuilder(s); // Replace the vertical bar with a null to conform to the Windows // filter string format requirements nullSeparatedFilter.Replace('|', '\0'); // Append two nulls at the end nullSeparatedFilter.Append('\0'); nullSeparatedFilter.Append('\0'); // Return the results as a string. return nullSeparatedFilter.ToString(); } ////// Handle the AddExtension property on newly acquired filenames, then /// call PromptUserIfAppropriate to display any necessary message boxes. /// /// Returns false if we need to redisplay the dialog and true otherwise. /// ////// Critical: due to call to PromptUserIfAppropriate, which displays /// message boxes with focus restore. /// TreatAsSafe: This method does not take external input for the call for /// PromptUserIfAppropriate. /// [SecurityCritical, SecurityTreatAsSafe] private bool ProcessFileNames() { // Only process the filenames if OFN_NOVALIDATE is not set. if (!GetOption(NativeMethods.OFN_NOVALIDATE)) { // Call the FilterExtensions private property to get // a list of valid extensions from the filter(s). // The first extension from FilterExtensions is the // default extension. string[] extensions = GetFilterExtensions(); // For each filename: // - Process AddExtension // - Call PromptUserIfAppropriate to display necessary dialog boxes. for (int i = 0; i < _fileNames.Length; i++) { string fileName = _fileNames[i]; // If AddExtension is enabled and we do not already have an extension: if (AddExtension && !Path.HasExtension(fileName)) { // Loop through all extensions, starting with the default extension for (int j = 0; j < extensions.Length; j++) { // Assert for a valid extension Invariant.Assert(!extensions[j].StartsWith(".", StringComparison.Ordinal), "FileDialog.GetFilterExtensions should not return things starting with '.'"); string currentExtension = Path.GetExtension(fileName); // Assert to make sure Path.GetExtension behaves as we think it should, returning // "" if the string is empty and something beginnign with . otherwise. // Use StringComparison.Ordinal as per FxCop CA1307 and CA130. Invariant.Assert(currentExtension.Length == 0 || currentExtension.StartsWith(".", StringComparison.Ordinal), "Path.GetExtension should return something that starts with '.'"); // Because we check Path.HasExtension above, files should // theoretically not have extensions at this stage - but // we'll go ahead and remove an existing extension if it // somehow slipped through. // // Strip out any extension that may be remaining and place the rest // of the filename in s. // // Changed to use StringBuilder for perf reasons as per FxCop CA1818 StringBuilder s = new StringBuilder(fileName.Substring(0, fileName.Length - currentExtension.Length)); // we don't want to append the extension if it contains wild cards if (extensions[j].IndexOfAny(new char[] { '*', '?' }) == -1) { // No wildcards, so go ahead and append s.Append("."); s.Append(extensions[j]); } // If OFN_FILEMUSTEXIST is not set, or if it is set but the filename we generated // does in fact exist, we update fileName and stop trying new extensions. if (!GetOption(NativeMethods.OFN_FILEMUSTEXIST) || File.Exists(s.ToString())) { fileName = s.ToString(); break; } } // Store this filename back in the _fileNames array. _fileNames[i] = fileName; } // Call PromptUserIfAppropriate to show necessary dialog boxes. if (!PromptUserIfAppropriate(fileName)) { // We don't want to display a bunch of message boxes // if one has already determined we need to return to // the file dialog, so we will return false to short // circuit additional processing. return false; } } } return true; } ////// Prompts the user with a System.Windows.MessageBox /// when a file does not exist. /// ////// Security Critical due to a call to MessageBoxWithFocusRestore. /// [SecurityCritical] private void PromptFileNotFound(string fileName) { MessageBoxWithFocusRestore(SR.Get(SRID.FileDialogFileNotFound, fileName), System.Windows.MessageBoxButton.OK, MessageBoxImage.Warning); } #endregion Private Methods //--------------------------------------------------- // // Private Properties // //--------------------------------------------------- #region Private Properties // If multiple files are selected, we only return the first filename. ////// Gets a string containing the full path of the file selected in /// the file dialog box. /// ////// Critical: Do not want to allow access to raw paths to Parially Trusted Applications. /// private string CriticalFileName { [SecurityCritical] get { if (_fileNames == null) // No filename stored internally... { return String.Empty; // So we return String.Empty } else { // Return the first filename in the array if it is non-empty. if (_fileNames[0].Length > 0) { return _fileNames[0]; } else { return String.Empty; } } } } ////// Gets a string containing the title of the file dialog. /// ////// Critical: due to calls to GetWindowTextLength and GetWindowText. /// // When showing message boxes onscreen, we want them to have the // same title bar as the file open or save dialog itself. We can't // just use the Title property, because if it's null the operating // system substitutes a standard localized title. // // The solution is this private property, which returns the title of the // file dialog (using the stored handle of the dialog _hwndFileDialog to // call GetWindowText). // // It is designed to only be called by MessageBoxWithFocusRestore. private string DialogCaption { [SecurityCritical] get { if (!UnsafeNativeMethods.IsWindow(new HandleRef(this, _hwndFileDialog))) { return String.Empty; } // Determine the length of the text we want to retrieve... int textLen = UnsafeNativeMethods.GetWindowTextLength(new HandleRef(this, _hwndFileDialog)); // then make a StringBuilder... StringBuilder sb = new StringBuilder(textLen + 1); // and call GetWindowText to fill it up... UnsafeNativeMethods.GetWindowText(new HandleRef(this, _hwndFileDialog), sb /*target string*/, sb.Capacity /* max # of chars to copy before truncation occurs */ ); // then return the results. return sb.ToString(); } } ////// Extracts the file extensions specified by the current file filter into /// an array of strings. None of the extensions contain .'s, and the /// default extension is first. /// ////// Thrown if the filter string stored in the dialog is invalid. /// private string[] GetFilterExtensions() { string filter = this._filter; Listextensions = new List (); // Always make the default extension the first in the list, // because other functions process files in order accepting the first // valid extension they find. It's a little strange if DefaultExt // is not in the filters list, but I guess it's legal. if (_defaultExtension != null) { extensions.Add(_defaultExtension); } // If we have filters, extract the extensions from the currently selected // filter and add them to the extensions list. if (filter != null) { // Filter strings are '|' delimited, so we split on them string[] tokens = filter.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); // Calculate the index of the token containing extension(s) selected // by the FilterIndex property. Remember FilterIndex is one based. // Multiply by 2 because each filter consists of 2 strings. // Now subtract one to get to the filter component. // // example: Text|*.txt|Pictures|*.jpg|Web Pages|*.htm // tokens[]: 0 1 2 3 4 5 // FilterIndex = 2 selects Pictures; (2*2)-1 = 3 points to *.jpg in tokens // int indexOfExtension = (_filterIndex * 2) - 1; // Check to be sure our filter index is not out of bounds (that is, // greater than the number of filters we actually have). // We multiply by 2 here because each filter consists of two strings, // description and extensions, both separated by | characters.. so // tokens.length is actually twice the number of filters we have. if (indexOfExtension >= tokens.Length) { throw new InvalidOperationException(SR.Get(SRID.FileDialogInvalidFilterIndex)); } // If our filter index is valid (0 is reserved by Windows for custom // filter functionality we don't expose, so filters must be 1 or greater) if (_filterIndex > 0) { // Find our filter in the tokens list, then split it on the // ';' character (which is the filter extension delimiter) string[] exts = tokens[indexOfExtension].Split(';'); foreach (string ext in exts) { // Filter extensions should be in the form *.txt or .txt, // so we strip out everything before and including the '.' // before adding the extension to our list. // If the extension has no '.', we just ignore it as invalid. int i = ext.LastIndexOf('.'); if (i >= 0) { // start the substring one beyond the location of the '.' // (i) and continue to the end of the string extensions.Add(ext.Substring(i + 1, ext.Length - (i + 1))); } } } } return extensions.ToArray(); } /// /// Gets an integer representing the Win32 common Open File Dialog OFN_* option flags /// used to display a dialog with the current set of property values. /// // // We bitwise AND _dialogOptions with all of the options we consider valid // before returning the resulting bitmask to avoid accidentally setting a // flag we don't intend to. Note that this list doesn't include a few of the // flags we set right before showing the dialog in RunDialog (like // NativeMethods.OFN_EXPLORER), since those are only added when creating // the OPENFILENAME structure. // // Also note that our private flags are not included in this list (like // OPTION_ADDEXTENSION) protected int Options { get { return _dialogOptions.Value & (NativeMethods.OFN_READONLY | NativeMethods.OFN_HIDEREADONLY | NativeMethods.OFN_NOCHANGEDIR | NativeMethods.OFN_NOVALIDATE | NativeMethods.OFN_ALLOWMULTISELECT | NativeMethods.OFN_PATHMUSTEXIST | NativeMethods.OFN_NODEREFERENCELINKS); } } #endregion Private Properties //---------------------------------------------------- // // Private Fields // //--------------------------------------------------- #region Private Fields // _dialogOptions is a set of bit flags used to control the behavior // of the Win32 dialog box. private SecurityCriticalDataForSet_dialogOptions; // These two flags are related to a fix for an issue where Windows // sends two FileOK notifications back to back after a sharing // violation occurs. See CDN_SHAREVIOLATION in HookProc for details. private bool _ignoreSecondFileOkNotification; private int _fileOkNotificationCount; // These private variables store data for the various public properties // that control the appearance of the file dialog box. private SecurityCriticalDataForSet _title; // Title bar of the message box private SecurityCriticalDataForSet _initialDirectory; // Starting directory private string _defaultExtension; // Extension appended first if AddExtension // is enabled private string _filter; // The file extension filters that display // in the "Files of Type" box in the dialog private int _filterIndex; // The index of the currently selected // filter (a default filter index before // the dialog is called, and the filter // the user selected afterwards.) This // index is 1-based, not 0-based. // Since we have to interop with native code to show the file dialogs, // we use the CharBuffer class to help with the marshalling of // unmanaged memory that stores the user-selected file names. /// /// Critical: This is a buffer that is operated on by unmanaged /// [SecurityCritical] private CharBuffer _charBuffer; // We store the handle of the file dialog inside our class // for a variety of purposes (like getting the title of the dialog // box when we need to show a message box with the same title bar caption) ////// Critical: The hWnd of the dialog is critical data. /// [SecurityCritical] private IntPtr _hwndFileDialog; // This is the array that stores the filename(s) the user selected in the // dialog box. If Multiselect is not enabled, only the first element // of this array will be used. ////// Critical: The full file paths are critical data. /// [SecurityCritical] private string[] _fileNames; // Constant to control the initial size of the character buffer; // 8192 is an arbitrary but reasonable size that should minimize the // number of times we need to grow the buffer. private const int FILEBUFSIZE = 8192; // OPTION_ADDEXTENSION is our own bit flag that we use to control our // own automatic extension appending feature. private const int OPTION_ADDEXTENSION = unchecked(unchecked((int)0x80000000)); #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- AttributeQuery.cs
- FieldToken.cs
- HtmlDocument.cs
- SwitchDesigner.xaml.cs
- CompressEmulationStream.cs
- ActivityInstanceReference.cs
- RemotingAttributes.cs
- DoubleLink.cs
- TextWriterTraceListener.cs
- SqlDataSourceSelectingEventArgs.cs
- Main.cs
- ItemAutomationPeer.cs
- AttributeCollection.cs
- BufferedStream.cs
- EditorServiceContext.cs
- XpsLiterals.cs
- BlurBitmapEffect.cs
- SendMailErrorEventArgs.cs
- DateTimeOffset.cs
- AnnotationHelper.cs
- XpsFilter.cs
- ellipse.cs
- Function.cs
- PropertiesTab.cs
- RecommendedAsConfigurableAttribute.cs
- TimeBoundedCache.cs
- OpenTypeLayoutCache.cs
- PropertyManager.cs
- ClientSettingsSection.cs
- DataServiceSaveChangesEventArgs.cs
- XMLSyntaxException.cs
- embossbitmapeffect.cs
- ValidationSummary.cs
- UInt64.cs
- SoapWriter.cs
- MenuCommandService.cs
- WebConfigurationHost.cs
- EntityDataSourceSelectedEventArgs.cs
- FixedPageAutomationPeer.cs
- BinaryCommonClasses.cs
- ProgressBarBrushConverter.cs
- TemplateControl.cs
- SynchronizedDispatch.cs
- MinimizableAttributeTypeConverter.cs
- FormsIdentity.cs
- TableLayoutSettings.cs
- Brushes.cs
- HttpWrapper.cs
- NativeObjectSecurity.cs
- CompositeKey.cs
- HttpHeaderCollection.cs
- KeyValueInternalCollection.cs
- FileDialogCustomPlace.cs
- Buffer.cs
- XPathDocumentBuilder.cs
- InsufficientExecutionStackException.cs
- SemaphoreFullException.cs
- SharedPersonalizationStateInfo.cs
- AuthenticationServiceManager.cs
- XmlSchemaObjectCollection.cs
- StringSorter.cs
- EventData.cs
- UInt64.cs
- XmlSchemaAttribute.cs
- XmlWrappingWriter.cs
- IdnMapping.cs
- TemplateBuilder.cs
- MarkupProperty.cs
- SessionStateUtil.cs
- EntityDataSourceColumn.cs
- InertiaRotationBehavior.cs
- PriorityChain.cs
- TextCompositionEventArgs.cs
- ObjectItemConventionAssemblyLoader.cs
- TagPrefixInfo.cs
- NetPipeSectionData.cs
- TabRenderer.cs
- ElementHost.cs
- PageParser.cs
- DBCSCodePageEncoding.cs
- SmiEventSink_Default.cs
- Canvas.cs
- Context.cs
- Speller.cs
- SQLBytes.cs
- CommandHelper.cs
- ActivityDesignerAccessibleObject.cs
- TextDecorationCollectionConverter.cs
- ResourceDisplayNameAttribute.cs
- WindowsScroll.cs
- RawStylusActions.cs
- StorageEndPropertyMapping.cs
- TemplateControlParser.cs
- ArgumentOutOfRangeException.cs
- ConfigurationSectionGroup.cs
- SocketInformation.cs
- CfgParser.cs
- SqlRowUpdatingEvent.cs
- BStrWrapper.cs
- CacheEntry.cs