Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Moving all my buttons to a frame?
Forum Updated to NodeBB v4.3 + New Features

Moving all my buttons to a frame?

Scheduled Pinned Locked Moved Solved General and Desktop
12 Posts 4 Posters 4.8k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    anarelle
    wrote on last edited by
    #1

    In order to learn Qt I have created an application using the GUI wizard from Qt Creator. That is, I have a QMainWindow and a *Ui::MainWindow ui; object that were automatically added for me. A header, source and designer files have been created as well.
    I've been working on my application for a while, added 12 buttons that I manually set up in a grid-style (I mean, I manually dragged & dropped the QPushButtons and created a sort of 3x4 buttons matrix).
    Since my application is a (very simple) game, I enable and disable some of the buttons as the game progresses. But now I found out that at at specific point in the execution I need to disable all 12 buttons and then re-enable only the ones that were enabled before I disabled all of them.
    I was told that, instead of keeping track of each button state, I could create a QFrame to contain all buttons and then disable the QFrame when needed, and after reenabling the QFrame my buttons would retain their original state.
    However, I'm struggling really hard to do this. I thought I should just add a QFrame, somehow add the buttons to it and then call frame.setEnable(false) and frame.setEnable(true). This seems not to be the case.
    I think I need some sort of layout and a central widget. But I think I already have those, having created my application through the IDE wizard?
    I tried adding this to the header file:

    QGridLayout* layout();
    

    Then on the designer I dropped a QFrame on my form (my main window, as my application only has one window).
    Then on the source file I added my 12 buttons to the layout:

    layout->addWidget(btn01);
    layout->addWidget(btn02);
    etc
    

    And set the layout to the frame:

    ui->frame->setLayout(layout);
    

    However, this doesn't even compile. I get these errors:

    invalid use of member (did you forget the '&' ?)
         layout->addWidget(btn01);
               ^
    
    base operand of '->' is not a pointer
    
    'btn01' was not declared in this scope
         layout->addWidget(btn01);
                           ^
    

    I've spent quite a few hours trying to understand how to do it, with no luck. All I want is to add my buttons to a container with the ability to be enabled/disabled (along with its contained widgets) and that the widgets within it retain their individual state. Is there a simpler way to achieve this?

    Thanks!

    1 Reply Last reply
    0
    • mrjjM Offline
      mrjjM Offline
      mrjj
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi
      You say you added
      QGridLayout* layout();
      to the .h file but that would be a function returning a QGridLayout pointer.
      did you mean
      QGridLayout* layout;
      with
      layout = new QGridLayout()
      ?

      Anyway,
      no need to add layout from code.
      On the ui->frame, in Designer, place one button on the frame. Right click outside
      button and select a layout. Then delete button. Frame will keep its layout.
      However, the one should think that ui->frame->layout() would
      be the way to address the layout but Creator do add
      a direct variable. so its ui->verticalLayout or similar.
      alt text

      So now from code simply add to this layout.

      Im not sure with the errors as you are not showing the full source.
      "'btn01' was not declared in this scope" means it dont know the button.
      maybe its ui->btn01 but its hard to guess at.

      1 Reply Last reply
      5
      • A Offline
        A Offline
        anil_arise
        wrote on last edited by
        #3

        @anarelle said in Moving all my buttons to a frame?:

        layout->addWidget(btn01);

        syntax error..

        use of gridlayout: SYNTAX
        layout->addWidget(btn01,0,0);
        layout->addWidget(btn02,0,1);

        1 Reply Last reply
        0
        • Maaz MominM Offline
          Maaz MominM Offline
          Maaz Momin
          wrote on last edited by
          #4

          @anarelle can you paste your ui_MainWindow.h code?

          1 Reply Last reply
          1
          • A Offline
            A Offline
            anarelle
            wrote on last edited by anarelle
            #5

            Thanks for your answers :)
            I have my game almost ready, so I'll post the relevant parts of the code.

            It's memory game where 12 tiles display on screen, then you click on two and try to match the images they show. My tiles are implemented as QPushButtons. Each one is clicked, an image is displayed (by calling a showImage() method that changes the button background). When a second tile is clicked, if there is a match the two buttons are disabled so user can't click on them again (and score increases). If there was no match, the two tiles go back to their initial state (no background) after 1 second.

            Also, when the first "tile" (button) is clicked I set enabled to false until a second button is clicked. If there is no match, then both buttons get their blank background again and they are setEnabled(true). I'm using a single shot QTimer to reset both tiles after a no-match.

            There is also a countdown for 1 minute, implemented as another QTimer. If the countdown reaches 0, then a message is displayed saying game is over. If after matching 2 tiles the match count is 6 (as there are 12 tiles), then a message shows saying the total score reached.

            This is what I have on mainwindow.h:

            namespace Ui {
                class MainWindow;
            }
            
            class MainWindow : public QMainWindow{
                Q_OBJECT
            
            
            public:
                explicit MainWindow(QWidget *parent = nullptr);
                ~MainWindow();
                QTimer *timer=new QTimer();
                QTime time{0,1,0};
                QVector<QString> tiles{"btn01", "btn02", "btn03", "btn04",
                                          "btn05", "btn06", "btn07", "btn08",
                                          "btn09", "btn10", "btn11", "btn12"};
                QHash<QString, QString> hash;
                int score=0;
                bool turnStarted=false;
                QPushButton* previousTile;
                QPushButton* currentTile;
                int pairsLeft=6;
                QMessageBox msgBox;
             
            
            private slots:
                void updateCountDown();
                void tileClicked();
                void randomize(QVector<QString> &tiles);
                void bind(QVector<QString> &tiles, QHash<QString, QString> &hash);
                void checkPartialResult();
                void turnTilesBack();
                void showBackground();
                void checkFinalResult();
                void updateCountDown();
            
            private:
                Ui::MainWindow *ui;
            };
            

            And this is part of mainwindow.cpp:

            MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){
                ui->setupUi(this);
            
                //Set up countdown
                connect(timer, SIGNAL(timeout()), this, SLOT(updateCountDown()));
                timer->start(1000);
                ui->countdown->setText(time.toString("m:ss"));
            
                //Button names are stored in a QVector, so they need to be sort randomly. After that, grab pairs of tiles and bind the name of an image file to each pair. Store this in a QHash where each key is a button name and its value is the image it should show when that button is clicked.
                randomize(tiles);
                bind(tiles, hash);
            
                //Connect each button to the same slot, which will figure out which button was pressed and show its associated image file accordingly
                connect(ui->btn01, SIGNAL(clicked()), this, SLOT(tileClicked()));
                connect(ui->btn02, SIGNAL(clicked()), this, SLOT(tileClicked()));
                //etc (all 12 tiles are set the same way)
            

            I won't post all my methods as it would be too long, but this is where I'm trying to make changes: this is the method called every time a second tile is clicked, to find out if there was a match:

            void MainWindow::definirResultadoParcial(){
                //check if there is a match (the current tile matches the previous tile in the turn)
                if (hash[currentTile->objectName()]==hash[previousTile->objectName()]){
                    score+=15;
                    pairsLeft--;
            
                    //if there is a match, find out if all 12 tiles have been matched.
                    checkFinalResult();
                }
                else{
                    score-=5;
            
                    //if there is no match, after 1 second turn back both tiles from current turn so they can be used in future turns
                    QTimer::singleShot(1000, this, SLOT(turnTilesBack()));
                }
            }
            

            When there is no match, I need the mis-matched tiles to remain on screen for 1 second and then turn them back (remove their backgrounds). However, as the QTimer seems to be running on a different thread, this makes it possible for the user to continue clicking on tiles, before the singleShot timer has timed out.

            So, when a second button is clicked and there is no match, I need to disable all my buttons for 1 second and then re-enable only the ones that hadn't been matched yet. That's why I'm trying to set up my buttons inside some sort of container that I can disable and re-enable without losing my buttons state.

            However, I didn't use any layouts or anything. I only dragged & dropped buttons on my form. So I don't have any layouts. Are they absolutely necessary? I'd actually like to avoid messing with my buttons if possible. What's the simplest way to achieve what I'm trying to do?

            Thanks again :)

            1 Reply Last reply
            0
            • mrjjM Offline
              mrjjM Offline
              mrjj
              Lifetime Qt Champion
              wrote on last edited by mrjj
              #6

              Hi
              Fist of all, layouts are only needed if you wish/need your tile area to be able to follow size of the Mainwindow so
              if user resize it, it can use more or less space. If a fixed size is ok, you dont need layout as such.
              However, its very common to do as screen can have many different resolutions. Especially laptops.

              Regarding, preventing clicking while 1 sec "timeout". The QTimer do not block the GUI tread and hence
              user can still click stuff.

              But you dont really need container to disable them.
              Just add a QList<QPushButtons *> buttonsList; to mainwindow.h
              then when you connect them, also append to this list.
              buttonsList.append(ui->btn01) etc.

              Then before
              QTimer::singleShot(1000, this, SLOT(turnTilesBack()));
              you simply loop over the buttonsList and disable each of them.
              Using a function like
              setEnableForAll(bool state)
              that loops the list, allows you to easy call with true or false to enable or disable on when needed.

              A 1 Reply Last reply
              1
              • mrjjM mrjj

                Hi
                Fist of all, layouts are only needed if you wish/need your tile area to be able to follow size of the Mainwindow so
                if user resize it, it can use more or less space. If a fixed size is ok, you dont need layout as such.
                However, its very common to do as screen can have many different resolutions. Especially laptops.

                Regarding, preventing clicking while 1 sec "timeout". The QTimer do not block the GUI tread and hence
                user can still click stuff.

                But you dont really need container to disable them.
                Just add a QList<QPushButtons *> buttonsList; to mainwindow.h
                then when you connect them, also append to this list.
                buttonsList.append(ui->btn01) etc.

                Then before
                QTimer::singleShot(1000, this, SLOT(turnTilesBack()));
                you simply loop over the buttonsList and disable each of them.
                Using a function like
                setEnableForAll(bool state)
                that loops the list, allows you to easy call with true or false to enable or disable on when needed.

                A Offline
                A Offline
                anarelle
                wrote on last edited by
                #7

                @mrjj Thanks :) maybe that's the best way to go, as I don't need to support different resolutions. I thought adding widgets to a container and then enabling/disabling the container would be easier, but it would actually require too many changes.

                mrjjM 1 Reply Last reply
                0
                • A anarelle

                  @mrjj Thanks :) maybe that's the best way to go, as I don't need to support different resolutions. I thought adding widgets to a container and then enabling/disabling the container would be easier, but it would actually require too many changes.

                  mrjjM Offline
                  mrjjM Offline
                  mrjj
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  @anarelle
                  well it should not BE super complicated to place QFrame on the form
                  and select all buttons and drag to the QFrame. and then let it handle enable/disable
                  for its children.
                  However, i do wonder how you reset the images on the buttons.
                  Dont you loop the buttons somehow there too ?

                  A 1 Reply Last reply
                  1
                  • mrjjM mrjj

                    @anarelle
                    well it should not BE super complicated to place QFrame on the form
                    and select all buttons and drag to the QFrame. and then let it handle enable/disable
                    for its children.
                    However, i do wonder how you reset the images on the buttons.
                    Dont you loop the buttons somehow there too ?

                    A Offline
                    A Offline
                    anarelle
                    wrote on last edited by
                    #9

                    @mrjj As for your question, I only have to reset 2 images on each turn (when the user unveils the second image, if it doesn't match with the first image then I remove the background on both):

                    void MainWindow::turnTilesBack(){
                        //return tiles from current turn to the default state (remove backgrounds)
                        previousTile->setStyleSheet("#" + previousTile->objectName() + "{ }");
                        currentTile->setStyleSheet("#" + currentTile->objectName() + "{ }");
                    
                        //re-enable both tiles so they can be used on another turn
                        currentTile->setEnabled(true);
                        previousTile->setEnabled(true);
                    }
                    

                    This only happens when there's no match. If there's a match then I only disable the buttons but the backgrounds are not changed, so the user can clearly see which tiles are left. So I don't have to keep the state of each button. I manage it on each turn. I was trying to avoid having to keep the state of the buttons (although I could do that by just adding the disabled ones to a vector), that's why I was trying to add the QFrame.

                    So in order to add a QFrame and add the buttons to it I shouldn't need a layout at all? That's where I was getting confused. I'll try adding the buttons to the QFrame directly and let you know how it turns out :)

                    mrjjM 1 Reply Last reply
                    0
                    • A anarelle

                      @mrjj As for your question, I only have to reset 2 images on each turn (when the user unveils the second image, if it doesn't match with the first image then I remove the background on both):

                      void MainWindow::turnTilesBack(){
                          //return tiles from current turn to the default state (remove backgrounds)
                          previousTile->setStyleSheet("#" + previousTile->objectName() + "{ }");
                          currentTile->setStyleSheet("#" + currentTile->objectName() + "{ }");
                      
                          //re-enable both tiles so they can be used on another turn
                          currentTile->setEnabled(true);
                          previousTile->setEnabled(true);
                      }
                      

                      This only happens when there's no match. If there's a match then I only disable the buttons but the backgrounds are not changed, so the user can clearly see which tiles are left. So I don't have to keep the state of each button. I manage it on each turn. I was trying to avoid having to keep the state of the buttons (although I could do that by just adding the disabled ones to a vector), that's why I was trying to add the QFrame.

                      So in order to add a QFrame and add the buttons to it I shouldn't need a layout at all? That's where I was getting confused. I'll try adding the buttons to the QFrame directly and let you know how it turns out :)

                      mrjjM Offline
                      mrjjM Offline
                      mrjj
                      Lifetime Qt Champion
                      wrote on last edited by
                      #10

                      @anarelle
                      Hi
                      Ah, so its only currentTile, previousTile you change image on so no loop.
                      Yes, you can also add buttons to a QFrame without a layout.
                      Its only required to use a layout if size of container changes and you wants its sub widgets to adjust to new size.
                      For your use case (disable all with parent/container) a layout is not needed.

                      A 1 Reply Last reply
                      0
                      • mrjjM mrjj

                        @anarelle
                        Hi
                        Ah, so its only currentTile, previousTile you change image on so no loop.
                        Yes, you can also add buttons to a QFrame without a layout.
                        Its only required to use a layout if size of container changes and you wants its sub widgets to adjust to new size.
                        For your use case (disable all with parent/container) a layout is not needed.

                        A Offline
                        A Offline
                        anarelle
                        wrote on last edited by
                        #11

                        @mrjj Yup, it was definitely easier without a layout. I just added a QFrame using the designer, selected all my buttons and dragged them onto the QFrame, then called frame->setEnabled(false) and frame->setEnabled(true) when needed and that was it. My individual buttons state isn't affected every time the frame state changes.

                        Thanks so much for everyone's help!

                        mrjjM 1 Reply Last reply
                        1
                        • A anarelle

                          @mrjj Yup, it was definitely easier without a layout. I just added a QFrame using the designer, selected all my buttons and dragged them onto the QFrame, then called frame->setEnabled(false) and frame->setEnabled(true) when needed and that was it. My individual buttons state isn't affected every time the frame state changes.

                          Thanks so much for everyone's help!

                          mrjjM Offline
                          mrjjM Offline
                          mrjj
                          Lifetime Qt Champion
                          wrote on last edited by
                          #12

                          @anarelle
                          Hi if issue is resolved, please mark the post Solved.

                          1 Reply Last reply
                          1

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved