Switching layout on button click Qt

I have a simple GUI with a label and a button. But when the button is clicked I want the label and button removed to show a new label with other text.

This to emulate different 'pages' in my GUI application. I have read about StackedLayout etc but I don't understand how to implement that in Rust. In this C++ documentation they use one QStackedLayout and multiple widgets, each widget representing a different page. But I can't do that in Rust because I can only have one widget because it needs to be upcasted etc (lifetimes issues and stuff).

I tried overriding the layout the widget uses on line 76, but that doesn't work. Compiles though.
I have no clue in how to do it. I am using the Qt-rust binding 0.5.0.

This is my code:

#![windows_subsystem = "windows"]

//Importing the required stuff
use cpp_core::{Ptr, Ref, StaticUpcast};
use qt_core::{qs, slot, ContextMenuPolicy, QBox, QObject, QPoint, SlotNoArgs, QString};
use qt_widgets::{
    QAction, QApplication, QLineEdit, QMenu, QMessageBox, QPushButton, QLabel, QTableWidget,
    QTableWidgetItem, QVBoxLayout, QWidget, SlotOfQPoint, SlotOfQTableWidgetItemQTableWidgetItem,
};
use std::rc::Rc;

//Struct for Main window
struct MainWindow {
    widget: QBox<QWidget>,
    button: QBox<QPushButton>,
}

//From form-struct to base-struct ??? 
impl StaticUpcast<QObject> for MainWindow {
    unsafe fn static_upcast(ptr: Ptr<Self>) -> Ptr<QObject> {
        ptr.widget.as_ptr().static_upcast()
    }
}

//Implementing functions for MainWindow
impl MainWindow {
    //Defining all the graphical elements in MainWindow
    //Returns the MainWindow in a Reference Counted Smart Pointer so it can have multiple owners
    //and doesn't get destroyed until it has no owners anymore
    fn new() -> Rc<MainWindow> {
        unsafe {
            //Widget
            let widget = QWidget::new_0a();
            let layout = QVBoxLayout::new_1a(&widget);

            //Label
            let label = QLabel::new();
            label.set_text(&qs("Label A!"));
            layout.add_widget(&label);

            //Button
            let button = QPushButton::from_q_string(&qs("Click me."));
            layout.add_widget(&button);

            widget.show();

            let this = Rc::new(Self {
                widget,
                button,
            });
            this.init();
            this
        }
    }

    //Binding functions/slots to elements etc
    unsafe fn init(self: &Rc<Self>) {
        self.button
            .clicked()
            .connect(&self.slot_build_option_a());
    }

    //Slots/Functions for buttons
    #[slot(SlotNoArgs)]
    unsafe fn build_option_a(self: &Rc<Self>) {
        /*
        while (self.widget.layout().count() > 0) {
            self.widget.layout().remove_item(
                self.widget.layout().take_at(0)
            );

            println!("{}", "Test 123...");
        }*/

        let layout = QVBoxLayout::new_0a();
        self.widget.set_layout(&layout);

        //Label
        let label = QLabel::new();
        label.set_text(&qs("Label B!"));
        layout.add_widget(&label);
        
    }
}

fn main() {
    QApplication::init(|_| unsafe {
        let _mw = MainWindow::new();
        QApplication::exec()
    })
}

I

Qt prints an error in the stderr: QWidget::setLayout: Attempting to set QLayout "" on QWidget "", which already has a layout. You should delete the previous layout first if you want to create new one, for example, like this:

self.widget.layout().to_box();

See QWidget::setLayout docs. However, I'm not sure why you need to create a new layout when you can just use the existing one.

I'd be glad if you can also elaborate on the issues with QStackedLayout. As far as I can see, it should not cause any special lifetime issues.

1 Like

This should work:

        for i in 0..self.widget.layout().count() {
            let it = self.widget.layout().take_at(i);
            if let Some(wi) = it.dynamic_cast::<QWidgetItem>().as_ref() {
                wi.widget().delete();
            }
        }
        self.widget.layout().delete();

This exactly how you should write in C++. Your code will have the same problem as you described,
if you rewrite it on C++, because of you removing from layout, but not deleting.

And I should note, that after that button lose it's layout.

1 Like

Thanks for the help both! This is the working code to finally make multiple pages in Qt!

#![windows_subsystem = "windows"]

//Importing the required stuff
use cpp_core::{Ptr, Ref, StaticUpcast, CppDeletable};
use qt_core::{qs, slot, ContextMenuPolicy, QBox, QObject, QPoint, SlotNoArgs, QString};
use qt_widgets::{
    QAction, QApplication, QLineEdit, QMenu, QMessageBox, QPushButton, QLabel, QTableWidget,
    QTableWidgetItem, QVBoxLayout, QWidget, SlotOfQPoint, SlotOfQTableWidgetItemQTableWidgetItem,
    QWidgetItem,
};
use std::rc::Rc;
use std::cell::RefCell;

//Struct for Main window
struct MainWindow {
    widget: QBox<QWidget>,
    button: QBox<QPushButton>,
}

//From form-struct to base-struct ??? 
impl StaticUpcast<QObject> for MainWindow {
    unsafe fn static_upcast(ptr: Ptr<Self>) -> Ptr<QObject> {
        ptr.widget.as_ptr().static_upcast()
    }
}

//Implementing functions for MainWindow
impl MainWindow {
    //Defining all the graphical elements in MainWindow
    //Returns the MainWindow in a Reference Counted Smart Pointer so it can have multiple owners
    //and doesn't get destroyed until it has no owners anymore
    fn new() -> Rc<MainWindow> {
        unsafe {
            //Widget
            let widget = QWidget::new_0a();
            let layout = QVBoxLayout::new_1a(&widget);

            //Label
            let label = QLabel::new();
            label.set_text(&qs("Label A!"));
            layout.add_widget(&label);

            //Button
            let button = QPushButton::from_q_string(&qs("Click me."));
            layout.add_widget(&button);

            widget.show();

            let this = Rc::new(Self {
                widget,
                button,
            });
            this.init();
            this
        }
    }

    //Binding functions/slots to elements etc
    unsafe fn init(self: &Rc<Self>) {
        self.button
            .clicked()
            .connect(&self.slot_build_option_a());
    }

    //Slots/Functions for buttons
    //
    //Function to build the second screen
    #[slot(SlotNoArgs)]
    unsafe fn build_option_a(self: &Self) {
        
        //DELETING all buttons, labels,...
        for i in 0..self.widget.layout().count() {
            let it = self.widget.layout().take_at(i);
            if let Some(wi) = it.dynamic_cast::<QWidgetItem>().as_ref() {
                wi.widget().delete();
            }
        }
        self.widget.layout().to_box();
        
        self.button.delete(); //Somehow needed to delete the button as well, maybe because it's defined as a struct property? 

        //Building new layout etc
        let layout = QVBoxLayout::new_1a(&self.widget);

        //Label
        let label = QLabel::new();
        label.set_text(&qs("Label B!"));
        layout.add_widget(&label);
        
    }
}

fn main() {
    QApplication::init(|_| unsafe {
        let _mw = MainWindow::new();
        QApplication::exec()
    })
}

I only don't understand line 80. I had to remove that button separately, why didn't it get deleted in the for loop? Because of it's longer lifetime because it's defined as a struct property?

@Riateche And the issue with StackedLayout is that I have no idea on how to implement that.
If you look at this page they give some C++ examples. In those examples the stackedLayout is kinda the parent holding the widgets as children. So for each page I'd have to make a different widget. But in Rust I need to upcast those widgets to keep them visible, but I can only upcast one

//From form-struct to base-struct ??? 
impl StaticUpcast<QObject> for MainWindow {
    unsafe fn static_upcast(ptr: Ptr<Self>) -> Ptr<QObject> {
        ptr.widget.as_ptr().static_upcast();
        //I tried this but didn't work (because of the return type of the function of course):
        //ptr.widget_b.as_ptr().static_upcast()
    }
}

So I thought I'd make a vector of widgets in the struct MainWindow, but also that didn't work.
Tl;dr, I actually have no idea on how to make my struct MainWindow and what properties to use for it to work with a stackedlayout holding multiple widgets.