context: // Copyright (c) Microsoft Corporation.  All rights reserved.
  
  //Copyright (c) Microsoft Corporation.  All rights reserved.
  using System;
  using System.Collections.ObjectModel;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.Windows;
  using System.Windows.Interop;
  using System.Windows.Markup;
  using Microsoft.SDK.Samples.VistaBridge.Interop;
  
  namespace Microsoft.SDK.Samples.VistaBridge.Library
  {
      /// <summary>
      /// The abstract base class for the common file dialogs.
      /// </summary>
      [ContentProperty("Controls")]
      public abstract class CommonFileDialog : IDialogControlHost
      {
          /// <summary>
          /// The collection of names selected by the user.
          /// </summary>
          protected readonly Collection<string> fileNames;
          internal NativeDialogShowState showState = 
              NativeDialogShowState.PreShow;
  
          private IFileDialog nativeDialog;
          private IFileDialogCustomize customize;
          private NativeDialogEventSink nativeEventSink;
          private bool? canceled;
          private bool resetSelections;
          private Window parentWindow;
  
          /// <summary>
          /// Contains a common error message string shared by classes that 
          /// inherit from this class.
          /// </summary>
          protected const string IllegalPropertyChangeString = 
              " cannot be changed while dialog is showing";
  
          #region Constructors
  
          /// <summary>
          /// Creates a new instance of this class.
          /// </summary>
          protected CommonFileDialog()
          {
              if (!Helpers.RunningOnVista)
                  throw new PlatformNotSupportedException(
                      "Common File Dialog requires Windows Vista or later.");
  
              fileNames = new Collection<string>();
              filters = new CommonFileDialogFilterCollection();
              controls = new DialogControlCollection<CommonFileDialogControl>(this);
          }
          
  		/// <summary>
          /// Creates a new instance of this class with the specified title.
          /// </summary>
          /// <param name="title">The title to display in the dialog.</param>
          protected CommonFileDialog(string title) : this()
          {
              this.title = title;
          }
  
          #endregion
  
          // Template method to allow derived dialog to create actual
          // specific COM coclass (e.g. FileOpenDialog or FileSaveDialog).
          internal abstract void InitializeNativeFileDialog();
          internal abstract IFileDialog GetNativeFileDialog();
          internal abstract void PopulateWithFileNames(
              Collection<string> names);
          internal abstract void CleanUpNativeFileDialog();
          internal abstract SafeNativeMethods.FOS GetDerivedOptionFlags(SafeNativeMethods.FOS flags);
  
          #region Public API
  
          // Events.
          /// <summary>
          /// Raised just before the dialog is about to return with a result.
          /// </summary>
          public event CancelEventHandler FileOk;
          /// <summary>
          /// Raised just before the user navigates to a new folder.
          /// </summary>
          public event EventHandler<CommonFileDialogFolderChangeEventArgs> FolderChanging;
          /// <summary>
          /// Raised when the user navigates to a new folder.
          /// </summary>
          public event EventHandler FolderChanged;
          /// <summary>
          /// Raised when the user changes the selection in the dialog's view.
          /// </summary>
          public event EventHandler SelectionChanged;
          /// <summary>
          /// Raised when the dialog is opened to notify the application of the initial chosen filetype.
          /// </summary>
          public event EventHandler FileTypeChanged;
          /// <summary>
          /// Raised when the dialog is opening.
          /// </summary>
          public event EventHandler Opening;
  
          private DialogControlCollection<CommonFileDialogControl> controls;
          /// <summary>
          /// Gets the collection of controls for the dialog.
          /// </summary>
          public DialogControlCollection<CommonFileDialogControl> Controls
          {
              get { return controls; }
          }
  
          private CommonFileDialogFilterCollection filters;
          /// <summary>
          /// Gets the filters used by the dialog.
          /// </summary>
          public CommonFileDialogFilterCollection Filters
          {
              get { return filters; }
          }
  
          private string title;
          /// <summary>
          /// Gets or sets the dialog title.
          /// </summary>
          /// <value>A <see cref="System.String"/> object.</value>
          public string Title
          {
              get { return title; }
              set 
              { 
                  title = value;
                  if (NativeDialogShowing)
                      nativeDialog.SetTitle(value);
              }
          }
  
          private bool addExtension;
          /// <summary>
          /// Gets or sets a value that determines whether the file extension
          /// set via the DefaultExtension property is appended to the file name.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value.</value>
  		/// 
          public bool AddExtension
          {
              get { return addExtension; }
              set { addExtension = value; }
          }
  
          // This will also work in Vista, but we need it in order to pick Libraries location
  #if WIN7
          private bool forceFileSystem = true;
  
          /// <summary>
          /// Allow to use not only file system locations 
          /// </summary>
          public bool ForceFileSystem
          {
              get { return forceFileSystem; }
              set
              {
                  ThrowIfDialogShowing("ForceFileSystem" + IllegalPropertyChangeString);
                  forceFileSystem = value;
              }
          }
  #endif
  
          // This is the first of many properties that are backed by the FOS_*
          // bitflag options set with IFileDialog.SetOptions(). 
          // SetOptions() fails
          // if called while dialog is showing (e.g. from a callback).
          private bool checkFileExists;
          /// <summary>
          /// Gets or sets a value to specify whether 
          /// the file returned must exist.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value. <b>True</b> if the file must exist.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool CheckFileExists
          {
              get { return checkFileExists; }
              set 
              {
                  ThrowIfDialogShowing("CheckFileExists" + IllegalPropertyChangeString);
                  checkFileExists = value; 
              }
          }
  
          private bool checkPathExists;
          /// <summary>
          /// Gets or sets a value to specify whether the file returned 
          /// must be in an existing folder.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value. <b>True</b> if the file must exist.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool CheckPathExists
          {
              get { return checkPathExists; }
              set 
              {
                  ThrowIfDialogShowing("CheckPathExists" + IllegalPropertyChangeString);
                  checkPathExists = value;
              }
          }
  
          private bool checkValidNames;
          /// <summary>Gets or sets a value that controls
          /// validation of file names.
          /// </summary>
          ///<value>A <see cref="System.Boolean"/> value. See remarks.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          /// <remarks>Specify <b>True </b>to check for situations that would prevent an application from opening the selected file, such as sharing violations or access denied errors.</remarks>
          public bool CheckValidNames
          {
              get { return checkValidNames; }
              set 
              {
                  ThrowIfDialogShowing("CheckValidNames" + IllegalPropertyChangeString);
                  checkValidNames = value; 
              }
          }
  
          private bool checkReadOnly;
          /// <summary>
          /// Gets or sets a value that determines 
          /// whether read-only items are returned.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value. <b>True</b> includes read-only items.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool CheckReadOnly
          {
              get { return checkReadOnly; }
              set 
              {
                  ThrowIfDialogShowing("CheckReadOnly" + IllegalPropertyChangeString);
                  checkReadOnly = value; 
              }
          }
  
          private bool restoreDirectory;
          /// <summary>
          /// Corresponds to the FOS_NOCHANGEDIR 
          /// file dialog option.
          /// </summary>
          /// <remarks>According to the win32 documentation,
          /// this value is not used.d</remarks>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool RestoreDirectory
          {
              get { return restoreDirectory; }
              set 
              {
                  ThrowIfDialogShowing("RestoreDirectory" + IllegalPropertyChangeString);
                  restoreDirectory = value; 
              }
          }
  
          private bool showPlacesList = true;
          /// <summary>
          /// Gets or sets a value that controls whether 
          /// to show or hide the list of pinned places that
          /// the user can choose from.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool ShowPlacesList
          {
  
              get { return showPlacesList; }
              set 
              {
                  ThrowIfDialogShowing("ShowPlacesList" + IllegalPropertyChangeString);
                  showPlacesList = value; 
              }
          }
  
          private bool addToMruList = true;
          /// <summary>
          /// Gets or sets a value that controls whether 
          /// to show or hide the list of places from which the 
          /// user has recently opened or saved items.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool AddToMruList
          {
              get { return addToMruList; }
              set 
              {
                  ThrowIfDialogShowing("AddToMruList" + IllegalPropertyChangeString);
                  addToMruList = value; 
              }
          }
  
          private bool showHiddenItems;
          ///<summary>
          /// Gets or sets a value that controls whether 
          /// to show hidden items.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool ShowHiddenItems
          {
              get { return showHiddenItems; }
              set 
              {
                  ThrowIfDialogShowing("ShowHiddenItems" + IllegalPropertyChangeString);
                  showHiddenItems = value; 
              }
          }
          private bool allowPropertyEditing;
          /// <summary>
          /// Gets or sets a value that controls whether 
          /// properties can be edited.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value. </value>
          public bool AllowPropertyEditing
          {
              get { return allowPropertyEditing; }
              set { allowPropertyEditing = value; }
          }
  
          private bool dereferenceLinks;
          ///<summary>
          /// Gets or sets a value that controls whether 
          /// shortcuts should be treated as their target items,
          /// allowing an application to open a .lnk file.
          /// </summary>
          /// <value>A <see cref="System.Boolean"/> value. <b>True</b> indicates that shortcuts should be treated as their targets. </value>
          /// <exception cref="System.InvalidOperationException">This property cannot be set when the dialog is visible.</exception>
          public bool DereferenceLinks
          {
              get { return dereferenceLinks; }
              set 
              {
                  ThrowIfDialogShowing("DereferenceLinks" + IllegalPropertyChangeString);
                  dereferenceLinks = value; }
          }
  
          private string defaultExtension = null;
  
          /// <summary>
          /// Gets or sets the default file extension to be added to file names.
          /// </summary>
          public string DefaultExtension
          {
              get { return defaultExtension; }
              set
              {
                  // Sanitize - null equals not set.
                  if (String.IsNullOrEmpty(value))
                      value = null;
  
                  defaultExtension = value;
              }
          }
  
          /// <summary>
          /// Tries to set the File(s) Type Combo to match the value in 
          /// 'defaultExtension'.  Only doing this if 'this' is a Save dialog 
          /// as it makes no sense to do this if only Opening a file.
          /// </summary>
          /// 
          /// <param name="dialog">The native/IFileDialog instance.</param>
          /// 
          private void SyncFileTypeComboToDefaultExtension(IFileDialog dialog)
          {
              // make sure it's a Save dialog and that there is a default 
              // extension to sync to.
              if (!(this is CommonSaveFileDialog) || defaultExtension == null ||
                  filters.Count <= 0)
              {
                  return;
              }
  
              // The native version of SetFileTypeIndex() requires an
              // unsigned integer as its parameter. This (having it be defined
              // as a uint right up front) avoids a cast, and the potential 
              // problems of casting a signed value to an unsigned one.
              uint filtersCounter = 0;
  
              CommonFileDialogFilter filter = null;
  
              for (filtersCounter = 0; filtersCounter < filters.Count; 
                  filtersCounter++)
              {
                  filter = (CommonFileDialogFilter)filters[(int)filtersCounter];
  
                  if (filter.Extensions.Contains(defaultExtension))
                  {
                      // set the docType combo to match this 
                      // extension. property is a 1-based index.
                      dialog.SetFileTypeIndex( filtersCounter + 1 );
  
                      // we're done, exit for
                      break;
                  }
              }
              
          }
  
          /// <summary>
          /// Gets the selected filename.
          /// </summary>
          /// <value>A <see cref="System.String"/> object.</value>
          /// <exception cref="System.InvalidOperationException">This property cannot be used when multiple files are selected.</exception>
          public string FileName
          {
              get
              {
                  CheckFileNamesAvailable();
                  
                  if (fileNames.Count > 1)
                      throw new InvalidOperationException("Multiple files selected - the FileNames property should be used instead.");
  
                  string returnFilename = fileNames[0];
  
                  // "If extension is a null reference (Nothing in Visual 
                  // Basic), the returned string contains the specified 
                  // path with its extension removed."  Since we do not want 
                  // to remove any existing extension, make sure the 
                  // defaultExtension property is NOT null.
  
                  // if we should, and there is one to set...
                  if (addExtension && !string.IsNullOrEmpty(defaultExtension))
                  {
                      returnFilename = System.IO.Path.ChangeExtension(returnFilename, defaultExtension);
                  }
  
                  return returnFilename;
              }
          }
  
          // Null = use default directory.
          private string initialDirectory;
          /// <summary>
          /// Gets or sets the initial directory displayed when the 
          /// dialog is shown.
          /// </summary>
          /// <value>A <see cref="System.String"/> object.</value>
          public string InitialDirectory
          {
              get { return initialDirectory; }
              set { initialDirectory = value; }
          }
  
          // Null = use default identifier.
          private Guid usageIdentifier;
          /// <summary>
          /// Gets or sets a value that enables a calling application 
          /// to associate a GUID with a dialog's persisted state.
          /// </summary>
          public Guid UsageIdentifier
          {
              get { return usageIdentifier; }
  
gt:             set { usageIdentifier = value; }