2

I select an image using the CMFCEditBrowseCtrl which has the name:

D:\WhatsApp Image 2020-04-02 at 13.03.48.jpeg

So it is now selected in the control:

Selected Image in browser control

Now, I decide to hit the Browse button again:

Browse File

See?

D:\WhatsApp Image 2020-04-02 at 13.03.48.jpeg appears to be truncated to to at 13.03.48.jpeg. But the moment I click the mouse into the filename control it then shows correct:

Click file name control

It doesn't always show the full name again if you click the edit box. But guaranteed, if you click OK it will be correct and complete.

This is going to be confusing for the user.


Update 1

If I click in the filename and click the HOME button on the keyboard then the rest of the file name comes into view.


Update 2

I have delved into the MFC source code for this bit and this is what it looks like:

case BrowseMode_File:
    {
        CString strFile;
        GetWindowText(strFile);

        if (!strFile.IsEmpty())
        {
            TCHAR fname [_MAX_FNAME];

            _tsplitpath_s(strFile, NULL, 0, NULL, 0, fname, _MAX_FNAME, NULL, 0);

            CString strFileName = fname;
            strFileName.TrimLeft();
            strFileName.TrimRight();

            if (strFileName.IsEmpty())
            {
                strFile.Empty();
            }

            const CString strInvalidChars = _T("*?<>|");
            if (strFile.FindOneOf(strInvalidChars) >= 0)
            {
                if (!OnIllegalFileName(strFile))
                {
                    SetFocus();
                    return;
                }
            }
        }

        CFileDialog dlg(TRUE, !m_strDefFileExt.IsEmpty() ? (LPCTSTR)m_strDefFileExt : (LPCTSTR)NULL, strFile, m_dwFileDialogFlags, !m_strFileFilter.IsEmpty() ? (LPCTSTR)m_strFileFilter : (LPCTSTR)NULL, NULL);
        if (dlg.DoModal() == IDOK && strFile != dlg.GetPathName())
        {
            SetWindowText(dlg.GetPathName());
            SetModify(TRUE);
            OnAfterUpdate();
        }

        if (GetParent() != NULL)
        {
            GetParent()->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
        }
    }
    break;
}

Update 3

I have tried to roll out my own class that overrides the OnBrowse handler. It has improved logic for setting the default filter index and default file extension:

#include "stdafx.h"
#include "MyMFCEditBrowseFileCtrl.h"

IMPLEMENT_DYNAMIC(CMyMFCEditBrowseFileCtrl, CMFCEditBrowseCtrl)

BEGIN_MESSAGE_MAP(CMyMFCEditBrowseFileCtrl, CMFCEditBrowseCtrl)
END_MESSAGE_MAP()

void CMyMFCEditBrowseFileCtrl::OnBrowse()
{
    CString strFile, strFileExtension;
    GetWindowText(strFile);

    if (!strFile.IsEmpty())
    {
        TCHAR fname[_MAX_FNAME];
        TCHAR ext[_MAX_EXT];

        _tsplitpath_s(strFile, NULL, 0, NULL, 0, fname, _MAX_FNAME, ext, _MAX_EXT);

        CString strFileName = fname;
        strFileName.TrimLeft();
        strFileName.TrimRight();

        if (strFileName.IsEmpty())
        {
            strFile.Empty();
        }

        strFileExtension = ext;
        strFileExtension.Trim();
        strFileExtension.MakeLower();

        const CString strInvalidChars = _T("*?<>|");
        if (strFile.FindOneOf(strInvalidChars) >= 0)
        {
            if (!OnIllegalFileName(strFile))
            {
                SetFocus();
                return;
            }
        }
    }

    int iFilterIndex = 2; // jpg - fallback
    m_strDefFileExt = _T("jpg");

    if (strFileExtension == _T(".gif"))
    {
        iFilterIndex = 1;
        m_strDefFileExt = _T("gif");
    }
    else if (strFileExtension == _T(".jpeg") || strFileExtension == _T(".jpg"))
    {
        iFilterIndex = 2;
        m_strDefFileExt = _T("jpg");
    }
    else if (strFileExtension == _T(".png"))
    {
        iFilterIndex = 3;
        m_strDefFileExt = _T("png");
    }
    else if (strFileExtension == _T(".tif") || strFileExtension == _T(".tiff"))
    {
        iFilterIndex = 4;
        m_strDefFileExt = _T("tif");
        
    }
    else if (strFileExtension == _T(".bmp"))
    {
        iFilterIndex = 5;
        m_strDefFileExt = _T("bmp");
    }

    CFileDialog dlg(TRUE, (LPCTSTR)m_strDefFileExt,
                    strFile, 
                    m_dwFileDialogFlags, 
                    !m_strFileFilter.IsEmpty() ? (LPCTSTR)m_strFileFilter : (LPCTSTR)NULL, NULL);

    dlg.m_pOFN->nFilterIndex = iFilterIndex;


    if (dlg.DoModal() == IDOK && strFile != dlg.GetPathName())
    {
        SetWindowText(dlg.GetPathName());
        SetModify(TRUE);
        OnAfterUpdate();
    }

    if (GetParent() != NULL)
    {
        GetParent()->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
    }
}

But, it still have this odd behaviour I described.


Update 4

This issue is technically related to CFileDialog. If I simply try:

CFileDialog dlgOpen(TRUE, _T("MWB"), _T("123456789abcdefghijklmnopqrstuvwxyz.mwb"), OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, strFilter, this);

Then all that is visibly selected is "rstuvwxyz.mwb".


Update 5

One of the replies here states:

This really isn't an MFC issue. The bad actor is the shell's COM object that implements the IFileDialog interface that is used by MFC under the hood. The following minimal example reproduces the problem using COM without any MFC code.

#include <Windows.h>
#include <ShlObj.h>


int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevious, LPWSTR szCommandline, INT nShow)
{
    HRESULT hr = CoInitialize(nullptr);
    if (SUCCEEDED(hr))
    {
        IFileDialog *pfd = nullptr;
        hr = CoCreateInstance(CLSID_FileOpenDialog,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&pfd));

        if (SUCCEEDED(hr))
        {
            COMDLG_FILTERSPEC rgFileSpec[] = {
                {L"MWB Files (*.mwb)", L"*.mwb"},
                {L"All Files (*.*)", L"*.*"}
            };

            hr = pfd->SetFileTypes(ARRAYSIZE(rgFileSpec), rgFileSpec);
            hr = pfd->SetFileName(L"123456789abcdefghijklmnopqrstuvwxyz.mwb");
            hr = pfd->Show(NULL);
            pfd->Release();
        }
    }

    CoUninitialize();

    return 0;
}
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • Not sure why this is happening but maybe try clearing the selection? If your control is, say `myEBC` then try: `myEBC.SetSel(-1,0,FALSE);` if you can put this in somewhere before the browse button is clicked. – Adrian Mole Oct 19 '20 at 10:37
  • @AdrianMole I tried your idea in my dialogs `OnInitDialog` but it made no difference. – Andrew Truckle Oct 19 '20 at 10:53
  • @AdrianMole In the end I think I am going to have to override this control and display my own file dialog. It has no intelligence. It won't set the file type index to match the existing file name. It won't set the file type index to match the default extension type (in no file specified). I was just hoping to use it out of the box to avoid an extra class. – Andrew Truckle Oct 19 '20 at 10:56
  • Related (but no solution): https://stackoverflow.com/questions/25210030/cfiledialog-truncates-offered-file-name – Andrew Truckle Oct 19 '20 at 13:13
  • 1
    FWIW, I use the `CMFCEditBrowseCtrl` quite a lot in my projects. However, I have 'built-in' a customized version, in which I override the `OnBrowse` function and use my own CFileDialog in the handler. – Adrian Mole Oct 19 '20 at 13:19
  • I reported it https://developercommunity.visualstudio.com/content/problem/1225634/problem-with-using-the-cfiledialog-class.html – Andrew Truckle Oct 19 '20 at 13:27
  • Try calling `GetOpenFileName` (the underlying Windows API call) instead and see if the problem is there as well. If it is you can file a defect report against the OS rather than MFC. MFC doesn't get much (if any) attention. And it has been like that literally for decades. I remember filing a documentation bug 20 years ago and received an email informing me that MFC were no longer actively maintained. – IInspectable Oct 20 '20 at 07:20
  • @IInspectable I confirm that it does the same thing. But when you use the old style file dialog it does not crop the name. I stumbled over this which I have never seen before in my life: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb776913(v=vs.85) I wondered if this was a suitable alternative? – Andrew Truckle Oct 20 '20 at 08:53

1 Answers1

1

This class solves it

FileDialogHack.h:

#pragma once
#include <afxdlgs.h>

/////////////////////////////////////////////////////////////////////////////
// CFileDialogHack dialog
// 
// solves bug in filedialog: 
// https://developercommunity.visualstudio.com/t/problem-with-using-the-cfiledialog-class/1225634
//
class CFileDialogHack : public CFileDialog
{
public:
    CFileDialogHack(BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL,
        DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL);
    virtual INT_PTR DoModal();
protected:
    HWINEVENTHOOK wineventhook();
public:
    void _WinEventProcCallback(HWINEVENTHOOK hWinEventHook,
        DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime);
    LRESULT _CallWndProc(int code, WPARAM wParam, LPARAM lParam);
protected:
    HWINEVENTHOOK m_hHook;
    HHOOK m_hWndProcHook;
    HWND m_hDialog;
    HWND m_hCombo;
    HWND m_hEdit;
    BOOL m_bHook;
    HWND m_hParent;
};

FileDialogHack.cpp:

#include "stdafx.h"
#include "FileDialogHack.h"

/////////////////////////////////////////////////////////////////////////////
// CMyFileDialogHack dialog

static CFileDialogHack* g_ptrfiledaloghack = nullptr;

CFileDialogHack::CFileDialogHack(BOOL bOpenFileDialog, LPCTSTR lpszDefExt, LPCTSTR lpszFileName,
    DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd)
    : CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd, 0, TRUE),
    m_hHook(0), m_hWndProcHook(0), m_hParent(0), m_hDialog(0), m_hCombo(0), m_hEdit(0), m_bHook(0)
{
}

LRESULT CFileDialogHack::_CallWndProc(int code, WPARAM wParam, LPARAM lParam)
{
    PCWPRETSTRUCT data = (PCWPRETSTRUCT)lParam;
    if (data->hwnd == m_hCombo && data->message == CBEM_GETEDITCONTROL)
    {
        m_hEdit = (HWND)data->lResult;
        return CallNextHookEx(0, code, wParam, lParam);
    }
    if (m_bHook)
    {
        if (data->hwnd == m_hEdit && data->message == EM_SETSEL &&
            data->wParam == 0 &&    data->lParam == -1)
            ::SendMessage(m_hEdit, EM_SETSEL, (WPARAM)0, (LPARAM)0);
        else if (data->hwnd == m_hParent && data->message == WM_ENTERIDLE)
        {
            m_bHook = 0;
            TCHAR text[MAX_PATH];
            ::GetWindowText(m_hEdit, text, MAX_PATH - 1);
            auto len = _tcslen(text);
            ::SendMessage(m_hEdit, EM_SETSEL, (WPARAM)0, (LPARAM)0);
            ::SendMessage(m_hEdit, EM_SETSEL, (WPARAM)0, (LPARAM)len);
            ::SetFocus(m_hEdit);
        }
    }
    return CallNextHookEx(0, code, wParam, lParam);
}

static LRESULT CALLBACK CallWndProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (g_ptrfiledaloghack)
        return g_ptrfiledaloghack->_CallWndProc(code, wParam, lParam);
    return CallNextHookEx(0, code, wParam, lParam);
}

VOID CALLBACK CFileDialogHack::_WinEventProcCallback(HWINEVENTHOOK /*hWinEventHook*/,
    DWORD dwEvent, HWND hwnd, LONG idObject, LONG /*idChild*/,
    DWORD /*dwEventThread*/, DWORD /*dwmsEventTime*/)
{
    if (idObject == OBJID_WINDOW && dwEvent == EVENT_OBJECT_CREATE)
    {
        if (IsWindow(hwnd) && ::GetDlgCtrlID(hwnd) == 1148)
        {
            m_hCombo = hwnd; // We have the file dialog's filename combobox field.          
            m_hDialog = ::GetParent(hwnd);
            m_hParent = ::GetParent(m_hDialog);
            m_hEdit = (HWND) ::SendMessage(m_hCombo, CBEM_GETEDITCONTROL, 0, 0);
            DWORD threadID = GetWindowThreadProcessId(m_hDialog, NULL);
            // Hook messages to the file dialog.
            m_hWndProcHook = SetWindowsHookEx(WH_CALLWNDPROCRET, CallWndProc, NULL, threadID);
        }
    }
}

static VOID CALLBACK WinEventProcCallback(HWINEVENTHOOK hWinEventHook,
    DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread,
    DWORD dwmsEventTime)
{
    if (g_ptrfiledaloghack)
        g_ptrfiledaloghack->_WinEventProcCallback(hWinEventHook,
            dwEvent, hwnd, idObject, idChild, dwEventThread, dwmsEventTime);
}

HWINEVENTHOOK CFileDialogHack::wineventhook()
{
    // Hook creation of the Open File Dialog.
    g_ptrfiledaloghack = this;
    m_bHook = 1;
    return SetWinEventHook(
        EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE,
        NULL, WinEventProcCallback, GetCurrentProcessId(), 0,
        WINEVENT_OUTOFCONTEXT);
}

INT_PTR CFileDialogHack::DoModal()
{
    m_hHook = wineventhook();
    auto r = __super::DoModal();
    UnhookWinEvent(m_hHook);
    UnhookWindowsHookEx(m_hWndProcHook);
    g_ptrfiledaloghack = nullptr;
    return r;
}
  • Thank you. Are you able to flesh out your answer with some explanations of what you have done with this code to make it work? Much appreciated. – Andrew Truckle Feb 17 '23 at 14:25