I have a need to add a custom action (say ‘About’ clicking which a QMessageBox needs to be displayed) in the system menu shown when the icon on the title bar of a QDialog is clicked. How do I achieve this?
Regards,
Bharath
You cannot do it with Qt because it's OS specific. But you can use GetSystemMenu and AppendMenu functions in Windows to modify the menu and then catch events that then item is clicked.
Here is a simple example from here. It appends a separator and an about item to the menu:
#include "windows.h"
// IDM_ABOUTBOX must be in the system command range
// (IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX)
// and (IDM_ABOUTBOX < 0xF000)
#define IDM_ABOUTBOX 0x0010
MyWidget::MyWidget() : QMainWindow()
{
...
HMENU hMenu = ::GetSystemMenu(winId(), FALSE);
if (hMenu != NULL)
{
::AppendMenuA(hMenu, MF_SEPARATOR, 0, 0);
::AppendMenuA(hMenu, MF_STRING, IDM_ABOUTBOX, "About MyApp...");
}
...
}
bool MyWidget::winEvent(MSG *m, long *result)
{
if (m->message == WM_SYSCOMMAND)
{
if ((m->wParam & 0xfff0) == IDM_ABOUTBOX)
{
*result = 0;
// open About dialog
about();
return (true);
}
}
return (false);
}
PRO-file:
LIBS += -lUser32
Related
How can we detect WM_LBUTTONDBLCLK on a button (ex. ID_FILE_NEW) on a Toolbar?
This seems to be straight forward with PreTranslateMessage(). I have tested with this code snippet.
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
// Detect Clicks in Toolbar detektieren
if (pMsg->hwnd == m_wndToolBar.GetSafeHwnd())
{
if (pMsg->message == WM_LBUTTONDBLCLK)
{
CPoint pt = pMsg->pt;
m_wndToolBar.ScreenToClient(&pt);
int nIdx = m_wndToolBar.CommandToIndex(ID_FILE_NEW);
CRect rcIdx;
m_wndToolBar.GetItemRect(nIdx, &rcIdx);
if (rcIdx.PtInRect(pt))
{
MessageBox(_T("yupii double click detected"));
return TRUE;
}
}
}
return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}
I'm checking if a button is checkbox of 32bit process on 64bit windows10.
The problem is that I can not distingush checkbox from normal button.
The buttons are different in Window-Detective:
(After I restart the application, even Window-Detective shows it is a button now!)
But the checkbox can't be recognized as checkbox in Spy++
BS_CHECKBOX is not listed.
Code (compiled as 32bit):
TEST_METHOD(ShouldCheckStyle) {
auto styleOfButton = ::GetWindowLongPtr((HWND)0x003F06E8, GWL_STYLE);
auto styleOfCheckbox = ::GetWindowLongPtr((HWND)0x01101642, GWL_STYLE);
auto bsOfButton = styleOfButton & BS_TYPEMASK;
auto bsOfCheckbox = styleOfCheckbox & BS_TYPEMASK;
auto resultOfButton = (bsOfButton == BS_CHECKBOX);
auto resultOfCheckbox = (bsOfCheckbox == BS_CHECKBOX);
auto debugger = 0;
}
Debug output
The code indicates they both have BS_OWNERDRAW. The above behaves the same for the button and the checkbox.
The weird thing is Window-Detective can recognize the style of checkbox. The code is same as I used above. Here's a piece of code:
Window* WindowManager::createWindow(HWND handle) {
WindowClass* windowClass = getWindowClassFor(handle);
String className = windowClass->getName().toLower();
if (className == "button") {
LONG typeStyle = GetWindowLong(handle, GWL_STYLE) & BS_TYPEMASK;
switch (typeStyle) {
case BS_CHECKBOX:
case BS_AUTOCHECKBOX:
case BS_3STATE:
case BS_AUTO3STATE: {
return new CheckBox(handle, windowClass);
}
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON: {
return new RadioButton(handle, windowClass);
}
case BS_GROUPBOX: {
return new GroupBox(handle, windowClass);
}
default: {
// If none of the above is true, then the control is just a Button
return new Button(handle, windowClass);
}
}
}
After some discussion, you can use GetWindowText to get the text from each control and compare the specific text.
BS_CHECKBOX cannot be detected from the properties of the "checkbox" control beacuse of BS_OWNERDRAW.
Creates an owner-drawn button. The owner window receives a WM_DRAWITEM
message when a visual aspect of the button has changed. Do not combine
the BS_OWNERDRAW style with any other button styles.
Try the below code:
WCHAR str1[20];
WCHAR str2[] = L"Agree me";
GetWindowText(hwnd_checkbox, str1, 256);
if (_tcscmp(str1, str2) == 0)
{
//it is checkbox
}
else
{
//it isn't checkbox
}
After you get the correct control handle of the checkbox, you can use SendDlgItemMessage or SendMessage to send BM_SETCHECK check message.
SendMessage(hwnd_checkbox, BM_SETCHECK, BST_CHECKED, 0);
I have created a SDL2 application, and would like it to minimize to the system tray, rather than appearing in the task bar.
SDL_MinimizeWindow doesn't do what I want, it leaves the task bar icon. Is there a way to achieve this with SDL?
There is no purely SDL2 way to do this, as Cody said, Shell_NotifyIcon is the function needed to create a notification area (system tray) icon.
The code I used to get the icon is
SDL_Window *window = SDL_CreateWindow("Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 200, 200, SDL_WINDOW_HIDDEN);
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
NOTIFYICONDATA icon;
if (SDL_GetWindowWMInfo(window, &info))
{
icon.uCallbackMessage = WM_USER + 1;
icon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
icon.hIcon = LoadIcon(NULL, IDI_INFORMATION);
icon.cbSize = sizeof(icon);
icon.hWnd = info.info.win.window;
strcpy_s(icon.szTip, "Test tip");
bool success = Shell_NotifyIcon(NIM_ADD, &icon);
}
This creates a hidden window, and an icon (using the default information icon).
To interact with this from SDL, you need to enable platform specific window management events, this is done as follows SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
After this, in the main event loop you must test for SDL_SYSWMEVENT, which contains information about how the user has interacted with the notification area icon. This also looks for the minimize event and hides the window which removes it from the task bar. This is achieved in the following snippet
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
{
switch (e.type)
{
case SDL_SYSWMEVENT:
if (e.syswm.msg->msg.win.msg == WM_USER + 1)
{
if (LOWORD(e.syswm.msg->msg.win.lParam) == WM_LBUTTONDBLCLK)
{
SDL_ShowWindow(window);
SDL_RestoreWindow(window);
}
}
break;
case SDL_QUIT:
running = false;
break;
case SDL_WINDOWEVENT:
if (e.window.event == SDL_WINDOWEVENT_MINIMIZED)
SDL_HideWindow(window);
break;
}
}
I tried following all the examples I could find but I'm missing something so I will put all the pieces here for others to view. FYI - I'm modifying the MFC Feature Pack example Slider.
I see the layer button (not a string or down arrow) if I select the button (click) I see the depress motion and get to the OnLayers() function with the ID of the button. I almost looks like the ReplaceButton() is doing nothing.
Any ideas?
Thanks
For the toolbar I've added ID_LAYERS_1
IDR_MAINFRAME TOOLBAR 16, 15
BEGIN
BUTTON ID_FILE_NEW
BUTTON ID_FILE_OPEN
BUTTON ID_FILE_SAVE
SEPARATOR
BUTTON ID_SLIDER
SEPARATOR
BUTTON ID_EDIT_CUT
BUTTON ID_EDIT_COPY
BUTTON ID_EDIT_PASTE
SEPARATOR
BUTTON ID_FILE_PRINT
SEPARATOR
BUTTON ID_APP_ABOUT
SEPARATOR
BUTTON ID_LAYERS_1
END
my menu is
IDR_LAYERS MENU
BEGIN
POPUP "Layers"
BEGIN
MENUITEM "0", ID_LAYERS_1
MENUITEM "1", ID_LAYERS_2
MENUITEM "2", ID_LAYERS_3
END
END
and the code
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
ON_WM_CREATE()
ON_WM_CLOSE()
ON_COMMAND(ID_SLIDER, OnSlider)
ON_COMMAND(ID_VIEW_CUSTOMIZE, OnViewCustomize)
ON_REGISTERED_MESSAGE(AFX_WM_RESETTOOLBAR, OnToolbarReset)
ON_REGISTERED_MESSAGE(AFX_WM_TOOLBARMENU, OnToolbarContextMenu)
ON_UPDATE_COMMAND_UI_RANGE(ID_LAYERS_1, ID_LAYERS_3, OnUpdateLayers)
ON_COMMAND_RANGE(ID_LAYERS_1, ID_LAYERS_3, OnLayers)
END_MESSAGE_MAP()
CMFCToolBarMenuButton* CreateLayerButton()
{
CMenu menu;
VERIFY(menu.LoadMenu(IDR_LAYERS));
CMFCToolBarMenuButton* pLayerButton = NULL;
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup != NULL);
if (pPopup != NULL)
{
HMENU hMenu = pPopup->GetSafeHmenu();
pLayerButton = new CMFCToolBarMenuButton(ID_LAYERS_1, hMenu, -1, NULL, FALSE);
}
return pLayerButton;
}
afx_msg LRESULT CMainFrame::OnToolbarReset(WPARAM wp, LPARAM)
{
UINT uiToolBarId = (UINT)wp;
if (uiToolBarId == IDR_MAINFRAME)
{
CSliderButton btnSlider(ID_SLIDER);
btnSlider.SetRange(0, 100);
m_wndToolBar.ReplaceButton(ID_SLIDER, btnSlider);
// layer button/menu
CMFCToolBarMenuButton* pLayerButton = CreateLayerButton();
m_wndToolBar.ReplaceButton(ID_LAYERS_1, *pLayerButton);
delete pLayerButton;
}
return 0;
}
void CMainFrame::OnUpdateLayers(CCmdUI* pCmdUI)
{
//pCmdUI->SetCheck(true);
}
void CMainFrame::OnLayers(UINT id)
{
}
I think you are using it the wrong way. Try this way:
CMenu menu;
VERIFY(menu.LoadMenu(IDR_LAYERS));
CString str;
str.LoadString (IDS_TEXT_OF_YOUR BUTTON);
m_wndToolBar.ReplaceButton (ID_LAYERS1,
CMFCToolBarMenuButton ( (UINT)-1, menu,
GetCmdMgr ()->GetCmdImage (ID_LAYERS1), str,TRUE));
I have a WH_CALLWNDPROC hook code which handles WM_INITDIALOG message to get information about message boxes. I could get "Message", "Title", "Buttons" but I couldnt get "icon" information. I'm trying to use a function like below:
long getIcon(HWND hwnd) { // handle of messagebox dialog
HWND hlbl = GetDlgItem(hwnd,20);
wcout << "LABEL HWND: " << hlbl << endl;
if (hlbl != NULL) {
LRESULT r = SendMessage(hlbl,WM_GETICON,0,0);
return (long)r;
}
return 0;
}
function always returns 0. I have checked by MS Spy++ and I saw that icon handle is 0.
What is the correct way to get icon?
The icon that is displayed on the message box dialog is implemented using a STATIC control with SS_ICON style. You can obtain the icon handle by sending that control the STM_GETICON message.
In the code in your question, the variable named hlbl is actually the window handle of the STATIC control that contains the icon. I'd name it hIconWnd. With that name change, the code to obtain the icon would look like this:
HICON getIcon(HWND hwnd) { // handle of messagebox dialog
HWND hIconWnd = GetDlgItem(hwnd, 20);
if (hIconWnd != NULL) {
return (HICON)SendMessage(hIconWnd, STM_GETICON, 0, 0);
}
return NULL;
}