Just wondering what the difference between MFC control messages prefixed with the following is:
LVN (e.g. LVN_ITEMCHANGED)
HDN (e.g. HDN_TRACK)
NM (e.g. NM_HOVER)
Also, I am using a ListControl and trapping when the user clicks on an item using the NM_CLICK message. I also want to trap when a user selects a new item view a key e.g. up/down arrow keys. Can anyone tell me which message I should be trapping for this?
Thanks
LVN = ListView Notification
HDN = HeaDer control Notification
NM = er..um.. "Notification for Mouse" ?
For change in selection, you need to handle the LVN_ITEMCHANGED notification:
NMLISTVIEW & nm = *(NMLISTVIEW *) pnmh;
if ( (nm.uNewState ^ nm.uOldState) & LVIS_SELECTED)
{
// nm.iItem was selected or deselected
if (!m_internalUIChange)
{
// see below
}
}
The first "if" checks if the "selected" state has changed. Note that, when selecting a different item in the list, this still fires twice: once for the old item to be deselected, and once for the new item to be selected. This is necessary, however, to fetch a "complete deselect".
This notification fires very often - even when you modify the control programmatically. If your handler should react only to user events, you will need at least a flag that you set durign these operations (I use a class together with a RAII-Lock for that, so I don't forget to reset it)
Related
Problem: can't trigger Vue-Select3 item dropdown selection programmatically in unit-tests.
I have tried with both vue-test-utils and #testing-library/vue as well as triggering a click event programmatically in a browser to force list item selection. However, none of these worked. The only way I managed to trigger selection is by getting a VueSelect instance and emitting 'input' event. I also tried to trigger different events on dropdown container, etc.
// Failed
// testing-library
const dropdownItem = getAllByTestId('dropdown-item')
fireEvent.click(dropdownItem[0])
// test-utils
wrapper.find('[data-testid=dropdown-item]').trigger('click')
// In browser
document.querySelectorAll('.vs__dropdown-item')[0].click()
// Success
wrapper.find(VueSelect).vm.$emit('input', payload)
Current result: When a click event is triggered nothing happens.
Expected result: When a click event is triggered Vue-Select should select the item and emit 'input' event.
I found a way of selecting an option directly. You can use the select function of the VueSelect component, like that:
const wrapper = mount(YourComponent)
const vueSelect = wrapper.find(VueSelect)
vueSelect.vm.select(yourOptionToSelect)
That will work if you just want to select a specific option.
But yes, if you want to simulate the click on one of the options, that would not be helpful. And I know that this way of changing the state of a component is not the preferred way of doing this, but it is the only solution I've found.
Let's say that I have a group of radio items in a wxMenu. I know that exactly one of these will be checked at any given time.
Does the wxMenu or some other construct hold onto the index of the checked item, or do I need to call the isChecked on each radio item till I find the checked element to find it's index?
I've asked this question about how to do that, but I'd much prefer wxWidgets saved me from doing that everywhere.
No, saving the index of the last selected item (as shown in ravenspoint's answer) or using wxMenuBarBase::IsChecked() until you find the selected radio button is the only way to do it.
For wxWidgets to provide access to the currently selected button it would need not only to store it (which means not forgetting to update not only when the selected changes, but also when items are inserted into/deleted from the menu, so it's already not completely trivial), but to somehow provide access to the radio items group you're interested in, which would require being able to identify it and currently there is no way to do it and adding it isn't going to be particularly simple.
What could be done easily, however, is writing a reusable function int GetIndexOfSelectedRadioItem(int firstItem) that would start at the given item and call IsChecked() on subsequent items until it returns true and return the offset of the item. You should be able to do it in your own code, but if you'd like to include such function in wxWidgets itself (as a static wxMenuBar method, probably), please don't hesitate to send patches/pull requests doing it!
It is easy enough to roll your own.
Bind an event handler to wxEVT_COMMAND_RADIOBUTTON_SELECTED for every button. In the handler, extract the ID of the selected radio button and store it somewhere.
Like this:
ResolMenu = new wxMenu();
ResolMenu->AppendRadioItem(idRcvLoRez,"Low Resolution");
ResolMenu->AppendRadioItem(idRcvMeRez,"Medium Resolution");
ResolMenu->AppendRadioItem(idRcvHiRez,"High Resolution");
ResolMenu->Check( idRcvLoRez, true );
Bind(wxEVT_MENU,&cFrame::onRcvRez,this,idRcvLoRez);
Bind(wxEVT_MENU,&cFrame::onRcvRez,this,idRcvMeRez);
Bind(wxEVT_MENU,&cFrame::onRcvRez,this,idRcvHiRez);
void onRcvRez( wxCommandEvent& event )
{
myRezID = event.GetId();
So first of all here is a screenshot of the said menu of Evernote, localized in French:
[]
As you can see, all the menu items in the main menu (by main menu I mean the one whose name is the application name, like here it is Evernote) are localized in French. There are lots of menu items which the Evernote app itself brings, like Évaluez Evernote pour Mac (Rate Evernote for Mac), Information du compte... (Account Info...), etc. Plus there are the standard OS X provided menu items like Quit Evernote, Preferences, etc which are also localized.
My questions:
How do I add a new item in this main menu? How to access this menu to add items?
How do I localize these items based on my app localization, both OS X provided default ones and the ones I add?
In the Evernote menu, everything seems to be localized except the Services menu option (the submenu options are however localized!)? Can't this be localized as well?
What I have tried:
fMenuBar = fMainWindow->menuBar();
fMenuFile = fMenuBar->addMenu(QObject::tr(qPrintable(String_Class::FileMenu))); //"File" in English, translated into other languages
fAboutAppAct = new QAction(QObject::tr(qPrintable(String_Class::About_App)), fMainWindow); //prints "About App", localized in all languages
fMenuFile->addAction(fAboutAppAct);
fAboutAppAct->setMenuRole(QAction::AboutRole); //otherwise it sits with the other file menu options in the File menu
//reset UI language slot, called whenver UI language is reset. It retranslates all strings in all menus, except this
void AppMenu::reTranslateUISlot()
{
fAboutAppAct->setText(QObject::tr(qPrintable(String_Class::About_App)));
}
Maybe you could reimplement in MainWindow or in AppMenu the changeEvent.
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
this->retranslateUi(this);
quickStart->retranslateUi(quickStart);
//etc...
} else {
QMainWindow::changeEvent(event);
}
}
You could force Widgets to retranslate themselves. But you need to have registered some QTranslator first.
For example, in the constructor of MainWindow (or in some config dialog) if it's possible to change language at runtime (what I've done in my software):
CustomizeOptionsDialog::CustomizeOptionsDialog(QWidget *parent)
: QDialog(parent, Qt::Tool)
{
// Load the language of the application
customTranslator.load(languages.value( SettingsPrivate::instance()->language()) );
// Translate standard buttons (OK, Cancel, ...)
defaultQtTranslator.load("qt_" + SettingsPrivate::instance()->language(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
QApplication::installTranslator(&customTranslator);
QApplication::installTranslator(&defaultQtTranslator);
}
Where language() returns "fr", "gb" or "cs" (initialized from a signal emitted when one has chosen a new language in options).
/** Change language at runtime. */
void CustomizeOptionsDialog::changeLanguage(const QString &language)
{
QString lang = languages.value(language);
SettingsPrivate *settings = SettingsPrivate::instance();
// If the language is successfully loaded, tells every widget that they need to be redisplayed
if (!lang.isEmpty() && lang != settings->language() && customTranslator.load(lang)) {
settings->setLanguage(language);
defaultQtTranslator.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
QApplication::installTranslator(&customTranslator);
/// TODO: reload plugin UI
QApplication::installTranslator(&defaultQtTranslator);
} else {
labelStatusLanguage->setText(tr("No translation is available for this language :("));
}
}
I hope it's helping.
I still haven't found the complete answer to my problems. But here some of the observations I have made over the last few days:
To be able to add menu items in the main menu, you have to set the menu role accordingly, i.e after adding it wherever you want to (it won't matter, because it will move out), you set the menu role like this:
fYourAction->setMenuRole(QAction::ApplicationSpecificRole);
This will add the menu item in the main menu. If you add more than item in this way, they will appear in the order in which you set their menu roles.
There are few specific roles Qt already provides - i.e for the About <app> item, Quit <app> item, Preferences... item, etc. They are mentioned here.
For example, if your action has a text "Foo", and you add it somewhere as a menu item, and set the role like
fFooAction->setMenuRole(QAction:: PreferencesRole);
then it will automatically move to the main menu and show as Preferences..., what text you actually put in the action will be immaterial. Whatever slot you have attached to it as a response to the triggered() signal will still fire correctly, though. Same goes for QAction::AboutRole as well, whatever text you add in that action, it will move to the main menu and show as About <your_app_name>.
The problem with QAction::AboutRole or QAction:: PreferencesRole is like I said, they won't localize even if you try. They will get localized only when the system locale changes, if you change it just within your app by installing a new translator, it won't change. The workaround? Avoid them and use QAction::ApplicationSpecificRole for all items you want to appear in the main menu. Then they will get properly localized as per your custom translator, and will respect whatever text you provide in the action, i.e if you give foo as text in the action, it will appear as foo in the main menu, and get localized accordingly. Again, mind you, when you are adding multiple items, set the role of the items in order of their appearance, i.e to simulate the Evernote menu above, first set the menu role for the about_app action, then the preferences action. Where you are adding them will be of no importance since they will be moved to a new menu, so the order in which you set the menu role for the items will determine the order in which they appear in the main menu.
The problem with the above approach is that I don't know how to insert separators between the items I am adding in the main menu. It is easy to do that in the menus we add, since we have access to the menu object, but here we don't have access (we add the items somewhere else, and make them move to the main menu by setting the menu role), so I don't know yet how to add multiple separators in the main menu.
I've created a wxGrid, populated it with data, and have created a column that contains checkboxes, and made them editable. All good so far.
co_Grid->SetReadOnly(at_RowCount, 24, false);
co_Grid->SetCellRenderer(at_RowCount, 24, new wxGridCellBoolRenderer);
co_Grid->SetCellEditor(at_RowCount, 24, new wxGridCellBoolEditor);
What I want to be able to do now is to add an event handler for the checkbox toggle event.
I've tried using the OnCellValueChanged event for the grid, but that only fires after the user leaves the cell, because before then the editor is still open (and the cell hasn't actually changed yet)
I'm pretty sure that I need to create an event handler for the wxGridCellBoolEditor but that's where I'm struggling.
I tried connecting an event in the OnEditorShown event, but that didn't go well (unhandled exception when I click on the cell to open the editor):
void cTeamGrid::OnEditorShown( wxGridEvent& ev )
{
int row = ev.GetRow(),
col = ev.GetCol();
co_Grid->GetCellEditor(row, col)->GetControl()->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED,
wxCommandEventHandler(cTeamGrid::OnGridCheckChange), NULL, this);
}
What am I doing wrong?
I had a similar problem myself. I bypassed it by setting the checkbox column to read-only and having the wxGrid control manually handle the click event to toggle the checkbox state (you also have to manage the double-click). This method is not the most orthodox, also because now each click on the cell, and not on the checkbox, will change the state. In my opinion, however, this can also be a desirable behaviour. In addition, this enables you to let the user change the checkbox with the keyboard (by capturing the KeyPress events).
I need to update a combobox with a new value so it changes the reflected text in it. The cleanest way to do this is after the comboboxhas been initialised and with a message.
So I am trying to craft a postmessage to the hwnd that contains the combobox.
So if I want to send a message to it, changing the currently selected item to the nth item, what would the postmessage look like?
I am guessing that it would involve ON_CBN_SELCHANGE, but I can't get it to work right.
You want ComboBox_SetCurSel:
ComboBox_SetCurSel(hWndCombo, n);
or if it's an MFC CComboBox control you can probably do:
m_combo.SetCurSel(2);
I would imagine if you're doing it manually you would also want SendMessage rather than PostMessage. CBN_SELCHANGE is the notification that the control sends back to you when the selection is changed.
Finally, you might want to add the c++ tag to this question.
A concise version:
const int index = 0;
m_comboBox.PostMessage(CBN_SELCHANGE, index);
What might be going wrong is the selection is being changed inside the selection change message handler, which result in another selection change message.
One way to get around this unwanted feedback loop is to add a sentinel to the select change message handler as shown below:
void onSelectChangeHandler(HWND hwnd)
{
static bool fInsideSelectChange = 0;
//-- ignore the change message if this function generated it
if (fInsideSelectChange == 0)
{
//-- turn on the sentinel
fInsideSelectChange = 1;
//-- make the selection changes as required
.....
//-- we are done so turn off the sentinel
fInsideSelectChange = 0;
}
}
if you fx want to change the title - which is the line shown when combobox is closed, then you can do following:
m_ComboBox.DeleteString(0); // first delete previous if any, 0 = visual string
m_ComboBox.AddString(_T("Hello there"));
put this in fx. in OnCloseupCombo - when event close a dropdownbox fires
ON_CBN_CLOSEUP(IDC_COMBO1, OnCloseupCombo)
This change is a new string not a selection of already assigned combobox elements