QT - Problems with switching .ui

137 Views Asked by At

My problem is that i found a way to switch UIs. But when it switch the UIs the .cpp of the UI will not load.

mainmenu.cpp

#include "mainmenu.h"
#include "ui_mainmenu.h"

MainMenu::MainMenu(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainMenu),
    newgame(new Ui::PlayerMenu),
    optionmenu(new Ui::OptionMenu)
{
    ui->setupUi(this);
    QPixmap background("../../res/Testbg.png");
    background = background.scaled(this->size(), Qt::IgnoreAspectRatio);
    QPalette palette;
    palette.setBrush(QPalette::Background, background);
    this->setPalette(palette);
}

MainMenu::~MainMenu()
{
    delete ui;
}

void MainMenu::on_pushButtonNewGame_clicked()
{
   changeAppearance(1);
}

void MainMenu::on_pushButtonOption_clicked()
{
   changeAppearance(2);
}

void MainMenu::changeAppearance(int id)
{

    if(id == 0)
    {
        ui->setupUi(this);
    }
    else if(id == 1)
    {
        newgame->setupUi(this);
    }
    else if(id ==2)
        optionmenu->setupUi(this);
}

mainmenu.h

#ifndef MAINMENU_H
#define MAINMENU_H

#include <QMainWindow>
#include "playermenu.h"
#include "optionmenu.h"

namespace Ui {
class MainMenu;
}

class MainMenu : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainMenu(QWidget *parent = 0);
    ~MainMenu();


private slots:
    void on_pushButtonNewGame_clicked();

    void on_pushButtonOption_clicked();

private:
    void changeAppearance(int id);


    Ui::MainMenu *ui;
    Ui::PlayerMenu *newgame;
    Ui::OptionMenu *optionmenu;
};

#endif // MAINMENU_H

playermenu.cpp

 #include "playermenu.h"
 #include "ui_playermenu.h"

PlayerMenu::PlayerMenu(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::PlayerMenu),
    levelmenu(new Ui::LevelMenu)
{
    ui->setupUi(this);
    QPixmap background("../../res/Testbg.png");
       background = background.scaled(this->size(),Qt::IgnoreAspectRatio);
       QPalette palette;
       palette.setBrush(QPalette::Background, background);
       this->setPalette(palette);
}
...

playermenu.h

#ifndef PLAYERMENU_H
#define PLAYERMENU_H

#include <QMainWindow>
#include <ui_playermenu.h>
#include "levelmenu.h"

namespace Ui {
class PlayerMenu;
}

class PlayerMenu : public QMainWindow, Ui::PlayerMenu
{
    Q_OBJECT

public:
    explicit PlayerMenu(QWidget *parent = 0);
    ~PlayerMenu();
...
private:
    Ui::PlayerMenu *ui;
    Ui::LevelMenu *levelmenu;
};

#endif // PLAYERMENU_H

I'm new to QT so i don't really know if this is the right way to do this. Does anyone have a clue where the Problem is or if there is a work around?

2

There are 2 best solutions below

0
docsteer On BEST ANSWER

It sounds like you want to have a single window which switches between different states. I wouldn't recommend using multiple .ui files to do this.. a couple of better ways might be:

  1. Use a QStackedWidget - you can add this in the UI designer, think of it like a set of pages which you select programatically. Use that and have your buttons change it to the appropriate page.

  2. Have multiple different classes for your different views, and set the central widget of the main window to different widgets when needed.

Personally, I would go for option 1.

0
Kuba hasn't forgotten Monica On

The classes in the Ui namespace are generated by uic and are meant to construct the widget hierarchy on an empty widget with no layout. By attempting your swap, you're abusing that code to do what it was never meant to.

To make your approach work, you'd have to first get rid of all the objects installed by the previous setupUi call. This is workable, but problematic - since there's no easy way to enumerate all the objects in the ui structure itself, you have to resort to iterating the laid-out children of your widget - and then there's no general way to know if those children originate from your other code, or from generated code.

Also, all of the user interfaces must be designed on top of a QMainWindow. Swapping out for an interface based on another widget type won't work, since the base widget is a QMainWindow that needs a centralWidget. It's a hack, but it works:

// https://github.com/KubaO/stackoverflown/tree/master/questions/ui-swap-42416275
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif

// mock uic output
namespace Ui {
struct MainMenu { void setupUi(QMainWindow*) {} };
struct PlayerMenu { void setupUi(QMainWindow*) {} };
struct OptionMenu { void setupUi(QMainWindow*) {} };
}

class MainMenuHack : public QMainWindow {
   Q_OBJECT
   enum class UiKind { MainMenu, PlayerMenu, OptionMenu };
   Ui::MainMenu uiMainMenu;
   Ui::PlayerMenu uiPlayerMenu;
   Ui::OptionMenu uiOptionMenu;
   void clearLayout(QLayout * layout) {
      if (!layout) return;
      while (layout->count()) {
         QScopedPointer<QLayoutItem> item{layout->takeAt(0)};
         if (!item)
            continue;
         delete item->widget();
         clearLayout(item->layout());
      }
   }
public:
   MainMenuHack(QWidget * parent = {}, Qt::WindowFlags flags = {}) :
      QMainWindow{parent, flags}
   {
      setAppearance(UiKind::MainMenu);
   }
   void setAppearance(UiKind kind) {
      clearLayout(layout());
      switch (kind) {
      case UiKind::MainMenu: return uiMainMenu.setupUi(this);
      case UiKind::PlayerMenu: return uiPlayerMenu.setupUi(this);
      case UiKind::OptionMenu: return uiOptionMenu.setupUi(this);
      }
   }
};

Notes: 1. Hold the ui objects by value, not by pointer. The additional indirection is useless. 2. Use strongly typed enums instead of magic constants to denote choices.

Alas, we don't need to resort to hacks. We can use QStackedWidget to swap the visible panes.

First, let's make a UiWidget class that wraps a given Ui:: type and its corresponding widget. It automatically sets up the children on the widget, adds itself to a stacked widget parent, and has a helper that sets it as a current widget in the stack.

template <typename Ui>
struct ui_traits : ui_traits<decltype(&Ui::setupUi)> {};
template <typename Ui, typename Widget>
struct ui_traits<void(Ui::*)(Widget*)> {
   using widget_type = Widget;
};
template <typename Ui, typename Widget = typename ui_traits<Ui>::widget_type>
struct UiWidget : Widget, Ui {
   UiWidget(QWidget * parent = {}) : Widget{parent} { this->setupUi(this); }
   UiWidget(QStackedWidget * parent) : UiWidget{static_cast<QWidget*>(parent)} {
      parent->addWidget(this);
   }
   void setCurrent() {
      auto stack = qobject_cast<QStackedWidget*>(this->parent());
      if (stack) stack->setCurrentWidget(this);
   }
};

Now each Ui class can be based on a different widget type, e.g. Ui::MainMenu can be based on QMainWindow but e.g. Ui::OptionMenu can be based on a QDialog.

The MainMenu can now simply be a QStackedWidget that contains the sub-widgets with their ui structures:

class MainMenu : public QStackedWidget {
   Q_OBJECT
   enum class UiKind { MainMenu, PlayerMenu, OptionMenu };
   UiWidget<Ui::MainMenu> uiMainMenu{this};
   UiWidget<Ui::PlayerMenu> uiPlayerMenu{this};
   UiWidget<Ui::OptionMenu> uiOptionMenu{this};
public:
   MainMenu(QWidget * parent = {}, Qt::WindowFlags flags = {}) :
      QStackedWidget{parent}
   {
      setWindowFlags(flags);
      setAppearance(UiKind::MainMenu);
   }
   void setAppearance(UiKind kind) {
      switch (kind) {
      case UiKind::MainMenu: return uiMainMenu.setCurrent();
      case UiKind::PlayerMenu: return uiPlayerMenu.setCurrent();
      case UiKind::OptionMenu: return uiOptionMenu.setCurrent();
      }
   }
};

In both MainMenu and MainMenuHack, the uiFoo members are-a their respective Ui::Class, e.g. uiMainMenu is-a Ui::MainMenu.

At this point, if the setAppearance can be made a non-public method, you don't need any indirection and can operate on uiFoo members directly: replace any setAppearance(UiKind::Foo) with uiFoo.setCurrent():

class MainMenu : public QStackedWidget {
   Q_OBJECT
   UiWidget<Ui::MainMenu> uiMainMenu{this};
   UiWidget<Ui::PlayerMenu> uiPlayerMenu{this};
   UiWidget<Ui::OptionMenu> uiOptionMenu{this};
public:
   MainMenu(QWidget * parent = {}, Qt::WindowFlags flags = {}) :
      QStackedWidget{parent}
   {
      setWindowFlags(flags);
      uiMainMenu.setCurrent();
   }
};

The code works under both Qt 5 and Qt 4.