WTL CListViewCtrl with status text - c++

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.

Related

Search icon in edit control overlapped by input area

I am trying to make a search edit control in MFC that has an icon displayed in the control window all the time (regardless the state and text of the control). I have written something like this many years ago and worked very well, but the code no longer works on Windows 7 and newer (maybe even Vista, but did not try that). What happens is that the image shown in the control is overlapped with the input area (see the picture below).
The idea behind the code:
have a class derived from CEdit (that handles painting in OnPaint)
the icon is displayed on the right and the edit area is shrunk based on the size of the icon
resizing is done differently for single-line and multiline edits. For single line I call SetMargins and for multiline edits I call SetRect.
this edit resizing is applied in PreSubclassWindow(), OnSize() and OnSetFont()
This is how the edit input size is applied:
void CSymbolEdit::RecalcLayout()
{
int width = GetSystemMetrics( SM_CXSMICON );
if(m_hSymbolIcon)
{
if (GetStyle() & ES_MULTILINE)
{
CRect editRect;
GetRect(&editRect);
editRect.right -= (width + 6);
SetRect(&editRect);
}
else
{
DWORD dwMargins = GetMargins();
SetMargins(LOWORD(dwMargins), width + 6);
}
}
}
The following image shows the problem with the single line edits (the images have been zoomed in for a better view). The yellow background is for highlighting purposes only, in real code I am using the COLOR_WINDOW system color. You can see that when the single line edit has text and has the input the left side image is painted over. This does not happen with the multiline edit where SetRect correctly sets the formatting rectangle.
I have tried using ExcludeClipRect to remove the area of the edit where the image is being displayed.
CRect rc;
GetClientRect(rc);
CPaintDC dc(this);
ExcludeClipRect(dc.m_hDC, rc.right - width - 6, rc.top, rc.right, rc.bottom);
DWORD dwMargins = GetMargins();
SetMargins(LOWORD(dwMargins), width + 6);
This does not seem to have any effect on the result.
For reference, this is the painting method, written years ago and used to work well on Windows XP, but not correct any more.
void CSymbolEdit::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect( &rect );
// Clearing the background
dc.FillSolidRect( rect, GetSysColor(COLOR_WINDOW) );
DWORD dwMargins = GetMargins();
if( m_hSymbolIcon )
{
// Drawing the icon
int width = GetSystemMetrics( SM_CXSMICON );
int height = GetSystemMetrics( SM_CYSMICON );
::DrawIconEx(
dc.m_hDC,
rect.right - width - 1,
1,
m_hSymbolIcon,
width,
height,
0,
NULL,
DI_NORMAL);
rect.left += LOWORD(dwMargins) + 1;
rect.right -= (width + 7);
}
else
{
rect.left += (LOWORD(dwMargins) + 1);
rect.right -= (HIWORD(dwMargins) + 1);
}
CString text;
GetWindowText(text);
CFont* oldFont = NULL;
rect.top += 1;
if(text.GetLength() == 0)
{
if(this != GetFocus() && m_strPromptText.GetLength() > 0)
{
oldFont = dc.SelectObject(&m_fontPrompt);
COLORREF color = dc.GetTextColor();
dc.SetTextColor(m_colorPromptText);
dc.DrawText(m_strPromptText, rect, DT_LEFT|DT_SINGLELINE|DT_EDITCONTROL);
dc.SetTextColor(color);
dc.SelectObject(oldFont);
}
}
else
{
if(GetStyle() & ES_MULTILINE)
CEdit::OnPaint();
else
{
oldFont = dc.SelectObject(GetFont());
dc.DrawText(text, rect, DT_SINGLELINE | DT_INTERNAL | DT_EDITCONTROL);
dc.SelectObject(oldFont);
}
}
}
I have looked at other implementations of similar edit controls and they all have the same fault now.
Obviously, the question is how do I exclude the image area from the input area of the control?
I think what's going on is that CPaintDC calls BeginPaint(), which sends a WM_ERASEBKGND to the edit box. I wasn't able to ignore it, so I guess it's being sent to maybe an internal STATIC window? Not sure.
Calling ExcludeClipRect() in your OnPaint() handler won't do anything because EDIT will reset the clipping region to the whole client area in either BeginPaint() or its own WM_PAINT handler.
However, EDIT sends a WM_CTRCOLOREDIT to its parent just before painting itself, but seemingly after setting the clipping region. So you can call ExcludeClipRect() there. Sounds like an implementation detail which may change with future versions of the common controls. Indeed, it seems to have done so already.
I did a quick test without MFC on Windows 7, here's my window procedure:
LRESULT CALLBACK wnd_proc(HWND h, UINT m, WPARAM wp, LPARAM lp)
{
switch (m)
{
case WM_CTLCOLOREDIT:
{
const auto dc = (HDC)wp;
const auto hwnd = (HWND)lp;
RECT r;
GetClientRect(hwnd, &r);
// excluding the margin, but not the border; this assumes
// a one pixel wide border
r.left = r.right - some_margin;
--r.right;
++r.top;
--r.bottom;
ExcludeClipRect(dc, r.left, r.top, r.right, r.bottom);
return (LRESULT)GetStockObject(DC_BRUSH);
}
}
return ::DefWindowProc(h, m, wp, lp);
}
I then subclassed the EDIT window to draw my own icon in WM_PAINT, and then forwarded the message so I didn't have to draw everything else myself.
LRESULT CALLBACK edit_wnd_proc(
HWND h, UINT m, WPARAM wp, LPARAM lp,
UINT_PTR id, DWORD_PTR data)
{
switch (m)
{
case WM_PAINT:
{
const auto dc = GetDC(h);
// draw an icon
ReleaseDC(h, dc);
break;
}
}
return DefSubclassProc(h, m, wp, lp);
}
Note that I couldn't call BeginPaint() and EndPaint() (the equivalent of constructing a CPaintDC) in WM_PAINT because the border wouldn't get drawn. I'm guessing it has something to do with calling BeginPaint() twice (once manually, once by EDIT) and the handling of WM_ERASEBKGND. YMMV, especially with MFC.
Finally, I set the margins right after creating the EDIT:
SendMessage(
e, EM_SETMARGINS,
EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM(0, margin));
You might also have to update the margins again if the system font changes.
Have a look at this tutorial... from www.catch22.net. It gives a clear picture of how to insert a button into edit control. Though it is an Win32 example, this can be improvised to MFC as the basic structure of MFC is using win32 apis.
http://www.catch22.net/tuts/win32/2001-05-20-insert-buttons-into-an-edit-control/#
It uses WM_NCCALCSIZE to restrict the text control.

C++ MFC Refresh Window

I am using Visual Studio 2010 with MFC and I am trying to make a rectangle that is red when a device is disconnected and green when it is. I have made the rectangle with the following code:
CRect lConnectStatus;
GetDlgItem( IDC_CONNECT_STATUS ) -> GetClientRect( &lConnectStatus );
GetDlgItem( IDC_CONNECT_STATUS ) -> ClientToScreen( &lConnectStatus );
ScreenToClient( &lConnectStatus );
mConnected.Create( GetSafeHwnd(), 10000 );
mConnected.SetPosition( lConnectStatus.left, lConnectStatus.top, lConnectStatus.Width(), lConnectStatus.Height() );
if( mDevice.IsConnected() ){
mConnected.SetBackgroundColor(0, 255, 0);
}
else{mConnected.SetBackgroundColor(0, 0, 255);}
I inserted this snippet into the OnInitDlg method and the rectangle does appear, but it doesn't change to green when the device gets connected. Is there anyway I can refresh the window so that the code is executed again and the colour changes to green?
What type of control is IDC_CONNECT_STATUS? If it is a static control you can eliminate all this code and handle WM_CTLCOLOR_STATIC in the parent dialog. Your message handler for that message will control the color of the static control. To refresh the static control call Invalidate on that control. That will cause it to call your WM_CTLCOLOR_STATIC message handler.
Solved It, As I am new to C++ I didn't know that putting the code snippet into the OnInitDlg() method wouldn't work. So I put the code into the OnPaint()method and used the functions Invalidate() and UpdateWindow() to force the window to refresh when the device was connected/disconnected. Thanks for your help.
Edit Thanks to Barmak for suggesting not to create the control in the OnPaint() method. I have updated the code below.
program::OnInitDlg(){
CRect lConnectStatus;
GetDlgItem( IDC_CONNECT_STATUS ) -> GetClientRect( &lConnectStatus );
GetDlgItem( IDC_CONNECT_STATUS ) -> ClientToScreen( &lConnectStatus );
ScreenToClient( &lConnectStatus );
mConnected.Create( GetSafeHwnd(), 10000 );
mConnected.SetPosition( lConnectStatus.left, lConnectStatus.top, lConnectStatus.Width(), lConnectStatus.Height() );
}
program::OnPaint(){
if( mDevice.IsConnected() ){
mConnected.SetBackgroundColor(0, 255, 0);
}
else{mConnected.SetBackgroundColor(0, 0, 255);}
}
program::Connect(){
Invalidate();
UpdateWindow();
}
program::disconnect(){
Invalidate();
UpdateWindow();
}

EnumChildWindows never calls its callback

I'm trying to manipulate a specific Internet Explorer 11 window. Using WinSpy++ I find that
The top level window's class is an IEFrame with the title of the document as the text (as returned by GetWindowText)
The actual web view class is called "Internet Explorer_Server" and is a child of the former.
I wrote a simple test case for finding the web view of IE11 opened on "https://encrypted.google.com/" in three different ways:
HWND FindIE_A()
{
// Use FindWindow, works!
HWND hWndTop = ::FindWindowA( NULL, "Google - Internet Explorer" );
// Find the web view window, the callback (FindIEServer) is NEVER called!
HWND hWnd = NULL;
::EnumChildWindows( hWndTop, &FindIEServer, (LPARAM)&hWnd );
return hWnd;
}
HWND FindIE_B()
{
// Use EnumChildWindows with NULL as parent, works!
HWND hWndTop = NULL;
::EnumChildWindows( NULL, &FindIEMain, (LPARAM)&hWndTop );
// Find the web view window, the callback (FindIEServer) is NEVER called!
HWND hWnd = NULL;
::EnumChildWindows( hWndTop, &FindIEServer, (LPARAM)&hWnd );
return hWnd;
}
HWND FindIE_C()
{
// Simple EnumWindows, works!
HWND hWndTop = NULL;
::EnumWindows( &FindIEMain, (LPARAM)&hWndTop );
// Find the web view window, the callback (FindIEServer) is NEVER called!
HWND hWnd = NULL;
::EnumChildWindows( hWndTop, &FindIEServer, (LPARAM)&hWnd );
return hWnd;
}
The callbacks that are very simple; get a property from the window and compare against a hard-coded value:
BOOL CALLBACK FindIEServer( HWND hWnd, LPARAM lParam )
{
char className[64];
::GetClassNameA( hWnd, className, sizeof(className) );
if ( !strcmp( className, "Internet Explorer_Server" ) )
{
*(HWND*)lParam = hWnd;
return FALSE;
}
return TRUE;
}
BOOL CALLBACK FindIEMain( HWND hWnd, LPARAM lParam )
{
char text[128];
::GetWindowTextA( hWnd, text, sizeof(text) );
if ( !strcmp( text, "Google - Internet Explorer" ) )
{
*(HWND*)lParam = hWnd;
return FALSE;
}
return TRUE;
}
EnumChildWindows failed (by not calling the callback AT ALL!) every time when provided with a parent window. Why?
The problem is that when I look for the window title, I was assuming there was only one window with that title. However Internet Explorer does some shenanigans and creates multiple windows with the same title however only one of them has the class IEFrame.
It just so happens that the first window found was the wrong one, it didn't have any children (and thus EnumChildWindows doesn't have anything to iterate over). Just adding an extra check for title + class works.
However as #wakjah suggested, it is better integrate IE (or any other browser) directly into your code. With google I found lots of documentation on how to do this with both IE and Chrome.

Static Text Color

I have written Following code which will apply color to all static text in one window, but
I want to apply two different colors in one window like ID:1234 where ID is another color and 1234 will be different color in one window. How can I do this? here is what i have done:
case WM_CTLCOLORSTATIC:
SetBkColor( hdc, COLORREF( :: GetSysColor( COLOR_3DFACE) ) );
//sets bckcolor of static text same as window color
if ( ( HWND ) lParam == GetDlgItem( hWnd, IDC_PID) )
{
SetTextColor( ( HDC ) wParam, RGB( 250, 50, 200));
return ( BOOL ) CreateSolidBrush ( GetSysColor( COLOR_3DFACE) );
}
break;
I'm not sure I understand your problem. Your code looks pretty much ok. One point worth noting is that you are responsible for cleaning up resources that you allocate. With the code above you are leaking the HBRUSH object created through a call to CreateSolidBrush. Since you don't need a custom brush you should rather be using GetSysColorBrush.
As a matter of taste I would filter on the control ID rather than its window handle using GetDlgCtrlID. Incorporating the changes your code should look like this:
case WM_CTLCOLORSTATIC:
switch ( GetDlgCtrlID( (HWND)lParam ) )
{
case IDC_PID:
//sets bckcolor of static text same as window color
SetBkColor( (HDC)wParam, COLORREF( GetSysColor( COLOR_3DFACE ) ) );
SetTextColor( (HDC)wParam, RGB( 250, 50, 200) );
return (INT_PTR)GetSysColorBrush( COLOR_3DFACE );
default:
// Message wasn't handled -> pass it on to the default handler
return 0;
}

How to load default icons for CTreeCtrl nodes

due to layout customization needs, I extended CTreeCtrl class.
I absolutely need to use CDC::DrawText() function to dinamically (re)write the text of the single nodes, WITHOUT calling SetItemText() function more than once(mandatory requisite).
Then, I wrote my own implementation of OnPaint() method.
I implemented also a DrawItems() method which draws nodes in CTreeCtrl.
Since I don't want to modify anything else than single labels beside the single nodes, then I need to re-implement most of original CTreeCtrl::OnPaint() code.
I only have two doubts:
How can I show the DEFAULT CTreeCtrl icons? I dont'need/don't want
custom icons.
How can I restore the default layout of selection mode for individual nodes?
Simply, currently selected nodes should be appear highlighted.
Some pieces of simplified, auto-explanatory code below:
void MyDlg::OnPaint()
{
CPaintDC dc(this);
CDC dc_ff;
CBitmap bm_ff;
CBitmap *bm_old;
CFont *font;
CFont *old_font;
CFont fontDC;
int old_mode;
GetClientRect(&m_Rect);
dc_ff.CreateCompatibleDC( &dc );
bm_ff.CreateCompatibleBitmap( &dc, m_Rect.Width(), m_Rect.Height() );
dc_ff.SelectObject( &bm_ff );
font = GetFont();
old_font = dc_ff.SelectObject( font );
// Could / Should be called here?
CWnd::DefWindowProc(WM_PAINT, (WPARAM)dc.m_hDC, 0);
old_mode = dc_ff.SetBkMode(TRANSPARENT);
dc_ff.FillSolidRect(m_Rect, dc_ff.GetBkColor());
DrawItems( &dc_ff ); // DrawItems() member function draws single nodes of CTreeCtrl
dc.BitBlt( m_Rect.left, m_Rect.top, m_Rect.Width(), m_Rect.Height(), &dc_ff, 0, 0, SRCCOPY);
dc_ff.SelectObject( old_font );
dc_ff.SetBkMode( old_mode );
dc_ff.SelectObject( bm_old );
}
void MyDlg::DrawItems( CDC *pDC )
{
// draw items
HTREEITEM show_item, parent;
CRect rc_item;
CString name;
DWORD tree_style;
int count = 0;
int state;
bool selected;
bool has_children;
show_item = GetFirstVisibleItem();
if ( show_item == NULL )
return;
color = pDC->GetTextColor();
tree_style = ::GetWindowLong( m_hWnd, GWL_STYLE );
do
{
state = GetItemState( show_item, TVIF_STATE );
parent = GetParentItem( show_item );
has_children = ItemHasChildren( show_item ) || parent == NULL;
selected = (state & TVIS_SELECTED) && ((this == GetFocus()) ||
(tree_style & TVS_SHOWSELALWAYS));
if ( GetItemRect( show_item, rc_item, TRUE ) )
{
if ( has_children || selected )
{
if ( selected )
{
// HERE i need to
}
else
// do some stuff...
if ( has_children )
{
HICON icon;
// HERE I need to load CTreeCtrl nodes _DEFAULT_icon
icon = LoadIcon(NULL, IDI_ASTERISK);
if ( icon != NULL )
DrawIconEx( pDC->m_hDC, rc_item.left - 18, rc_item.top, icon, 16, 16,0,0, DI_NORMAL );
}
}
if ( !has_children )
{
HICON icon;
*// HERE I need to load CTreeCtrl nodes _DEFAULT_icon*
icon = LoadIcon(NULL, IDI_ASTERISK);
if ( icon != NULL )
DrawIconEx( pDC->m_hDC, rc_item.left - 18, rc_item.top, icon, 16, 16,0,0, DI_NORMAL );
}
name = GetItemText( show_item );
// ...
if ( selected )
{
pDC->DrawText( "Temp", rc_item, DT_LEFT );
}
else
{
pDC->DrawText( "Temp", rc_item, DT_LEFT );
}
//if ( state & TVIS_BOLD )
// pDC->SelectObject( font );
}
} while ( (show_item = GetNextVisibleItem( show_item )) != NULL );
}
All I need, is source code of an almost-standard CTreeCtrl::OnPaint() implementation.
Any suggestion/help is appreciated. :-)
Thanks
IT.
You don't need to overload onPaint. If you set a tree items text as LPSTR_TEXTCALLBACK, the CtreeCtrl will fire the message TVN_GETDISPINFO to retrieve new text every time that item is displayed. Regeister a message handler using ON_NOTIFY if it's in a parent window or ON_NOTIFY_REFLECT if you are subclassing CTreeCtrl. This message handler can assign the text you want but allow the treeCtrl to continue drawing as normal.
TVN_GETDISPINFO Documentation
If you went the parent Cwnd route, youd need
So you'd need to assign the message handler in the cpp file:
BEGIN_MESSAGE_MAP(MyCWnd, CWnd)
ON_NOTIFY(TVN_GETDISPINFO, tree_ctl_id, CustomTreeControl::OnGetdispinfo)
END_MESSAGE_MAP()
Function prototype in header
afx_msg void OnGetdispinfo( NMHDR* pNMHDR, LRESULT* pResult );
This at the end of your class definition
DECLARE_MESSAGE_MAP()
And the actual function to handle the request
void ColumnTreeControl::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
NMTVDISPINFO * pDispInfo = (NMTVDISPINFO )pNMHDR;
TVITEM item = &pDispInfo->item;
if(item->mask & TVIF_TEXT )
{
item->pszText " YOUR CODE HERE";
}
*pResult = 0;
}