Чем отличается QMainWindow и QWidget?
Начал знакомство с PyQt5 и столкнулся c непониманием в чем отличия между виджетами QMainWindow и QWidget ?
И такой вопрос, у меня есть класс, который отображает QWidget , а можно ли создать класс с QMainWindow и туда поместить разные созданные виджеты?
Отслеживать
72.6k 106 106 золотых знаков 38 38 серебряных знаков 55 55 бронзовых знаков
задан 20 апр 2020 в 15:07
575 2 2 серебряных знака 13 13 бронзовых знаков
Можно, Qt Main Window Framework doc.qt.io/qt-5/qmainwindow.html#qt-main-window-framework
20 апр 2020 в 15:11
а собственно чем отличаются эти классы?
20 апр 2020 в 15:22
QMainWindow наследует QWidget и добавляет необходимый минимум элементов, свойственных главному окну: статусбар, главное меню, центральный виджет и что-то там еще
20 апр 2020 в 15:39
спасибо, помогли.
20 апр 2020 в 16:19
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Главное окно QMainWindow В более крупных, более сложных и более функциональных приложениях мы обычно наследуем класс QMainWindow для разработки. Это главное окно предоставляет очень хорошую основу для построения пользовательского интерфейса приложения.
Класс главного окна QMainWindow предоставляет нам
- строку меню, Menu Bar
- панель инструментов, Toolbars
- виджеты Dock и
- строку состояния, Status Bar и мы можем добавить их столько, сколько захотим. Кроме того, это позволяет нам быстро разработать функционально сложное и удобное приложение.
Блокнот / Notepad Простое приложение блокнота, чтобы понять, как использовать QMainWindow
import sys from PyQt5.QtGui import QIcon from PyQt5.QtCore import QMimeData from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QAction, QFileDialog, QMessageBox,\ QFontDialog, QColorDialog class Demo(QMainWindow): # В начале класса мы устанавливаем несколько переменных класса, которые используются # для определения того, был ли сохранен текст, сохраняется ли это в первый раз, # и для сохранения пути к файлу: is_saved = True is_saved_first = True path = '' def __init__(self): super(Demo, self).__init__() # Затем мы начинаем добавлять строку меню, панель инструментов и строку состояния: self.file_menu = self.menuBar().addMenu('File') self.edit_menu = self.menuBar().addMenu('Edit') self.help_menu = self.menuBar().addMenu('Help') self.file_toolbar = self.addToolBar('File') self.edit_toolbar = self.addToolBar('Edit') self.status_bar = self.statusBar() # Далее нам нужно только добавить различные действия к строке меню и панели инструментов, # то есть QAction. Этот класс обычно используется с панелью меню и панелью инструментов. # Вы можете думать о действии как о команде. Всякий раз, когда пользователь нажимает # на действие, запускается определенная команда, и программа выполняет соответствующую команду. # Теперь нам нужно создать несколько действий: self.new_action = QAction('New', self) self.open_action = QAction('Open', self) self.save_action = QAction('Save', self) self.save_as_action = QAction('Save As', self) self.close_action = QAction('Close', self) self.cut_action = QAction('Cut', self) self.copy_action = QAction('Copy', self) self.paste_action = QAction('Paste', self) self.font_action = QAction('Font', self) self.color_action = QAction('Color', self) self.about_action = QAction('Qt', self) # Поскольку это Notepad, должен быть текстовый редактор, поэтому, конечно, # в функции инициализации класса должен быть создан экземпляр элемента управления QTextEdit: self.text_edit = QTextEdit(self) # С помощью функции вырезания и вставки мы естественно думаем # о буфере обмена и классе QMimeData: self.mime_data = QMimeData() # QMimeData self.clipboard = QApplication.clipboard() # буфере обмена # Вызвать метод setCentralWidget() класса QMainWindow, чтобы установить # центральный элемент управления главного окна, # в котором мы устанавливаем поле редактирования текста text_edit для центрального # элемента управления. self.setCentralWidget(self.text_edit) # Вызовите метод resize(), чтобы установить для окна соответствующий размер; self.resize(450, 600) self.menu_init() # После того, как строка меню установлена, мы можем установить панель инструментов: self.toolbar_init() # Настройка строки состояния очень проста. self.status_bar_init() self.action_init() # установить действия self.text_edit_int() def menu_init(self): """ добавляем соответствующее действие в строку меню: """ # Видно, что мы можем добавлять к нему действия, вызывая метод addAction(). # Метод addSeparator(), как следует из названия, добавляет разделитель, что означает, # что close_action отделено от четырех вышеуказанных действий, что выглядит более организованным. self.file_menu.addAction(self.new_action) self.file_menu.addAction(self.open_action) self.file_menu.addAction(self.save_action) self.file_menu.addAction(self.save_as_action) self.file_menu.addSeparator() self.file_menu.addAction(self.close_action) self.edit_menu.addAction(self.cut_action) self.edit_menu.addAction(self.copy_action) self.edit_menu.addAction(self.paste_action) self.edit_menu.addSeparator() self.edit_menu.addAction(self.font_action) self.edit_menu.addAction(self.color_action) self.help_menu.addAction(self.about_action) def toolbar_init(self): self.file_toolbar.addAction(self.new_action) self.file_toolbar.addAction(self.open_action) self.file_toolbar.addAction(self.save_action) self.file_toolbar.addAction(self.save_as_action) self.edit_toolbar.addAction(self.cut_action) self.edit_toolbar.addAction(self.copy_action) self.edit_toolbar.addAction(self.paste_action) self.edit_toolbar.addAction(self.font_action) self.edit_toolbar.addAction(self.color_action) def status_bar_init(self): # Нам просто нужно вызвать метод showMessage() и передать состояние, # которое программа хочет отобразить при открытии программы. self.status_bar.showMessage('Ready to compose') def action_init(self): # Передайте параметр QIcon через метод setIcon(), чтобы установить значок действия. # Метод setShortCut() используется для установки сочетания клавиш. # Здесь мы устанавливаем сочетание клавиш нового действия на Ctrl + N. # Тогда нажатие Ctrl+N эквивалентно нажатию этого действия (независимо от платформы, # здесь используется Ctrl равномерно. (Даже на Mac, но после запуска программы пользователи # Mac должны нажать Ctrl + N). # Метод setToolTip() можно использовать для установки небольшого всплывающего приглашения. # Когда указатель мыши находится над действием, будет отображаться соответствующее # приглашение (конечно, мы также можем использовать этот метод для других объектов, # таких как QPushButton). # Метод setStatusTip() предназначен для установки информации в строке состояния. # Когда курсор мыши находится над действием, строка состояния отображает # соответствующую информацию. # Наконец, мы связываем сработавший сигнал new_action с пользовательской функцией слота: self.new_action.setIcon(QIcon('images/new.ico')) self.new_action.setShortcut('Ctrl+N') self.new_action.setToolTip('Create a new file') self.new_action.setStatusTip('Create a new file') self.new_action.triggered.connect(self.new_func) self.open_action.setIcon(QIcon('images/open.png')) self.open_action.setShortcut('Ctrl+O') self.open_action.setToolTip('Open an existing file') self.open_action.setStatusTip('Open an existing file') self.open_action.triggered.connect(self.open_file_func) self.save_action.setIcon(QIcon('images/save.png')) self.save_action.setShortcut('Ctrl+S') self.save_action.setToolTip('Save the file') self.save_action.setStatusTip('Save the file') self.save_action.triggered.connect(lambda: self.save_func(self.text_edit.toHtml())) self.save_as_action.setIcon(QIcon('img/save_as.png')) self.save_as_action.setShortcut('Ctrl+A') self.save_as_action.setToolTip('Save the file to a specified location') self.save_as_action.setStatusTip('Save the file to a specified location') self.save_as_action.triggered.connect(lambda: self.save_as_func(self.text_edit.toHtml())) self.close_action.setIcon(QIcon('img/exit.png')) # close.ico self.close_action.setShortcut('Ctrl+E') self.close_action.setToolTip('Close the window') self.close_action.setStatusTip('Close the window') self.close_action.triggered.connect(self.close_func) self.cut_action.setIcon(QIcon('img/cut.png')) self.cut_action.setShortcut('Ctrl+X') self.cut_action.setToolTip('Cut the text to clipboard') self.cut_action.setStatusTip('Cut the text') self.cut_action.triggered.connect(self.cut_func) self.copy_action.setIcon(QIcon('img/copy.png')) self.copy_action.setShortcut('Ctrl+C') self.copy_action.setToolTip('Copy the text') self.copy_action.setStatusTip('Copy the text') self.copy_action.triggered.connect(self.copy_func) self.paste_action.setIcon(QIcon('img/paste.png')) self.paste_action.setShortcut('Ctrl+V') self.paste_action.setToolTip('Paste the text') self.paste_action.setStatusTip('Paste the text') self.paste_action.triggered.connect(self.paste_func) self.font_action.setIcon(QIcon('img/font.png')) self.font_action.setShortcut('Ctrl+T') self.font_action.setToolTip('Change the font') self.font_action.setStatusTip('Change the font') self.font_action.triggered.connect(self.font_func) self.color_action.setIcon(QIcon('img/color.png')) self.color_action.setShortcut('Ctrl+R') self.color_action.setToolTip('Change the color') self.color_action.setStatusTip('Change the color') self.color_action.triggered.connect(self.color_func) self.about_action.setIcon(QIcon('img/about.png')) self.about_action.setShortcut('Ctrl+Q') self.about_action.setToolTip('What is Qt?') self.about_action.setStatusTip('What is Qt?') self.about_action.triggered.connect(self.about_func) # В конце у нас остается только поле ввода текста QTextEdit, оставленное неустановленным. # Подключите сигнал textChanged к функции слота. # В функции slot(text_changed_func) мы можем установить переменную self.is_saved: def text_edit_int(self): self.text_edit.textChanged.connect(self.text_changed_func) def text_changed_func(self): if self.text_edit.toPlainText(): self.is_saved = False else: self.is_saved = True # Перед созданием нового файла нам нужно определить, сохранен ли текущий текст. # Если нет, появится всплывающее окно с вопросом, хотите ли вы сохранить его. # Если вы нажмете Да, вы вызовете функцию save_func() для его сохранения. # Если вы нажмете Нет, чтобы сохранить, он будет очищен. # Если вы нажмете `Отмена` для отмены, никакие действия не будут предприняты, # если они были сохранены, вы можете просто очистить поле для редактирования текста; def new_func(self): if not self.is_saved: choice = QMessageBox.question(self, '', 'Do you want to save the text?', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if choice == QMessageBox.Yes: self.save_func() self.text_edit.clear() elif choice == QMessageBox.No: self.text_edit.clear() else: pass else: self.text_edit.clear() # Метод установки действия open_action похож, мы в основном рассмотрим функцию подключенного слота: # Логика очень похожа на новое действие, за исключением того, что операция очистки поля # редактирования текста заменяется операцией открытия диалогового окна файла; def open_file_func(self): if not self.is_saved: choice = QMessageBox.question(self, '', 'Do you want to save the text?', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if choice == QMessageBox.Yes: self.save_func() file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)') if file: with open(file, 'r') as f: self.text_edit.clear() self.text_edit.setText(f.read()) self.is_saved = True elif choice == QMessageBox.No: file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)') if file: with open(file, 'r') as f: self.text_edit.clear() self.text_edit.setText(f.read()) self.is_saved = True else: pass else: file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)') if file: with open(file, 'r') as f: self.text_edit.clear() self.text_edit.setText(f.read()) self.is_saved = True # Функции слотов def save_func(self, text): if self.is_saved_first: self.save_as_func(text) else: with open(self.path, 'w') as f: f.write(text) self.is_saved = True def save_as_func(self, text): self.path, _ = QFileDialog.getSaveFileName(self, 'Save File', './', 'Files (*.html *.txt *.log)') if self.path: with open(self.path, 'w') as f: f.write(text) self.is_saved = True self.is_saved_first = False # Функция close_func() ниже аналогична методу реализации события закрытия окна. # Обратите внимание, что поскольку в нашем блокноте используется цвет, мы не можем вызвать # метод toPlainText() класса QTextEdit, поскольку этот метод получает чистый текст, # цвет будет потерян. Метод toHtml() должен быть вызван для сохранения цвета; def close_func(self): if not self.is_saved: choice = QMessageBox.question(self, 'Save File', 'Do you want to save the text?', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if choice == QMessageBox.Yes: self.save_func(self.text_edit.toHtml()) self.close() elif choice == QMessageBox.No: self.close() else: pass def closeEvent(self, QCloseEvent): if not self.is_saved: choice = QMessageBox.question(self, 'Save File', 'Do you want to save the text?', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if choice == QMessageBox.Yes: self.save_func(self.text_edit.toHtml()) QCloseEvent.accept() elif choice == QMessageBox.No: QCloseEvent.accept() else: QCloseEvent.ignore() # Функция слота, связанная с помощью cut_action, выглядит следующим образом: # Метод self.text_edit.textCursor() может получить текущий указатель # текстового поля редактирования (тип QTextCursor), а затем вызвать метод selection(), # чтобы получить содержимое, в настоящий момент выбранное указателем, но типом в данный # момент является QTextDocumentFragment, мы Вам нужно вызвать метод toHtml(), # чтобы получить текстовое содержимое. # Когда пользователь режет, обрезанный текст должен исчезнуть, # поэтому вызовите метод removeSelectedText() QTextCursor. def cut_func(self): self.mime_data.setHtml(self.text_edit.textCursor().selection().toHtml()) self.clipboard.setMimeData(self.mime_data) self.text_edit.textCursor().removeSelectedText() # Функция слота copy_action выглядит следующим образом, метод тот же, за исключением того, # что текст не нужно удалять в это время: def copy_func(self): self.mime_data.setHtml(self.text_edit.textCursor().selection().toHtml()) self.clipboard.setMimeData(self.mime_data) # В слоте функции paste_action нам просто нужно вызвать метод insetHtml() # для вставки текста в буфер обмена (этот метод вставит текст в позицию указателя): def paste_func(self): self.text_edit.insertHtml(self.clipboard.mimeData().html()) def font_func(self): font, ok = QFontDialog.getFont() if ok: self.text_edit.setFont(font) def color_func(self): color = QColorDialog.getColor() if color.isValid(): self.text_edit.setTextColor(color) # Слот-функция, связанная с about_action, является самой простой, # просто откройте окно сообщения о Qt: def about_func(self): QMessageBox.aboutQt(self, 'About Qt') if __name__ == '__main__': app = QApplication(sys.argv) demo = Demo() demo.show() sys.exit(app.exec_())
Классы Главного Окна в Qt 4
Qt 4 предоставляет новый набор классов основного окна, которые превосходят классы основного окна в Qt 3 обеспечивая большую эффективность при сохранении удобства в использовании.
- Обзор Классов Главного Окна
- Классы Главного Окна
- Пример Кода
- Что Изменилось по Сравнению с Qt 3?
Обзор Классов Главного Окна
Классы связанные с главным окном были полностью переработаны для того, чтобы удовлетворить множество требований и разрешить множество проблем с которыми столкнулись наши клиетны и внутренние разработчики. Цель данного перепроектирования — обеспечить более последовательную и эффективную структуру управления главным окном.
Функциональные возможности панели инструметов и встроенного окна обеспечиваются двумя независимыми классами: QToolBar и QDockWidget. Панели инструметов и встроенные виджеты располагаются в отдельных областях. Подобное поведение отличается от поведения в Qt 3 где QToolBar наследует функциональные возможности QDockWidget и где компоненты обоих типов располагаются в одних и тех-же областях. В результате мы получили более последовательное и предсказуемое поведение. Панели инструметов и встроенные виджеты сохраняют обратную связь в то время, как их перемещают в новую позицию.

Диаграмма приведенная выше показывает компоновку главного окна, которое содержит панели инструметов и встроенные виджеты. Каждая из угловых областей может использоваться любой из смежных областей встроенных виджетов позволяя задавать точное расположение виджетов.
Панели инструметров и встроенные виджеты являются дочерними окнами по отношению к главному окну. Больше нет необходимости удочерять их главным окном. Вместо этого компоновщики используются для управления расположением панелей инструметов и состыковкой виджетов. В результате этого старый класс QDockArea больше не требуется в Qt 4.
Классы Главного Окна
Qt 4 предоставляет следующие классы для управления главным окном и связанными с ним компонентами пользовательского интерфейса:
- QMainWindow остается центральным классом вокруг которого может быть построено приложение. Интерфейс данного класса был упрощен и многие функциональные возможности перенесены в классы QDockWidget и QToolBar.
- QDockWidget предоставляет виджет, который может быть использован для создания перемещаемых панелей инструметов или окон помощи. Встоенный виджет хранит сведения о себе и может быть перемещен, закрыт или использован как внешнее окно.
- QToolBar представляет собой родовой виджет панели инструметов и может содержать множество различных виджетов связанных с выполнением каких-либо действий, таких как кнопки, выпадающие меню, выпадающие списки и счетчики. Упор в Qt 4 на объединение действий привел к удобному сотрудничеству панелей инструметов с горячими клавишами клавиатуры и меню.
Пример Кода
Прямое использование QMainWindow. Вообще мы создаем подклассы QMainWindow и основываем меню, панели инструметов и встроенные виджеты внутри конструктора QMainWindow.
Чтобы создать панель меню к главному окну, мы просто создаем меню и добавляем его в панель меню главного окна. Обратите внимание, что функция QMainWindow::menuBar() автоматически создает панель меню при первом вызове. Вы также можете вызвать QMainWindow::setMenuBar() для того, чтобы использовать собственное меню в главном окне.
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) < . newAct = new QAction(tr("&New"), this); newAct->setShortcut(tr("Ctrl+N")); newAct->setStatusTip(tr("Create a new file")); connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); openAct = new QAction(tr("&Open. "), this); openAct->setShortcut(tr("Ctrl+O")); openAct->setStatusTip(tr("Open an existing file")); connect(openAct, SIGNAL(triggered()), this, SLOT(open())); .
Как только действия созданы, их можно связать с компонентами главного окна. Для начала добавляем их к выпадающим меню:
fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(newAct); fileMenu->addAction(openAct); . fileMenu->addSeparator(); .
Классы QToolBar и QMenu используют систему действий Qt для поддержания последовательного API. В вышеприведенном коде некоторые действия были добавлены в меню с помощью функции QMenu::addAction(). QToolBar также поддерживает эту функцию облегчая повторное использование действий в различных частях главного окна. Это избавляет от ненужного повторения кода.
Мы создаем дочернее по отношению к главному окну меню и добавляем в него нужные действия:
fileToolBar = addToolBar(tr("File")); fileToolBar->addAction(newAct); fileToolBar->addAction(openAct); .
fileToolbar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); addToolBar(Qt::TopToolBarArea, fileToolbar);
В данном примере расположение панели инструментов ограничего верхней и нижней частями главного окна и первоначальное положение уставновлено как верхняя. Мы можем видеть, что действия, определенные как, newAct и openAct будут отображены и в меню и на панели инструментов.
QDockWidget используется подобно QToolBar. Мы создаем встроенный виджет как дочерний по отношению к главному окну и добавляем в него виджеты дочерние по отношению к встроенному вижету:
contentsWindow = new QDockWidget(tr("Table of Contents"), this); contentsWindow->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::LeftDockWidgetArea, contentsWindow); headingList = new QListWidget(contentsWindow); contentsWindow->setWidget(headingList);
В данном примере месторасположение встроенного виджета ограничено левой и правой областью встраиваиваемых виджетов и первоначально определено как левая.
API QMainWindow позволяет программисту настраивать какие встраиваемые виджеты какой угол занимают и как они состыковываются. Если требуется, положение по умолчанию может быть изменено с помощью функции QMainWindow::setCorner():
setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
Вышеприведенная диаграмма демонстрирует, как изменятся области встраиваемых виджетов после выполнения данного кода. Обратите внимание, что левая и правая область встраиваемых виджетов займет верхние и нижние углы главного окна.

После того, как созданы все компоненты главного окна, приступаем к созданию центрального виджета с помощью следующего кода:
QWidget *centralWidget = new QWidget(this); setCentralWidget(centralWidget);
Центральный виджет может быть любым из подклассов QWidget.
Что Изменилось по Сравнению с Qt 3?
Хотя класс QMainWindow в Qt 3 обеспечивал поддержку панелей инструментов, встраиваемый виджетов и прочих страндартных компонентов пользовательского интерфейса, это делалось с помощью большого количества функций-членов QMainWindow. В Qt 4 класс QMainWindow многие из функций управления в QDockWidget и QToolBar. В результате эти классы в Qt 4 используются слегка различными способами.
QMainWindow продолжает поддерживать функцию menuBar(), но меню всегда строится с использованием объектов QAction. Все виды меню построены с использованием общего класса QMenu.
QPopupMenu *fileMenu = new QPopupMenu(this); openAction->addTo(fileMenu); saveAction->addTo(fileMenu); . menuBar()->insertItem(tr("&File"), fileMenu);
QMenu *fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(openAction); fileMenu->addAction(saveAction); .
Панели инструментов строятся по тем-же самым принципам, что и меню, и обеспечивают новое, более последовательное поведение:
QToolBar *fileTools = new QToolBar(this, "file toolbar"); openAction->addTo(fileTools); saveAction->addTo(fileTools); .
QToolBar *fileTools = addToolBar(tr("File Tool Bar")); fileTools->addAction(openAction); fileTools->addAction(saveAction); .
Поведение встраиваемых виджетов теперь настраивается через функции-члены QDockWidget. Для примера сравните старый и новый способы создания встраиваемого виджета, располагающегося в левой области встраиваемых виджетов главного окна.
QDockWidget *dockWidget = new QDockWidget(this); mainWin->moveDockWidget(dockWidget, Qt::DockLeft);
QDockWidget *dockWidget = new QDockWidget(mainWindow); mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
Данные изменения представляют из себя нечто большее, чем косметические улучшения API Qt. Они позволяют реализовать более последовательное поведение. Мы надеемся улучшать эти классы и далее поддерживая обратную связь с клиентами.
PyQt6 — полное руководство для новичков

К старту курса по разработке на Python делимся детальным руководством по работе с современным PyQt. Чтобы читать было удобнее, мы объединили несколько статей в одну:
За подробностями приглашаем под кат.
Простое приложение Hello World! на Python и Qt6
PyQt — это библиотека Python для создания приложений с графическим интерфейсом с помощью инструментария Qt. Созданная в Riverbank Computing, PyQt является свободным ПО (по лицензии GPL) и разрабатывается с 1999 года. Последняя версия PyQt6 — на основе Qt 6 — выпущена в 2021 году, и библиотека продолжает обновляться. Это руководство можно также использовать для PySide2, PySide6 и PyQt5.
Сегодня используются две основные версии: PyQt5 на основе Qt5 и PyQt6 на основе Qt6. Обе почти полностью совместимы, за исключением импорта и отсутствия поддержки некоторых продвинутых модулей из Qt6. В PyQt6 вносятся изменения в работу пространств имён и флагов, но ими легко управлять. В этом руководстве мы узнаем, как использовать PyQt6 для создания настольных приложений.
Сначала создадим несколько простых окон на рабочем столе, чтобы убедиться, что PyQt работает, и разберём базовые понятия. Затем кратко изучим цикл событий и то, как он связан с программированием графического интерфейса на Python. В заключение поговорим о QMainWindow с полезными элементами интерфейса, такими как панели инструментов и меню. Подробно я расскажу о них в следующих руководствах.
Создание приложения
pip install pyqt6 # и на будущее pip install pyqt-tools
Сначала создадим новый файл Python с любым названием (например app.py) и сохраним его. Исходный код приложения показан ниже. Введите его полностью и постарайтесь не ошибиться. Если что-то напутаете, Python укажет, что именно:
from PyQt6.QtWidgets import QApplication, QWidget import sys # Только для доступа к аргументам командной строки # Приложению нужен один (и только один) экземпляр QApplication. # Передаём sys.argv, чтобы разрешить аргументы командной строки для приложения. # Если не будете использовать аргументы командной строки, QApplication([]) тоже работает app = QApplication(sys.argv) # Создаём виджет Qt — окно. window = QWidget() window.show() # Важно: окно по умолчанию скрыто. # Запускаем цикл событий. app.exec() # Приложение не доберётся сюда, пока вы не выйдете и цикл # событий не остановится.
Запускаем приложение из командной строки, как и любой скрипт Python:
python3 app.py
Выполнив его, мы увидим окно. В Qt автоматически создаётся окно с обычным оформлением, возможностью его перетаскивать и менять размер. То, что вы увидите, зависит от платформы, где этот пример выполняется. Вот как отображается это окно на Windows, macOS и Linux (Ubuntu):

Разбор кода
Пройдём код построчно, чтобы понять, что именно происходит. Сначала мы импортируем классы PyQt для приложения: здесь это обработчик приложения QApplication и базовый пустой виджет графического интерфейса QWidget (оба из модуля QtWidgets):
from PyQt6.QtWidgets import QApplication, QWidget
Основные модули для Qt: QtWidgets, QtGui и QtCore.
Возможен ещё from import * , но этот вид импорта обычно не приветствуется в Python. Дальше создаём экземпляр QApplication и передаём sys.arg (список Python с аргументами командной строки, передаваемыми приложению):
app = QApplication(sys.argv)
Если не будете использовать аргументы командной строки для управления Qt, передайте пустой список:
app = QApplication([])
Затем создаём экземпляр QWidget, используя имя переменной window:
window = QWidget() window.show()
В Qt все виджеты верхнего уровня — окна, то есть у них нет родительского элемента и они не вложены в другой виджет или макет. В принципе, окно можно создать, используя любой виджет.
Виджеты без родительского элемента по умолчанию невидимы. Поэтому после создания объекта window необходимо всегда вызывать функцию .show(), чтобы сделать его видимым. .show() можно удалить, но тогда, запустив приложение, вы не сможете выйти из него!
В окне находится пользовательский интерфейс приложения. У каждого приложения он как минимум один. Приложение (по умолчанию) завершает работу при закрытии последнего окна.
Наконец, вызываем app.exec(), чтобы запустить цикл события.
Что такое «цикл событий»?
Прежде чем вывести окно на экран, разберём ключевые понятия, касающиеся организации приложений в мире Qt. Если вам уже знакомы циклы событий, можете пропустить эту часть статьи.
Основной элемент всех приложений в Qt — класс QApplication. Для работы каждому приложению нужен один — и только один — объект QApplication, который содержит цикл событий приложения. Это основной цикл, управляющий всем взаимодействием пользователя с графическим интерфейсом:

При каждом взаимодействии с приложением — будь то нажатие клавиши, щелчок или движение мыши — генерируется событие, которое помещается в очередь событий. В цикле событий очередь проверяется на каждой итерации: если найдено ожидающее событие, оно вместе с управлением передаётся определённому обработчику этого события. Последний обрабатывает его, затем возвращает управление в цикл событий и ждёт новых событий. Для каждого приложения выполняется только один цикл событий.
Класс QApplication содержит цикл событий Qt (нужен один экземпляр QApplication). Приложение ждёт в цикле событий новое событие, которое будет сгенерировано при выполнении действия. Всегда выполняется только один цикл событий.
QMainWindow
Итак, в Qt любые виджеты могут быть окнами. Например, если заменить QtWidget на QPushButton. В этом примере получается окно с одной нажимаемой кнопкой:
import sys from PyQt6.QtWidgets import QApplication, QPushButton app = QApplication(sys.argv) window = QPushButton("Push Me") window.show() app.exec()
Классно, но не очень полезно на самом деле: редко когда нужен пользовательский интерфейс, состоящий только из одного элемента управления. Зато возможность с помощью макетов вкладывать одни виджеты в другие позволяет создавать сложные пользовательские интерфейсы внутри пустого QWidget.
В Qt уже есть решение для окна — виджет QMainWindow, имеющий стандартные функции окна для использования в приложениях, который содержит панели инструментов, меню, строку состояния, закрепляемые виджеты и многое другое. Рассмотрим эти расширенные функции позже, а пока добавим в приложение простой, пустой QMainWindow:
import sys from PyQt6.QtWidgets import QApplication, QMainWindow app = QApplication(sys.argv) window = QMainWindow() window.show() # Запускаем цикл событий. app.exec()
Запускаем и видим главное окно. Точно такое же, как и раньше.
QMainWindow пока не очень интересный. Добавим контент. Чтобы сделать настраиваемое окно, лучше создать подкласс QMainWindow, а затем настроить окно в блоке __init__. Так окно станет независимым в плане поведения. Итак, добавляем подкласс QMainWindow — MainWindow:
import sys from PyQt6.QtCore import QSize, Qt from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton # Подкласс QMainWindow для настройки главного окна приложения class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") button = QPushButton("Press Me!") # Устанавливаем центральный виджет Window. self.setCentralWidget(button) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Для этого демо используем QPushButton. Основные виджеты Qt всегда импортируются из пространства имён QtWidgets, как и классы QMainWindow и QApplication. При использовании QMainWindow задействуем .setCentralWidget для размещения виджета (здесь виджет — QPushButton) в QMainWindow, по умолчанию он занимает всё окно. Как добавлять в окна несколько виджетов? Об этом поговорим рассмотрим в руководстве по макетам.
При создании подкласса из класса Qt, чтобы разрешить Qt настраивать объект, всегда нужно вызывать функцию super __init__.
В блоке __init__ сначала используем .setWindowTitle(), чтобы поменять заголовок главного окна. Затем добавляем первый виджет — QPushButton — в середину окна. Это один из основных виджетов Qt. При создании кнопки можно ввести текст, который будет на ней отображаться. Вызываем .setCentralWidget() в окне. Это специальная функция QMainWindow, которая позволяет установить виджет на середину окна.
Запускаем и снова видим окно, но на этот раз с виджетом QPushButton в центре. Нажатие кнопки ничего не даст — с этим мы разберёмся после:

Скоро мы подробно рассмотрим другие виджеты, но, если вам не терпится и хочется забежать вперёд, можете заглянуть в документацию QWidget. Попробуйте добавить различные виджеты в окно.
Изменение размеров окон и виджетов
Сейчас размер окна можно свободно поменять: щёлкните мышью на любой угол и перетаскивайте, меняя таким образом размер. Можно дать возможность пользователям самим менять размер приложений, а можно установить ограничения на минимальные или максимальные размеры или фиксированный размер окна.
В Qt размеры определяются с помощью объекта QSize. Он принимает параметры ширины и высоты. Например, так создаётся окно фиксированного размера 400 x 300 пикселей:
import sys from PyQt6.QtCore import QSize, Qt from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton # Подкласс QMainWindow для настройки главного окна приложения class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") button = QPushButton("Press Me!") self.setFixedSize(QSize(400, 300)) # Устанавливаем центральный виджет Window. self.setCentralWidget(button) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Запускаем и видим окно фиксированного размера. Поменять его размер не получится.

Элемент управления maximize отключён на Windows и Linux. На macOS можно развернуть приложение на весь экран, но размер центрального виджета не изменится.
Кроме .setFixedSize() можно также вызвать .setMinimumSize() и .setMaximumSize(), чтобы установить минимальный и максимальный размеры соответственно. Попробуйте сами! Эти методы регулирования размеров работают в любом виджете. Продолжить изучение Python вы сможете на наших курсах:
- Курс Python-разработчик
- Профессия Fullstack-разработчик на Python
- Курс «Python для веб-разработки»
А ещё вы можете приобрести книгу автора этих уроков или продолжить чтение.
Слоты и сигналы
Ранее мы рассмотрели классы QApplication и QMainWindow, цикл событий и добавили в окно простой виджет. А теперь изучим механизмы Qt для взаимодействия виджетов и окон друг с другом. В статью внесены изменения, связанные с PyQt6.
Мы создали окно и добавили в него простой виджет push button, но кнопка пока бесполезна. Нужно связать действие нажатия кнопки с происходящим. В Qt это делается с помощью сигналов и слотов или событий.
Сигналы — это уведомления, отправляемые виджетами, когда что-то происходит. Этим «чем-то» может быть что угодно — нажатие кнопки, изменение текста в поле ввода или изменение текста в окне. Многие сигналы инициируются в ответ на действия пользователя, но не только: в сигналах могут отправляться данные с дополнительным контекстом произошедшего.
Можно также писать собственные сигналы, их мы рассмотрим позже.
Слоты в Qt — это приёмники сигналов. Слотом в приложении на Python можно сделать любую функцию (или метод), просто подключив к нему сигнал. Принимающая функция получает данные, отправляемые ей в сигнале. У многих виджетов Qt есть встроенные слоты, а значит, виджеты можно подключать друг к другу напрямую.
Рассмотрим основные сигналы Qt и их использование для подключения виджетов в приложениях. Сохраните эту заготовку приложения в файле app.py:
import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle("My App") app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Сигналы QPushButton
Сейчас у нас есть QMainWindow с центральным виджетом QPushButton. Подключим эту кнопку к пользовательскому методу Python. Создадим простой настраиваемый слот the_button_was_clicked, принимающий сигнал clicked от QPushButton:
import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") button = QPushButton("Press Me!") button.setCheckable(True) button.clicked.connect(self.the_button_was_clicked) # Устанавливаем центральный виджет Window. self.setCentralWidget(button) def the_button_was_clicked(self): print("Clicked!") app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Запускаем. Если нажать на кнопку, в консоли появится текст Clicked! («Нажата!»):
Clicked! Clicked! Clicked! Clicked!
Получение данных
В сигналах может отправляться дополнительная информация о произошедшем. И сигнал .clicked — не исключение: с его помощью сообщается о нажатом (или переключенном) состоянии кнопки. Для обычных кнопок это значение всегда False, поэтому первый слот проигнорировал эти данные. Включим возможность нажатия кнопки, чтобы увидеть этот эффект. Ниже добавляется второй слот и выводится состояние нажатия:
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") button = QPushButton("Press Me!") button.setCheckable(True) button.clicked.connect(self.the_button_was_clicked) button.clicked.connect(self.the_button_was_toggled) self.setCentralWidget(button) def the_button_was_clicked(self): print("Clicked!") def the_button_was_toggled(self, checked): print("Checked?", checked)
Запускаем! Если нажать на кнопку, она подсветится и станет checked («Нажатой»). Чтобы отключить её, нажимаем ещё раз. Найдите состояние нажатия в консоли:
Clicked! Checked? True Clicked! Checked? False Clicked! Checked? True Clicked! Checked? False Clicked! Checked? True
К сигналу подключается сколько угодно слотов, в которых можно реагировать сразу на несколько версий сигналов.
Хранение данных
Текущее состояние виджета на Python часто хранят в переменной, что позволяет работать со значениями без доступа к исходному виджету. Причём для их хранения используются отдельные переменные или словарь. В следующем примере сохраняем значение кнопки checked («Нажата») в переменной button_is_checked в self:
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button_is_checked = True self.setWindowTitle("My App") button = QPushButton("Press Me!") button.setCheckable(True) button.clicked.connect(self.the_button_was_toggled) button.setChecked(self.button_is_checked) self.setCentralWidget(button) def the_button_was_toggled(self, checked): self.button_is_checked = checked print(self.button_is_checked)
Сначала устанавливаем переменной значение по умолчанию True, а затем используем это значение, чтобы установить исходное состояние виджета. Когда состояние виджета меняется, получаем сигнал и соответственно обновляем переменную.
Эта же схема применима к любым виджетам PyQt. Если в виджете нет сигнала, которым отправляется текущее состояние, нужно получить значение из виджета прямо в обработчике. Например, здесь мы проверяем состояние checked («Нажата») в нажатом обработчике:
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button_is_checked = True self.setWindowTitle("My App") self.button = QPushButton("Press Me!") self.button.setCheckable(True) self.button.released.connect(self.the_button_was_released) self.button.setChecked(self.button_is_checked) self.setCentralWidget(self.button) def the_button_was_released(self): self.button_is_checked = self.button.isChecked() print(self.button_is_checked)
Сохраним ссылку на кнопку в self, чтобы получить к ней доступ в слоте.
Сигнал released срабатывает, когда кнопка отпускается, при этом состояние нажатия не отправляется. Его получают из кнопки в обработчике, используя .isChecked().
Изменение интерфейса
Мы уже видели, как принимаются сигналы и выводятся на консоль результаты. Но что происходит с интерфейсом, когда нажимают на кнопку? Обновим метод слота, чтобы изменить кнопку, поменяв текст, отключив её и сделав её недоступной. И отключим пока состояние, допускающее нажатие:
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") self.button = QPushButton("Press Me!") self.button.clicked.connect(self.the_button_was_clicked) self.setCentralWidget(self.button) def the_button_was_clicked(self): self.button.setText("You already clicked me.") self.button.setEnabled(False) # Также меняем заголовок окна. self.setWindowTitle("My Oneshot App")
Снова нужен доступ к кнопке в методе the_button_was_clicked, поэтому сохраняем ссылку на неё в self. Чтобы поменять текст кнопки, передаём str в .setText(). Чтобы отключить кнопку, вызываем .setEnabled() с аргументом False. И запускаем программу. Если нажать на кнопку, текст изменится и кнопка станет недоступной.
В методах слота можно не только менять кнопку, которая активирует сигнал, но и делать всё что угодно. Например, поменять заголовок окна, добавив в метод the_button_was_clicked эту строку:
self.setWindowTitle("A new window title")
Большинство виджетов, в том числе QMainWindow, имеют свои сигналы. В следующем, более сложном примере подключим сигнал .windowTitleChanged в QMainWindow к пользовательскому методу слота. А также сделаем для этого слота новый заголовок окна:
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton import sys from random import choice window_titles = [ 'My App', 'My App', 'Still My App', 'Still My App', 'What on earth', 'What on earth', 'This is surprising', 'This is surprising', 'Something went wrong' ] class MainWindow(QMainWindow): def __init__(self): super().__init__() self.n_times_clicked = 0 self.setWindowTitle("My App") self.button = QPushButton("Press Me!") self.button.clicked.connect(self.the_button_was_clicked) self.windowTitleChanged.connect(self.the_window_title_changed) # Устанавливаем центральный виджет Window. self.setCentralWidget(self.button) def the_button_was_clicked(self): print("Clicked.") new_window_title = choice(window_titles) print("Setting title: %s" % new_window_title) self.setWindowTitle(new_window_title) def the_window_title_changed(self, window_title): print("Window title changed: %s" % window_title) if window_title == 'Something went wrong': self.button.setDisabled(True) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Сначала создаём список заголовков окна и выбираем один из них наугад, используя встроенную функцию Python random.choice(). Подключаем пользовательский метод слота the_window_title_changed к сигналу окна .windowTitleChanged.
При нажатии на кнопку заголовок окна случайным образом изменится. Если новый заголовок окна изменится на Something went wrong («Что-то пошло не так»), кнопка отключится.
Запускаем! Нажимайте на кнопку, пока заголовок не изменится на Something went wrong. В этом примере стоит обратить внимание вот на что:
- Сигнал windowTitleChanged при установке заголовка окна выдаётся не всегда. Он срабатывает, только если новый заголовок отличается от предыдущего: если один и тот же заголовок устанавливается несколько раз, сигнал срабатывает только в первый раз. Чтобы избежать неожиданностей, важно перепроверять условия срабатывания сигналов при их использовании в приложении.
- С помощью сигналов создаются цепочки. Одно событие — нажатие кнопки — может привести к тому, что поочерёдно произойдут другие. Эти последующие эффекты отделены от того, что их вызвало. Они возникают согласно простым правилам. И это отделение эффектов от их триггеров — один из ключевых принципов, которые учитываются при создании приложений с графическим интерфейсом. Возвращаться к этому будем на протяжении всего курса.
Мы рассмотрели сигналы и слоты, показали простые сигналы и их использование для передачи данных и состояния в приложении. Теперь переходим к виджетам Qt, которые будут использоваться в приложениях вместе с сигналами.
Подключение виджетов друг к другу напрямую
Мы уже видели примеры подключения сигналов виджетов к методам Python. Когда сигнал из виджета срабатывает, вызывается метод Python, из сигнала он получает данные. Но для обработки сигналов не всегда нужна функция Python — можно подключать виджеты друг к другу напрямую.
Добавим в окно виджеты QLineEdit и QLabel. В __init__ для окна и подключим сигнал редактирования строки .textChanged к методу .setText в QLabel. Когда в QLineEdit меняется текст, он сразу будет поступать в QLabel (в метод .setText):
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget import sys class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") self.label = QLabel() self.input = QLineEdit() self.input.textChanged.connect(self.label.setText) layout = QVBoxLayout() layout.addWidget(self.input) layout.addWidget(self.label) container = QWidget() container.setLayout(layout) # Устанавливаем центральный виджет Window. self.setCentralWidget(container) app = QApplication(sys.argv) window = MainWindow() window.show() app.exec()
Внимание: чтобы подключить входные данные к метке, нужно определить и эти данные, и метку. В этом коде в макет добавляются два виджета и устанавливаются в окне. Подробно рассмотрим макеты позже, а пока не обращайте на них внимания.

Введите текст в верхнем поле — он сразу появится в виде метки.
У большинства виджетов Qt есть доступные слоты, к которым подключается любой сигнал, возврощающий тот же тип, что он принимает. В документации по виджетам, в разделе Public Slots («Общедоступные слоты»), имеются слоты для каждого виджета. Посмотрите документацию для QLabel.
События
Любое взаимодействие пользователя с приложением Qt — это событие. Есть много типов событий, каждое из которых — это отдельный тип взаимодействия. В Qt события представлены объектами событий, в которые упакована информация о произошедшем. События передаются определённым обработчикам событий в виджете, где произошло взаимодействие.
Определяя пользовательские или расширенные обработчики событий, можно менять способ реагирования виджетов на них. Обработчики событий определяются так же, как и любой другой метод, но название обработчика зависит от типа обрабатываемого события.
QMouseEvent — одно из основных событий, получаемых виджетами. События QMouseEvent создаются для каждого отдельного нажатия кнопки мыши и её перемещения в виджете. Вот обработчики событий мыши:
QMainWindow vs QWidget vs QDialog?
I’ve been working as an IT specialist for a while now, so I am not totally a newbie, but I am new to programming specifically. I’m tackling QT as a starting point for my journey. I’m at that awkward stage where I have more questions than answer and I often don’t even know what questions I should be asking. Just for now, I am having trouble with this fork in the road.
I am having trouble deciphering the difference between QMainWindow, QWidget and QDialog? Before you say read the documentation or just google it, I have, but I just still don’t get it. I don’t know when to use one over another. Are the differences hierarchal, roles based, preference?? As far as I can gather, QWidget is the overall parent to everything and Qdialog are prompts like «are you sure you want o quit» or error message. QMainWindow just seems the same as QWidget. Please help, I feel like I can’t even really start creating an app until I know the difference between these and know which to use. Thank you in advance.