How to know when a menu item has been clicked? - windows

I'm using the winsafe crate and want to know when a menu item's been clicked in a window.
flags & MF_MOUSESELECT as u16 should be 1 if so and 0 if not, but it's always 32768 everytime the event is fired, even if it's just from the user hovering a menu item, or even clicking away to make it close.
Why?
self.wnd.on().wm(winsafe::co::WM::MENUSELECT, {
move |params| {
let wparam = params.wparam;
let lparam = params.lparam;
let flags = (wparam >> 16 & 0xffff) as u16;
let MF_MOUSESELECT = 0x00008000 as u32;
println!("{}", flags & MF_MOUSESELECT as u16);
// always 32768
0
}
});
The menu is generated by a resource script which is compiled and embedded in the program:
1 MENU
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
{
POPUP "&File"
{
MENUITEM "&Open", 1
MENUITEM "&Save", 2
}
POPUP "&Help"
{
MENUITEM "&About", 3
}
}

You should handle WM_COMMAND. There is actually built in functionality for menus. Simply provide the id you specified in your resource file to check when the correponding menu item is clicked.
Replace your event listener with these:
self.wnd.on().wm_command(co::CMD::Menu, 1, {
move || {
println!("Open clicked.")
}
});
self.wnd.on().wm_command(co::CMD::Menu, 2, {
move || {
println!("Save clicked.")
}
});
self.wnd.on().wm_command(co::CMD::Menu, 3, {
move || {
println!("About clicked.")
}
});

Related

Assert IsMenu exception when attempting to populate a dynamic menu

I'm attempting to populate a "submenu" in my dialog box from an array of strings as shown in this answer.
My attempt looks like the following:
#define ID_APP0 14000
#define ID_APP1 14001
#define ID_APP2 14002
#define ID_APP3 14003
#define ID_APP4 14004
#define ID_APP5 14005
#define ID_APP6 14006
#define ID_APP7 14007
void SoftwareDlg::DynamicAppMenu()
{
CMenu MyMainMenu;
VERIFY(MyMainMenu.LoadMenu(IDR_MENU1));
CMenu* SomeMenu = MyMainMenu.GetSubMenu(0);
if (SomeMenu)
{
for (auto i = 0; i < 1; i++)
{
SomeMenu->AppendMenu(MF_STRING, 14000+i, Client::m_vszAppArr[i]);
}
}
}
...but I'm getting an exception from the assert below immediately after(during?) the AppendMenu() function.
_AFXWIN_INLINE BOOL CMenu::AppendMenu(UINT nFlags, UINT_PTR nIDNewItem, LPCTSTR lpszNewItem)
{ ASSERT(::IsMenu(m_hMenu)); return ::AppendMenu(m_hMenu, nFlags, nIDNewItem, lpszNewItem); }
I no longer know how to continue debugging this issue because the LoadMenu() function appears to work correctly, and none of the variables are populated where the exception takes place.
Am I possibly just calling this in the wrong place? It's happening(conditionally) inside of a scheduled member function of the dialog box... does it need to happen somewhere like OnDraw(), OnPaint(), or something?
Edit1: Value of SettingsMenu->m_hMenu
Edit2: IDR_MENU1 resource definition:
IDR_MENU1 MENU
BEGIN
POPUP "Tools"
BEGIN
MENUITEM "New Test F11", ID_TOOLS_NEWTEST
MENUITEM "Export Data", ID_TOOLS_EXPORTDATA
MENUITEM "Upload Data", ID_TOOLS_UPLOADDATA
MENUITEM "Test Control", ID_TOOLS_TESTCONTROL
END
POPUP "Settings"
BEGIN
POPUP "USB_PORT"
BEGIN
MENUITEM "Serial Ports", ID_PORT_SERIALPORTS, INACTIVE
MENUITEM "COM1", ID_PORT_COM1
MENUITEM "COM2", ID_PORT_COM2
MENUITEM "COM3", ID_PORT_COM3
MENUITEM "COM4", ID_PORT_COM4
MENUITEM "COM5", ID_PORT_COM5
MENUITEM "COM6", ID_PORT_COM6
MENUITEM "COM7", ID_PORT_COM7
MENUITEM "COM8", ID_PORT_COM8
END
MENUITEM "Debug Mode", ID_SETTINGS_DEBUGMODE
MENUITEM "Display in OSD", ID_SETTINGS_DISPLAYINOSD
MENUITEM "Test Upload Mode", ID_SETTINGS_TESTUPLOADMODE
MENUITEM "Preferences", ID_SETTINGS_PREFERENCES
POPUP "Target Window"
BEGIN
MENUITEM "Placeholder", ID_TARGETWINDOW_PLACEHOLDER
END
END
END
As hinted to by #Vlad Feinstein's comment, CMenu MyMainMenu; should not have been declared inside the function... That fix allowed me to get past the assertion, but the new menu items were still not appearing. I began catching and logging the return values from the creation and deletion functions which lead me to realize that because I already had an existing menu, I needed to use a pointer to that menu(as opposed to creating my own that I then left un-used).
My final function ended up looking like this:
void SoftwareDlg::DynamicAppMenu(){
CMenu* MainMenu = GetMenu();
CMenu* SettingsMenu = MainMenu->GetSubMenu(1);
CMenu* TargetAppMenu = SettingsMenu->GetSubMenu(5);
if (TargetAppMenu)
{
BOOL appended = false;
BOOL deleted = false;
for (auto i = 0; i < Client::m_vszAppArr.size(); i++)
{
appended = TargetAppMenu->AppendMenu(MF_STRING, 14000+i, Client::m_vszAppArr[i].c_str());
}
deleted = TargetAppMenu->DeleteMenu(ID_TARGETWINDOW_PLACEHOLDER, MF_BYCOMMAND);
OutputDebugString(("String appended: " + std::to_string(appended)).c_str());
OutputDebugString(("Placeholder deleted: " + std::to_string(deleted)).c_str());
}
}

How to check if a button is checkbox on 64 bit windows?

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

Adding a drop-down menu button to a CMFCToolbar, not seeing menu

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

Add custom action to System Menu in a QDialog

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

how to make jqgrid advanced search dialog keyboard accessible

Answer in how to enable enter in jqgrid advanced search window describes how to enable enter and other keys in jqgrid advanced search dialog.
After clicking Add group, Add subgrup, Delete rule or Delete group button in advanced search dialog Enter and other keys are still ignored. How set focus to added element or after delete remaining element to enable Enter and other keys?
The current version of the Advanced Searching dialog (see definition of jqFilter in the grid.filter.js) recreate all controls of the dialog on change of someone. See the code of reDraw which looks
this.reDraw = function() {
$("table.group:first",this).remove();
var t = this.createTableForGroup(p.filter, null);
$(this).append(t);
if($.isFunction(this.p.afterRedraw) ) {
this.p.afterRedraw.call(this, this.p);
}
};
How one can see the first line $("table.group:first",this).remove(); delete the content of all filter. The current focus will be lost and one have the problems which you described.
I suggest to fix the code of reDraw using document.activeElement element which was introduced in Internet Explorer originally (at least in IE4) and which is supported now in all web browsers because it's part of HTML5 standard (see here). The element which has focus originally will be destroyed and one will unable to set focus on it later. So I suggest to save the element name of the element and it's classes (like input.add-group or input.add-rule.ui-add) and to find the position of the element on the searching dialog. Later, after the dialog element will be recreated we'll set focus on the element with the same index.
I suggest to change the code of reDraw to the following
this.reDraw = function() {
var activeElement = document.activeElement, selector, $dialog, activeIndex = -1, $newElem, $buttons,
buttonClass,
getButtonClass = function (classNames) {
var arClasses = ['add-group', 'add-rule', 'delete-group', 'delete-rule'], i, n, className;
for (i = 0, n = classNames.length; i < n; i++) {
className = classNames[i];
if ($.inArray(className, arClasses) >= 0) {
return className;
}
}
return null;
};
if (activeElement) {
selector = activeElement.nodeName.toLowerCase();
buttonClass = getButtonClass(activeElement.className.split(' '));
if (buttonClass !== null) {
selector += '.' + buttonClass;
if (selector === "input.delete-rule") {
$buttons = $(activeElement).closest('table.group')
.find('input.add-rule,input.delete-rule');
activeIndex = $buttons.index(activeElement);
if (activeIndex > 0) {
// find the previous "add-rule" button
while (activeIndex--) {
$newElem = $($buttons[activeIndex]);
if ($newElem.hasClass("add-rule")) {
activeElement = $newElem[0];
selector = activeElement.nodeName.toLowerCase() + "." +
getButtonClass(activeElement.className.split(' '));
break;
}
}
}
} else if (selector === "input.delete-group") {
// change focus to "Add Rule" of the parent group
$newElem = $(activeElement).closest('table.group')
.parent()
.closest('table.group')
.find('input.add-rule');
if ($newElem.length > 1) {
activeElement = $newElem[$newElem.length-2];
selector = activeElement.nodeName.toLowerCase() + "." +
getButtonClass(activeElement.className.split(' '));
}
}
$dialog = $(activeElement).closest(".ui-jqdialog");
activeIndex = $dialog.find(selector).index(activeElement);
}
}
$("table.group:first",this).remove();
$(this).append(this.createTableForGroup(this.p.filter, null));
if($.isFunction(this.p.afterRedraw) ) {
this.p.afterRedraw.call(this, this.p);
}
if (activeElement && activeIndex >=0) {
$newElem = $dialog.find(selector + ":eq(" + activeIndex + ")");
if ($newElem.length>0) {
$newElem.focus();
} else {
$dialog.find("input.add-rule:first").focus();
}
}
};
Like one can see on the next demo the focus in the Searching Dialog stay unchanged after pressing on the "Add subgroup" or "Add rule" buttons. I set it on the "Add rule" buttons of the previous row group in case of pressing "Delete group".
One more demo use jQuery UI style of the buttons and the texts in the buttons (see the answer). After clicking on the "Delete" (rule or group) button I tried to set the focus to the previous "Add Rule" button because setting of the focus on another "Delete" (rule or group) button I find dangerous.
Additionally in the demo I use
afterShowSearch: function ($form) {
var $lastInput = $form.find(".input-elm:last");
if ($lastInput.length > 0) {
$lastInput.focus();
}
}
because it seems me meaningful to set initial focus on the last input field at the dialog opening.
UPDATED: I find additionally meaningful to set focus on the current clicked buttons "Add subgroup", "Add rule" or "Delete group". The advantage one sees in the case it one first click some button with the mouse and then want to continue the work with keyboard. So I suggest to change the line
inputAddSubgroup.bind('click',function() {
to
inputAddSubgroup.bind('click',function(e) {
$(e.target).focus();
To change the line
inputAddRule.bind('click',function() {
to
inputAddRule.bind('click',function(e) {
$(e.target).focus();
and the line
inputDeleteGroup.bind('click',function() {
to
inputDeleteGroup.bind('click',function(e) {
$(e.target).focus();
and the line
ruleDeleteInput.bind('click',function() {
to
ruleDeleteInput.bind('click',function(e) {
$(e.target).focus();

Resources