So I want to create the gui interface in Qt/cpp for the server which can be in many different states, and depending on its state the buttons in gui need to be set differently ex:
GUI:
button1 - unchecked and enabled to click
button2 - disabled(grayed out)
button3 - disabled
button3 - disabled
Then after click button1
GUI:
button1 - checked
button2 - enabled to click
button3 - enabled to click
button3 - enabled to click
But for example if server is in different state and you connect via gui the buttons should look like this:
GUI:
button1 - checked
button2 - enabled to click
button3 - disabled to click
button3 - disabled to click
Is there some established pattern/way of handling that intuitively? The biggest problem here is that if the server has a lot of different states that need the buttons to be set in a lot of different configurations. The only thing I can come up with is mapping the state of all buttons to the specific state but well... there's a lot of buttons and a lot of states.
You could try using flags, the idea is that when an event happens and you want the GUI to change you set a flag which in turn is recalled in a loop. Below you can see the general idea and concept.
If you change the state of the flag you will get a different output and it will loop over and over listening for events just write the GUI code for each in the different states.
#include <iostream>
using namespace std;
int getserverstatusfunction() {/*your code to check server status returns 0,1 or 2*/
return 0;
}
UI.button.click(true) { getresult = 1; }; //change the UI state when the button is clicked
int main() {
bool running;
while (running){
int getresult = getserverstatusfunction();
if (getresult == 0)
{
cout << "Draw flag one interface code\n";
}
else if (getresult == 1)
{
cout << "Draw flag two interface code\n";
}
else {
cout << "Draw flag three interface code\n";
}
system("pause");
return 0;
}
I've found the best way to do this is just to have a single slot-method (e.g. UpdateAllButtonStates() that updates all of your buttons and checkboxes, e.g.:
void MyWindow::UpdateAllButtonStates() // Qt-slot method
{
const bool shouldButton1BeEnabled = [...];
button1->setEnabled(shouldButton1BeEnabled);
const bool shouldButton2BeEnabled = [...];
button2->setEnabled(shouldButton2BeEnabled);
[... and so on for all enables and checked/unchecked states in your GUI]
}
... then anytime your program's internal state has changed in any way that might require an update to one or more buttons/checkboxes in the GUI, call this method explicitly, or set up a signal/slot connection that will call it for you.
The advantage of doing it this way is the simplicity -- with this approach, it's trivial to guarantee that your GUI widgets will be in updated to the expected state after any internal-state-change, because there is only one code-path to write and debug. The alternative (trying to come up with the correct transitional behavior for every possible state-change in your program) quickly leads to an intractable amount of complexity, and endless debugging and hair-pulling.
You might think the downside is inefficiency -- after all, we are updating all the buttons even though in any cases, only one of them may have changed -- but Qt's code is smart enough that calling setEnabled(false) on a button that is already disabled is a no-op (likewise calling setEnabled(true) on a button that is already enabled, and so on), so the heavyweight code of redrawing a widget's pixels will only be executed when the widget's state has actually changed.
The logic inside UpdateAllButtonStates() that calculates shouldButton1BeEnabled, etc, does get executed a lot, but it usually ends up being pretty trivial logic so that turns out not to be important. However, if for some reason that logic turns out to be expensive, you have the option of reducing the frequency at which UpdateAllButtonStates() gets executed, by using asynchronous execution and a boolean "dirty-bit", e.g.:
void MyWindow::ScheduleUpdateAllButtonStates() // Qt-slot method
{
if (_dirtyBit == false)
{
_dirtyBit = true;
QTimer::singleShot(0, this, SLOT(UpdateAllButtonStates()));
}
}
void MyWindow::UpdateAllButtonStates()
{
if (_dirtyBit == false) return;
_dirtyBit = false;
// Update button enables/checked states as previously, here
}
... then have all your internal-state-change code call ScheduleUpdateAllButtonStates() rather than calling UpdateAllButtonStates() directly; the advantage is that even if ScheduleUpdateAllButtonStates() gets called 500 times in a row, it will only result in UpdateAllButtonStates() getting called once, during the next iteration of Qt's event loop.
Enabling/disabling button UI logic can be very dirty and difficult to manage and trace. Also, sometimes we want to be in particular state and want to make a minor change like changing state of just one button. Here is an approach. It's generic but you will have to adrop it accordingly with your UI.
#include <iostream>
class UIState
{
protected:
bool btn1;
bool btn2;
bool btn3;
public:
UIState()
{
btn1 = false;
btn2 = false;
btn3 = false;
}
virtual void setBtn1State(bool new_state)
{
btn1 = new_state;
std::cout << btn1 << btn2 << btn3 << std::endl;
};
virtual void setBtn2State(bool new_state)
{
btn2 = new_state;
std::cout << btn1 << btn2 << btn3 << std::endl;
};
virtual void setBtn3State(bool new_state)
{
btn3 = new_state;
std::cout << btn1 << btn2 << btn3 << std::endl;
};
};
class UIStateAllEnabled : public UIState
{
public:
UIStateAllEnabled()
{
btn1 = true;
btn2 = true;
btn3 = true;
std::cout << btn1 << btn2 << btn3 << std::endl;
}
};
class UIStateAllDisabled : public UIState
{
public:
UIStateAllDisabled()
{
btn1 = false;
btn2 = false;
btn3 = false;
std::cout << btn1 << btn2 << btn3 << std::endl;
}
};
class UI
{
UIState * currentState;
public:
UI()
{
currentState = NULL;
}
~UI()
{
if (currentState != NULL)
{
delete currentState;
std::cout << "deleted current state" << std::endl;
}
}
void setState(UIState * new_state)
{
// should also check for if already current state?
UIState * prevState = currentState;
currentState = new_state;
if (prevState != NULL)
{
delete prevState;
std::cout << "deleted previous state" << std::endl;
}
}
void setBtn1State(bool new_state)
{
currentState->setBtn1State(new_state);
};
void setBtn2State(bool new_state)
{
currentState->setBtn2State(new_state);
};
void setBtn3State(bool new_state)
{
currentState->setBtn3State(new_state);
};
};
int main()
{
UI ui;
// enable all buttons
ui.setState(new UIStateAllEnabled);
// Now say you want to change state of a particular button within this state.
ui.setBtn1State(false);
ui.setBtn3State(false);
// switch to a completely new state, disable all buttons
ui.setState(new UIStateAllDisabled);
// customize within that sate
ui.setBtn3State(true);
return 0;
}
Related
I have created a simplified example of a state machine transitioning between 2 states on a button click (+ a checkbox showing the active state). I am seeing more calls to the transitions'eventTest method than I expect.
#include <QtStateMachine/QStateMachine>
#include <QtStateMachine/QSignalTransition>
#include <QtWidgets/QApplication>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QPushButton>
class MySignalTransition : public QSignalTransition
{
public:
MySignalTransition(QString name, QPushButton* button, QCheckBox* checkBox) :
QSignalTransition(button, &QAbstractButton::clicked),
cb(checkBox)
{
setObjectName(name);
}
protected:
bool eventTest(QEvent* e) override {
qDebug() << objectName() << "tested on" << e->type() << '|' << cb->isChecked();
return QSignalTransition::eventTest(e);
}
private:
QCheckBox* cb;
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(true);
QWidget w;
w.setFixedSize(200, 200);
QPushButton button(QStringLiteral("Click me"), &w);
QCheckBox checkbox(&w);
button.move(10, 80);
checkbox.move(10, 20);
QStateMachine m;
QState s1, s2;
m.addState(&s1);
m.addState(&s2);
m.setInitialState(&s1);
s1.assignProperty(&checkbox, "checked", false);
s2.assignProperty(&checkbox, "checked", true);
//button will be used to make the transition back and forth between states s1 and s2
MySignalTransition t1("Transition 1", &button, &checkbox), t2("Transition 2", &button, &checkbox);
t1.setTargetState(&s2);
t2.setTargetState(&s1);
s1.addTransition(&t1);
s2.addTransition(&t2);
w.show();
m.start();
return app.exec();
}
On top of the checkbox, I placed a qDebug() << ... to watch when Qt attempts to perform transitions.
The first attempt is right when the state machine starts, before I do any click on the button. I guess (but I am not sure) that this is expected.
The real problem happens when I actually do click on the button. I see:
eventTest is called from the starting state (event->type() == QEvent::None)
eventTest is called from the starting state (event->type() == QEvent::StateMachineSignal)
eventTest is called from the target state (event->type() == QEvent::None)
The one call I am most surprised about is point 1.
What causes the state machine to try the transition again when I click on the button with QEvent::None? This is especially surprising to me because it is always a transition the state machine has already tried (either on start or on the previous click).
If I were to create another transition out of s1 and/or s2, should I be worried that transition could have a higher priority than my signal transitions without any way for me to control it?
Edit
To address #Scheff's cat comment below, I changed the transition class to be:
class MySignalTransition : public QSignalTransition
{
public:
MySignalTransition(QString name, QPushButton* button, QCheckBox* checkBox) :
QSignalTransition(button, &QAbstractButton::clicked),
cb(checkBox)
{
setObjectName(name);
}
protected:
bool event(QEvent* e) override {
return false;
}
bool eventTest(QEvent* e) override {
qDebug() << objectName() << "tested on" << e->type() << '|' << cb->isChecked();
return transition;
}
private:
QCheckBox* cb;
public:
inline static bool transition = false;
};
and right after creating the push button, added:
QObject::connect(&button, &QPushButton::clicked, []() { MySignalTransition::transition = true; });
By doing that, the state machine lets me do 1 click before it starts an infinite loop.
When clicking, MySignalTransition::transition is set to true, then eventTest is called.
With a breakpoint, it is easy to see eventTest is never called with event->type() == QEvent::StateMachineSignal.
To me, this is a clear indication there is more to it than just probing.
First, it would need to do the probing with the actual event, not with a dummy event.
Second, it would not do the transition if the result of the probing was correct, I still should see the 2nd call to eventTest.
Last, I thought maybe the state machine tries to execute as many transitions as it can and will then pass the click signal to whatever other state it lands on.However, I am not very satisfied by that explanation because the attempt to execute an automatic transition is already done when entering the state (it is the third call to eventTest) and because I think my click should be applied on the state that is active at the very moment I do it, not on just "whatever other state it lands on".
I'm working on a new project and an implementing a basic scene change. I have the different scenes setup as their own classes, with the intialisation function being used to create and reposition different SFML objects. I saw this answer and have written my scene switcher similarly:
// Create scene monitoring variable
int scene[2];
scene[0] = 0; // Set current scene to menu
scene[1] = 0; // Set scene change to no
...
// Check for scene change
if(scene[1] == 0) {
// Run tick function based on current scene
switch(scene[0]) {
case 0:
// Main menu - run tick function
menu.tick();
}
}
if(scene[1] == 1) {
// Reset scene that you've changed to
switch(scene[0]) {
case 0:
// Main menu - reset it
menu = Menu(window, scene); // <-- Reinitialise menu here
}
// Set change variable to 0
scene[1] = 0;
}
You can see the full code on the github repository.
However, this doesn't seem to work properly - as soon as a scene change is made, the screen goes blank. The class is reintialised (I added a cout to check), the draw function is still run and mouse clicks are still processed, yet nothing appears in the window.
Am I doing something wrong here?
Doing things that way can lead into leak memory errors. I suggest you a different approach: the StateStack
How this works?
The basics of having a StateStack object is store each possible state of your game/app into a stack. This way, you can process each one in the stack order.
What is an State?
An State is something that can be updated, drawn and handle events. We can make an interface or an abstract class to make our screens behave like a State.
Which are the advantages?
With a stack structure, you can easily control how your different scenes are going to handle the three different processing methods. For instance. If you have a mouse click while you're in a pause menu, you won't that click event to reach the menu state or the "game" state. To achieve this, the solution is really easy, simply return false in your handleEvent method if you don't want the event go further this particular state. Note that this idea is also expandable to draw or update methods. In your pause menu, you won't update your "game" state. In your "game" state you won't draw tour menu state.
Example
With this points in mind, this is one possible way of implementation. First, the State interface:
class State{
public:
virtual bool update() = 0;
virtual bool draw(sf::RenderTarget& target) const = 0;
// We will use a vector instead a stack because we can iterate vectors (for drawing, update, etc)
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack) = 0;
};
Following this interface we can have a example MenuState and PauseState:
MenuState
class MenuState : public State{
public:
MenuState(){
m_count = 0;
m_font.loadFromFile("Roboto-Regular.ttf");
m_text.setFont(m_font);
m_text.setString("MenuState: " + std::to_string(m_count));
m_text.setPosition(10, 10);
m_text.setFillColor(sf::Color::White);
}
virtual bool update() {
m_count++;
m_text.setString("MenuState: " + std::to_string(m_count));
return true;
}
virtual bool draw(sf::RenderTarget &target) const{
target.draw(m_text);
return true;
}
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
if (e.type == sf::Event::KeyPressed){
if (e.key.code == sf::Keyboard::P){
stack.push_back(new PauseState());
return true;
}
}
return true;
}
private:
sf::Font m_font;
sf::Text m_text;
unsigned int m_count;
};
PauseState
class PauseState : public State{
public:
PauseState(){
sf::Font f;
m_font.loadFromFile("Roboto-Regular.ttf");
m_text.setFont(m_font);
m_text.setString("PauseState");
m_text.setPosition(10, 10);
m_text.setFillColor(sf::Color::White);
}
virtual bool update() {
// By returning false, we prevent States UNDER Pause to update too
return false;
}
virtual bool draw(sf::RenderTarget &target) const{
target.draw(m_text);
// By returning false, we prevent States UNDER Pause to draw too
return false;
}
virtual bool handleEvent(sf::Event e, std::vector<State*> &stack){
if (e.type == sf::Event::KeyPressed){
if (e.key.code == sf::Keyboard::Escape){
stack.pop_back();
return true;
}
}
return false;
}
private:
sf::Font m_font;
sf::Text m_text;
};
By the way, while I was doing this, I notice that you must have the fonts as an attribute of the class in order to keep the reference. If not, when your text is drawn, its font is lost ant then it fails. Another way to face this is using a resource holder, which is much more efficient and robust.
Said this, our main will look like:
Main
int main() {
// Create window object
sf::RenderWindow window(sf::VideoMode(720, 720), "OpenTMS");
// Set window frame rate
window.setFramerateLimit(60);
std::vector<State*> stack;
// Create menu
stack.push_back(new MenuState());
// Main window loops
while (window.isOpen()) {
// Create events object
sf::Event event;
// Loop through events
while (window.pollEvent(event)) {
// Close window
if (event.type == sf::Event::Closed) {
window.close();
}
handleEventStack(event, stack);
}
updateStack(stack);
// Clear window
window.clear(sf::Color::Black);
drawStack(window, stack);
// Display window contents
window.display();
}
return 0;
}
The stack functions are simple for-loop but, with the detail that iterate the vector backwards. This is the way to imitate that stack behavior, starting from top (size-1 index) and ending at 0.
Stack functions
void handleEventStack(sf::Event e, std::vector<State*> &stack){
for (int i = stack.size()-1; i >=0; --i){
if (!stack[i]->handleEvent(e, stack)){
break;
}
}
}
void updateStack(std::vector<State*> &stack){
for (int i = stack.size() - 1; i >= 0; --i){
if (!stack[i]->update()){
break;
}
}
}
void drawStack(sf::RenderTarget &target, std::vector<State*> &stack){
for (int i = stack.size() - 1; i >= 0; --i){
if (!stack[i]->draw(target)){
break;
}
}
}
You can learn more about StateStacks and gamedev in general with this book
I am trying to write a program in gtkmm but the buttons will not show up. I've done everything I know to make these buttons show, but nothing has been working. I have even included the 'show all' methods in both the main and win_home.cpp files but still nothing happens. The program DOES however go through the code, as the cout statements are all being printed. Does anyone have any idea why these buttons would not be showing up?
main.cpp:
#include <gtkmm.h>
#include <iostream>
#include "win_home.h"
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "com.InIT.InITPortal");
std::cout << "Creating Portal Window" << std::endl;
HomeGUI win_home;
win_home.set_default_size(600,400);
win_home.set_title("St. George InIT Home");
return app->run(win_home);
}
win_home.cpp:
#include "win_home.h"
HomeGUI::HomeGUI()
{
//build interface/gui
this->buildInterface();
//show_all_children();
//register Handlers
//this->registerHandlers();
}
HomeGUI::~HomeGUI()
{
}
void HomeGUI::buildInterface()
{
std::cout << "Building Portal Interface" << std::endl;
m_portal_rowbox = Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 5);
add(m_portal_rowbox);
Gtk::Button m_pia_button = Gtk::Button("Printer Install Assistant");
m_portal_rowbox.pack_start(m_pia_button, false, false, 0);
m_pia_button.show();
Gtk::Button m_inventory_button = Gtk::Button("Inventory");
m_inventory_button.show();
m_portal_rowbox.pack_start(m_inventory_button, false, false, 0);
m_inventory_button.show();
//add(m_portal_rowbox);
//m_portal_rowbox.show_all();
m_portal_rowbox.show();
this->show_all_children();
std::cout << "Completed Portal Interface" << std::endl;
return;
}
void HomeGUI::registerHandlers()
{
}
In void HomeGUI::buildInterface() you have constructed 2 buttons and they are added it to your box container. When the function returns the buttons are destroyed as they are now out of scope. Since they no longer exist they can not be visible.
So for you first button you would use something like this:
Gtk::Button * m_pia_button = Gtk::manage(
new Gtk::Button("Printer Install Assistant"));
m_portal_rowbox.pack_start(&m_pia_button, false, false, 0);
m_pia_button.show();
I expect you would need easy access to your buttons throughout the life time of your window. The easiest way is to have the buttons as a member of your class. It will be constructed as an empty button and you just need to set the label afterwards.
class HomeGUI {
....
// A button (empty)
Gtk::Button m_pia_button;
....
};
....
void HomeGUI::buildInterface()
{
....
m_pia_button.set_label("Printer Install Assistant");
m_portal_rowbox.pack_start(m_pia_button, false, false, 0);
m_pia_button.show();
....
}
I have searched far and wide and thought different options for quite a while, and am now absolutely stumped. I created a simple class that makes 16 buttons and assigns IDs to them in the constructor. I would like each of the buttons to have an event triggered when clicked.
Class in the header:
class step16
{
///signals and buttons
private:
wxButton* sequencer [16];
long* ids = new long[16];
public:
step16(wxFrame* frame);
~step16();
};
Declaration of the functions in the source file:
///constructor for 16 step sample sequencer class
step16::step16(wxFrame* frame)
{
///clear all signals on initialization and create buttons
for(int i = 0; i < 16; i++){
ids [i] = wxNewId();
sequencer[i] = new wxButton(frame,ids[i],wxString::Format(_("")),
wxPoint(i*30 , 0,wxSize(30,20) );
}
}
///destructor for the 16 step sequencer class
step16::~step16(){delete[]signals;}
The only way I know how to add click events to buttons in wxWidgets is using the Connect() method in the initialization part of the Main wxFrame, but connecting them in that part of the program would not bring the desired results. Mainly because I need a new set of 16 buttons with unique IDs and events in every instance of the step16 class. How would I go about adding unique click events to each of these buttons?
You can use Bind to bind a handler in any class that is derived from wxEventHandler (i.e. just about any standard wxWidgets class, including wxFrame).
Pass the ID of the button to the Bind() call so your event handler knows which button has been pressed.
For example your step16 constructor could look like this:
///constructor for 16 step sample sequencer class
step16::step16(wxFrame* frame)
{
///clear all signals on initialization and create buttons
for(int i = 0; i < 16; i++)
{
ids [i] = wxNewId();
sequencer[i] = new wxButton(frame,ids[i],wxString::Format(_("")),
wxPoint(i*30,0), wxSize(30,20));
/// Add it to something so I can test this works!
frame->GetSizer()->Add(sequencer[i]);
/// Bind the clicked event for this button to a handler
/// in the Main Frame.
sequencer[i]->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
&MainFrame::OnPress,
(MainFrame*)frame);
}
}
In this example, I have created the event handler in the MainFrame class, a pointer to an instance of which is passed to ctor for step16.
You can distinguish between the button presses using event.GetId() which will be the value set by the line:
ids [i] = wxNewId();
The MainFrame::OnPress method could look like this:
void MainFrame::OnPress(wxCommandEvent& event)
{
long firstID = *theStep16->GetIDs();
switch(event.GetId() - firstID)
{
case 0:
std::cout << "First button" << std::endl;
break;
case 1:
std::cout << "Second button" << std::endl;
break;
default:
std::cout << "One of the other buttons with ID "
<< event.GetId() << std::endl;
}
}
I have two menu items. When item 1 is disabled, I want item 2 to be disabled as well. In the OnUpdate handler of menu item 1, I have tried to use "t_pMenu = pCmdUI->m_pMenu;", "t_pMenu = pCmdUI->m_pSubMenu;" and "t_pMenu = pCmdUI->m_pParentMenu;" but I always get NULL t_pMenu. How can I achieve this purpose?
void CDummyView::OnUpdateMenuItem1(CCmdUI* pCmdUI)
{
if(m_bShowMenuItem1)
{
pCmdUI->SetText("Hide Features")
CMenu * t_pMenu = pCmdUI->m_pSubMenu;
if(t_pMenu != NULL)
t_pMenu->EnableMenuItem(ID_MENU_ITEM2, MF_ENABLED);
}
else
{
pCmdUI->SetText("Show Features")
CMenu * t_pMenu = pCmdUI->m_pParentMenu;
if(t_pMenu != NULL)
t_pMenu->EnableMenuItem(ID_MENU_ITEM2, MF_GRAYED);
}
}
void CDummyView::OnUpdateMenuItem2(CCmdUI* pCmdUI)
{
...
}
Never handle to different command IDs in one handler.
Each handler is called more than once if there are buttons and menu items. Also you don't know the sequence. When you alter the item2 in Item1 handler it may be enabled again when the handler for Item2 is called later.
When you have a flag named m_bShowMenuItem1 just use it.
void CDummyView::OnUpdateMenuItem1(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bShowMenuItem1);
}
void CDummyView::OnUpdateMenuItem2(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bShowMenuItem1);
}
I got it work. Below is the code I tried. Two flag variables m_bShowFeatures and m_bShowSmallFetures are initialized to be TRUE.
void CDummyView::OnMenuItem1()
{
m_bShowFeatures = !m_bShowFeatures;
m_pDoc->UpdateAllViews(NULL, SHOW_HIDE_ALL_FEATURES);
}
void CDummyView::OnUpdateMenuItem1(CCmdUI* pCmdUI)
{
if(m_bShowFeatures)
pCmdUI->SetText("Hide Features")
else
pCmdUI->SetText("Show Features")
}
void CDummyView::OnMenuItem2()
{
m_bShowSmallFetures= !m_bShowSmallFetures;
m_pDoc->UpdateAllViews(NULL, SHOW_HIDE_SMALL_FEATURES);
}
void CDummyView::OnUpdateMenuItem2(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bShowFetures)
if(m_bShowSmallFetures)
pCmdUI->SetText("Hide Small Features")
else
pCmdUI->SetText("Show Small Features")
}
So OnUpdateMenuItem2() does get invoked after OnMenuItem1() is called when Menu Item 1 is clicked. I didn't expect that.