PopupMenu or Combobox with submenus in WTableView
Added by Martin Melcher 8 months ago
Hi,
I have a WTableView where some items can be edited in drop-down menus.
Since WComboBox does not support sub-menus (correct?), I'm making my replacement using WPopupMenu, attached to a WPushButton.
This works fine normally, but inside the WTableView, when I press the button, the WPopupMenu is shown and immediately disappears before the user can take any action.
I'm posting just parts of the code to keep it reasonably simple. Am I doing this wrong? Any reason why the popup menu will not stay open, after the push button is clicked, until the user selects a menu item?
Or is there a more simple way to do this, using somehow a WComboBox that has sub-menus?
Here is my "mock" Combo-Box with submenus:
class NestedPopupMenu : public WPopupMenu
{
//------- Class based on WPopupMenu with some functions to conveniently add sub-menus
Signal<unsigned> SelectionEvent; //Emitted when the user makes a selection
// ...
};
class NestedPopupSelector : public WContainerWidget
{
NestedPopupMenu *Menu; //My WPopupMenu
WText *Text;
WPushButton *Button;
Signal<unsigned>SelectionEvent;
bool HasSelection;
unsigned MySelection;
public:
NestedPopupSelector() : HasSelection{false}
{
Button=addWidget(make_unique<WPushButton>("V"));
Text=addWidget(make_unique<WText>(""));
Text->addStyleClass("SelectorText");
Menu=new NestedPopupMenu();
Menu->GetSelectionEvent().connect(this, &NestedPopupSelector::SelectionTrigger);
Button->setMenu(unique_ptr<NestedPopupMenu>(Menu));
}
Signal<unsigned> &GetSelectionEvent() {return SelectionEvent;}
void SelectionTrigger(unsigned ID)
{
SetSelection(ID);
SelectionEvent.emit(ID);
}
void ClearEntries() {Menu->ClearEntries();}
void AddEntry(unsigned ID, const string &name) {Menu->AddEntry(ID,name);}
void SetSelection(unsigned ID) //Displays the name of the selected items in the WText box when the user made a selection
{
HasSelection=true;
MySelection=ID;
string name;
if (Menu->FindName(ID, name))
{
Text->setText(name);
}
}
void MakeMenu() { //Creates the submenu entries, to be called after all entries were added with AddEntry
Menu->MakeMenu();
if (HasSelection)
SetSelection(MySelection);
}
};
My Item Delegate class (implementen of WAbstractItemDelegate) will create a custom widget in the update finction:
unique_ptr<WWidget> SimItemDelegate::update(WWidget *widget, const WModelIndex &index, WFlags<ViewItemRenderFlag> flags)
{
SimItemWidget *MyWidget=FindWidget(widget); //Checks if widget is indeed my custom widget of type SimItemWidget and returns it
if (MyWidget)
{
MyWidget->SetState(any_cast<ItemContents&>(index.data()));
MyWidget->SetFlags(flags);
return nullptr;
}
// else - will construct a new widget
}
Finally, in my custem widget, the SetFlags function will call SimItemWidget::BuildNew() wo build my "Mock" combo box:
void SimItemWidget::BuildNew()
{
// Check if the new editor view needs to be created, if yes:
MySelector=addWidget(make_unique<NestedPopupSelector>());
MySelector->GetSelectionEvent().connect(this, &SimItemWidget::ComboBoxSelection);
UpdateSelectionMenu(); //Here the menu entries will be added and finally MySelector->MakeMenu() will be called
}
Replies (1)
RE: PopupMenu or Combobox with submenus in WTableView - Added by Martin Melcher 8 months ago
Hi,
after days of playing around, I found what the issue is - but I need to know if this expected behavior or not.
In short: Adding a WPushButton with a connected WPopupMenu in a WTableView works only when setting setEditTriggers(Wt::EditTrigers::SingleClicked). Without calling setEditTriggers or with any other argument, the WPopupMenu won't stay open when the butten is clicked, just flash for a fraction of a second.
Here is a simplified C++ code - I dumbed my widget down to remove all other possible root causes:
class DumbItemModel : public Wt::WAbstractItemModel {
public:
DumbItemModel() {}
virtual int rowCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const override {
if (!parent.isValid()) // Only the root has children
return rowCount_;
else
return 0; // No children of children
}
virtual int columnCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const override {
return columnCount_; // Same for all rows
}
virtual cpp17::any data(const Wt::WModelIndex& index, ItemDataRole role = Wt::ItemDataRole::Display) const override {
if (role == Wt::ItemDataRole::Display)
return std::string("test");
return cpp17::any();
}
virtual cpp17::any headerData(int section, Orientation orientation=Orientation::Horizontal, ItemDataRole role=ItemDataRole::Display) const override {
if (role == Wt::ItemDataRole::Display)
return std::string("test");
return cpp17::any();
}
void populate() { //Added to mimic the behavior of the original widget
beginInsertRows(Wt::WModelIndex(), 0, 2);
columnCount_ = 3;
endInsertColumns();
beginInsertColumns(Wt::WModelIndex(), 0, 2);
rowCount_ = 3;
endInsertRows();
}
virtual Wt::WModelIndex index(int row, int column, const Wt::WModelIndex& parent = Wt::WModelIndex()) const override {
if (!parent.isValid() && row >= 0 && row < rowCount_ && column >= 0 && column < columnCount_)
return createIndex(row, column, nullptr);
return Wt::WModelIndex();
}
virtual Wt::WModelIndex parent(const Wt::WModelIndex& index) const override {
return Wt::WModelIndex(); // No parent since it's a flat model
}
private:
int rowCount_ = 0;
int columnCount_ = 0;
};
class DumbItemDelegate : public Wt::WAbstractItemDelegate {
public:
DumbItemDelegate() = default;
~DumbItemDelegate() override = default;
// Overloading the update function
virtual unique_ptr<WWidget> update (WWidget *widget, const WModelIndex &index, WFlags< ViewItemRenderFlag > flags) override
{
if (!widget) {
// Create a new container widget
auto container = std::make_unique<Wt::WContainerWidget>();
// Create a push button
auto button = container->addWidget(std::make_unique<Wt::WPushButton>("Test"));
// Create a popup menu
auto menu = std::make_unique<Wt::WPopupMenu>();
menu->addItem("Item1");
menu->addItem("Item2");
// Set the menu to the button
button->setMenu(std::move(menu));
return container;
}
// Return nullptr if index is valid as we are focusing on the null case
return nullptr;
}
// Overloading editState (stub implementation)
virtual cpp17::any editState (WWidget *widget, const WModelIndex &index) const override
{
return Wt::cpp17::any();
}
// Overloading setEditState (stub implementation)
virtual void setEditState (WWidget *widget, const WModelIndex &index, const cpp17::any &value) const override {
// Do nothing for now
}
// Overloading setModelData (stub implementation)
virtual void setModelData (const cpp17::any &editState, WAbstractItemModel *model, const WModelIndex &index) const override{}
};
class MyTestWidget : public Wt::WContainerWidget {
public:
MyTestWidget() {
auto model=std::make_shared<DumbItemModel>();
auto delegate=std::make_shared<DumbItemDelegate>();
auto TableView = addWidget(std::make_unique<Wt::WTableView>());
TableView->setAlternatingRowColors(true);
TableView->setSortingEnabled(false);
TableView->setSelectionBehavior(SelectionBehavior::Items);
TableView->setSelectionMode(SelectionMode::Extended);
model->populate();
TableView->setModel(model);
TableView->setItemDelegate(delegate);
TableView->setHeaderItemDelegate(delegate);
//-------------------------------------------
//The push button is functional only when this line is present. WHY??---------------------
TableView->setEditTriggers(Wt::EditTrigger::SingleClicked);
//-------------------------------------------
}
};