I have a CTreeCtrl in a dialog and catch TVN_SELCHANGED messages, but execution doesn't go into handlers. What can be wrong?
My code:
BEGIN_MESSAGE_MAP(CMaterParamExtDlg, CDialog)
ON_NOTIFY(TVN_ITEMCHANGING, IDC_MATERIAL_PROP_TREE, OnSelChangingTreeCtrl)
ON_NOTIFY(TVN_ITEMCHANGED, IDC_MATERIAL_PROP_TREE, OnSelChangedTreeCtrl)
END_MESSAGE_MAP()
void CMaterParamExtDlg :: OnSelChangedTreeCtrl (NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_TreeCtrl != 0)
{
HTREEITEM treeitem = m_TreeCtrl->GetSelectedItem();
CString treeitemtext = m_TreeCtrl->GetItemText(treeitem);
MessageBox(treeitemtext);
}
*pResult = 0;
}
void CMaterParamExtDlg :: OnSelChangingTreeCtrl (NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_TreeCtrl != 0)
{
HTREEITEM treeitem = m_TreeCtrl->GetSelectedItem();
CString treeitemtext = m_TreeCtrl->GetItemText(treeitem);
MessageBox(treeitemtext);
}
*pResult = 0;
}
I tried to use breakpoints to see if execution goes into handlers but nothing happens.
Tree Control properties:
CONTROL "",IDC_MATERIAL_PROP_TREE,"SysTreeView32",TVS_HASBUTTONS
| TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP
| TVS_TRACKSELECT | WS_BORDER | WS_HSCROLL | WS_TABSTOP,4,4,115,218
Pretty simple. Use TVN_SELCHANGED instead of TVN_ITEMCHANGED. Ditto for TVN_ITEMCHANGING.
Related
I made two groups of toolbar:
`
if (!m_wndToolBar1.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | CBRS_TOOLTIPS | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar1.LoadToolBar(IDR_MAINFRAME_256)) return -1;
m_wndToolBar1.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndToolBar1);
`
I make it in a DockPane and another one:
`
if (!m_wndToolBar2.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar2.LoadToolBar(IDR_MAINFRAME_256_3D)) return -1;
m_wndToolBar2.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndToolBar2);
Put tehe MESSAGE MAP:
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CTunnelMainFrame::OnNeedText)
And the function:
BOOL CTunnelMainFrame::OnNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
UINT_PTR nId = pNMHDR->idFrom - 1;
CMFCToolBarButton* pBtnToolBar = m_wndToolBar1.GetButton(nId);
if (pBtnToolBar)
{
TCHAR szBuff[64];
::LoadString(AfxGetResourceHandle(), pBtnToolBar->m_nID, szBuff, sizeof(szBuff) / sizeof(TCHAR));
pTTT->lpszText = szBuff;
pTTT->hinst = AfxGetResourceHandle();
}
return TRUE;
}
`
So, I expect that the tooltip will appear only on m_wndToolBar1's dock pane, but in fact it also appear on m_wndToolBar2's dock pane. But with STRINGTABLE ID (string message) that belongs to m_wndToolBar1. My question is how to disable tooltip on m_wndToolBar2's dock pane? or to make other OnNeedText funtion that only wokrs on m_wndToolBar2's dock pane?
Thanks~
1)
BOOL CMainFrame::OnNeedText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint point;
GetCursorPos(&point);
CRect rcToolbar1;
m_wndToolBar.GetWindowRect(&rcToolbar1);
if(rcToolbar1.PtInRect(point) == FALSE)
return TRUE;
UINT_PTR nId = pNMHDR->idFrom - 1;
NMTTDISPINFO* pTTT = (NMTTDISPINFO*)pNMHDR;
CMFCToolBarButton *pBtn1 = m_wndToolBar.GetButton(nId);
if (pBtn1)
{
TCHAR szBuff[64];
LoadString(AfxGetResourceHandle(), pBtn1->m_nID, szBuff, sizeof(szBuff) / sizeof(TCHAR));
//_stprintf(szBuff, L"Controle ID:%d", pBtn1->m_nID);
pTTT->lpszText = szBuff;
pTTT->hinst = AfxGetResourceHandle();
}
return TRUE;
}
2)don't using TTN_NEEDTEXT, using the prompt instead, Resource View->Toolbar, set the m_wndToolBar1's prompt with text and set the m_wndToolBar2's prompt with empty text
I have write a windows client, which UI framework is ATL/WTL. And There is a ListView (CListViewCtrl in ATL) with LVS_OWNERDRAWFIXED style.
For support Accessibility tool JAWS.I Want Jaws read ListView item.
I follow the doc:https://learn.microsoft.com/en-us/windows/win32/winauto/server-annotation-sample
0.Impl of IAccPropServer for support GetPropValue .
class ListViewAccServer : public IAccPropServer
{
ULONG m_Ref;
IAccPropServices * m_pAccPropSvc;
public:
/* skip over ListViewAccServer/ ~ListViewAccServer
*/
static ListViewAccServer * CreateProvider(HWND hControl)
{
ATL::CComPtr<IAccPropServices> pAccPropSvc;
HRESULT hr = pAccPropSvc.CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER);
if (hr == S_OK && pAccPropSvc)
{
ListViewAccServer * pLVServer = new (std::nothrow) ListViewAccServer(pAccPropSvc);
if (pLVServer)
{
MSAAPROPID propid = PROPID_ACC_NAME;
//pAccPropSvc->SetHwndPropServer(hControl, (DWORD)OBJID_CLIENT, CHILDID_SELF, &propid, 1, pLVServer, ANNO_CONTAINER);
pAccPropSvc->SetHwndPropServer(hControl, (DWORD)OBJID_CLIENT, CHILDID_SELF, &propid, 1, pLVServer, ANNO_CONTAINER);
pLVServer->Release();
}
return pLVServer;
}
return NULL;
}
/* skip over: Addref/Release/QI
*/
HRESULT STDMETHODCALLTYPE GetPropValue(const BYTE * pIDString,
DWORD dwIDStringLen, MSAAPROPID idProp, VARIANT * pvarValue,
BOOL * pfGotProp)
{
if (!pfGotProp)
return E_POINTER;
pvarValue->vt = VT_EMPTY;
*pfGotProp = FALSE;
//HWND hwnd;
DWORD idObject;
DWORD idChild;
HWND dwHcontrol;
if (S_OK != m_pAccPropSvc->DecomposeHwndIdentityString(pIDString,
dwIDStringLen, &dwHcontrol, &idObject, &idChild))
{
return S_OK;
}
HWND Hwnd = dwHcontrol;
// Only supply name string for child elements, not the listview itself
if (idChild != CHILDID_SELF)
{
if (idProp == PROPID_ACC_NAME)//Jaws should read acc name?
{
CString str;
str.Format(L"Line index %d", idChild);
OutputDebugPrintfW(_T("GetPropValue str =%s\n"), str);
BSTR bstr = ::SysAllocString((LPCTSTR)str.GetString());
pvarValue->vt = VT_BSTR;
pvarValue->bstrVal = bstr;
*pfGotProp = TRUE;
}
}
return S_OK;
}
};
1.initUI
//if Set 'LVS_OWNERDRAWFIXED',JAWS will read onDraw Item "Dummy List Item"
m_lvMain.Create(m_hWnd, rc, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
LVS_REPORT | LVS_AUTOARRANGE | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | WS_TABSTOP|LVS_OWNERDRAWFIXED,
WS_EX_CLIENTEDGE, LIST_ID);
/*
m_lvMain.Create(m_hWnd, rc, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
LVS_REPORT | LVS_AUTOARRANGE | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | WS_TABSTOP ,
WS_EX_CLIENTEDGE, LIST_ID);
*/
//otherwise JAWS will read Hello,Hello1,Hello2...
m_lvMain.InsertColumn(0, _T("Column"), LVCFMT_LEFT, 200);
m_lvMain.InsertItem(0, _T("Hello"));
m_lvMain.InsertItem(0, _T("Hello1"));
m_lvMain.InsertItem(0, _T("Hello2"));
m_lvMain.InsertItem(0, _T("Hello3"));
2.Bind Listview m_hwnd with ListViewAccServer
m_pAccServer = ListViewAccServer::CreateProvider(m_lvMain.m_hWnd);
3
LRESULT OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
//LRESULT onDraw(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)(lParam);
//OutputDebugPrintfW(_T("OnDrawItem i = %d, %d, %08x \n"), pDIS->itemID, pDIS->itemData, pDIS->hwndItem);
BOOL bSelected = ((pDIS->itemState & ODS_SELECTED) == ODS_SELECTED);
BOOL bFocus = ((pDIS->itemState & ODS_FOCUS) == ODS_FOCUS);
HDC hDC = pDIS->hDC;
RECT rc = pDIS->rcItem;
HBRUSH bg = (HBRUSH)(::GetStockObject(WHITE_BRUSH));
if (bSelected)
{
bg = (HBRUSH)(::GetStockObject(LTGRAY_BRUSH));
}
if (bFocus)
{
bg = (HBRUSH)(::GetStockObject(LTGRAY_BRUSH));
}
HPEN pn = (HPEN)(::GetStockObject(BLACK_PEN));
::SelectObject(hDC, bg);
::SelectObject(hDC, pn);
::SetTextColor(hDC, RGB(0, 0, 0));
const wchar_t *text = L"Dummy List Item";
::Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
::DrawText(hDC, text, wcslen(text), &rc, DT_SINGLELINE | DT_VCENTER);
return S_OK;
}
When use UP and down array By Keyboard alert in ListView items.I want Jaws read out "Line index i" in GetPropValue.
But
1: If I set ListView with style:LVS_OWNERDRAWFIXED.On some PC, Jaws will read out "Dummy List Item". On Some PC, It read nothing.
2: If remove style:LVS_OWNERDRAWFIXED. Jaws will read out 'Hello','Hello1'...
both 1 or 2 do not read out GetPropValue give string"Line index i".
The log shows GetPropValue is be called:
GetPropValue str =Line index 1
GetPropValue str =Line index 2
My test PC machine OSs are Win10,Win7
So what's the problem?
Thanks a lot.
PS:At last , I found this:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=73496
I can use Insert+6 to bring up the JAWS Configuration Manager.
- click OK to Add a New Configuration (for your application)
- in the Configuration Manager dialog, type "listv" in the filter to see the page containing the "Rely on MSAA for ListViews" checkbox.
- check "Rely on MSAA for ListViews"
- click OK to save and exit the Configuration Manager.
So Jaws default do not use MSAA to get information?
I am developing a custom credential provider and I have to show a progress screen with a cancel button. I have seen in some credentials providers and pgina plugins that a screen is displayed with a Cancel button when credential provider is working. I have attached a screenshot of it. I have managed to show the error screen with an Ok button using the following code:
*pcpgsr = CPGSR_NO_CREDENTIAL_NOT_FINISHED;
SHStrDupW(L"Authentication Failed", ppwszOptionalStatusText);
*pcpsiOptionalStatusIcon = CPSI_ERROR;
Now I need to show this progress screen with a cancel button. Any advice how can it be achieved? Also, how to handle the event that fires when this button is pressed?
As I understand your scenario you want to do something in background presenting to the user "wait screen".
You must run a separate thread for background work and change the layout of your credential tile to leave visible only one text element with "Wait..." content and no submit button.
Once your background thread complete its work you may reveal submit button and let user to continue to logon.
For example, have a look at embedded Smartcard Credential Porvider and its behaviour on insertion and removal of the card.
#js.hrt You can run your main thread as dialog, while your background thread does the job. The cancel button would be the control in the dialog, allowing to cancel it. If you need more info, let me know, I can provide some details, as this is the way we do it.
#js.hrt Briefly, you need two classes: dialog and thread.
When you create a dialog, it will create a thread, which will run what you need, and show cancel button. Clicking on it will terminate your thread. Some code below. Hope it helps.
class Thread {
public:
Thread(GUI* object);
virtual ~Thread();
bool start( bool ) {
::CreateThread( NULL, 0, threadRun, lpParameter, dwCreationFlags,
&m_dwThreadId );
}
static DWORD WINAPI threadRun( void* lpVoid ) {
DWORD dwReturn( 0 );
dwReturn = m_object->yourProcessToRun();
return dwReturn;
}
protected:
GUI* m_object;
Runnable* m_lpRunnable;
};
Then, class for your UI, similar to this
#include "atlwin.h"
class GUI: public CDialogImpl<GUI> {
public:
enum { IDD = IDD_FOR_YOUR_DIALOG };
GUI();
~GUI();
BEGIN_MSG_MAP(GUI)
MESSAGE_HANDLER(WM_INITDIALOG,OnInitDialog)
COMMAND_ID_HANDLER(ID_CANCEL,OnCancel)
MESSAGE_HANDLER(WM_TIMER,OnTimer)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
LRESULT OnInitDialog(UINT,WPARAM,LPARAM, BOOL&) {
myThread = new Thread(this);
m_nTimerID = SetTimer(1,3000,NULL);
myThread->start();
}
LRESULT OnCancel(WORD,WORD,HWND,BOOL& ) {
if(NULL != myThread) {
DWORD exitCode = 0;
myThread->getExitCode(exitCode);
if(exitCode == STILL_ACTIVE)
myThread->terminate();
delete myThread;
myThread = NULL;
}
EndDialog(IDCANCEL);
return true;
}
LRESULT OnTimer(UINT,WPARAM wParam,LPARAM,BOOL&) {
if(wParam != m_nTimerID)
return FALSE;
m_timerticks++;
return FALSE;
}
LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL&) {
KillTimer(m_nTimerID);
return FALSE;
}
virtual int yourProcessToRun() {};
void onFinishProgress(int retCode = IDOK) {
if (retCode != IDCANCEL) {
delete myThread;
myThread = NULL;
KillTimer(m_nTimerID);
EndDialog(retCode);
}
}
private:
Thread* myThread;
UINT m_nTimerID;
UINT m_timerticks;
};
The resource for dialog could be like this:
IDD_FOR_YOUR_DIALOG DIALOGEX 0, 0, 309, 80
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP
| WS_CAPTION
CAPTION "Whatever"
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
PUSHBUTTON "Cancel",ID_CANCEL,113,50,84,14
CTEXT "Static",IDC_FOR_SOMETHING,7,7,295,20
END
#js.hrt If you don't mind to post your code, I'll make it run.
Can't comment to your's message directly, as I limit by site
requirements
#js.hrt Per your request.
class Thread {
public:
Thread(GUI* object);
virtual ~Thread();
bool start( bool ) {
::CreateThread( NULL, 0, threadRun, lpParameter, dwCreationFlags,
&m_dwThreadId );
}
static DWORD WINAPI threadRun( void* lpVoid ) {
DWORD dwReturn( 0 );
dwReturn = m_object->yourProcessToRun();
return dwReturn;
}
protected:
GUI* m_object;
Runnable* m_lpRunnable;
};
Then, class for your UI, similar to this
#include "atlwin.h"
class GUI: public CDialogImpl<GUI> {
public:
enum { IDD = IDD_FOR_YOUR_DIALOG };
GUI();
~GUI();
BEGIN_MSG_MAP(GUI)
MESSAGE_HANDLER(WM_INITDIALOG,OnInitDialog)
COMMAND_ID_HANDLER(ID_CANCEL,OnCancel)
MESSAGE_HANDLER(WM_TIMER,OnTimer)
MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
LRESULT OnInitDialog(UINT,WPARAM,LPARAM, BOOL&) {
myThread = new Thread(this);
m_nTimerID = SetTimer(1,3000,NULL);
myThread->start();
}
LRESULT OnCancel(WORD,WORD,HWND,BOOL& ) {
if(NULL != myThread) {
DWORD exitCode = 0;
myThread->getExitCode(exitCode);
if(exitCode == STILL_ACTIVE)
myThread->terminate();
delete myThread;
myThread = NULL;
}
EndDialog(IDCANCEL);
return true;
}
LRESULT OnTimer(UINT,WPARAM wParam,LPARAM,BOOL&) {
if(wParam != m_nTimerID)
return FALSE;
m_timerticks++;
return FALSE;
}
LRESULT OnDestroy(UINT,WPARAM,LPARAM,BOOL&) {
KillTimer(m_nTimerID);
return FALSE;
}
virtual int yourProcessToRun() {};
void onFinishProgress(int retCode = IDOK) {
if (retCode != IDCANCEL) {
delete myThread;
myThread = NULL;
KillTimer(m_nTimerID);
EndDialog(retCode);
}
}
private:
Thread* myThread;
UINT m_nTimerID;
UINT m_timerticks;
};
The resource for dialog could be like this:
IDD_FOR_YOUR_DIALOG DIALOGEX 0, 0, 309, 80
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP
| WS_CAPTION
CAPTION "Whatever"
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
PUSHBUTTON "Cancel",ID_CANCEL,113,50,84,14
CTEXT "Static",IDC_FOR_SOMETHING,7,7,295,20
END
I assume you are looking for IConnectableCredentialProviderCredential::Connect().
You need to implement the IConnectableCredentialProviderCredentialinterface and put your logic to the Connect() function. It is called right after Submit button pressed.
The Connect() function will give you the IQueryContinueWithStatus interface. In this interface you need to call the QueryContinue() function periodically, to handle Cancel button or some system events.
For more information look at this article: https://learn.microsoft.com/en-us/windows/win32/api/credentialprovider/nf-credentialprovider-iconnectablecredentialprovidercredential-connect
How can be CHAIN_MSG_MAP_MEMBERused for two members ?
The example below works fine with a single listview and a single CHAIN_MSG_MAP_MEMBER.
With both I have a crash.
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler
{
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
return CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg);
}
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
CHAIN_MSG_MAP_MEMBER(m_listView) //<
CHAIN_MSG_MAP_MEMBER(m_listView2) //< ISSUE: crash with both, works with one.
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// create a list box
RECT r = {0,0,182,80};
m_listView.Create(m_hWnd,r,CListViewCtrl::GetWndClassName(),WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | LVS_REPORT, WS_EX_CLIENTEDGE );
RECT r2 = {0,80,182,80+80};
m_listView2.Create(m_hWnd,r2,CListViewCtrl::GetWndClassName(),WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | LVS_REPORT, WS_EX_CLIENTEDGE );
...
populate
}
}
class MyListView : public CWindowImpl<MyListView, CListViewCtrl>,
public CCustomDraw<MyListView>
{
public:
BEGIN_MSG_MAP(MyListView)
CHAIN_MSG_MAP(CCustomDraw<MyListView>)
END_MSG_MAP()
DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
...
}
DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
...
}
DWORD OnSubItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
...
}
............
}
CCustomDraw is WTL class and assumes you use BEGIN_MSG_MAP_EX, as opposed to ATL's BEGIN_MSG_MAP.
#include <atlcrack.h>
class MyListView : public CWindowImpl<MyListView, CListViewCtrl>,
public CCustomDraw<MyListView>
{
public:
BEGIN_MSG_MAP_EX(MyListView) // <<--- Here we go
CHAIN_MSG_MAP(CCustomDraw<MyListView>)
END_MSG_MAP()
};
The rule of thumb is to never use BEGIN_MSG_MAP at all as long as you leverage WTL.
Also, it is worth to mention that this use of CHAIN_MSG_MAP_MEMBER makes no sense to me.
I haven't been able to find a concise chunk of code that allows me to add/display tooltips to a CStatic (and CLed) control. Obviously, the standard code to do so does not apply for this type of control. Can someone post code snippets?
I hope this code will solve your problem .One important thing make NOTIFY property of CStatic =TRUE.
if( !m_ToolTip.Create(this))
{
TRACE0("Unable to create the ToolTip!");
}
else
{
CWnd* pWnd = GetDlgItem(IDC_STATIC_MASTER_PWD);
m_ToolTip.AddTool(pWnd,"Ok");
m_ToolTip.Activate(TRUE);
}
Let me know if any problem.
I don't know if this is still needed, but here's what I used to solve the problem:
just add SS_NOTIFY to dwStyle when creating the static label. (or simply set "Nofity" "True" in the properties). That worked fine for me.
When I add CStatic on Dialog based autocreated mfc application, tooltips don't show until I add RelayEvent in pretranslate dialog message
BOOL CTooltipStaticDlg::PreTranslateMessage(MSG* pMsg)
{
m_ToolTip.RelayEvent(pMsg);
return CDialog::PreTranslateMessage(pMsg);
}
I've had success with multiline tooltips using this simple class:
Create a class for ToolTips:
class ToolTip
{
public:
static HWND CreateToolTip(int toolID, HWND hDlg, UINT id);
};
Next, implement a tooltip creation function:
HWND ToolTip::CreateToolTip(int toolID, HWND hDlg, UINT id)
{
if (!toolID || !hDlg || !id)
{
return FALSE;
}
CString strTTText;
strTTText.LoadString( id );
// Get the window handle of the control to attach the TT to.
HWND hwndTool = ::GetDlgItem(hDlg, toolID);
// Create the tooltip window
HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL,
WS_POPUP |TTS_ALWAYSTIP,// | TTS_BALLOON,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hDlg, NULL,
AfxGetInstanceHandle() , NULL);
if (!hwndTool || !hwndTip)
{
return (HWND)NULL;
}
// Associate the tooltip with the tool.
TOOLINFO toolInfo = { 0 };
toolInfo.cbSize = sizeof(toolInfo);
toolInfo.hwnd = hDlg;
toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
toolInfo.uId = (UINT_PTR)hwndTool;
toolInfo.lpszText = (char*)(LPCTSTR)strTTText;
::SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
::SendMessageA(hwndTip, TTM_SETMAXTIPWIDTH, 0, 40); // force multi-line
return hwndTip;
}
Call it somewhere in your InitDialog:
CMyDialog::InitDialog()
{
ToolTip::CreateToolTip( PickAUniqueNumber, m_hWnd, IDS_MY_RESOURCE_STRING );
}
I've had on my dialog label with assigned custom ID IDC_PATH. I needed to turn on Notify flag (SS_NOTIFY) of the label and I needed to overload CWnd method OnToolHitTest and handle tooltip hit test like this:
INT_PTR CPath::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
INT_PTR r = CWnd::OnToolHitTest(point,pTI);
this->ClientToScreen(&point);
CRect rcLbl;
GetDlgItem(IDC_PATH)->GetWindowRect(&rcLbl);
if( rcLbl.PtInRect(point) )
{
pTI->uFlags |= TTF_IDISHWND;
pTI->uFlags &= ~TTF_NOTBUTTON;
pTI->uId = (UINT_PTR)GetDlgItem(IDC_PATH)->m_hWnd;
return IDC_PATH;
}
return r;
}
Then my dialog started to receive TTN_NEEDTEXT notification, which I handled and dynamicaly set text for tooltip.
BOOL CPath::OnTtnNeedText(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
{
UNREFERENCED_PARAMETER(id);
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)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 == IDC_PATH)
{
pTTT->lpszText = (LPSTR)(LPCTSTR)m_FullDestPath;
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
}