Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1134
  • Last Modified:

CFileDialog questions

Hi,

My installation directory looks like this:

    C:\....\.....\........\MyApp
                                 |
                                 ---- MyApp.exe
                                 |
                                 ---- SaveSubDirectory\

I am opening up a save file dialog so the user can save a file. How can I:

1) Pass 'SaveSubDirectory' as the initial folder where the save file dialog is pointed to? (Can I pass a relative path?)
2) Prevent the user from navigating out of that directory (so they can only save files in that initial directory).

Right now I'm popping up the save dialog like:

    CString strAllowedFormats = "XML (*.xml)|*.xml||";
    CFileDialog fd(FALSE, "pdf", "hello", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR, strAllowedFormats, NULL);
    if (fd.DoModal() != IDOK) {
        return;
    }

Thanks
0
minnirok
Asked:
minnirok
  • 4
  • 3
  • 2
  • +1
1 Solution
 
jkrCommented:
Almost there - try

    CString strAllowedFormats = "XML (*.xml)|*.xml||";
    CFileDialog fd(FALSE, "pdf", "hello", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR, strAllowedFormats, NULL);

    fd.m_ofn.lpstrInitialDir= "C:\\path\\MyApp\\SaveSubDirectory";

    if (fd.DoModal() != IDOK) {
        return;
    }
0
 
mahesh1402Commented:
>>Pass 'SaveSubDirectory' as the initial folder where the save file dialog is pointed to? (Can I pass a relative path?)

CFileOpen::m_ofn.lpstrInitialDir <== this is initial directory of file open dialog box.. you can set this path to point to any location.

>>2) Prevent the user from navigating out of that directory (so they can only save files in that initial directory).

option for this may be hook your dialog box to customize it and handle event to restrict user.

or simply

Derive from CFileDialog and override OnFolderChange(). You can redirect the user if he navigates to a path you don't like.

-MAHESH
0
 
mahesh1402Commented:
you can use example code from here : http://www.codeproject.com/dialog/xfiledialog.asp <==

MAHESH
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
minnirokAuthor Commented:
Anything else I can do besides deriving from CFileDialog (I'm a little lazy). If not, that's ok too. The initial path suggestion worked fine though. Just want to prevent them from switching save directories.

Thanks
0
 
jkrCommented:
>>Anything else I can do besides deriving from CFileDialog

As I wrote, just set that path:

    CString strAllowedFormats = "XML (*.xml)|*.xml||";
    CFileDialog fd(FALSE, "pdf", "hello", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR, strAllowedFormats, NULL);

    fd.m_ofn.lpstrInitialDir= "C:\\path\\MyApp\\SaveSubDirectory"; // <----- here!

    if (fd.DoModal() != IDOK) {
        return;
    }
0
 
DanRollinsCommented:
As tempting as that
   OFN_NOCHANGEDIR
looks, setting it does *not* prevent the user from selecting different directory.. he can browse anywhere on the filesystem or on the network or any PIDL and then click the [save] button and
   fd.szFilename
will contain the path to that directory.

You can easily write a hook proc to prevent the user from OKing the save with the unwanted path, but a smart U/I designer tries to not let a user complete his input and then say, "Nope!  You must do something else!"  The goal is to provide only the available options to the user in the first place.

There is an option that looks promising.  If you use the
   OFN_ENABLEINCLUDENOTIFY | OFN_EXPLORER | OFN_ENABLEHOOK
flags, then you can decide which items (including folders) to display... by nixing every folder except the one that was passed in originally as the initial path, you effectively limit the user to selecting only that one.

This is not more than about 30 lines of code (and does not require deriving from CFileDialog), but I'm pressed for time right now and can't put it together for you right now.  If you are interested in that option, let me know...

-- Dan
0
 
minnirokAuthor Commented:
@Dan
That would be awesome if you could give an example, I'm not pressed for time.

@jkr
Sorry jkr, I meant to say that your initial directory suggestion worked perfectly - I was just wondering about that method to prevent them from navigating out of that intial dir (the OFN_NOCHANGEDIR doesn't have anything to do with that). I could be misunderstanding your post, please let me know if so,

Thanks guys
0
 
mahesh1402Commented:
As I said previously you need to hook / subclass CFileDialog to handle events like changing folder....

http://www.codeguru.com/cpp/w-d/dislog/dialogforselectingfolders/comments.php/c1883/?thread=24384 <== here you will get example code about how to subclass CFolderDialog and how to handle notifications OnFolderChange / OnFileNameChange.

-MAHESH
0
 
DanRollinsCommented:
It turned out that the OFN_ENABLEINCLUDENOTIFY is no good... it does not let you look at standard filesystem files and folder, so you don't get a chance to eliminate folders from the list.  

In the following code, I used a bunch of tricks and workarounds but the result is streamlined and functional.

The hook proc handles two kinds of notifications.  The CDN_FOLDERCHANGE gets called early on and then (hopefully) never again.  What it does is look at the ListControl and remove the folders from the list (so a user can't select it and change to a different directory).

It also disables the two controls at the top that would allow a user to change to a different directory.  THe only flaw is if the user types (eg, .. (dot dot) or a C:\somedir into the text box.  In that case, the proc that deletes folders from the list will see an invalid filename and will display an error message and cancel the operation.  I was hoping to avoid that, but I'm not seeing any way around it.  I'd prefer to just set the directory back, but I can't see how to do that in the hook.  

Note: you could handle that in the calling proc... just set a flag when forcing the cancel and the in the calling proc, loop back doing DoModal() again, resetting to the defualt (only allowed directory).

At the top of SaveAs_OnNotify is a creepy bit of business -- there is a bug in MFC that makes it ASSERT (for no good reason) when you are using a hookproc.  What you see there is a workaround I found on google groups.  An alternative is to call
    ::GetSaveFileName(&fd.m_ofn);  
instead of fd.DoModal() (as shown in the code)

-- Dan
0
 
DanRollinsCommented:
BOOL WINAPI SaveAs_OnNotify( HWND hWnd, NMHDR* phdr )
{
      //--------------------- a workaround for a bug in MFC
      _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
      pThreadState->m_pAlternateWndInit= NULL;

      //---------------------------------- Learn the initial directory
      OFNOTIFY* pON= (OFNOTIFY*)phdr;
      CString sDir= pON->lpOFN->lpstrInitialDir;
      sDir += "\\";
      //---------------------------------- disable two pesky comtrols
      HWND hwndDlg= ::GetParent(hWnd );
      HWND hwndCombo= GetDlgItem( hwndDlg, 0x471 );
      ::EnableWindow( hwndCombo, FALSE );
      HWND hwndToolbar= ::FindWindowEx(hwndDlg, 0, "ToolbarWindow32", 0 );
      ::EnableWindow( hwndToolbar, FALSE );
      
      //---------------------------------- access the ListView control
      HWND hwndTmp= GetDlgItem( hwndDlg, 0x461 );
      HWND hwndList= GetDlgItem( hwndTmp, 1 );
      
      CListCtrl ctlList;
      ctlList.Attach(hwndList);
      int nCnt= ctlList.GetItemCount();
      //--------------- for each item in the list, if it is a dir, delete
      for (int j=nCnt-1; j>=0; j-- ) {
            CString sItem= ctlList.GetItemText( j, 0 );
            sItem.Insert(0,sDir);
            CFileFind cf;
            if ( cf.FindFile( sItem ) ) {
                  cf.FindNextFile();
                  if ( cf.IsDirectory() ) {
                        ctlList.DeleteItem(0);      // remove it from the list
                  }
            }
            else {            // the user used some trick like typing .. (dot dot) to change dirs
                  AfxMessageBox("Invalid Save As... option\n"
                        "Please Save in the specified folder", MB_ICONEXCLAMATION );
                  ::PostMessage(hwndDlg, WM_COMMAND, IDCANCEL, 0 );
                  break;
            }
            cf.Close();
      }      
      ctlList.Detach();
      return( 0 );
}

UINT WINAPI MyHookProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
      switch (msg) {
            case WM_NOTIFY:
                  NMHDR* phdr = (NMHDR*)lParam;
                  switch( phdr->code ) {
                        case CDN_TYPECHANGE:
                        case CDN_FOLDERCHANGE:
                  return( SaveAs_OnNotify (hWnd, (NMHDR *)lParam) );
            }
      }
      return 0;
}
 
void CD27Dlg::OnButton1()
{
      CString strAllowedFormats = "XML (*.xml)|*.xml|All Files (*.*)|*.*||";
      CFileDialog fd(FALSE, "XML", "TheFilename",
            OFN_ENABLEINCLUDENOTIFY | OFN_EXPLORER | OFN_ENABLEHOOK |
            OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR,
            strAllowedFormats, NULL
      );

      fd.m_ofn.lpstrInitialDir= "C:\\temp\\xmlstuff";
      fd.m_ofn.lpfnHook=MyHookProc;

      int nRet= fd.DoModal();
      // int nRet= ::GetSaveFileName(&fd.m_ofn);  //--- alternative for workaround for a bug in MFC
      if (nRet == IDOK ) {
            MessageBox( fd.m_ofn.lpstrFile, "Selected filename..." );
      } else {
            MessageBox( "Canceled" );
      }
}
0
 
DanRollinsCommented:
Here's a more compact version the uses that "looping" idea I mentioned.  I went ahead and wrote a derived class that overrides DoModal... looping back if the user attempts to do anything to change to a different directory.  In all cases it Beeps and resets to the initial directory and filename -- including that pesky case where the user manually types in a filename with a directory.

It also avoids the awkwards MFC bug-fix related to  pThreadState->m_pAlternateWndInit ASSERT (since we are deriving, it is handled without error).

The classs implementation code is containe entriely in the header, and can be inserted right into the source file (as shown below) or it can be made into a separate .H file and #included normally.

Not that the initial (only) directory is set as the second parm in the CTor -- that the is parm order is a little different from that of CFileDialog.

//======================================================
class CFileDialogNoChdir : public CFileDialog
{
public:
      CFileDialogNoChdir(BOOL bf,  // TRUE for FileOpen, FALSE for FileSaveAs
            LPCSTR sOnlyDir,         // added for this derived object
            LPCTSTR lpszDefExt = NULL,
            LPCTSTR lpszFileName = NULL,
            DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
            LPCTSTR lpszFilter = NULL,
            CWnd* pParentWnd = NULL) :
            CFileDialog(b, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd)
      {
            m_ofn.lpstrInitialDir= sOnlyDir;
            m_sOnlyDir= sOnlyDir;
            m_sDefaultFile= lpszFileName;
            m_fAttemptedChdir= FALSE;
      }
      virtual void OnFolderChange() {
            char sBuf[ _MAX_PATH ];
            GetParent()->SendMessage( CDM_GETFOLDERPATH, (WPARAM)sizeof(sBuf), (LPARAM)sBuf );
            if (m_sOnlyDir.CompareNoCase(sBuf) != 0 ) {
                  MessageBeep(-1);
                  m_fAttemptedChdir= TRUE;
                  GetParent()->PostMessage( WM_COMMAND, IDCANCEL );
            }
      }
      virtual int DoModal() {
            while( TRUE ) {
                  m_fAttemptedChdir= FALSE;
                  strcpy(m_szFileName, m_sDefaultFile );
                  m_ofn.lpstrInitialDir= m_sOnlyDir;

                  int nRet= CFileDialog::DoModal();
                  if (nRet == IDCANCEL && !m_fAttemptedChdir ) { // regular cancel
                        return( IDCANCEL );
                  }
                  if ( nRet == IDOK ) {
                        CString sFile= GetFileName();
                        CString sPath= GetPathName();
                        CString sPathSel= sPath.Left( sPath.GetLength()-(sFile.GetLength()+1) );
                        if ( sPathSel.CompareNoCase(m_sOnlyDir) != 0) {
                              MessageBeep(-1);
                        }
                        else {
                              return (nRet);
                        }
                  }
            }
      }
      BOOL m_fAttemptedChdir;
      CString m_sOnlyDir;
      CString m_sDefaultFile;
};
//======================================================

void CD27Dlg::OnButton3()
{
      CString sAllowedFormats = "XML (*.xml)|*.xml|All Files (*.*)|*.*||";
      DWORD dwFlags= OFN_EXPLORER | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR;

      CString sDefaultFilename="NewDocument1.xml";
      CString sOnlyAllowedDir= "C:\\temp\\xmlstuff";

      CFileDialogNoChdir fd(FALSE, sOnlyAllowedDir, "XML",
            sDefaultFilename, dwFlags, sAllowedFormats );
      int nRet= fd.DoModal();
      if (nRet == IDOK ) {
            MessageBox( fd.m_ofn.lpstrFile, "Selected filename OK!..." );
      }
}
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 4
  • 3
  • 2
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now