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;
}
Related
In a standard C++/MFC MDI doc/view project, I want to implement a tracking tooltip in the view (the tabbed view windows which generally occupy most of the main frame window). So, in class MyAppView, I have a member CToolTipCtrl tooltip. Function MyAppView::OnInitialUpdate() contains the initialization
BOOL ok0 = tooltip.Create(this, TTS_ALWAYSTIP);
CRect clientRect; GetClientRect(&clientRect);
BOOL ok2 = tooltip.AddTool(this, LPSTR_TEXTCALLBACK, &clientRect, 1234/*tool ID*/);
tooltip.Activate(TRUE);
to make the entire client area of the view be the "tool". The message map contains an entry
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnNeedToolTipText)
and the function OnNeedToolTipText is defined as
BOOL MyAppView::OnNeedToolTipText(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
{
UNREFERENCED_PARAMETER(id);
NMTTDISPINFO *pTTT = (NMTTDISPINFO *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if(nID == 1234)
{
// Come here when text is needed for tracking tooltip
}
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
_stprintf_s(pTTT->szText, sizeof(pTTT->szText) / sizeof(TCHAR),
_T("Control ID = %d"), nID);
pTTT->hinst = AfxGetResourceHandle();
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
}
What happens is that only placing the mouse on the menu items (File, Edit, View, Window, Help) causes the code to enter OnNeedToolTipText, with an ID of 0-5. Moving the mouse into the client area (the view) does nothing.
How can I get the tooltip to appear in the client area of the view only?
Visual Studio 2017; C++; 64-bit Windows 7
In order to solve the problem you need to do the following:
BOOL CMyAppView::PreTranslateMessage(MSG* pMsg)
{
switch (pMsg->message)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
if (m_pToolTip->GetSafeHwnd () != NULL)
{
m_pToolTip->RelayEvent(pMsg);
}
break;
}
return CScrollView::PreTranslateMessage(pMsg);
}
If you want a tracking tooltip in a view, these are the steps to follow:
Create tooltip and add the tool.
void CToolTipDemoView::OnInitialUpdate()
{
// ...
m_toolTip.Create(this, TTS_ALWAYSTIP | TTS_NOANIMATE);
m_toolTip.AddTool(this, _T("Doesn't matter"));
}
Handle WM_MOUSEMOVE message. First, call _TrackMouseEvent in order to further receive WM_MOUSELEAVE and activate the tooltip. Second, update the tooltip text, and show it at mouse pointer coordinates.
void CToolTipDemoView::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bTrackingMouseLeave)
{
TRACKMOUSEEVENT tme = { 0 };
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = m_hWnd;
::_TrackMouseEvent(&tme);
m_toolTip.Activate(TRUE);
m_bTrackingMouseLeave = TRUE;
}
if (m_pointLastMousePos != point)
{
CString strText;
strText.Format(_T("x = %d y = %d"), point.x, point.y);
m_toolTip.UpdateTipText(strText, this);
m_toolTip.Popup();
m_pointLastMousePos = point;
}
CScrollView::OnMouseMove(nFlags, point);
}
Handle WM_MOUSELEAVE and deactivate the tooltip.
void CCToolTipDemoView::OnMouseLeave()
{
m_bTrackingMouseLeave = FALSE;
// mouse pointer leaves the window so deactivate the tooltip
m_toolTip.Activate(FALSE);
CScrollView::OnMouseLeave();
}
Notes:
there is no more necessary to handle TTN_NEEDTEXT.
also, there is no more necessary to override PreTranslateMessage
So I went back to see what I could be missing. I wrote this stuff over 10 years ago. I had also overridden a CWnd member
virtual INT_PTR OnToolHitTest( CPoint point, TOOLINFO* pTI ) const;
With:
INT_PTR HERichView::OnToolHitTest( CPoint point, TOOLINFO* pTI ) const
{
pTI->hwnd = m_hWnd;
pTI->uId = point.x + ( point.y << 16 );
CRect rect;
GetClientRect( rect );
pTI->rect= rect;
pTI->lpszText= LPSTR_TEXTCALLBACK;
return pTI->uId;
}
And I checked, it won't work without this. So your:
ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTip )
Should get called if you add the above. And only EnableToolTips( ); Should be needed.
I have not succeeded in getting the tracking tooltip to work within MFC. The closest I have come is
In message map: ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnNeedToolTipText)
In OnInitialUpdate: BOOL ok1 = EnableTrackingToolTips(TRUE);
In override of virtual function OnToolHitTest:
pTI->hwnd = m_hWnd;
pTI->uId = (UINT_PTR)m_hWnd;
pTI->uFlags = TTF_IDISHWND | TTF_ALWAYSTIP | TTF_TRACK | TTF_NOTBUTTON | TTF_ABSOLUTE | TTF_SUBCLASS;
pTI->lpszText = LPSTR_TEXTCALLBACK;
return pTI->uId;
In OnNeedToolTipText:
NMTTDISPINFO *pTTT = (NMTTDISPINFO *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
CURSORINFO ci; ci.cbSize = sizeof(CURSORINFO); // get something interesting to display
GetCursorInfo(&ci);
_stprintf_s(pTTT->szText, sizeof(pTTT->szText) / sizeof(TCHAR),
_T("Control ID = %lld at (%d, %d)"), nID, ci.ptScreenPos.x, ci.ptScreenPos.y);
pTTT->hinst = AfxGetResourceHandle();
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
This produces the following peculiar behavior. When I start the app and move the mouse cursor into the client area of the CScrollView, a tooltip appears right next to the cursor.
If I move the mouse carefully (smoothly) the tooltip tracks properly. After a while, though, it disappears, and no further mouse motions, including leaving the CScrollView window and returning, make it re-appear.
I think what is happening is that when the mouse cursor moves over the tooltip window, the tooltip is turned off, permanently. This disappearance does not seem to be time-related (e g, due to auto-pop); if the mouse is left untouched, the tooltip remains indefinitely.
I have created TreeView like this:
TreeView=CreateWindowEx(0, WC_TREEVIEW, TEXT("Tree View"), WS_VISIBLE | WS_CHILD, 0, 0, 200, 500, hwnd, (HMENU)ID_TREE_VIEW, GetModuleHandle(NULL), NULL);
Now I added one item to it like shown on this website.
It all okay, but after hours and hours of googling I still didn't found answer to these questions:
How to add subitems (nodes)?
How to add checkbox on each item (how to determine if specified checkbox is checked)?
EDIT #4:
In response to OPs request, I have added an example that removes checkbox from a parent node.
THE PROBLEM IS THAT CHECKBOX STILL APPEARS WHEN USER SELECTS A NODE AND PRESSES SPACEBAR.
This question solves that problem.
EDIT #3:
I have added the code that creates a node that is already checked.
It is the second child bode in the WM_CREATE handler.
END OF EDIT
case WM_CREATE:
{
// this is your treeview
TreeView = CreateWindowEx(0, WC_TREEVIEW,
TEXT("Tree View"), WS_VISIBLE | WS_CHILD,
0, 0, 200, 500,
hwnd, (HMENU)ID_TREE_VIEW, GetModuleHandle(NULL), NULL);
/************ enable checkboxes **************/
DWORD dwStyle = GetWindowLong( TreeView , GWL_STYLE);
dwStyle |= TVS_CHECKBOXES;
SetWindowLongPtr( TreeView , GWL_STYLE, dwStyle );
/************ add items and subitems **********/
// add root item
TVINSERTSTRUCT tvis = {0};
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is root item";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = TVI_ROOT;
HTREEITEM hRootItem = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// and here is an example of removing a checkbox
// from a specific item/subitem in case you ever need it
TVITEM tvi;
tvi.hItem = hRootItem ;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
TreeView_SetItem( TreeView, &tvi );
// add firts subitem for the hTreeItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is first subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
HTREEITEM hTreeSubItem1 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// now we insert second subitem for hRootItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT | TVIF_STATE; // added extra flag
tvis.item.pszText = L"This is second subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
// for demonstration purposes let us check this node;
// to do that add the following code, and add the extra flag for
// mask member like above
tvis.item.stateMask = TVIS_STATEIMAGEMASK;
tvis.item.state = 2 << 12;
HTREEITEM hTreeSubItem2 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// let us expand the root node so we can see if checked state is really set
TreeView_Expand( TreeView, hRootItem, TVE_EXPAND );
}
return 0L;
EDIT #2:
Here Is the part that explains how to check if item is checked ( it now properly checks when you click on a checkbox and when you press spacebar! ) :
case WM_NOTIFY:
{
LPNMHDR lpnmh = (LPNMHDR) lParam;
if( lpnmh->idFrom == ID_TREE_VIEW ) // if this is our treeview control
{
switch( lpnmh->code ) // let us filter notifications
{
case TVN_KEYDOWN: // tree has keyboard focus and user pressed a key
{
LPNMTVKEYDOWN ptvkd = (LPNMTVKEYDOWN)lParam;
if( ptvkd->wVKey == VK_SPACE ) // if user pressed spacebar
{
// get the currently selected item
HTREEITEM ht = TreeView_GetSelection( ptvkd->hdr.hwndFrom );
// Prepare to test items state
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = (HTREEITEM)ht;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem( ptvkd->hdr.hwndFrom, &tvItem );
// Return zero if it's not checked, or nonzero otherwise.
if( (BOOL)(tvItem.state >> 12) - 1 )
MessageBox( hwnd, L"Not checked!", L"", MB_OK );
else
MessageBox( hwnd, L"Checked!", L"", MB_OK );
}
}
return 0L; // see the documentation for TVN_KEYDOWN
case NM_CLICK: // user clicked on a tree
{
TVHITTESTINFO ht = {0};
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints( HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1 );
TreeView_HitTest(lpnmh->hwndFrom, &ht);
if(TVHT_ONITEMSTATEICON & ht.flags)
{
// Prepare to receive the desired information.
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = (HTREEITEM)ht.hItem;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem( lpnmh->hwndFrom, &tvItem );
// Return zero if it's not checked, or nonzero otherwise.
if( (BOOL)(tvItem.state >> 12) - 1 )
MessageBox( hwnd, L"Not checked!", L"", MB_OK );
else
MessageBox( hwnd, L"Checked!", L"", MB_OK );
}
}
default:
break;
}
}
}
break;
The relevant idea for proper testing when spacebar is pressed is handling of TVN_KEYDOWN message.
We use this message to get NMTVKEYDOWN structure filled, which will give us virtual key code of the pressed button and the HWND of the treeview that sent the notification.
Now we use TreeView_GetItem() macro to get the currently selected node and we check its state the same way we did when we did hit testing.
My only problem is concerning this part from the documentation for TVN_KEYDOWN:
Return value
If the wVKey member of lParam is a character key code, the character
will be used as part of an incremental search. Return nonzero to
exclude the character from the incremental search, or zero to include
the character in the search. For all other keys, the return value is
ignored.
I just do not know what to do with the return result so I have put 0L.
Important note: If you need to return value from dialog box procedure use something like this:
SetWindowLongPtr( hwnd, DWLP_MSGRESULT, (LONG_PTR)1 );
return TRUE;
see the remarks for Return value in this documentation and use SetWindowLongPtr instead of SetWindowLong so you can support both x32 and x64 versions of Windows.
That would be all. Hopefully you have your problem solved. If you need further help leave a comment.
END OF EDIT
I have never done checking if tree item is checked but I believe that accepted answer to this question is the way to go.
NOTE:
I would highly appreciate if there someone who can provide code snippet for showing how to determine if treeview node is checked or not.
My application has a message-only window that is launched from a newly created thread. The thread function creates the message-only window and runs the message pump. The problem I am having is that the message pump never seems to get the WM_CLOSE message. I've removed error-handling to simplify the posted code. Does anyone know what I'm doing wrong?
Constructor:
this->m_hInstance = ::GetModuleHandle( NULL );
this->m_wcx.cbSize = sizeof(WNDCLASSEX); // size of structure
this->m_wcx.style = CS_HREDRAW | CS_VREDRAW; // initially minimized
this->m_wcx.lpfnWndProc = &WndProc; // points to window procedure
this->m_wcx.cbClsExtra = 0; // no extra class memory
this->m_wcx.cbWndExtra = 0; // no extra window memory
this->m_wcx.hInstance = m_hInstance; // handle to instance
this->m_wcx.hIcon = ::LoadIcon( NULL, IDI_APPLICATION ); // default app icon
this->m_wcx.hCursor = ::LoadCursor( NULL, IDC_ARROW ); // standard arrow cursor
this->m_wcx.hbrBackground = NULL; // no background to paint
this->m_wcx.lpszMenuName = NULL; // no menu resource
this->m_wcx.lpszClassName = s_pwcWindowClass; // name of window class
this->m_wcx.hIconSm = NULL; // search system resources for sm icon
this->m_atom = ::RegisterClassEx( &m_wcx );
this->m_hNotifyWindowThread = ::CreateThread(
NULL, // no security attributes
0, // use default initial stack size
reinterpret_cast<LPTHREAD_START_ROUTINE>(NotifyWindowThreadFn), // function to execute in new thread
NULL, // thread parameters
0, // use default creation settings
NULL // thread ID is not needed
);
Destructor:
::DestroyWindow( this->m_hWnd );
::WaitForSingleObject( this->m_hNotifyWindowThread, NW_DEFAULT_TIMEOUT ); // <-- Seems to get stuck here.
::UnregisterClass( s_pwcWindowClass, this->m_hInstance );
Thread function:
s_ptInterface->pobjNotifyWindow->m_hWnd = ::CreateWindow(
s_pwcWindowClass, // window class name
s_pwcWindowName, // window name
WS_ICONIC, // window style is minimized
0, // initial horizontal position
0, // initial vertical position
CW_USEDEFAULT, // window width
0, // window height
NULL, // no parent window
NULL, // no menu
s_ptInterface->pobjNotifyWindow->GetInstanceHandle(), // associated instance
NULL // no additional info for WM_CREATE
);
::ShowWindow( s_ptInterface->pobjNotifyWindow->GetWindowHandle(), SW_HIDE );
::UpdateWindow( s_ptInterface->pobjNotifyWindow->GetWindowHandle();
dbt.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
dbt.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
dbt.dbcc_classguid = s_guidForCP210xDevices;
m_hNotify = RegisterDeviceNotification( m_hWnd, &dbt, DEVICE_NOTIFY_WINDOW_HANDLE );
while ( (blRetVal = ::GetMessage(
&msg, // message structure
NULL, // retrieve messages for all windows on this thread
0, // lowest message value to retrieve
0 // highest message value to retrieve
)) != 0 )
{
if ( blRetVal == -1 )
{
return ::GetLastError();
}
else
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
}
Window procedure:
switch ( uMsg )
{
case WM_CLOSE:
if ( m_hNotify != NULL )
{
::UnregisterDeviceNotification( m_hNotify );
m_hNotify = NULL;
}
::DestroyWindow( hWnd );
break;
case WM_DESTROY:
::PostQuitMessage( 0 );
break;
case WM_DEVICECHANGE:
if ( pHeader != NULL )
{
if ( pHeader->dbch_devicetype == DBT_DEVTYP_PORT )
{
switch ( wParam)
{
case DBT_DEVICEREMOVECOMPLETE: // Device is gone
::EnterCriticalSection( &(s_ptInterface->csSerialPort) );
s_ptInterface->pobjSerialPort->Close();
::LeaveCriticalSection( &(s_ptInterface->csSerialPort) );
break;
case DBT_DEVICEARRIVAL: // System detected device
::EnterCriticalSection( &(s_ptInterface->csSerialPort) );
s_ptInterface->pobjSerialPort->Open();
::LeaveCriticalSection( &(s_ptInterface->csSerialPort) );
break;
default:
// Do nothing.
break;
}
}
}
break;
default:
// Do nothing.
break;
}
return ::DefWindowProc( hWnd, uMsg, wParam, lParam );
WM_CLOSE is sent when Windows asks your app to close the window. When the user clicks the upper right Close button or presses Alt+F4 for example. None of that is going to happen, you called DestroyWindow(). You need to use WM_DESTROY instead. Which is fine, no need to veto the close request.
I made an editor which written in pure WinAPI. Some users want the caption icon become a drag source of the file that opened in editor, like what the explorer window does. I have no idea to implement such feature. Can someone give me example please?
Here is a sample showing how to use the system menu ("caption icon") to detect when to initiate a drag-drop operation:
class SysMenuDragSample
: public CWindowImpl< SysMenuDragSample, CWindow, CFrameWinTraits >
{
private:
static const UINT WM_SHOWSYSTEMMENU = 0x313;
bool mouse_down_in_sys_menu_;
public:
BEGIN_MSG_MAP_EX( SysMenuDragSample )
MSG_WM_NCLBUTTONDOWN( OnNcLButtonDown )
MSG_WM_NCLBUTTONUP( OnNcLButtonUp )
MSG_WM_MOUSEMOVE( OnMouseMove )
MSG_WM_LBUTTONUP( OnLButtonUp )
END_MSG_MAP()
SysMenuDragSample()
: mouse_down_in_sys_menu_( false )
{
}
void BeginDragDropOperation()
{
// TODO: Implement
}
void OnNcLButtonDown( UINT hit_test, CPoint cursor_pos )
{
if( hit_test == HTSYSMENU )
{
mouse_down_in_sys_menu_ = true;
SetCapture();
// NOTE: Future messages will come through WM_MOUSEMOVE, WM_LBUTTONUP, etc.
}
else
SetMsgHandled( FALSE );
}
void OnNcLButtonUp( UINT hit_test, CPoint cursor_pos )
{
if( hit_test == HTSYSMENU )
{
// This message and hit_test combination should never be received because
// SetCapture was called in OnNcLButtonDown.
assert( false );
}
else
SetMsgHandled( FALSE );
}
void OnMouseMove( UINT modifiers, CPoint cursor_pos )
{
if( mouse_down_in_sys_menu_ )
{
ClientToScreen( &cursor_pos );
UINT hit_test = SendMessage( WM_NCHITTEST, 0, MAKELPARAM( cursor_pos.x, cursor_pos.y ) );
if( hit_test != HTSYSMENU )
{
// The cursor has moved outside of the system menu icon so begin the drag-
// drop operation.
mouse_down_in_sys_menu_ = false;
ReleaseCapture();
BeginDragDropOperation();
}
}
else
SetMsgHandled( FALSE );
}
void OnLButtonUp( UINT modifiers, CPoint cursor_pos )
{
if( mouse_down_in_sys_menu_ )
{
// The button was released inside the system menu so simulate a normal click.
mouse_down_in_sys_menu_ = false;
ReleaseCapture();
ClientToScreen( &cursor_pos );
SendMessage( WM_SHOWSYSTEMMENU, 0, MAKELPARAM( cursor_pos.x, cursor_pos.y ) );
}
else
SetMsgHandled( FALSE );
}
};
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.