0

I have created descendent TFileOpenDialog and TFileSaveDialog. Are the GUID's for both the same or are they different? I used this for both TFileOpen and TFileSave dialogs:

FileDialog.QueryInterface(StringToGUID('{8016B7B3-3D49-4504-A0AA-2A37494E606F}'), FileDialogCustomize);

I have created two demos to test the component. One run time and the other design time.

I ask about the GUID's because when I create the TFileOpenDialog descendent at run time the TFileOpenDialog is shown with a title of 'Open File' and the preview is shown on the right hand side of the dialog. It is perfect and functions as you would expect but when I install the dialogs and run the design time demo the dialog appears with a title of 'Save File' and no preview is shown.

I can not see why this might occur. If necessary I can show some code but I do not see why the design time and run time would behave differently as the dialogs are created differently:

//TFileOpenDialog descendent
CoCreateInstance(CLSID_FileOpenDialog, nil, CLSCTX_INPROC_SERVER,
    IFileOpenDialog, FileDialog);
//TFileSaveDialog descendent
CoCreateInstance(CLSID_FileSaveDialog, nil, CLSCTX_INPROC_SERVER,
    IFileSaveDialog, FileDialog);

Here is the Open Dialogs Code:

{ TIEWin7FileOpenDialog }

constructor TIEWin7FileOpenDialog.Create(AOwner: TComponent);
begin
  FillOpenFileTypesFilter;
  inherited Create(AOwner);
end;

destructor TIEWin7FileOpenDialog.Destroy;
begin
  inherited Destroy;
end;

procedure TIEWin7FileOpenDialog.FillOpenFileTypesFilter;
{ Fill the TIEWin7FileOpenDialog FFilterArray with FileTypes }
begin
  CoCreateInstance(CLSID_FileOpenDialog, nil, CLSCTX_INPROC_SERVER,
    IFileOpenDialog, FileDialog);
  { Create an array of file types for OpenDialog }
  SetLength(FFilterArray, 26);
  FFilterArray[0].pszName := PWideChar(Widestring('Common Graphic Files'));
  FFilterArray[0].pszSpec :=
    PWideChar(Widestring
    ('*.tif;*.tiff;*.gif;*.png;*.apf;*.cur;*.pcx;*.ani;*.jpg;*.jpeg;*.jp2;*.j2k*;*.bmp;*.ico;*.emf;*.wmf;*.tga;*.wdp;*.hdp;*.avi')
    );
  FFilterArray[1].pszName := PWideChar(Widestring('JPEG Bitmap'));
  FFilterArray[1].pszSpec := PWideChar(Widestring('*.jpg;*.jpeg;*.jpe;*.jif'));
  FFilterArray[2].pszName := PWideChar(Widestring('TIFF Bitmap'));
  FFilterArray[2].pszSpec :=
    PWideChar(Widestring('*.tif;*.tiff;*.fax;*.g3n;*.g3f;*.xif'));
  FFilterArray[3].pszName := PWideChar(Widestring('CompuServe Bitmap'));
  FFilterArray[3].pszSpec := PWideChar(Widestring('*.gif'));
  FFilterArray[4].pszName := PWideChar(Widestring('PaintBrush'));
  FFilterArray[4].pszSpec := PWideChar(Widestring('*.pcx'));
  FFilterArray[5].pszName := PWideChar(Widestring('Windows Bitmap'));
  FFilterArray[5].pszSpec := PWideChar(Widestring('*.bmp;*.dib;*.rle'));
  FFilterArray[6].pszName := PWideChar(Widestring('Windows Icon'));
  FFilterArray[6].pszSpec := PWideChar(Widestring('*.ico'));
  FFilterArray[7].pszName := PWideChar(Widestring('Windows Cursor'));
  FFilterArray[7].pszSpec := PWideChar(Widestring('*.cur'));
  FFilterArray[8].pszName := PWideChar(Widestring('Portable Network Graphics'));
  FFilterArray[8].pszSpec := PWideChar(Widestring('*.png'));
  FFilterArray[9].pszName := PWideChar(Widestring('DICOM Bitmap'));
  FFilterArray[9].pszSpec := PWideChar(Widestring('*.dcm;*.dic;*.dicom;*.v2'));
  FFilterArray[10].pszName := PWideChar(Widestring('Windows Metafile'));
  FFilterArray[10].pszSpec := PWideChar(Widestring('*.wmf'));
  FFilterArray[11].pszName := PWideChar(Widestring('Targa Bitmap'));
  FFilterArray[11].pszSpec :=
    PWideChar(Widestring('*.tga;*.targa;*.vda;*.icb;*.vst;*.pix'));
  FFilterArray[12].pszName :=
    PWideChar(Widestring('Portable Pixmap, GrayMap, BitMap'));
  FFilterArray[12].pszSpec := PWideChar(Widestring('*.pxm;*.ppm;*.pgm;*.pbm'));
  FFilterArray[13].pszName := PWideChar(Widestring('Wireless Bitmap'));
  FFilterArray[13].pszSpec := PWideChar(Widestring('*.wbmp'));
  FFilterArray[14].pszName := PWideChar(Widestring('JPEG2000'));
  FFilterArray[14].pszSpec := PWideChar(Widestring('*.jp2'));
  FFilterArray[15].pszName := PWideChar(Widestring('JPEG2000 Code Stream'));
  FFilterArray[15].pszSpec := PWideChar(Widestring('*.j2k;*.jpc;*.j2c'));
  FFilterArray[16].pszName := PWideChar(Widestring('Multipage PCX'));
  FFilterArray[16].pszSpec := PWideChar(Widestring('*.dcx'));
  FFilterArray[17].pszName := PWideChar(Widestring('Camera RAW'));
  FFilterArray[17].pszSpec :=
    PWideChar(Widestring
    ('*.crw;*.cr2;*.nef;*.raw;*.pef;*.raf;*.x3f;*.bay;*.orf;*.srf;*.mrw;*.dcr;*.sr2')
    );
  FFilterArray[18].pszName := PWideChar(Widestring('Photoshop PSD'));
  FFilterArray[18].pszSpec := PWideChar(Widestring('*.psd'));
  FFilterArray[19].pszName :=
    PWideChar(Widestring('ImageEn Vectorial Objects'));
  FFilterArray[19].pszSpec := PWideChar(Widestring('*.iev'));
  FFilterArray[20].pszName := PWideChar(Widestring('ImageEn Layers'));
  FFilterArray[20].pszSpec := PWideChar(Widestring('*.lyr'));
  FFilterArray[21].pszName :=
    PWideChar(Widestring('ImageEn Layers and Objects'));
  FFilterArray[21].pszSpec := PWideChar(Widestring('*.all'));
  FFilterArray[22].pszName := PWideChar(Widestring('Microsoft HD Photo'));
  FFilterArray[22].pszSpec := PWideChar(Widestring('*.wdp;*.hdp'));
  FFilterArray[23].pszName := PWideChar(Widestring('Video For Windows'));
  FFilterArray[23].pszSpec := PWideChar(Widestring('*.avi'));
  FFilterArray[24].pszName := PWideChar(Widestring('Mpeg'));
  FFilterArray[24].pszSpec := PWideChar(Widestring('*.mpeg;*.mpg'));
  FFilterArray[25].pszName := PWideChar(Widestring('Windows Media Video'));
  FFilterArray[25].pszSpec := PWideChar(Widestring('*.wmv'));
  { Set the FileTypes Filter Array }
  FilterArray := FFilterArray;
end;

function TIEWin7FileOpenDialog.DoExecute: BOOL;
{ Create the dialogs and add control items }
const
  dwVisualGroup1ID: DWORD = 1200;
  dwVisualGroup2ID: DWORD = 1300;
  dwVisualGroup3ID: DWORD = 1400;
  dwVisualGroup4ID: DWORD = 1500;
  dwVisualGroup5ID: DWORD = 1600;
  dwVisualGroup6ID: DWORD = 1700;
  dwVisualGroup7ID: DWORD = 1800;
var
  iFileDialogEvent: TFileDialogEvent;
  iCookie: cardinal;
  iWideString: Widestring;
  iFilename: PWideChar;
  iFileTypeIndex: Integer;
  hr: HResult;
  ShellItem: IShellItem;
  iOptionsSet: cardinal;
begin
  { Add labels }
  FileDialog.QueryInterface(IFileDialogCustomize, FileDialogCustomize);
  FileDialogCustomize.StartVisualGroup(dwVisualGroup1ID, 'Frames');
  FileDialogCustomize.AddControlItem(1, 0, 'Frames: ');
  FileDialogCustomize.AddText(1, '');
  FileDialogCustomize.EndVisualGroup;
  { Add a visual group for BitDepth }
  FileDialogCustomize.StartVisualGroup(dwVisualGroup2ID, 'BitDepth');
  FileDialogCustomize.AddControlItem(2, 1, 'BitDepth: ');
  FileDialogCustomize.AddText(2, '');
  FileDialogCustomize.EndVisualGroup;
  { Add a visual group for Dimensions }
  FileDialogCustomize.StartVisualGroup(dwVisualGroup3ID, 'Dimensions');
  FileDialogCustomize.AddControlItem(3, 2, 'Dimensions:');
  FileDialogCustomize.AddText(3, '');
  FileDialogCustomize.EndVisualGroup;
  { Add a visual group for DPI }
  FileDialogCustomize.StartVisualGroup(dwVisualGroup4ID, 'DPI');
  FileDialogCustomize.AddControlItem(4, 3, 'DPI:');
  FileDialogCustomize.AddText(4, '');
  FileDialogCustomize.EndVisualGroup;
  { Add a visual group for FileType }
  FileDialogCustomize.StartVisualGroup(dwVisualGroup5ID, 'File Type');
  FileDialogCustomize.AddControlItem(5, 4, 'File Type:');
  FileDialogCustomize.AddText(5, '');
  FileDialogCustomize.EndVisualGroup;
  { Add a visual group for FileSize }
  FileDialogCustomize.StartVisualGroup(dwVisualGroup6ID, 'File Size');
  FileDialogCustomize.AddControlItem(6, 5, 'File Type:');
  FileDialogCustomize.AddText(6, '');
  FileDialogCustomize.EndVisualGroup;
  { Add a visual group for MemorySize }
  FileDialogCustomize.StartVisualGroup(dwVisualGroup7ID, 'Memory Size');
  FileDialogCustomize.AddControlItem(7, 6, 'Memory:');
  FileDialogCustomize.AddText(7, '');
  FileDialogCustomize.EndVisualGroup;
  { Set Default Extension }
  iWideString := self.DefaultExtension;
  FileDialog.SetDefaultExtension(PWideChar(iWideString));
  { Set Default Filename }
  iWideString := FileName;
  iFilename := PWideChar(FileName);
  FileDialog.SetFilename(iFilename);
  { Set FileType (filter) index }
  iFileTypeIndex := 1;
  FileDialog.SetFileTypeIndex(iFileTypeIndex);
  self.FileTypeIndex := iFileTypeIndex;
  { Set Options }
  iOptionsSet := 0;
  if fdoOverwritePrompt in Options then
    iOptionsSet := iOptionsSet + FOS_OVERWRITEPROMPT;
  if fdoStrictFileTypes in Options then
    iOptionsSet := iOptionsSet + FOS_STRICTFILETYPES;
  if fdoNoChangeDir in Options then
    iOptionsSet := iOptionsSet + FOS_NOCHANGEDIR;
  if fdoPickFolders in Options then
    iOptionsSet := iOptionsSet + FOS_PICKFOLDERS;
  if fdoForceFileSystem in Options then
    iOptionsSet := iOptionsSet + FOS_FORCEFILESYSTEM;
  if fdoAllNonStorageItems in Options then
    iOptionsSet := iOptionsSet + FOS_ALLNONSTORAGEITEMS;
  if fdoNoValidate in Options then
    iOptionsSet := iOptionsSet + FOS_NOVALIDATE;
  if fdoAllowMultiSelect in Options then
    iOptionsSet := iOptionsSet + FOS_ALLOWMULTISELECT;
  if fdoPathMustExist in Options then
    iOptionsSet := iOptionsSet + FOS_PATHMUSTEXIST;
  if fdoFileMustExist in Options then
    iOptionsSet := iOptionsSet + FOS_FILEMUSTEXIST;
  if fdoCreatePrompt in Options then
    iOptionsSet := iOptionsSet + FOS_CREATEPROMPT;
  if fdoShareAware in Options then
    iOptionsSet := iOptionsSet + FOS_SHAREAWARE;
  if fdoNoReadOnlyReturn in Options then
    iOptionsSet := iOptionsSet + FOS_NOREADONLYRETURN;
  if fdoNoTestFileCreate in Options then
    iOptionsSet := iOptionsSet + FOS_NOTESTFILECREATE;
  if fdoHideMRUPlaces in Options then
    iOptionsSet := iOptionsSet + FOS_HIDEMRUPLACES;
  if fdoHidePinnedPlaces in Options then
    iOptionsSet := iOptionsSet + FOS_HIDEPINNEDPLACES;
  if fdoNoDereferenceLinks in Options then
  iOptionsSet := iOptionsSet + FOS_NODEREFERENCELINKS;
  if fdoDontAddToRecent in Options then
    iOptionsSet := iOptionsSet + FOS_DONTADDTORECENT;
  if fdoForceShowHidden in Options then
    iOptionsSet := iOptionsSet + FOS_FORCESHOWHIDDEN;
  if fdoDefaultNoMiniMode in Options then
    iOptionsSet := iOptionsSet + FOS_DEFAULTNOMINIMODE;
  if fdoForcePreviewPaneOn in Options then
    iOptionsSet := iOptionsSet + FOS_FORCEPREVIEWPANEON;
  FileDialog.SetOptions(iOptionsSet);
  { The array of file types for OpenDialog is initialized in OnCreate }
  { Set the FileTypes }
  FileDialog.SetFileTypes(26, FFilterArray);
  { Setup the TFileDialogEvents }
  iFileDialogEvent := TFileDialogEvent.Create;
  iFileDialogEvent.FType := 'Open';
  iFileDialogEvent.QueryInterface(IFileDialogEvents, FileDialogEvents);
  FileDialog.Advise(iFileDialogEvent, iCookie);
  { Show the dialog }
  hr := FileDialog.Show(Application.Handle);
  if hr = 0 then
  begin
    ShellItem := nil;
    hr := FileDialog.GetResult(ShellItem);
    if hr = 0 then
    begin
      hr := ShellItem.GetDisplayName(SIGDN_FILESYSPATH, iFilename);
      if hr = 0 then
        FileName := iFilename;
    end;
    Result := true;
  end
  else
  begin
    Result := False;
  end;
  FileDialog.Unadvise(iCookie);
end;

function TIEWin7FileOpenDialog.Execute: Boolean;
{ Handle Execute which calls DoExecute before the dialog is visible }
begin
  Result := DoExecute;
end;

procedure TIEWin7FileOpenDialog.OnExecute;
{ Handle Execute which calls DoExecute before the dialog is visible }
begin
  DoExecute;
end;
Bill
  • 2,993
  • 5
  • 37
  • 71
  • The Open and Save dialogs are implemented as different COM objects, so they have different `CLSID` and `IID` values from each other, but they both implement `IFileDialog` (IID `42F85136-DB7E-439C-85F1-E4075D135FC8`). If you are creating the dialogs using the correct CLSIDs, there is no way an open dialog can display the save dialog's title, unless you are setting the title yourself. – Remy Lebeau Sep 23 '15 at 17:08
  • `8016B7B3-3D49-4504-A0AA-2A37494E606F` is the IID for `IFileDialogCustomize`, which both dialogs also implement. BTW, you don't need to use `StrToGUID()` when calling `QueryInterface()`, use the actual interface type and let the compiler figure out the IID for it: `FileDialog.QueryInterface(IFileDialogCustomize, FileDialogCustomize);` – Remy Lebeau Sep 23 '15 at 17:11
  • Have any idea why the run time works perfectly and the design time does not? – Bill Sep 23 '15 at 17:14
  • Also not only is the title is wrong but the preview is not present either with design time. Run time is shown as expected. – Bill Sep 23 '15 at 17:20
  • What you describe suggests you really are creating two different dialogs. Please show the actual (relevant) code for the dialogs. Or post a [MCVE](http://stackoverflow.com/help/mcve) that demonstrates the same problem. – Remy Lebeau Sep 23 '15 at 17:30
  • Ok. I'll post the units code. – Bill Sep 23 '15 at 17:33
  • Remy... I have tried to modify my post to show the code, but despite my attempts to reduce the code down to the bare minimum, I can not get the code block to post. Can I send you a zip file with the runtime and design time demo to look at? There is about 800 lines of code in the unit involved. – Bill Sep 23 '15 at 18:56
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90468/discussion-between-remy-lebeau-and-bill). – Remy Lebeau Sep 23 '15 at 19:07
  • I posted just the TIEWin7FileOpenDialog code. – Bill Sep 23 '15 at 19:23
  • @Bill Why not take the time to make a [mcve]? – David Heffernan Sep 23 '15 at 19:42

1 Answers1

1

I would suggest an alternative approach - derive TIEWin7FileOpenDialog from Delphi's existing Vcl.Dialogs.TFileOpenDialog component, which wraps the IFileOpenDialog interface for you.

Have the constructor populate the inherited TFileOpenDialog.FileTypes collection as needed, and override the virtual TFileOpenDialog.DoOnExecute() method to add your visual groups and hook up your IFileDialogControlEvents handler. DoOnExecute() is called after the IFileDialog is created and before its Show() method is called. The IFileDialog interface will be available in the TFileOpenDialog.Dialog property.

Everything else your code is manually doing is already handled by TFileOpenDialog for you.

Try this:

uses
  ..., Vcl.Dialogs, Winapi.ShlObj;

type
  TIEWin7FileOpenDialog = class(TFileOpenDialog)
  protected
    procedure DoOnExecute; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

  TIEWin7FileOpenDialogControlEvents = class(TInterfacedObject, IFileDialogEvents, IFileDialogControlEvents)
  public
    // implement IFileDialogControlEvents as needed.
    // TFileOpenDialog already has an IFileDialogEvents
    // implementation so just ignore those methods, though
    // you still have to implement them here...
  end;

constructor TIEWin7FileOpenDialog.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  { Fill the FileTypes collection }
  with FileTypes.Add do
  begin
    DisplayName := 'Common Graphic Files';
    FileMask := '*.tif;*.tiff;*.gif;*.png;*.apf;*.cur;*.pcx;*.ani;*.jpg;*.jpeg;*.jp2;*.j2k*;*.bmp;*.ico;*.emf;*.wmf;*.tga;*.wdp;*.hdp;*.avi';
  end;
  with FileTypes.Add do
  begin
    DisplayName := 'JPEG Bitmap';
    FileMask := '*.jpg;*.jpeg;*.jpe;*.jif';
  end;
  // and so on...

  { Set FileType (filter) index }
  FileTypeIndex := 1;
end;

procedure TIEWin7FileOpenDialog.DoOnExecute;
const
  dwVisualGroup1ID: DWORD = 1200;
  dwVisualGroup2ID: DWORD = 1300;
  dwVisualGroup3ID: DWORD = 1400;
  dwVisualGroup4ID: DWORD = 1500;
  dwVisualGroup5ID: DWORD = 1600;
  dwVisualGroup6ID: DWORD = 1700;
  dwVisualGroup7ID: DWORD = 1800;
var
  DialogCustomize: IFileDialogCustomize;
  DialogEvents: TIEWin7FileOpenDialogControlEvents;
  Cookie: Cardinal;
begin
  DialogCustomize := Self.Dialog as IFileDialogCustomize;

  { Add labels }
  DialogCustomize.StartVisualGroup(dwVisualGroup1ID, 'Frames');
  DialogCustomize.AddControlItem(1, 0, 'Frames: ');
  DialogCustomize.AddText(1, '');
  DialogCustomize.EndVisualGroup;
  { Add a visual group for BitDepth }
  DialogCustomize.StartVisualGroup(dwVisualGroup2ID, 'BitDepth');
  DialogCustomize.AddControlItem(2, 1, 'BitDepth: ');
  DialogCustomize.AddText(2, '');
  DialogCustomize.EndVisualGroup;
  { Add a visual group for Dimensions }
  DialogCustomize.StartVisualGroup(dwVisualGroup3ID, 'Dimensions');
  DialogCustomize.AddControlItem(3, 2, 'Dimensions:');
  DialogCustomize.AddText(3, '');
  DialogCustomize.EndVisualGroup;
  { Add a visual group for DPI }
  DialogCustomize.StartVisualGroup(dwVisualGroup4ID, 'DPI');
  DialogCustomize.AddControlItem(4, 3, 'DPI:');
  DialogCustomize.AddText(4, '');
  DialogCustomize.EndVisualGroup;
  { Add a visual group for FileType }
  DialogCustomize.StartVisualGroup(dwVisualGroup5ID, 'File Type');
  DialogCustomize.AddControlItem(5, 4, 'File Type:');
  DialogCustomize.AddText(5, '');
  DialogCustomize.EndVisualGroup;
  { Add a visual group for FileSize }
  DialogCustomize.StartVisualGroup(dwVisualGroup6ID, 'File Size');
  DialogCustomize.AddControlItem(6, 5, 'File Type:');
  DialogCustomize.AddText(6, '');
  DialogCustomize.EndVisualGroup;
  { Add a visual group for MemorySize }
  DialogCustomize.StartVisualGroup(dwVisualGroup7ID, 'Memory Size');
  DialogCustomize.AddControlItem(7, 6, 'Memory:');
  DialogCustomize.AddText(7, '');
  DialogCustomize.EndVisualGroup;

  { Setup the TFileDialogEvents }
  DialogEvents := TIEWin7FileOpenDialogControlEvents.Create;
  DialogEvents.FType := 'Open';
  Self.Dialog.Advise(DialogEvents, Cookie);

  { trigger OnExecute event handler }
  inherited;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • [dcc32 Error] IEWin7FileDialogs.pas(72): E2137 Method 'DoExecute' not found in base class so I changed it to Execute and it compiles, but causes an exception at DialogCustomize := Self.Dialog as IFileDialogCustomize;? Self.Dialog is nil – Bill Sep 23 '15 at 21:28
  • Changed Execute to procedure TIEWin7FileOpenDialog.DoOnExecute;... now it compiles and functions. Thank-you – Bill Sep 23 '15 at 22:06
  • `Execute()` would be the wrong method to override, since it is the main workhorse for `TFileOpenDialog`. The `OnExecute` event would be fine to use, though. `OnExecute` is called by `DoOnExecute()`, not `DoExecute()` (my bad!). Override `DoOnExecute()`, if you can. It is `strict protected`, so hopefully the compiler will still allow you to override it across unit boundaries. I never use `strict` so I'm not clear on its sematics. In any case, I have corrected the typo in my answer. – Remy Lebeau Sep 23 '15 at 22:08
  • I wound up using { Protected declarations } procedure DoOnExecute; override; – Bill Sep 23 '15 at 22:44
  • Remy, boy your version is far simpler than mine was... thank-you – Bill Sep 23 '15 at 22:45