MFC: Create tooltip for a Tab in CTabView - c++

I want to create a ToolTip for a tab on a CTabView which is in one of the CSplitterWnd panes. In this case the tab is a CHtmlView. I put the CToolTipCtrl m_ToolTip in the CMainFrame as public. I create it on OnCreate(), then attempt to add test text to it on OnInitialUpdate() of the CHtmlView tab item. But when hovering over the tab or client area of the tab, no tool tip shows. What am I doing wrong or missing?
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
// ...
m_ToolTip.Create(this);
return 0;
}
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
// create spliter windows - one of which will end up with CTabView
}
// One of the tabs in the CTabView is based on CHtmlView
void CMyHtmlView::OnInitialUpdate()
{
__super::OnInitialUpdate();
CToolTipCtrl &tooltip=((CMainFrame*) AfxGetMainWnd())->m_ToolTip;
tooltip.AddTool(this, _T("Test Tooltip"));
tooltip.Activate(TRUE);
}
Abandoning the above, I implemented the suggestion on page 941-942 in the book Programming Windows with MFC second Edition to derive from CToolTipCtrl and implement new functions that use TTF_SUBCLASS. I tried it with in the CMyTabView on the client area of the tabs. It sort of works with the Edit and Rich Views but not HTML view. By sort of, I mean, it works maybe twice then doesn't come up again. Here's that part:
int CMyTabView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CTabView::OnCreate(lpCreateStruct) == -1)
return -1;
AddView(RUNTIME_CLASS(CMyEditView), _T("Tab1"));
AddView(RUNTIME_CLASS(CMyRichView), _T("Tab2"));
AddView(RUNTIME_CLASS(CMyHtmlView), _T("Tab3"));
m_ToolTip.Create(this, TTS_ALWAYSTIP);
CMFCTabCtrl &tabctrl=GetTabControl();
for (int i=0; i < tabctrl.GetTabsNum(); ++i) {
m_ToolTip.AddWindowTool(tabctrl.GetTabWnd(i), _T("Test Tool Tip"));
}
return 0;
}
Not working on the HTML View was fine because I really just want it on the tab. So I change it up and got rid of the stuff above (except the .Create) and added:
void CMyTabView::OnInitialUpdate()
{
CTabView::OnInitialUpdate();
CMFCTabCtrl &tabctrl=GetTabControl();
CRect rc;
tabctrl.GetTabsRect(rc);
if (!rc.IsRectEmpty()) {
m_ToolTip.AddRectTool(&tabctrl, _T("test tip"), &rc, (UINT_PTR) tabctrl.GetSafeHwnd());
}
}
But that doesn't work. It tried using this as well, still didn't work. So while something worked, still can't get it to work on the tabs? Any ideas?

Related

MFC: How do you implement a context menu for CRichEditView in a CTabView tab?

I have a CTabView with one of the tabs a CRichEditView. Rich text is added to the control and shows fine. If I select text within the CRichEditView the toolbar edit items work fine (for example, copy highlights, and if I click on it, it copies to the clipboard). However, I found that if I selected text and right click there is no context menu with a CRichEditView like there was with CEditView. Searching the Internet, I found an implementation for CRichEditView::GetContextMenu() to try and use. It first had an assert failure because the CDocument is not a rich text type, so for testing, I removed it (commented out below) and ended up with the following:
HMENU CMyRichView::GetContextMenu(WORD seltyp, LPOLEOBJECT lpoleobj, CHARRANGE* lpchrg)
{
// TODO: Add your specialized code here and/or call the base class
/*
CRichEditCntrItem* pItem = GetSelectedItem();
if (pItem == NULL || !pItem->IsInPlaceActive())*/
{
CMenu menuText;
menuText.LoadMenu(IDR_CONTEXT_EDIT_MENU);
CMenu* pMenuPopup = menuText.GetSubMenu(0);
menuText.RemoveMenu(0, MF_BYPOSITION);
return pMenuPopup->Detach();
}
}
Where the IDR_CONTEXT_EDIT_MENU is:
IDR_CONTEXT_EDIT_MENU MENU
BEGIN
POPUP "edit"
BEGIN
MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY
END
END
Now when I right-click I see the context menu. However, when I choose "copy", nothing happens. So I mapped the ID_EDIT_COPY so I could set a break point to see if it was called.
void CMyRichView::OnEditCopy()
{
// TODO: Add your command handler code here
ASSERT_VALID(this);
GetRichEditCtrl().Copy();
}
It's not if the context item is used, but it is if the toolbar is used.
What am I missing and doing wrong?
TIA!!
If the message goes to CTabView then add CTabView::OnEditCopy handler.
Otherwise, you can override PreTranslateMessage as shown below, this will make sure the message is sent to CMyRichEditView::OnEditCopy.
BOOL CMyRichEditView::PreTranslateMessage(MSG *msg)
{
if(msg->message == WM_CONTEXTMENU || msg->message == WM_RBUTTONDOWN)
{
CMenu menu;
menu.LoadMenu(IDR_CONTEXT_EDIT_MENU);
int c = menu.GetMenuItemCount();
CMenu* popup = menu.GetSubMenu(0);
popup->TrackPopupMenu(0, msg->pt.x, msg->pt.y, this, NULL);
return TRUE;
}
return CRichEditView::PreTranslateMessage(msg);
}

Example code for CMFCMenuButton?

Sorry for the newbie question, but can anyone point me at sample code that illustrates the use of the CMFCMenuButton? The Microsoft help refers to "New Controls samples", but these samples seem to be in the Visual Studio 2008 "Feature Pack", and this refuses to install on my system since I'm running VS 2013 and don't have VS 2008. I haven't been able to find the samples as stand-alone code.
To be specific, I have a dialog bar in which I want a button labelled Save with drop-down options of Save All and Save Visible (with Save All the default). But any working code would at least get me started.
Declare data members:
CMFCMenuButton m_button_menu;
CMenu m_menu;
Also add the button's id to message map and data exchange:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_BN_CLICKED(IDC_MFCMENUBUTTON1, OnButtonMenu)
...
END_MESSAGE_MAP
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_MFCMENUBUTTON1, m_button_menu);
}
Define:
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
//...
m_menu.LoadMenu(IDR_MENU1);
m_button_menu.m_hMenu = m_menu.GetSubMenu(0)->GetSafeHmenu();
return TRUE;
}
Where IDR_MENU1 is a regular menu bar and we get its first submenu. For example:
IDR_MENU1 MENU
BEGIN
POPUP "Dummy"
BEGIN
MENUITEM "&Item1", ID_FILE_ITEM1
MENUITEM "&Item2", ID_FILE_ITEM2
END
END
If button's drop-down arrow is clicked, a popup menu appears, menu result is passed to OnButtonMenu. If left side of button is clicked, then OnButtonMenu is called directly, without showing a popup menu.
void CMyDialog::OnButtonMenu()
{
CString str;
switch (m_button_menu.m_nMenuResult)
{
case ID_FILE_ITEM1:
str = L"first menu item clicked";
break;
case ID_FILE_ITEM2:
str = L"second menu item clicked";
break;
default:
str = L"Button click (popup menu did not appear, or menu ID is not handled)";
break;
}
MessageBox(str);
}
** When working with docking controls, dialog bars, etc. MFC may run its own subclass, I don't think DoDataExchange gets called. m_button_menu could be invalid. GetDlgItem can be used to find the correct pointer:
CMFCMenuButton* CMyDlgBar::GetButtonMenu()
{
CMFCMenuButton* pButton = &m_button_menu;
if (!IsWindow(pButton->m_hWnd))
pButton = (CMFCMenuButton*)GetDlgItem(IDC_MFCMENUBUTTON1);
return pButton;
}
Everywhere else we use GetButtonMenu() instead of m_button_menu. For example:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//...
m_dlgbar.Create(...);
m_dlgbar.m_menu.LoadMenu(IDR_MENU1);
m_dlgbar.GetButtonMenu()->m_hMenu = m_dlgbar.m_menu.GetSubMenu(0)->GetSafeHmenu();
return 0;
}
void CMainFrame::OnButtonMenu()
{
CString str;
switch (GetButtonMenu()->m_nMenuResult)
...
}
What if the Drop-Down Arrow does not show?
Then read the answer here that explains the changes needed to your RC file.

CMFCCaptionMenuButton alternative?

I need to create a caption bar button for a CDockablePane which will call up a menu with various options. I tried to use CMFCCaptionMenuButton and the button and menu show up but the message map methods for the menu ids don't fire. The MFC documentation states that CMFCCaptionMenuButton is meant for internal infrastructure and not really for your code.
So assuming that is what my problem is should I be using a CMFCCaptionBarButton and then making a separate popup menu? Has anyone made a similar caption bar based menu in MFC before?
Here's some slimmed down code snippets in case I just made a stupid mistake in hooking up the events:
BEGIN_MESSAGE_MAP(CDockPane, CDockablePane)
ON_COMMAND(ID_MORPH_BROWSER, OnMorphBrowser)
END_MESSAGE_MAP()
void CDockPane::OnPressButtons(UINT nHit)
{
// only for custom button handling don't call base
// close, maximize, and pin will be handled by default
switch (nHit)
{
case ID_MORPHTEST:
{
CMorphMenuButton* pButton = dynamic_cast<CMorphMenuButton*>(m_arrButtons.GetAt(m_morphIndex));
pButton->ShowMenu(this);
break;
}
}
}
void CDockPane::SetCaptionButtons()
{
CDockablePane::SetCaptionButtons(); // for close, pin etc
m_morphIndex = m_arrButtons.Add(new CMorphMenuButton(ID_MORPHTEST));
}
void CDockPane::OnMorphBrowser()
{
// do stuff on menu item click
}
Edit: Removed previous code no longer in use
Now that the sound of crickets chirping has dwindled in the background I guess I'll post the workaround I currently have in place:
Instead of inheriting and extending CMFCCaptionMenuButton I build my class by extending CMFCCaptionButton. I then create a menu and provide a ShowMenu method to be explicitly called when handling the custom button events as well as overriding GetIconID to return a particular system icon for the button for each menu added to the caption bar ending up with something like this for the example outlined in the question:
#pragma once
// CMorphMenuButton command target
class CMorphMenuButton : public CMFCCaptionButton
{
public:
CMorphMenuButton(UINT nHit);
virtual ~CMorphMenuButton();
virtual CMenuImages::IMAGES_IDS GetIconID (BOOL bHorz, BOOL bMaximized) const;
void ShowMenu(CWnd* pWnd);
private:
CMenu m_dockMenu;
CMenu* m_subMenu;
};
// MorphMenuButton.cpp : implementation file
//
#include "stdafx.h"
#include "MorphMenuButton.h"
// CMorphMenuButton
CMorphMenuButton::CMorphMenuButton(UINT nHit)
: CMFCCaptionButton(nHit)
{
SetMiniFrameButton(); // already defaulted?
m_dockMenu.LoadMenu(IDR_DOCKPANE); // resource ID for dock pane menus
}
CMorphMenuButton::~CMorphMenuButton()
{
m_dockMenu.DestroyMenu();
}
CMenuImages::IMAGES_IDS CMorphMenuButton::GetIconID(BOOL bHorz, BOOL bMaximized) const
{
return CMenuImages::IdArrowForward;
}
void CMorphMenuButton::ShowMenu(CWnd* pWnd)
{
CRect windowRect, buttonRect;
pWnd->GetWindowRect(&windowRect);
buttonRect = GetRect();
CPoint menuPos(windowRect.left + buttonRect.right, windowRect.top + buttonRect.bottom);
m_subMenu = m_dockMenu.GetSubMenu(0);
if (!m_subMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, menuPos.x, menuPos.y, pWnd))
{
DWORD id = GetLastError();
wchar_t errMsg[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, id, 0, errMsg, sizeof(errMsg), 0);
MessageBox(0, errMsg, L"Error", MB_OK);
}
}
The setting of caption bar buttons and handling of click events for both buttons and menus are the same as defined in the question and this works.

win32 CTabctrl: Drawing "child" windows in the active tab

I have a resizable dialog that contains a CTabCtrl, the tab control has 4 tabs that when clicked on displays one of four different CTreeCtrls.
I have derived a class from CTabCtrl, which keeps track of its "child" controls like so:
...
class Container: public CTabCtrl {
vector<CWnd*> _children;
....
int Container::AddTab(CWnd* Child) {
CString txt;Child->GetWindowText(txt);
_children.push_back(Child);
int idx = this->InsertItem(this->GetItemCount(), txt, 0);
if(idx == 0) {
CRect c;
this->GetWindowRect(&c);
GetParent()->ScreenToClient(&c);
this->AdjustRect(FALSE, c);
Child->SetWindowPos(&wndTop, c.left, c.top, c.Width(), c.Height(), SWP_SHOWWINDOW);
this->SetCurSel(idx);
} else Child->ShowWindow(SW_HIDE);
return idx;
}
And I attempt to draw the child controls like so:
void Container::OnTabChanging(NMHDR*, LRESULT* pResult) { // hide the changed from tab
int selected = this->GetCurSel();
if(selected != -1)
{
// move old window to bottom of the zorder and hide
_children[selected]->SetWindowPos(&wndBottom, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_HIDEWINDOW);
ASSERT(!_children[selected]->IsWindowVisible());
}
*pResult = 0;
}
// show the child for the tab being changed to
void CNodeContainer::OnTabChanged(NMHDR* pNMHDR, LRESULT* pResult) {
int selected = this->GetCurSel();
ASSERT(selected!=-1);
CRect c;
this->GetWindowRect(&c);
GetParent()->ScreenToClient(&c);
this->AdjustRect(FALSE, c);
_children[selected]->SetWindowPos(&wndTop, c.left, c.top, c.Width(), c.Height(), SWP_SHOWWINDOW|SWP_FRAMECHANGED);
*pResult = 0;
}
However the child controls, whilst they appear, don't always draw correctly, they sort of mix up their content together and only show the right content when i click on them (the actual tree controls).
Is this the best way of drawing and moving windows around in the zorder, what am I missing?
Many thanks
bg
Instead of just changing the z-order of your children, completely hide every child except the top one. I use the same approach in a custom CTabCtrl and it works fine.
Its fixed now - the problem came from the fact that the in the resize code for the tabctrl, I was using movewindow to move the child windows into place - This was changing the zorder of the child windows.
This could solve the problem after your window or tab apears. Try to use
this->RedrawWindow();
In OnTabChanging() function before it returns.

MFC Tabbed Documents - how to enable middle-mouse button to close document?

If you create a new MFC application (with MFC Feature Pack), and using all the defaults, click Finish. It creates an MDI application with the new "Tabbed Documents" style.
I think these are great except it really annoys me that I can't close a Tabbed Document window by middle-clicking on the tab.
This is possible in Firefox, IE, Chrome and more importantly VS2008. But clicking the middle-button on a tab doesn't do anything.
I cannot figure out how to override the tab bar to allow me to handle the ON_WM_MBUTTONDOWN message. Any ideas?
Edit: Guessing I need to subclass the CMFCTabCtrl returned from CMDIFrameWndEx::GetMDITabs...
No subclassing needed (phew). Managed to get it working by hijacking the PreTranslateMessage of the mainframe. If the current message is a middle-mouse-button message, I check the location of the click. If it was on a tab then I close that tab.
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
switch (pMsg->message)
{
case WM_MBUTTONDBLCLK:
case WM_MBUTTONDOWN:
{
//clicked middle button somewhere in the mainframe.
//was it on a tab group of the MDI tab area?
CWnd* pWnd = FromHandle(pMsg->hwnd);
CMFCTabCtrl* tabGroup = dynamic_cast<CMFCTabCtrl*>(pWnd);
if (tabGroup)
{
//clicked middle button on a tab group.
//was it on a tab?
CPoint clickLocation = pMsg->pt;
tabGroup->ScreenToClient(&clickLocation);
int tabIndex = tabGroup->GetTabFromPoint(clickLocation);
if (tabIndex != -1)
{
//clicked middle button on a tab.
//send a WM_CLOSE message to it
CWnd* pTab = tabGroup->GetTabWnd(tabIndex);
if (pTab)
{
pTab->SendMessage(WM_CLOSE, 0, 0);
}
}
}
break;
}
default:
{
break;
}
}
return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}