How to made JAWS readout ListView Item use Server Annotation - c++

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?

Related

Create derived control at runtime cause assertion

I am trying to create a control at run-time, but it causes assertion, and I don't know what's causing it. The control I am using is the Tree ComboBox Control from this link: https://www.codeproject.com/Articles/187762/Tree-ComboBox-Control
I added the code to register the class as follow:
CTreeComboBox::CTreeComboBox()
...
{
...
RegisterWindowClass();
}
CTreeComboBox::~CTreeComboBox()
{
m_BrushAlert.DeleteObject();
}
BOOL CTreeComboBox::RegisterWindowClass()
{
WNDCLASS wndcls;
HINSTANCE hInst = AfxGetInstanceHandle();
if (!(::GetClassInfo(hInst, _T("TreeComboBox"), &wndcls)))
{
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = ::DefWindowProc;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInst;
wndcls.hIcon = NULL;
wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
wndcls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = _T("TreeComboBox");
if (!AfxRegisterClass(&wndcls))
{
AfxThrowResourceException();
return FALSE;
}
}
return TRUE;
}
I tried to create the control at run-time using the following code in the test program:
BOOL CTestComboBoxDlg::OnInitDialog()
{
...
m_ComboBox2.CreateEx(WS_EX_CLIENTEDGE, _T("TreeComboBox"), _T(""), WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP,
CRect(0, 0, 100, 50), this, 100000, NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
I also tried creating the control using a button click event thinking that I should let the GUI finish initializing, but the same error occur:
void CTestComboBoxDlg::OnBnClickedButton1()
{
m_ComboBox2.CreateEx(WS_EX_CLIENTEDGE, _T("TreeComboBox"), _T(""), WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP,
CRect(0, 0, 100, 50), this, 100000, NULL);
}
When I run the program, it stopped at the file dbgrptt.cpp at the following line:
__try
{
if (_CRT_ASSERT == nRptType && _InterlockedIncrement(&_crtAssertBusy) > 0)
{
/* use only 'safe' functions -- must not assert in here! */
_ERRCHECK(_itoa_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
OutputDebugStringA("Second Chance Assertion Failed: File ");
OutputDebugStringA(szFile ? szFile : "<file unknown>");
OutputDebugStringA(", Line ");
OutputDebugStringA(szLineMessage);
OutputDebugStringA("\n");
It stop here===> _CrtDbgBreak();
retval=-1;
__leave;
}
The program run fine if I create the control manually using the Visual Studio GUI editor, so I am not sure what's wrong. Can you help me figure out how to create this control at run-time?
Note: change the statement: TRACE1(_T("Item selected: %s\n"), GetItemText(hItem)); to TRACE(_T("Item selected: %s\n"), GetItemText(hItem)); in the file ComboTreeCtrlExt.cpp if you if you want to run the code and are using MFC
To answer my own question. Move the following code from the CTreeComboBox::PreSubclassWindow() to the CTreeComboBox::OnCreate()
CRect rect(0, 0, 0, 0);
DWORD dwStyle = WS_POPUP | WS_BORDER;
CWnd* pWnd = &m_Tree;
pWnd->CreateEx(0, WC_TREEVIEW, NULL, dwStyle, rect, GetParent(), 0, NULL);
m_Tree.Init(this);
GetClientRect(rect);
SetDroppedWidth(rect.Width());
SetDroppedHeight(m_nDroppedHeight);
dwStyle = CBS_DROPDOWNLIST & GetStyle();
ASSERT(CBS_DROPDOWNLIST == dwStyle);

An Aero caption title bar issue using DWM API on the windows 10

In order to draw the icon on the caption title bar, I have refereed this MSDN article and used DWM API to create my customize client area by calling DwmExtendFrameIntoClientArea.
my code:
CMainFrame::CMainFrame()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
TRACE0("DWM is enabled\n");
TCHAR szLogoPath[MAX_PATH];
GetModuleFileName ( GetModuleHandle(NULL), szLogoPath, _countof(szLogoPath) );
PathRemoveFileSpec ( szLogoPath );
PathAppend ( szLogoPath, _T("lena.bmp") );
m_pLogoImage = m_pLogoImage->FromFile ( CT2CW(szLogoPath) );
if(NULL == m_pLogoImage)
TRACE0("load image fail\n");
}
void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
int xFrame = 2;
int yFrame = 2;
int nTHight = 30;
NCCALCSIZE_PARAMS * p;
RECT * rc;
RECT aRect;
RECT bRect;
RECT acRect;
p = (NCCALCSIZE_PARAMS *)lpncsp;
CopyRect(&bRect,&p->rgrc[1]);
CopyRect(&aRect,&p->rgrc[0]);
acRect.left = aRect.left + xFrame;
acRect.top = aRect.top - nTHight;
acRect.right = aRect.right - xFrame;
acRect.bottom = aRect.bottom - yFrame;
CopyRect(&p->rgrc[0],&acRect);
CopyRect(&p->rgrc[1],&aRect);
CopyRect(&p->rgrc[2],&bRect);
CFrameWnd::OnNcCalcSize(TRUE, lpncsp);
}
LRESULT CMainFrame::OnNcHitTest(CPoint p)
{
BOOL dwm_enabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&dwm_enabled)))
{
LRESULT result = 0;
if (!DwmDefWindowProc(m_hWnd, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y), &result))
result = HitTestNCA(m_hWnd, p);
if (result == HTNOWHERE && GetForegroundWindow() != this)
{
return HTCAPTION;
}
return result;
}
return CWnd::OnNcHitTest(p);
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if(cs.hMenu!=NULL)
{
::DestroyMenu(cs.hMenu);
cs.hMenu = NULL ;
}
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.style = WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_OVERLAPPED| WS_SYSMENU | WS_THICKFRAME;
cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
cs.lpszClass = AfxRegisterWndClass(0);
return TRUE;
}
void CMainFrame::OnActivate(UINT nState,CWnd* pWndOther,BOOL bMinimized )
{
CFrameWnd::OnActivate(nState,pWndOther,bMinimized);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
{
if(nState == WA_ACTIVE )
{
MARGINS margins = {-1};
/*margins.cyTopHeight = 30;
margins.cxLeftWidth = 0;
margins.cxRightWidth = 0;
margins.cyBottomHeight = 0;*/
HRESULT hr = DwmExtendFrameIntoClientArea(m_hWnd, &margins);
if (!SUCCEEDED(hr))
TRACE0("Failed in DwmExtendFrameIntoClientArea\n");
}
}
}
void CMainFrame::OnNcPaint()
{
CFrameWnd::OnPaint();
CDC* dc = GetWindowDC();
RECT rcClient;
GetWindowRect(&rcClient);
dc->FillSolidRect(0,0,RECTWIDTH(rcClient),RECTHEIGHT(rcClient),RGB(255,0,0));
CPaintDC gdc(this); // device context for painting
Graphics gr(gdc.m_hDC);
gr.DrawImage ( m_pLogoImage, 0, 0 );
ReleaseDC(dc);
}
The result under Windows 7 is fine.
However, my window appears another unknown caption title bar under Windows 10.
I found out the unknown caption is caused by WS_THICKFRAME in the cs.style.
If I remove WS_THICKFRAME, the unknown cation bar will disappear, but I cannot resizing the border of my window. Furthermore, my program cannot capture the minimum, maximum and the close button message on my custom caption bar anymore.
I want to remove the unknown title bar without any side effect.
Does anyone could provide me a good solution or suggestion?
Best Regards,
When using DwmExtendFrameIntoClientArea, it means frame is extended in to client area. It is no longer in non-client area. So there is no need to override OnNcPaint, you can do all of the painting in OnPaint
void CMainFrame::OnPaint()
{
CPaintDC dc(this);
//paint titlebar area (this used to be the non-client area)
CRect rc;
GetClientRect(&rc);
rc.bottom = titlebar_height;
CDC memdc;
memdc.CreateCompatibleDC(&dc);
BITMAPINFOHEADER bmpInfoHeader = {
sizeof(BITMAPINFOHEADER), rc.Width(), -rc.Height(), 1, 32 };
HBITMAP hbitmap = CreateDIBSection(
dc, (BITMAPINFO*)(&bmpInfoHeader), DIB_RGB_COLORS, NULL, NULL, 0);
auto oldbitmap = memdc.SelectObject(hbitmap);
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memdc, 0, 0, SRCCOPY);
memdc.SelectObject(oldbitmap);
DeleteObject(hbitmap);
//begin normal paint
//The new client area begins below titlebar_height which we define earlier
GetClientRect(&rc);
rc.top = titlebar_height;
dc.FillSolidRect(&rc, RGB(0, 0, 255));
Gdiplus::Image *image = Gdiplus::Image::FromFile(L"file.jpg");
Gdiplus::Graphics gr(dc);
gr.DrawImage(image, 0, 0);
delete image;
}
Use a member variable CRect m_border to keep track of border's thickness. You can use AdjustWindowRectEx to find the thickness of the borders.
void CMainFrame::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
CFrameWnd::OnActivate(nState, pWndOther, bMinimized);
titlebar_height = 100;
//find border thickness
if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_THICKFRAME)
{
m_border = { 0,0,0,0 };
AdjustWindowRectEx(&m_border, GetWindowLongPtr(m_hWnd,
GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
m_border.left = abs(m_border.left);
m_border.top = abs(m_border.top);
}
else if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_BORDER)
{
m_border = { 1,1,1,1 };
}
else
{
m_border = { 0,0,0,0 };
}
//Extend frame in to client area
MARGINS margins = { 0 };
margins.cyTopHeight = titlebar_height; //<<=== *** edited
DwmExtendFrameIntoClientArea(m_hWnd, &margins);
SetWindowPos(NULL, 0, 0, 0, 0,
SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
}
m_border will be for example {7,7,7,7};
Allow Windows to do the painting on left, right, bottom border. The top border is the only one changed
void CMainFrame::OnNcCalcSize(BOOL validate, NCCALCSIZE_PARAMS FAR* lpncsp)
{
if (validate)
{
lpncsp->rgrc[0].left += m_border.left;
lpncsp->rgrc[0].right -= m_border.right;
lpncsp->rgrc[0].bottom -= m_border.bottom;
}
else
{
CFrameWnd::OnNcCalcSize(validate, lpncsp);
}
}
see also How to glow the minimum. maximum and close button?

Creating TreeView with nodes and checkboxes

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.

Add tooltip to a CStatic

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;
}

How to show my own context menu in Internet Explorer

I'm writing add-on for Internet Explorer 9 and I have to change default context menu to my own. I'm writing a BHO in C++ and I'm using ATL. I managed to handle event of showing context menu (HTMLDocumentEvents2::oncontextmenu), but I can't display my own one. Here is the code fired when you click right mouse button:
VARIANT_BOOL STDMETHODCALLTYPE CSpellCheckerBHO::OnContextMenu( IHTMLEventObj *pEvtObj)
{
HMENU contextMenu = CreatePopupMenu();
MENUITEMINFO item_info = { 0 };
item_info.cbSize = sizeof(MENUITEMINFO);
item_info.fMask = MIIM_TYPE | MIIM_ID;
item_info.fType = MFT_STRING;
item_info.wID = 0;
item_info.dwTypeData = L"TEST";
item_info.cch = 4;
BOOL result = InsertMenuItem(contextMenu, 0, FALSE, &item_info);
HWND browserHandle = 0;
HRESULT hr = _webBrowser->get_HWND((LONG_PTR*)&browserHandle);
result = TrackPopupMenuEx(contextMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, 0,0, browserHandle , NULL);
return VARIANT_FALSE;
}
_webBrowser is a pointer to IWebBrowser2 object, I got it from SetSite function.
The standard context menu is not displayed (due to returning VARIANT_FALSE), but TrackPopupMenuEx does nothing and returns 0.
Do you know what I am doing wrong? I need simple menu with some text items.
I fgured it out. Igor Tandetnik helped me on IE addon forum. HWND was from different proccess and TrackPopupMenuEx expects HWND belonging to the calling thread. Here's the code that works:
VARIANT_BOOL STDMETHODCALLTYPE CSpellCheckerBHO::OnContextMenu( IHTMLEventObj *pEvtObj)
{
HMENU contextMenu = CreatePopupMenu();
MENUITEMINFO item_info = { 0 };
item_info.cbSize = sizeof(MENUITEMINFO);
item_info.fMask = MIIM_ID | MIIM_STRING;
item_info.wID = 0;
item_info.dwTypeData = L"TEST";
item_info.cch = 4;
BOOL result = InsertMenuItem(contextMenu, 0, TRUE, &item_info);
CComPtr<IDispatch> dispDoc;
_webBrowser->get_Document(&dispDoc);
CComQIPtr<IOleWindow> oleWindow = dispDoc;
HWND browserHandle;
oleWindow->GetWindow(&browserHandle);
CComQIPtr<IHTMLEventObj2> htmlEventObj = pEvtObj;
long x, y;
htmlEventObj->get_screenX(&x);
htmlEventObj->get_screenY(&y);
result = TrackPopupMenuEx(contextMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, browserHandle , NULL);
return VARIANT_FALSE;
}