Dziwny artefakt w menu kontekstowym od Windowsa

0

Czołem wszystkim

Potrzebuję wyświetlić w swojej aplikacji menu kontekstowe takie jak widać w eksploratorze Windowsa.Pogóglowałem,znalazłem przykłady (niestety,tylko oparte o WinAPI albo MFC) oraz czego w dokumentacji MSDN szukać,napisałem i voila,menu się pokazało.
Niestety,za dobrze by było jakby podziałało od razu.
Jak widać na załączonym obrazku po pierwsze nie wiadomo czemu menu Share With jest puste,a po drugie (podejrzewam związek z pierwszym) na prawo wyskakuje jakiś artefakt.Przy okazji widać też kod,który owo menu tworzy.

Ktoś wie,dlaczego to się pojawia i jak ów nieporządany efekt wyeliminować?Ewentualnie (to już byłby kompletny wypas) jakiś sposób na dobranie się do tego systemowego menusa przy użyciu Qt?

Na prośbę Azariena kod w wersji tekstowej:

void SystemMenuInQt::mousePressEvent(QMouseEvent *e)
{
	if(e->button() == Qt::RightButton)
	{

		IShellFolder *desktopShell;

		HRESULT result = SHGetDesktopFolder(&desktopShell);
		if(SUCCEEDED(result))
		{
			LPITEMIDLIST pidl = NULL;

			if(SUCCEEDED(desktopShell->ParseDisplayName(NULL, NULL,L"C:\\Repo\\CWay", NULL, &pidl, NULL)))
			{		

				IShellFolder *targetFolder;
				if(SUCCEEDED(desktopShell->BindToObject(pidl, NULL, IID_IShellFolder,(void**) &targetFolder)))
				{
					IContextMenu *menu;

					LPCITEMIDLIST relative = ILFindLastID(pidl);
					HRESULT result = targetFolder->GetUIObjectOf(NULL, 1,&relative, IID_IContextMenu, NULL,(void**) &menu);
		
					if(SUCCEEDED(result))
					{
						DestroyMenu(systemMenu);

						systemMenu = CreatePopupMenu();
						result = menu->QueryContextMenu(systemMenu,0,1,0x7FFF,CMF_NORMAL);

						int selectedMenuItem = TrackPopupMenuEx(systemMenu,TPM_RETURNCMD|TPM_LEFTALIGN|TPM_TOPALIGN|TPM_LEFTBUTTON ,e->globalPos().x(),e->globalPos().y(),winId(),NULL);
						e->accept();

						CMINVOKECOMMANDINFO info = { 0 };
						info.cbSize = sizeof(info);
						info.hwnd = winId();
						info.lpVerb = MAKEINTRESOURCEA(selectedMenuItem);
						menu->InvokeCommand(&info);

						//QMessageBox::information(this,QString(),"Selected item ID = " + QString::number(selectedMenuItem));

						menu->Release();						
					}

					targetFolder->Release();
				}
			}

			desktopShell->Release();
		}
	}
}
0

w WinAPI jest to samo, czyli to nie wina Qt.

daj se z tym luz ;-)

0

jak bardzo ma być to "znane z eksploratora windows menu kontekstowe"? Może wystarczy jeśli zbudujesz statyczne menu z takimi samymi opcjami i takimi samymi ikonkami co standardowe menu kontekstowe Windowsa?
Inne podjecie to spróbować odczytać (np z rejestrów) co powinno sie znaleźć w tym menu i je budować przez Qt?

0

Może być statyczne z takimi samymi opcjami.
Powiem szczerze,że sam już właśnie zacząłem rozmyśliwać nad takim rozwiązaniem jak proponujesz Marku,bo z tym WinAPI do shella to co krok jakieś cyrki.A Qt ma klasę QSettings którą sobie rejestr odczytam,zaś zbudowanie poszczególnych obiektów QAction i wsadzenie ich do QMenu to będzie proste.
Ale,to znów mnie czeka ostre góglowanie gdzież odpowiednie wpisy w rejestrze przesiadują...

0

Do funkcji TrakPopupMenu podajesz uchwyt okna winId() - aby obsłużyć popupy i ikonki w menu, musisz w tymże oknie przekierować kilka komunikatów do IContextMenu, a dokładniej do IContextMenu3 lub IContextMenu2.
Zerknij do dokumentacji metody IContextMenu3::HandleMenuMsg2 - jest tam podanych kilka komunikatów:

[...]In the case of some messages, such as WM_INITMENUPOPUP, WM_DRAWITEM, WM_MENUCHAR, or WM_MEASUREITEM, the client object being called may provide owner-drawn menu items.[...]

Ale żeby nie ograniczać aplikacji do jakiegos ścisłego zestawu komunikatów, co w przyszłych wydaniach Windowsa może blokować nowe funkcjonalności, warto odpalić HandleMenuMsg2() dla wszystkich przychodzących komunikatów i ewentualnie sprawdzić, czy zostały "zjedzone" - wtedy pomijamy kroki doprowadzające działania domyślnego - do odpalenia funkcji DefWindowProc.

Ja mam przykład w MFC - jest sobie IDE z okienkiem listującym pliki z jakiegoś folderu. W klasie obsługującej to okienko mam zmienną m_hWnd (twoje winId()) oraz m_ctxmenu o typie IContextMenu*. Tą drugą zmienną ustawiam przed powołaniem menu do życia i zeruję, gdy TrakPopupMenu zwróci wartość.
Komunikaty wyłapuję tutaj:

// ta stała jest pierwszym ID menu dodanym przez QueryContextMenu
//ctxm->QueryContextMenu(hPopup, GetMenuItemCount(hPopup), ShellFileView_ShellMenuFirstId,
//	ShellFileView_ShellMenuFirstId+200, CMF_NORMAL);
#define ShellFileView_ShellMenuFirstId 100

LRESULT ShellFileView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	IContextMenu2 *ctxm2;
	IContextMenu3 *ctxm3;
	HRESULT hr;
	LRESULT result;

	// tutaj dodaję obsługę "dymków" - IQueryInfo - możesz pominąć
	if (message==WM_NOTIFY && ((NMHDR*)lParam)->code==TTN_GETDISPINFOA)
	{
		OnGetTooltipA((NMTTDISPINFOA*)lParam, &result);
		return result;
	}
	if (message==WM_NOTIFY && ((NMHDR*)lParam)->code==TTN_GETDISPINFOW)
	{
		OnGetTooltipW((NMTTDISPINFOW*)lParam, &result);
		return result;
	}
	const MSG *msg = GetCurrentMessage();
	::SendMessage(m_tooltip, TTM_RELAYEVENT, 0, (LPARAM)msg);

	// tutaj obsługa IContextMenu, włączając podpowiedzi na statusbarze
	// m_ctxmenu jest niezerowe tylko wtedy, gdy "shellowe" menu jest aktywne
	if (m_ctxmenu)
	{
		if (message==WM_MENUSELECT && !(HIWORD(wParam) & MF_POPUP))
		{
			TCHAR text[256];
			CStatusBar *status = ((CMainFrame *)AfxGetApp()->GetMainWnd())->GetStatusBar();

			if (!m_ctxmenu->GetCommandString(LOWORD(wParam)-ShellFileView_ShellMenuFirstId, GCS_HELPTEXT, 0, text, sizeof(text)/sizeof(text[0])))
				status->SetPaneText(0,text);
			else
				status->SetPaneText(0,_T(""));
		}
		// jeżeli IContextMenu3 jest dostępne
		if (!m_ctxmenu->QueryInterface(IID_IContextMenu3, (void**)&ctxm3))
		{
			hr = ctxm3->HandleMenuMsg2(message, wParam, lParam, &result);
			ctxm3->Release();
			if (SUCCEEDED(hr)) return result;
		}
		// ostatecznie IContextMenu2
		else if (!m_ctxmenu->QueryInterface(IID_IContextMenu2, (void**)&ctxm2))
		{
			hr = ctxm2->HandleMenuMsg(message, wParam, lParam);
			ctxm2->Release();
			if (SUCCEEDED(hr)) return 0;
		}
	}

	// tutaj ukrywam komunikat WM_CONTEXTMENU, by MFC nie otworzyło mi domyślnego menu
	return (message==WM_CONTEXTMENU) ? 0 : CWnd::WindowProc(message, wParam, lParam);
}
0

W takim razie użyjemy standardowego, choć nowocześniejszego, hookowania za pomocą SetWindowSubclass:

LRESULT __stdcall MenuSubclassProc(HWND hWnd,UINT message,WPARAM wParam,
	LPARAM lParam,UINT_PTR uIdSubclass,DWORD_PTR dwRefData)
{
	// uIdSubclass i dwRefData są te same, co w SetWindowSubclass()
	IContextMenu *m_ctxmenu = (IContextMenu*)dwRefData;
	// tutaj opcjonalna obsługa dymków, help-text i zjadanie WM_CONTEXTMENU
	/*switch (message)
	{
		case WM_NOTIFY:
			if (((NMHDR*)lParam)->code==TTN_GETDISPINFOA) ...
			break;
		case WM_MENUSELECT:
			if (!(HIWORD(wParam) & MF_POPUP))
				// m_ctxmenu->GetCommandString() + statusbar.settext();
			break;
		case WM_CONTEXTMENU: // zjadamy jeśli trzeba
			return 0;
	}*/
	// a tutaj IContextMenu*::
	if (!m_ctxmenu->QueryInterface(IID_IContextMenu3, (void**)&ctxm3))
	// ... {} else sprawdzamy IContextMenu2 

	return DefSubclassProc(hWnd,message,wParam,lParam);
}

//Twój kod: obuduj TrackPopupMenuEx() w dwie nowe linie: (123456 to dowolna liczba)
SetWindowSubclass(winId(), MenuSubclassProc, (UINT_PTR)123456, (DWORD_PTR)menu);
int selectedMenuItem = TrackPopupMenuEx(...
RemoveWindowSubclass(winId(), MenuSubclassProc, (UINT_PTR)123456);

Można użyć SetWindowLong z indeksem GWL_WNDPROC, ale to stara i niebezpieczna technologia, poza tym trzeba by jakoś przekazać IContextMenu.

0

By w Qt4 obsłużyć komunikaty Windows-a nieznane dla Qt trzeba skorzystać z QCoreApplication::winEventFilter.
W Qt5 zmienili API na bardziej poręczne: QCoreApplication::installNativeEventFilter i QCoreApplication::removeNativeEventFilter (szczególnie cenne, gdy to biblioteka chce skorzystać z WinApi).

0

Tak się zastanawiam czy nie prościej usunąć tą pozycję menu tuż przed wyświetleniem?

1 użytkowników online, w tym zalogowanych: 0, gości: 1