So, I have a CDialog resource that I have had for a long time and I decided to add a statusbar to it. Here is the resource:
All controls fit nicely in the dialog. Now, at runtime this is what it looks like:
The tutorial I followed was here and for the most part it works. Here is my setup code:
///////////////////////////////
m_StatusBar.Create(this); //We create the status bar
m_StatusBar.SetIndicators(indicators, 2); //Set the number of panes
CRect rect;
GetClientRect(&rect);
//Size the two panes
m_StatusBar.SetPaneInfo(0, ID_INDICATOR_DATE,
SBPS_NORMAL, 200);
m_StatusBar.SetPaneInfo(1, ID_INDICATOR_MEETING_TYPE, SBPS_STRETCH, 0);
//This is where we actually draw it on the screen
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,
ID_INDICATOR_DATE);
GetDynamicLayout()->AddItem(m_StatusBar.GetSafeHwnd(),
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
///////////////////////////////
I have tried without WindowsBlinds and the issues are still there.
So my issues are:
1/ The controls are overlapping the status bar. How do I accurately set these controls in the resource editor so this issue won't happen? How should it be resolved? Hit n miss?
2/ My dialog supports resizing by using the dynamic layouts and it has the OBM_SIZE in the bottom right:
void CResizingDialog::InitialiseResizeIcon(CBitmap& rBmpResize, CStatic& rLblResize, CWnd* pDialog)
{
CRect rcIcon, rcClient;
if (pDialog != nullptr)
{
rBmpResize.LoadOEMBitmap(OBM_SIZE);
rLblResize.Create(nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP,
CRect(0, 0, 16, 16), pDialog, IDC_STATIC_RESIZE);
rLblResize.SetBitmap(rBmpResize);
pDialog->GetClientRect(rcClient);
rLblResize.GetClientRect(rcIcon);
rLblResize.SetWindowPos(&CWnd::wndTop,
rcClient.right - rcIcon.Width(),
rcClient.bottom - rcIcon.Height(), 0, 0, SWP_NOSIZE);
CMFCDynamicLayout *pDynamicLayout = pDialog->GetDynamicLayout();
if (pDynamicLayout != nullptr)
{
CMFCDynamicLayout::MoveSettings moveSettings = CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100);
CMFCDynamicLayout::SizeSettings sizeSettings = CMFCDynamicLayout::SizeNone();
pDynamicLayout->AddItem(rLblResize.GetSafeHwnd(), moveSettings, sizeSettings);
}
}
}
How do I avoid the issue that you can see there now in the bottom right?
Update
It looked like I should use CreateEx And use this style SBARS_SIZEGRIP. Then stop creating my own resize icon. I assume the two grippers will look the same. So this might be one of the answers.
I tried using the above flag but unfortunately I can't use it:
This gripper is not consistent with the other one I am using so I need to keep my original one instead.
Update 2
I now realise that the gripper is always created anyway, so I had two grippers there! I have now derived my own statusbar class and switched off the default gripper:
BOOL CCreateReportStatusBar::PreCreateWindow(CREATESTRUCT& cs)
{
BOOL bRet = CStatusBar::PreCreateWindow(cs);
cs.style &= ~SBARS_SIZEGRIP;
return bRet;
}
So now I only have the one gripper. But my two issues still remain.
Update 3
I stumbled over this. In theory if I override this DrawGripper method I should be able to render my own gripper instead. Doesn't work. The method is never called.
Update 4
I decided not to fight the system. I have let the status bardraw the themes gripper and I have adjusted my resizing dialog class to also draw the themes gripper. So all is good.
Related
(Update, see original question below)
After doing a bit of digging, I'm basically trying to understand the following; In the context of an MDI application, if a menu (which is associated with a specific CChildWnd) has an MF_OWNERDRAW, why are the ON_WM_MEASUREITEM and ON_WM_DRAWITEM events send to the CMainWnd instead of the CChildWnd?
In my InitInstance, the document template is registered and the associated menu is modified to add the MF_OWNERDRAW:
BOOL CMyApp::InitInstance()
{
// ...
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_CHILDFRAME,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CFooWnd),
RUNTIME_CLASS(CFooView)
);
if (pDocTemplate->m_hMenuShared != NULL) {
CMenu* pMenu = CMenu::FromHandle(pDocTemplate->m_hMenuShared);
// Add MF_ONWERDRAW to the items that need it.
pMenu->ModifyMenu([item_id], MF_BYCOMMAND | MF_OWNERDRAW, [item_id]);
}
AddDocTemplate(pDocTemplate);
// ...
}
So, once the document template is registered, the menu associated with the document/frame is modified to add the MF_ONWERDRAW flag to each of the required items (the color selection items in my case).
However, why are the OnMeasureItem and OnDrawItem events send to the CMainWnd and not the CFooWnd? And how can I direct the events to the CFooWnd instead?
The reason I'am asking, if I have 5 different types of documents in my MDI application, each needing custom menus, then the CMainWnd basically becomes a mess of message handling. The logical place for the custom menu logic is in the CChildWnd, not the CMainWnd.
Original question:
I'm doing some work on a very old application (MFC 4.2) and I'm running into a problem with drawing in a menu item.
The original application has a menu to select a color and it actually draws the colors in the menu when opened so it easier for the user to select the color.
The behavior for this implemented in CMainWnd using the OnMeasureItem and the OnDrawItem.
class CMainWnd : public CMDIFrameWnd
{
DECLARE_DYNCREATE(CMainWnd)
protected:
afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
DECLARE_MESSAGE_MAP()
};
Then, in the implementation (omitted bits and pieces for brevity):
BEGIN_MESSAGE_MAP(CMainWnd, CMDIFrameWnd)
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemWidth = ::GetSystemMetrics(SM_CYMENU) * 4;
lpmis->itemHeight = ::GetSystemMetrics(SM_CYMENU) * 1;
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CBrush* pBrush;
// draw the hover/selection rectangle
pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT :
COLOR_MENU));
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
// load a checkbox icon into a bitmap
BITMAP bm;
CBitmap bitmap;
bitmap.LoadOEMBitmap(OBM_CHECK);
bitmap.GetObject(sizeof(bm), &bm);
// if color/item selected then draw the checkbox
if (lpdis->itemState & ODS_CHECKED) {
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
dc.BitBlt(
lpdis->rcItem.left + 4,
lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / bm.bmWidth,
bm.bmHeight,
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(pOldBitmap);
}
// draw the actual color bar
pBrush = new CBrush(CPaintDoc::m_crColors[lpdis->itemID - ID_COLOR_BLACK]);
CRect rect = lpdis->rcItem;
rect.DeflateRect(6, 4);
rect.left += bm.bmWidth;
dc.FillRect(rect, pBrush);
delete pBrush;
dc.Detach();
}
What the OnDrawItem does is; it draws a horizontal color bar with a color, prefixed by a check icon if that color is selected and the menu item being hovered over is highlighted by a box being drawn around it.
However, since I'm turning this application into a Multidoc application and I don't really feel that this logic should be in the CMainWnd (since none of the other documents will have this type of menu), but that it should be part of the CChildWnd (which inherits from CMDIChildWnd).
But when I move this logic to that class, when I run the application, I get following message in the console logger:
Warning: unknown WM_MEASUREITEM for menu item 0x0082.
And none of the custom menu behavior seems to work.
so, the question is; How can move the custom behavior of a menu into the frame class of an MDI document rather than having it located in the application main frame?
I figured out a work around. Not ideal but I can understand that this is a quirk in the framework, i.e. the menu seems to be part of the MainWnd so from a technical point of view, that is where the ON_WM_MEASUREITEM and ON_WM_DRAWITEM would be handled.
Anyhow, my work around. Basically capture the events in the MainWnd and then delegate the behaviour to the ChildWnd. The trick here (I guess) is to figure out what ChildWnd to delegate to since in an MDI application there can be any number of different ChildWnd's (each with their own Document and View types).
The work around:
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoMeasureItem(nIDCtl, lpmis);
}
}
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoDrawItem(nIDCtl, lpdis);
}
}
}
Pretty straight forward, in the context of the MainWnd, get a pointer to the active MDI ChildWnd, check if it is active, then check the type by using IsKindOf and RUNTIME_CLASS and if so, voila, delegate the behavior to the ChildWnd. To DoMeasureItem and the DoDrawItem are just public methods implemented on the ChildWnd (see question for details).
I thought it be nice to add a Status Bar with percentage and other information to a CDialogEx that is used for viewing an image. But it doesn't seem that you can simply use a CMFCStatusBar or a CStatusBar and have it just work.
I found various samples, but none of them have the statusbar outside the client area and moves as resized? The different methods simply create a statusbar and it ends up hidden under a horizontal scrollbar and if you resize the window, the statusbar is sitting there in the middle of the dialog.
Is there an easy way or full example of having a statusbar on a CDialogEx that can be resized like a normal window?
Is there an easy way or full example of having a statusbar on a CDialogEx that can be resized like a normal window?
Yes! Once you have created the status bar you can add it to the dynamic layout for resizing:
//This is where we actually draw it on the screen
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,
ID_INDICATOR_MEETING_TYPE);
GetDynamicLayout()->AddItem(m_StatusBar.GetSafeHwnd(),
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
I have a status bar (not CMFCStatusBar as it will not work, but CStatusBar is OK) on two dialogs in my application.
When Dynamic Layout is not automatically enabled
Here is an updated example for when Dynamic Layout is not automatically enabled for you (CDialogEx with no controls):
BOOL CMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
if (!m_StatusBar.Create(this)) {
TRACE0("Failed to create status bar\n");
return -1;
}
m_StatusBar.SetIndicators(indicators, _countof(indicators));
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
EnableDynamicLayout();
auto pdlmanager=GetDynamicLayout();
if (pdlmanager) {
if (pdlmanager->Create(this)) {
pdlmanager->AddItem(m_StatusBar.GetSafeHwnd(), CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
}
}
// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
return TRUE;
}
Catering for horizontal scroll bars
NIf you have a horizontal scrollbar the StatusBar will end up above it; therefore you may have to create separate CWnd and add it to the dynamic layout (it would also be the nIDLeftOver of the RepositionBars()).
Here's how you can add the a "view" window for the contents so scrollbars can be contained within the view area:
BOOL CMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
if (!m_StatusBar.Create(this)) {
TRACE0("Failed to create status bar\n");
return -1;
}
m_StatusBar.SetIndicators(indicators, _countof(indicators));
CRect rc;
GetClientRect(&rc);
CString clsname=AfxRegisterWndClass(0);
m_ImageView.Create(clsname, _T(""), WS_CHILD | WS_VISIBLE, rc, this, IDC_MY_VIEW);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, IDC_MY_VIEW);
EnableDynamicLayout();
auto pdlmanager=GetDynamicLayout();
if (pdlmanager) {
if (pdlmanager->Create(this)) {
pdlmanager->AddItem(m_StatusBar.GetSafeHwnd(), CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
pdlmanager->AddItem(m_ImageView.GetSafeHwnd(), CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
return TRUE;
}
I have a multi-purpose CDialog that supports resizing. It can display content in 3 variations.
Variation 1:
Variation 2:
Variation 3:
The dialogue controls are using the dynamic layout settings from the resource editor.
Variation 1 is fine and need no changes.
Variation 2 does not display the combo and date button. As a result I would like the "Text will ..." label to be down at the bottom and the "edit" box to be taller.
Variation 3 has a similar issue where the date button should move to the bottom and the edit box be taller.
Can this be achieved by changing the dynamic layout in code?
Update
I tried this in OnInitDialog:
if (!m_bShowWeekCombo)
{
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
CRect rctNote;
m_staticInfo.GetWindowRect(rctNote);
ScreenToClient(rctNote);
m_staticInfo.MoveWindow(rctCombo.left, rctCombo.top, rctNote.Width(), rctNote.Height());
}
At first I thought it was working:
The note is now at the bottom. But as soon as I resize the window:
The note has reverted to the original position.
I know I have this answer to a similar issue but do I really have to re-build the whole layout?
Update 2
if (!m_bShowWeekCombo)
{
CRect rctEdit;
m_editText.GetWindowRect(rctEdit);
ScreenToClient(rctEdit);
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
CRect rctNote;
m_staticInfo.GetWindowRect(rctNote);
ScreenToClient(rctNote);
//m_staticInfo.MoveWindow(rctCombo.left, rctCombo.top, rctNote.Width(), rctNote.Height());
m_staticInfo.SetWindowPos(NULL, rctCombo.left, rctCombo.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
m_editText.SetWindowPos(NULL, 0, 0, rctEdit.Width(), rctEdit.Height() + (rctCombo.top - rctNote.top),
SWP_NOMOVE | SWP_NOZORDER);
if (m_pDynamicLayout)
{
if (!m_pDynamicLayout->HasItem(m_staticInfo.m_hWnd))
{
m_pDynamicLayout->AddItem(m_staticInfo.m_hWnd,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
}
else
{
TRACE(L"item already has dynamic move/size\n");
}
if (!m_pDynamicLayout->HasItem(m_editText.m_hWnd))
{
m_pDynamicLayout->AddItem(m_editText.m_hWnd,
CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
else
{
TRACE(L"item already has dynamic move/size\n");
}
}
}
When I try the above the control width is the original width, even though the dialog had restored to wider dialog width.
CMFCDynamicLayout reads the dialog resource, it stores the coordinates for the child controls as well as their dynamic resize/move properties.
This is all done in CDialog::OnInitDialog. If you move the child control, example, m_staticInfo then CMFCDynamicLayout doesn't know you moved/resized the control. So upon the next dialog resize request, CMFCDynamicLayout uses the old values.
You can add dynamic resize/move for all controls expcept m_staticInfo and other controls which you intend to move manually. Then add m_staticInfo separately:
BOOL CMyDialog::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
m_staticInfo.SetWindowPos(NULL, rctCombo.left, rctCombo.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
if(m_pDynamicLayout)
{
if(!m_pDynamicLayout->HasItem(m_staticInfo.m_hWnd))
{
m_pDynamicLayout->AddItem(m_staticInfo.m_hWnd,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeNone());
}
else
{
TRACE(L"item already has dynamic move/size\n");
AfxDebugBreak(0);
}
}
return 1;
}
Internally, MFC calls LoadDynamicLayoutResource(m_lpszTemplateName) to initialize dynamic size/move. But documentation says not to use this method directly.
Clarification
If you are using a dialog that supports resizing then you must remember to calculate the new width and height when you move the control to the new position. You would then use one of the appropriate Size calls. For example:
// The EDIT control height now needs increasing
iNewEditHeight = rctButton.top - iTextMarginY - rctEdit.top;
m_editText.SetWindowPos(nullptr, 0, 0, iNewWidth, iNewEditHeight, SWP_NOMOVE | SWP_NOZORDER);
It is up to you to workout how you want your control initially re-sized.
Then, in OnInitDialog I called a new method:
void CEditTextDlg::SetupDynamicLayout()
{
if (m_pDynamicLayout != nullptr)
{
m_pDynamicLayout->AddItem(IDC_BUTTON_INSERT_DATE,
CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
m_pDynamicLayout->AddItem(IDC_STATIC_INFO,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
m_pDynamicLayout->AddItem(IDC_EDIT_TEXT,
CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
If you don't set the width correctly when using SetWindowPos and only use SizeNone() it will not resize correctly.
I'm having a bug in my code that's kicking my ass, so after much attempted debugging I finally decided to see if anyone else knew what my issue was.
I'm trying to add a grid object to a dialog that I have, but I keep hitting the assert mentioned in the title and I don't know why.
LONG myDialog::OnInitDialog(UINT wParam, LONG lParam)
{
BOOL bRet = super::OnInitDialog();
InitGridControl();
InitLayout();
myApp.ActiveDocChangeEvent->Attach(
RefMemberDelegate1(*this, &myDialog::OnNewDoc), this); // attach to event so I know when document is created
return bRet;
}
void myDialog::OnNewDoc(CDerivedDocument* pNewDoc)
{
pNewDoc->SetMyDialog(this); // when new document is created, set pointer to dialog
}
void myDialog::InitGridControl()
{
CRect rect;
// Get the grid area rectangle and set it up.
GetDlgItem(IDC_GRID)->GetClientRect(rect);
GetDlgItem(IDC_GRID)->MapWindowPoints(this, &rect); // replacing dummy image with the grid
m_Grid = new myGridCtrl;
bool result = m_Grid->Create(WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP, rect, this, IDC_GRID);
// Set the appropriate options
//...options...
m_Grid->InsertColumn(0, _T("Name"), 100); // doesn't seem to crash here, which means grid is created okay?
}
void myDialog::PopulateGridControl(BOOL bRedraw, CDerivedDocument * pDoc)
{
if (GetSafeHwnd() == NULL)
return;
// get handles to document and stuff
m_Grid->SetRedraw(FALSE); // ** ASSERT() CALL IS HERE **
m_Grid->RemoveAll();
// other stuff..
}
/////////////////////
// In CDocument, once it is created...
CDerivedDocument::SetMyDoc(myDialog * pDlg)
{
pDlg->PopulateGridControl(true,this);
}
Any idea what's going on? I mean, I only create the dialog once everything has been initialized, so there shouldn't be a problem there. m_Grid.Create() returns true, so creation is successful. Why is SetRedraw() hitting the assert that the m_hWnd isn't a handle to a window? Where does m_hWnd get set anyway?
Thanks for any help you can offer.
Cheers
Are you sure the dialog is created when you call
CDerivedDocument::SetMyDoc(myDialog * pDlg)?
What I see is that you are loading the grid (& dialog) from document, you should rather load the dialog and grid from the view using the document.
This may not be the direct cause of your assert trouble but nevertheless an improvement. It might just put things in the right order and fix this issue.
I have a class derived from CTreeCtrl. In OnCreate() I replace the default CToolTipCtrl object with a custom one:
int CMyTreeCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CTreeCtrl::OnCreate(lpCreateStruct) == -1)
return -1;
// Replace tool tip with our own which will
// ask us for the text to display with a TTN_NEEDTEXT message
CTooltipManager::CreateToolTip(m_pToolTip, this, AFX_TOOLTIP_TYPE_DEFAULT);
m_pToolTip->AddTool(this, LPSTR_TEXTCALLBACK);
SetToolTips(m_pToolTip);
// Update: Added these two lines, which don't help either
m_pToolTip->Activate(TRUE);
EnableToolTips(TRUE);
return 0;
}
My message handler looks like this:
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CMyTreeCtrl::OnTtnNeedText)
However I never receive a TTN_NEEDTEXT message. I had a look with Spy++ and it also looks like this message never gets sent.
What could be the problem here?
Update
I'm not sure whether this is relevant: The CTreeCtrl's parent window is of type CDockablePane. Could there be some extra work needed for this to work?
Finally! I (partially) solved it:
It looks like the CDockablePane parent window indeed caused this problem...
First I removed all the tooltip-specific code from the CTreeCtrl-derived class. Everything is done in the parent pane window.
Then I edited the parent window's OnCreate() method:
int CMyPane::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDockablePane::OnCreate(lpCreateStruct) == -1)
return -1;
const DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
TVS_CHECKBOXES | TVS_DISABLEDRAGDROP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT |
TVS_INFOTIP | TVS_NOHSCROLL | TVS_SHOWSELALWAYS;
// TREECTRL_ID is a custom member constant, set to 1
if(!m_tree.Create(dwStyle, m_treeRect, this, TREECTRL_ID ) )
{
TRACE0("Failed to create trace tree list control.\n");
return -1;
}
// m_pToolTip is a protected member of CDockablePane
m_pToolTip->AddTool(&m_tree, LPSTR_TEXTCALLBACK, &m_treeRect, TREECTRL_ID);
m_tree.SetToolTips(m_pToolTip);
return 0;
}
Unforunately we cannot simply call AddTool() with less parameters because the base class will complain in the form of an ASSERT about a uFlag member if there is no tool ID set.
And since we need to set the ID, we also need to set a rectangle. I created a CRect member and set it to (0, 0, 10000, 10000) in the CTor. I have not yet found a working way to change the tool's rect size so this is my very ugly workaround. This is also why I call this solution partial. Update: I asked a question regarding this.
Finally there is the handler to get the tooltip info:
// Message map entry
ON_NOTIFY(TVN_GETINFOTIP, TREECTRL_ID, &CMobileCatalogPane::OnTvnGetInfoTip)
// Handler
void CMyPane::OnTvnGetInfoTip(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMTVGETINFOTIP>(pNMHDR);
// This is a CString member
m_toolTipText.ReleaseBuffer();
m_toolTipText.Empty();
// Set your text here...
pGetInfoTip->pszText = m_toolTipText.GetBuffer();
*pResult = 0;
}
I believe you still have to enable the tooltip, even though you are replacing the builtin.
EnableToolTips(TRUE);
Well, since that did not work for you and since no-one more expert has offered any help, here a few more suggestions from me. Although they are lame, they might get you moving again:
Make sure your OnCreate() rotine is actually being executed.
Enable the tool tip BEFORE you replace it.
The code I use to do this looks like this. ( I confess I do not understand all the details, I copied it from some sample code, it worked and so I never looked at it any more. )
// Enable the standard tooltip
EnableToolTips(TRUE);
// Disable the builtin tooltip
CToolTipCtrl* pToolTipCtrl = (CToolTipCtrl*)CWnd::FromHandle((HWND)::SendMessage(m_hWnd, LVM_GETTOOLTIPS, 0, 0L));
I haven't tried in a CTreeCtrl but I think you should call RelayEvent for the tooltip ctrl to know when the tooltip has to be displayed. Try this:
MyTreeCtrl.h:
virtual BOOL PreTranslateMessage(MSG* pMsg);
MyTreeCtrl.cpp:
BOOL CMyTreeCtrl::PreTranslateMessage(MSG* pMsg)
{
m_pToolTip.Activate(TRUE);
m_pToolTip.RelayEvent(pMsg);
return CTreeCtrl::PreTranslateMessage(pMsg);
}
I hope this help.
Don't you have to override OnToolHitTest()?
(old) Resource 1
(old) Resource 2:
In addition to returning the hit code (nHit), you also have to fill out the TOOLINFO struct. Here's how VIRGIL does it in CMainFrame::OnToolHitTest:
int nHit = MAKELONG(pt.x, pt.y);
pTI->hwnd = m _ hWnd;
pTI->uId = nHit;
pTI->rect = CRect(CPoint(pt.x-1,pt.y-1),CSize(2,2));
pTI->uFlags |= TTF _ NOTBUTTON;
pTI->lpszText = LPSTR _ TEXTCALLBACK;
Most of this is obvious—like setting hwnd and uId—but some of it is less so. I set the rect member to a 2-pixel-wide, 2-pixel-high rectangle centered around the mouse location. The tooltip control uses this rectangle as the bounding rectangle of the "tool," which I want to be tiny, so moving the mouse anywhere will constitute moving outside the tool. I set TTF _ NOTBUTTON in uFlags because the tooltip is not associated with a button. This is a special MFC flag defined in afxwin.h; MFC uses it to do help for tooltips. There's another MFC-extended flag for tooltips, TTF _ ALWAYSTIP. You can use it if you want MFC to display the tip even when your window is not active.
You may have noticed that so far I haven't told MFC or the tooltip or the TOOLINFO what the actual text of the tip is. That's what LPSTR _ TEXTCALLBACK is for. This special value tells the tooltip control (the internal, thread-global one that MFC uses) to call my window back to get the text. It does this by sending my window a WM _ NOTIFY message with notification code TTN _ NEEDTEXT.
Try to specifically handle all tooltip ids:
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, &CMyTreeCtrl::OnNeedTipText)
If that doesn't work, you may have to manually call RelayEvent() from PreTranslateMessage().