CFileDialog file name change? - mfc

I've successfully subclassed a CFileDialog and added an area with a few controls that control load/save file format using SetTemplate(). My control message handlers are being called correctly.
Depending on the file name typed, my controls may need to update. I'm getting OnFileNameChange() when the file list is clicked, and OnTypeChange() when the file type combo box is altered.
However, when a file name is simply typed, how can I get notification?
I've tried adding a PreTranslateMessage() to this CFileDialog subclass, but it's not getting called for anything. I know how to check pMsg->message == WM_KEYDOWN but if I detect one, how would I know that it was a key pressed in the file input field? And since the key hasn't gotten to the control yet, GetEditBoxText(), GetFileName() etc. won't work...
I've also tried add the following to my constructor:
OPENFILENAME& ofn = GetOFN();
ofn.lpfnHook = &OFNHook;
With the function:
UINT_PTR CALLBACK OFNHook( HWND hdlg, UINT uiMsg,
WPARAM wParam, LPARAM lParam ) {
if ( uiMsg == WM_KEYDOWN )
MyLogging( "here" );
return 0;
}
OFNHook() was called a lot, but uiMsg never equaled WM_KEYDOWN . So same questions as before: how do I know the key is for the file field, how do I get that file field's value AFTER the key has been applied, etc.

This solution doesn't feel good but this is what I ended up with:
1) Make a timer:
BOOL WinTableEditFileDialog::OnInitDialog() {
// There doesn't seem to be a way to get events for changes in the edit
// field, so instead we check it on a timer.
timerid = SetTimer( 1, 100, NULL );
}
2) In timer, use GetPathName() to get drive, directory path, and sometimes filename. Then use GetWindowText() to get the exact text in the text field if the user is editing it. Sometimes GetPathName() returns the live edit (it seems, when no file is selected above) and sometimes not. So, I inspect both halves a bit then make a result out of one or the other or both.
void WinTableEditFileDialog::OnTimer( UINT_PTR nIdEvent ) {
CComboBox* pcombo = (CComboBox*) GetParent()->GetDlgItem( cmb1 );
// GetPathName() often doesn't update on text field edits, such as when
// you select a file with the mouse then edit by hand. GetWindowText()
// does respond to edits but doesn't have the path. So we build the full
// name by combining the two.
char szFull[1024];
strncpy( szFull, sizeof( szFull ), GetPathName() );
szFull[ sizeof( szFull ) - 1 ] = '\0';
char* pcFile = strrchr( szFull, '\\' );
if ( pcFile )
pcFile++; // skip slash
else
pcFile = szFull;
CComboBox* pcomboFileName = (CComboBox*) GetParent()->GetDlgItem( cmb13 );
pcomboFileName->GetWindowText( pcFile, sizeof( szFull ) - ( pcFile-szFull ) );
// If the user has typed a full path, then we don't need GetPathName();
if ( isalpha( pcFile[0] ) && pcFile[1] == ':' )
pcomboFileName->GetWindowText( szFull, sizeof( szFull ) );

Related

MFC: OnNcCreate() not called; need to set BS_OWNERDRAW flag for CButton subclass

I'm not an expert on MFC, but I've made a dozen or so custom controls over the last 15+ years. I've just made a CButton with custom graphics.
Here's the problem: Of course I need BS_OWNERDRAW to be set. I have an OnNcCreate() method I've cut and pasted for 15+ years that's always worked in my custom controls, but for some reason it's not now being called.
I have no interest or even understanding of OnNcCreate() except for the fact that it was the place I ended up getting BS_OWNERDRAW set the first time I tried writing a custom control.
Question: What would keep OnNcCreate() from being called? Or, is there another place I could or should be setting BS_OWNERDRAW ?
A working class is defined with:
class CGood : public CStatic {
The non-working one is defined with:
class CBad : public CButton {
They both prototype the method thusly:
afx_msg int OnNcCreate( LPCREATESTRUCT lpCreateStruct );
The method is written identically (except the class name):
int CGood::OnNcCreate( LPCREATESTRUCT pcrs ) {
wStyleBits = LOWORD( pcrs->style );
DWORD dwStyleSuperclass = MAKELONG( ES_LEFT, HIWORD( pcrs->style ) );
dwStyleSuperclass |= BS_OWNERDRAW;
::SetWindowLong( m_hWnd, GWL_STYLE, dwStyleSuperclass );
pcrs->style = dwStyleSuperclass;
return CStatic::OnNcCreate( pcrs );
}
The dialog is using both the good and bad classes utilizing the Visual Studio 2017 dialog editor, as CustomControl. Both the good and bad classes have Extended Style 0x0 and Style 0x50010000. Both er Disabled=False, Help ID=False, Visible=True, Class=Good or Bad as needed.
When run the application calls each class's RegisterControlClass() method:
static WNDPROC pfnWndProc = NULL;
BOOL CBad::RegisterControlClass() {
WNDCLASS wcls;
static const TCHAR szClass[] = _T( "CBad" );
if ( ::GetClassInfo( AfxGetInstanceHandle(), szClass, &wcls ) )
return wcls.lpfnWndProc == ( WNDPROC ) CBad::WndProcHook;
VERIFY( ::GetClassInfo( NULL, _T( "button" ), &wcls ) );
pfnWndProc = wcls.lpfnWndProc;
wcls.lpfnWndProc = CBad::WndProcHook;
wcls.hInstance = AfxGetInstanceHandle();
wcls.lpszClassName = szClass;
return RegisterClass( &wcls ) != 0;
}
LRESULT CALLBACK EXPORT CBad::WndProcHook( HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam ) {
CBad* pthis = new CBad();
pthis->Attach( hWnd );
pthis->m_pfnSuper = pfnWndProc;
::SetWindowLong( hWnd, GWL_WNDPROC, ( DWORD )AfxWndProc );
#ifdef STRICT
return ::CallWindowProc( AfxWndProc, hWnd, msg, wParam, lParam );
#else
return ::CallWindowProc( ( FARPROC )AfxWndProc, hWnd, msg, wParam, lParam );
#endif
}
(Funny: I note in CGood, derived from CStatic, mistakenly has "button" there and works fine; it stops working if I change to "static" . I'll look into that later. The bad class has "button" and indeed is a CButton subclass.)
Things I've done to confirm the issue: When I remove the BS_OWNERDRAW from the CGood::OnNcCreate(), it stops calling DrawItem(). So I'm sure that's the place this flag is being set.
Putting a breakpoint in CGood::OnNcCreate() and CBad::OnNcCreate(), the CBad one simply isn't called.
I've found examples on the internet of setting BS_OWNERDRAW in PreSubclassWindow(), OnCreate(), and CreateEx(), none of which work, in some cases for reasons I think I understand. (EG you can't just set flags in OnCreate() and call the superclass method, because it is ultimately getting the flags not from the param you give it but from some other source.)
I've tried changing CBad's superclass to CStatic and still the OnNcCreate() isn't being called.
I've tried setting BS_OWNERDRAW in the dialog's OnInitDialog() and that does get the CBad button subclass painting correctly. So I know I'm not accidently creating them with some other subclass, etc.
CBad* pcbut = (CBad*) GetDlgItem( IDC_MARKCHECK );
pcbut->ModifyStyle(0, BS_OWNERDRAW);
Ahh! I simply forgot to add it to the message map!
BEGIN_MESSAGE_MAP( CBad, CButton )
//{{AFX_MSG_MAP( CBad )
ON_WM_NCCREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Display formatted text on selecting item in the Combobox

I have a combobox in that I want to display different string on selecting an item in Combo.
My combo box is a dropdown combobox.
For eg: I have following in my combobox.
Alex - Manager
Rain - Project Lead
Shiney - Engineer
Meera - Senior Engineer
OnSelecting an item in combobox I want to diaply only name i.e. Alex.
I tried below code
struct details{
CString name;
CString des;
};
BOOL CComboTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
details d1;
d1.name = _T("alex");
d1.des =_T("manager");
m_vec.push_back(d1);
details d2;
d2.name = _T("Rain");
d2.des =_T("Engineer");
m_vec.push_back(d2);
// TODO: Add extra initialization here
for(int i=0;i<m_vec.size();i++)
{
m_ctrlCombo.AddString(m_vec[i].name+m_vec[i].des);
m_ctrlCombo.SetItemData(i,(DWORD_PTR)&m_vec[i]);
}
m_ctrlCombo.SelectString(-1,m_vec[0].name);
m_ctrlCombo.SetWindowText(m_vec[0].name);
return TRUE; // return TRUE unless you set the focus to a control
}
void CComboTestDlg::OnCbnSelchangeCombo1()
{
int nItem = m_ctrlCombo.GetCurSel();
details* det = (details*)m_ctrlCombo.GetItemData(nItem);
PostMessage(SETCOMBOTEXT,IDC_COMBO1,(LPARAM)(LPCTSTR)det->name);
}
BOOL CComboTestDlg::PreTranslateMessage(MSG* pMsg)
{
MSG msg1=*pMsg;//I am loosing the value after checking ..so storing temp.
MSG msg;
CopyMemory(&msg, pMsg, sizeof(MSG));
HWND hWndParent = ::GetParent(msg.hwnd);
while (hWndParent && hWndParent != this->m_hWnd)
{
msg.hwnd = hWndParent;
hWndParent = ::GetParent(hWndParent);
}
if (pMsg->message==SETCOMBOTEXT && (pMsg->wParam == IDC_COMBO1))
SetDlgItemText(IDC_COMBO1, (LPCTSTR)pMsg->lParam);
if(pMsg->message==WM_KEYDOWN)
{
if(pMsg->wParam==VK_RETURN && msg.hwnd ==m_ctrlCombo.m_hWnd )
{
OnCbnSelchangeCombo1();
}
}
return CDialog::PreTranslateMessage(pMsg);
}
I am able to achieve my requirement OnComboSelChange() and Arrow Keys event but on pressing enter key after using arrow keys in combo box, it is not showing formatted text in combo box.
I think the most reliable and easy to implement solution is to subclass the edit control of the combobox. Intercept the WM_SETTEXT message and change the text as you like before forwarding it to the rest of the chain (finally the original window proc).
Install the sub class proc in OnInitDialog():
COMBOBOXINFO cbi{ sizeof(cbi) };
if( m_ctrlCombo.GetComboBoxInfo( &cbi ) )
{
SetWindowSubclass( cbi.hwndItem, ComboEditSubClassProc, 0, 0 );
}
ComboEditSubClassProc() could look like this:
LRESULT CALLBACK ComboEditSubClassProc( HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch( uMsg )
{
case WM_SETTEXT:
{
CString text = reinterpret_cast<LPCTSTR>( lParam );
// Extract the name (everything before "-").
CString name = text.SpanExcluding( _T("-") );
name.TrimRight();
// Forward the modified text to any other sub class procs, aswell
// as the original window proc at the end of the chain.
return DefSubclassProc( hWnd, uMsg, 0, reinterpret_cast<LPARAM>( name.GetString() ) );
}
case WM_NCDESTROY:
{
// We must remove our subclass before the subclassed window gets destroyed.
// This message is our last chance to do that.
RemoveWindowSubclass( hWnd, ComboEditSubClassProc, uIdSubclass );
break;
}
}
return DefSubclassProc( hWnd, uMsg, wParam, lParam );
}
Notes:
Contrary to my original solution of processing CBN_SELCHANGE, the current solution also works correctly if the combobox drop-down list is closed by pressing Return or is dismissed.
I think it is in general more reliable because we don't have to rely on the order of the notifications. The combobox has to finally call WM_SETTEXT to change the content of the edit control so this message will always be received.
There will also be no flickering as in the original solution where the text was first changed by the combobox and then modified by our code only after the fact.

TabCtrl_GetItem macro not working as expected

I'm creating a basic notepad program, and when the user clicks close, I want it to ask the user if they want to save the current document opened. I'm using a tabbed interface, and trying to retrieve the filename ( text on tab ) so I have a MessageBox that says "Would you like to save: untitled.txt" or similar. I'm having trouble getting the file name. This is what I currently have:
case ID_FILE_CLOSE: // When the close button is clicked
{
HWND hEdit, hTabs;
hTabs = GetDlgItem( hwnd, IDC_MAIN_TAB );
int curTab = TabCtrl_GetCurSel( hTabs );
TCITEM curtitem;
TabCtrl_GetItem( hTabs, curTab, &curtitem );
// Check for file name
MessageBox( hwnd, curtitem.pszText, "Test", MB_OK );
}
break;
This is the error I keep getting in a popup box with Break, Continue, Ignore buttons:
Unhandled exception at 0x7597d298 in notepadpremium.exe: 0xC0000005: Access violation reading location 0xcccccccc.
I'm using MS Visual C++ Express 2010.
I also have a listbox with the filenames that also show the extension ( almost like notepad++ document switcher ) and tried LB_GETITEMDATA through a message, but that always returned blank. I think that was because I use LB_ADDSTRING to add it to the listbox. ( the listbox and tabs are interconnected, when you click on a file in the listbox, it changes to the corresponding tab ). Why isnt my code working the way it should?
Read the documentation:
pitem
Type: LPTCITEM
Pointer to a TCITEM structure that specifies the information to retrieve and receives information about the tab. When the message is sent, the mask member specifies which attributes to return. If the mask member specifies the TCIF_TEXT value, the pszText member must contain the address of the buffer that receives the item text, and the cchTextMax member must specify the size of the buffer.
You are not initializing the TCITEM at all. You need to tell TabCtrl_GetItem() what data to retrieve, and more importantly what buffer you provide to receive that data into. You are not doing any of that, you are passing random data to TabCtrl_GetItem(), which is why it crashes.
Try this instead:
case ID_FILE_CLOSE: // When the close button is clicked
{
HWND hTabs = GetDlgItem( hwnd, IDC_MAIN_TAB );
int curTab = TabCtrl_GetCurSel( hTabs );
TCHAR szFileName[MAX_PATH+1] = {0};
TCITEM curtitem = {0};
curitem.mask = TCIF_TEXT;
curitem.pszText = szFileName;
curitem.cchTextMax = MAX_PATH;
if (TabCtrl_GetItem( hTabs, curTab, &curtitem ))
{
// also from the documentation:
//
// "the control may change the pszText member of the structure
// to point to the new text instead of filling the buffer with
// the requested text. The control may set the pszText member
// to NULL to indicate that no text is associated with the item."
//
// which means you cannot rely on the szFileName[] buffer actually
// containing the filename, you have to use whatever buffer the
// TCITEM is actually pointing at, which may or may not be the
// szFileName buffer...
MessageBox( hwnd, curitem.pszText, TEXT("Test"), MB_OK );
}
}
break;
As for your ListBox issue, you said you are using LB_ADDSTRING to add strings to the ListBox, but are using LB_GETITEMDATA to retrieve them. That is wrong. You need to use LB_GETTEXTLEN and LB_GETTEXT instead. LB_GETITEMDATA is used to retrieve user-defined data that was added to the ListBox using LB_SETITEMDATA.

Reset cursor in WM_SETCURSOR handler properly

INTRODUCTION AND RELEVANT INFORMATION:
I have made an application that needs to change the look of a cursor into hand when mouse hovers above the static control, but resets it to a normal cursor otherwise.
My initial application was in full screen mode, but recently terms have changed and it must have a resizable window.
This means that my handler for WM_SETCURSOR must be rewritten to reflect newly introduced changes.
Cursors are loaded in WM_CREATE, and I have defined class cursor, like this:
// cursors
case WM_CREATE:
hCursorHand = LoadCursor( NULL, IDC_HAND );
hCursorArrow = LoadCursor( NULL, IDC_ARROW );
// other stuff
In my class:
WNDCLASSEX wc;
// ...
wc.hCursor = hCursorArrow;
//...
This is my old WM_CURSOR handler ( code is simplified for clarity purposes ):
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
SetCursor(hCursorHand);
else
SetCursor(hCursorArrow);
return TRUE;
If cursor hovers above static control, then my handler changes it to hand, else sets it to default cursor ( arrow ).
Bellow is the picture I have sketched in Paint that displays the desired look of the cursor when it hovers above static control, it is on the client area, and when user resizes window.
If additional code snippets are required, ask and I will edit my post, but for now, they are omitted to keep the post short and concise.
I work on Windows XP, using MS Visual Studio C++ and pure Win32 API.
MY EFFORTS TO SOLVE PROBLEM:
Bellow are the code snippets that I have tried, but they all failed:
First snippet:
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
{
SetCursor(hCursorHand);
return TRUE;
}
else
return DefWindowProc( hWnd, msg, lParam, wParam );
Second Snippet:
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
{
SetCursor(hCursorHand);
return TRUE;
}
break; // based on MSDN example
Third snippet:
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
{
SetCursor(hCursorHand);
return TRUE;
}
else
return FALSE;
These set cursor to hand no matter where it is.
If I leave my WM_SETCURSOR handler unchanged, the only problem I get is that instead of sizing arrows, I get regular arrow ( as the cursor’s look ) when I hover over the border, but window can be sized.
If I comment out my WM_SETCURSOR handler, sizing arrows and cursor arrow appear properly, but cursor doesn’t change into hand when hovers above static control ( which is logical, since there is no WM_SETCURSOR handler to change it ).
I have browsed through SO archive, looked on MSDN, CodeProject , DaniWeb, Cprogramming and CodeGuru, but had no success.
Looking through those, I have found an example where people compare low word of the lParam against hit test code.
Looking through MSDN I have found link for hit test values ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms645618%28v=vs.85%29.aspx ) and I have found link for cursor types (http://msdn.microsoft.com/en-us/library/windows/desktop/ms648391%28v=vs.85%29.aspx ).
Currently I am reading them, because I think that I will have to load additional cursor resources, take several comparisons of hit test values, and then use those resources to set cursor look accordingly.
QUESTION:
I really would like my WM_SETCURSOR handler to look like this:
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
{
SetCursor(hCursorHand);
return TRUE;
}
else
// reset cursor's look to default
so I ask the community to instruct me on how to do this.
If this isn’t possible, then I will consider using multiple if statements to check the hit test code, and set cursor’s look accordingly.
Of course, if there is better solution for my problem, please suggest it, I will consider it as well.
Thank you.
Regards.
In general, if you handle the WM_SETCURSOR message you must either
Call SetCursor() to set the cursor, and return TRUE, or
If the message came from a child window, return FALSE for default processing, or
If the message is from your own window, pass the message through to DefWindowProc()
I think the last two points aren't made quite clear by the MSDN docs.
The window under the mouse pointer gets the first WM_SETCURSOR message. If it handles it and returns at that point, nothing else happens. If however it calls DefWindowProc(), then DWP forwards the message to the window's parent to handle. If the parent chooses not to handle it, it can return FALSE and the DefWindowProc processing will continue.
But this only applies if the message came from a previous call to DWP. If the message originated with the window itself, rather than a child, returning TRUE or FALSE without setting the cursor means the cursor won't be set at all.
Another thing: although your question didn't specify, I'm assuming from your use of GetDlgItem() that your top-level window is a dialog. If that's true, you can't just return TRUE or FALSE for a message - you need to return the value using SetWindowLongPtr() and store the return value in DWLP_MSGRESULT. Returning FALSE from a dialog procedure indicates that you didn't handle the message at all - this is equivalent to passing a message through to DefWindowProc().
So I think the proper handling for your situation is, in your top-level window:
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
{
SetCursor(hCursorHand);
SetWindowLongPtr(hwnd, DWLP_MSGRESULT, TRUE);
return TRUE;
}
return FALSE;
If your top-level window isn't in fact a dialog, you would do this:
case WM_SETCURSOR:
if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) )
{
SetCursor(hCursorHand);
return TRUE;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
Here's my first example, if cursor go to menubar, cursor changes to cursor hand:
HCURSOR cursorHand = LoadCursor(NULL, IDC_HAND);
case WM_SETCURSOR:
if(LOWORD(lParam) == HTMENU)
{
SetCursor(cursorHand);
}
break;
Here's my second example, if cursor goes to button, cursor changes to cursorHand:
HCURSOR cursorHand = LoadCursor(NULL, IDC_HAND);
case WM_SETCURSOR:
if(LOWORD(lParam) == buttonId)//declare you
{
SetCursor(cursorHand);
}
break;
Warning:menubar and button is not created! Create you and please, check my answer example.
The question as I understand it is, given a parent window with one more more child windows (one of which is a static control) how do you set the cursor to the hand when the cursor is over the static control, and the arrow when the cursor is over the client area of the parent window, and let default processing take place when the cursor is over a non-client area of the parent window.
To check things out, I wrote a simple program with a top-level window with a static control as a child window. My first attempt was the following:
1) Set the class cursor of the top-level window to LoadCursor(NULL, IDC_ARROW). This allows the default processing to set the cursor to the arrow when appropriate.
2) Keep track of the position of the mouse cursor by handling the WM_MOUSEMOVE message by calling my HandleWMMouseMove function as follows:
case WM_MOUSEMOVE:
HandleWMMouseMove(lParam);
break;
//ptCurrMousePos is a global variable of type POINT
static void HandleWMMouseMove(LPARAM mousepos)
{
ptCurrMousePos.x = (int)(short)(LOWORD(mousepos));
ptCurrMousePos.y = (int)(short)(HIWORD(mousepos));
}
3) and then all I had to do was handle the WM_SETCURSOR message by calling my HandleWMSetCursor function as follows:
case WM_SETCURSOR:
if (HandleWMSetCursor())
return TRUE;
break;
//hwndFrame is a handle to the top-level window.
//hwndStatic is a handle to the static control.
static BOOL HandleWMSetCursor(void)
{
if (ChildWindowFromPoint(hwndFrame, ptCurrMousePos)==hwndStatic) {
SetCursor(hCursorHand);
return TRUE;
}
return FALSE;
}
this worked fine but I couldn't understand how the code posted in the question was working even partially. So I asked the questioner whether the child window was really a static control. The answer was yes but it was created with the SS_NOTIFY style. So I created my child window with this style and my code stopped working, but the code posted with the question started to work. With the help of the Spy++ program distributed with Visual Studio I learned the following.
Static controls are normally transparent (not in the visual sense, but meaning that even when the mouse is over the transparent window, Windows will consider the mouse to be over the window underneath the transparent window).
When you create a static control with SS_NOTIFY the control is no longer transparent (i.e. it starts to receive an process mouse messages (like WM_MOUSEMOVE), so my code stopped working because it never received WM_MOUSE messages when the cursor was over the static control. On the other hand the code in the question did not work when the static control was created without SS_NOTIFY because without this style the static control was transparent, which meant that the WARAM in the WM_SETCURSOR message was never equal to the static control (in other words Windows never considered the mouse to be over the static control because it was transparent).
It is possible to combine the two approaches by changing the WM_SETCURSOR handler function to the following:
//hwndFrame is a handle to the top-level window.
//hwndStatic is a handle to the static control.
static BOOL HandleWMSetCursor(WPARAM wParam)
{
if (((HWND)wParam == hwndStatic) || (ChildWindowFromPoint(hwndFrame, ptCurrMousePos)==hwndStatic)) {
SetCursor(hCursorHand);
return TRUE;
}
return FALSE;
}

WTL CListViewCtrl with status text

I have a Windows Template Library CListViewCtrl in report mode (so there is a header with 2 columns) with owner data set. This control displays search results. If no results are returned I want to display a message in the listbox area that indicates that there were no results. Is there an easy way to do this? Do you know of any existing controls/sample code (I couldn't find anything).
Otherwise, if I subclass the control to provide this functionality what would be a good approach?
I ended up subclassing the control and handling OnPaint like this:
class MsgListViewCtrl : public CWindowImpl< MsgListViewCtrl, WTL::CListViewCtrl >
{
std::wstring m_message;
public:
MsgListViewCtrl(void) {}
BEGIN_MSG_MAP(MsgListViewCtrl)
MSG_WM_PAINT( OnPaint )
END_MSG_MAP()
void Attach( HWND hwnd )
{
SubclassWindow( hwnd );
}
void SetStatusMessage( const std::wstring& msg )
{
m_message = msg;
}
void OnPaint( HDC hDc )
{
SetMsgHandled( FALSE );
if( GetItemCount() == 0 )
{
if( !m_message.empty() )
{
CRect cRect, hdrRect;
GetClientRect( &cRect );
this->GetHeader().GetClientRect( &hdrRect );
cRect.top += hdrRect.Height() + 5;
PAINTSTRUCT ps;
SIZE size;
WTL::CDCHandle handle = this->BeginPaint( &ps );
handle.SelectFont( this->GetFont() );
handle.GetTextExtent( m_message.c_str(), (int)m_message.length(), &size );
cRect.bottom = cRect.top + size.cy;
handle.DrawText( m_message.c_str(), -1, &cRect, DT_CENTER | DT_SINGLELINE | DT_VCENTER );
this->EndPaint( &ps );
SetMsgHandled( TRUE );
}
}
}
};
After the search runs, if there are no results, I call SetStatusMessage and the message is displayed centered under the header. That's what I wanted. I'm kind of a newbie at subclassing controls so I'm not sure if this is the best solution.
If you're on Vista or later, handle the LVN_GETEMPTYMARKUP notification. For pre-Vista, you'll need to paint the message yourself.
Another idea is to have another control, with the same size and position as the list control, but hidden. Could be an edit control, static text, browser control, or what have you.
Then when you don't have any search results, you put the message in this control, and un-hide it. When the user does another search that returns results, you hide this control and show the results in the list view normally.