Search code examples
pythonpyqt5

How to add widget to the right side of horizontal layout (PyQt5)?


I want to put Amplitude slider and hello button on the right hand side. I want to create a horizontal layout but with matplotlib widget and frequency slider be in vertical layout of each other. The amplitude slider and hello button also in vertical layout of each other. I want something like this.

Thank for the help.

Below is my code so far. enter image description here

#!/usr/bin/python3
import sys
import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QSlider, QLabel, QPushButton, QFileDialog, QDialog, QHBoxLayout
from PyQt5.QtCore import Qt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

class SinewaveApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Sine Wave with Frequency and Amplitude Sliders")
        self.setGeometry(100, 100, 800, 600)

        self.central_widget = QWidget(self)
        self.setCentralWidget(self.central_widget)

        self.layout = QVBoxLayout(self.central_widget)

        self.create_widgets()
        self.setup_plot()

    def create_widgets(self):
        # Graph area
        self.figure, self.ax = plt.subplots()
        self.canvas = FigureCanvas(self.figure)
        self.layout.addWidget(self.canvas)

        # Sliders and hello Box Layout
        right_layout = QHBoxLayout()

        # Sliders area
        left_layout = QVBoxLayout()

        # Frequency Slider
        self.freq_slider_label = QLabel("Frequency:")
        left_layout.addWidget(self.freq_slider_label)
        self.freq_slider = QSlider()
        self.freq_slider.setOrientation(Qt.Horizontal)
        self.freq_slider.setMinimum(1)
        self.freq_slider.setMaximum(10)
        self.freq_slider.setTickInterval(1)
        self.freq_slider.setTickPosition(QSlider.TicksBothSides)
        left_layout.addWidget(self.freq_slider)


        # Add sliders layout to the sliders and hello layout
        right_layout.addLayout(left_layout)
        
        # Amplitude Slider
        self.amp_slider_label = QLabel("Amplitude:")
        right_layout.addWidget(self.amp_slider_label)
        self.amp_slider = QSlider()
        self.amp_slider.setOrientation(Qt.Horizontal)
        self.amp_slider.setMinimum(1)
        self.amp_slider.setMaximum(10)
        self.amp_slider.setTickInterval(1)
        self.amp_slider.setTickPosition(QSlider.TicksBothSides)
        right_layout.addWidget(self.amp_slider)        

        # hello button
        self.button = QPushButton("hello", self)
        right_layout.addWidget(self.button)

        # Add the sliders and hello layout to the main layout
        self.layout.addLayout(right_layout)

        # Connect button to dialog
        self.button.clicked.connect(self.show_hello_window)

        # Connect sliders to plot update
        self.freq_slider.valueChanged.connect(self.update_plot)
        self.amp_slider.valueChanged.connect(self.update_plot)

    def setup_plot(self):
        self.x = np.linspace(0, 2 * np.pi, 1000)
        self.freq = 1 # Initial frequency
        self.amp = 1 # Initial amplitude

        y = self.amp * np.sin(self.freq * self.x)
        self.line, = self.ax.plot(self.x, y, label='Sine Wave')
        self.ax.legend()
        self.ax.set_xlabel('Time')
        self.ax.set_ylabel('Amplitude')
        self.canvas.draw()

    def update_plot(self):
        frequency = self.freq_slider.value()
        amplitude = self.amp_slider.value()

        y = amplitude * np.sin(frequency * self.x)
        self.line.set_ydata(y)
        self.canvas.draw()

    def show_hello_window(self):
        hello_dialog = helloDialog(self)
        hello_dialog.exec_()

class helloDialog(QDialog):
    def __init__(self, parent):
        super().__init__(parent)
        self.setWindowTitle("hello")
        self.setGeometry(200, 200, 300, 150)
        layout = QVBoxLayout()
        label = QLabel("Say Hello", self)
        layout.addWidget(label)

        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWin = SinewaveApp()
    mainWin.show()
    sys.exit(app.exec_())

Solution

  • Your layouts are organized in the wrong way.

    When complex layouts are required, you must consider things hierarchically: in your case, the top layout is a horizontal layout, but you're using QVBoxLayout instead.

    For such a situation you may consider a combination of box and grid layouts.

    +---------- main horizontal layout -----------+
    |                    |                        |
    | +----- grid -----+ | +----- vertical -----+ |
    | |                | | |                    | |
    | |     canvas     | | | +-- horizontal --+ | |
    | |                | | | | label | slider | | |
    | +-------+--------+ | | +-------+--------+ | |
    | |       |        | | |                    | |
    | | label | slider | | |  button            | |
    | |       |        | | |                    | |
    | +-------+--------+ | +--------------------+ |
    +--------------------+------------------------+
    

    So, consider the following changes:

    class SinewaveApp(QMainWindow):
        def __init__(self):
            ...
            self.layout = QHBoxLayout(self.central_widget)
            ...
    
        def create_widgets(self):
            ...
            left_layout = QGridLayout()
            self.layout.addLayout(left_layout, stretch=2)
            left_layout.addWidget(self.canvas, 0, 0, 1, 2)
            left_layout.addWidget(self.freq_slider_label, 1, 0)
            left_layout.addWidget(self.freq_slider, 1, 1)
    
            right_layout = QVBoxLayout()
            self.layout.addLayout(right_layout, stretch=1)
            right_top = QHBoxLayout()
            right_layout.addLayout(right_top)
            right_top.addWidget(self.amp_slider_label)
            right_top.addWidget(self.amp_slider)
            right_layout.addWidget(self.button, alignment=Qt.AlignLeft)
            right_layout.setAlignment(Qt.AlignTop)