Диалоговые окна
Contents
Диалоговые окна#
Диалоговые окна чаще всего используются для кратковременного взаимодействия с пользователем. Все они наследуются от класса QDialog. Диалоговое окно появляется поверх родительского (посередине родительского, если указан параметр parent
), при этом обычно родительское окно становится не активных до тех пор, пока пользователь не закрое диалоговое окно.
Создавать диалоговые окна можно было бы и вручную (например, наследуясь от QDialog
и модифицируя его поведение под свои цели), но для большинства ситуаций в Qt
заготовлены удобные шаблонные варианты.
Отображение сообщений. QMessageBox
#
Простейшее назначение, ради которого может применяться диалоговое окно — вывести некоторое сообщение пользователю. В таких случаях удобнее всего использовать QMessageBox.
Есть два подхода использовать класс QMessageBox
.
Создать его экземпляр и вызвать его метод
exec
;Использовать статический метод (например, QMessageBox.information).
Список статических методов, главная разница между которыми — иконка, следующий:
about — сообщение о приложении, иконка родительского виджета по умолчанию;
critical — сообщение о критической ошибке;
information — информационное сообщение;
question — вопрос пользователю;
warning — предупреждение (например, сообщение о некритической ошибке).
При ручном создании экземпляра иконку можно выставить методом setIcon, текст сообщения — setText, заголовок диалогового окна — setWindowTitle. Статические методы принимают эти значения в качестве параметров, а иконка определяется типом статического метода.
Этих методов в целом достаточно, чтобы создать полноценное информационное сообщение. Код под спойлером ниже создаёт приложение, которое выводит диалоговое сообщение методом QMessageBox.information
при нажатии на кнопку.
Код.
import sys
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
self.setCentralWidget(widget)
# buttons
push_button = QPushButton("Show message")
push_button.clicked.connect(self.show_message)
layout.addWidget(push_button)
def show_message(self):
QMessageBox.information(
self,
"My first dialog",
"Hello, World!"
)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Заметим, что в прошлом примере в окне была единственная кнопка Ok
. Для информационных сообщений такого функционала может быть достаточно, но, например, для вопросов может потребоваться несколько кнопок, чтобы пользователь имел несколько опций ответа. В качестве иллюстрации таких диалоговых окон можно привести сообщения, которые появляются при попытке закрыть программу с несохраненными изменениями файла: большинство программ в таких случаях не закрываются сразу, а спрашивают пользователя, хочет ли он
закрыть программу без сохранения изменений,
сохранить изменения и выйти,
отменить закрытие программы и продолжить работу.
Виджет QMessageBox
позволяет без труда добавлять в диалоговое окно ряд стандартных кнопок, Для этого используется перечисление QMessageBox.StandardButton, у которого возможны следующие значения:
QMessageBox.Ok
QMessageBox.Open
QMessageBox.Save
QMessageBox.Cancel
QMessageBox.Close
QMessageBox.Discard
QMessageBox.Apply
QMessageBox.Reset
QMessageBox.RestoreDefaults
QMessageBox.Help
QMessageBox.SaveAll
QMessageBox.Yes
QMessageBox.YesToAll
QMessageBox.No
QMessageBox.NoToAll
QMessageBox.Abort
QMessageBox.Retry
QMessageBox.Ignore
Выставить их можно передав значения этого перечисления (объединить несколько кнопок можно знаком логического “или” |
) в качестве параметра конструктора или одного из упомянутых методов или используя метод setStandardButtons у созданного экземпляра класса QMessageBox
. Методами setDefaultButton и setEscapeButton можно выставить кнопки, которые будут считаться при нажатии клавиш enter
и esc
соответственно.
Note
Положение стандартных кнопок на разных платформах отличается. Так, например, в операционных системах семейства windows
кнопка Ok
располагается левее кнопки Cancel
, а в maxOS
— наоборот. Qt
сам определит корректное с точки зрения операционной системы положение переданных кнопок, если использовать предлагаемые им методы и кнопки. При ручном подходе можно по ошибке нарушить стандарты операционной системы и тем самым вызвать путаницу для пользователя: там где он привык видеть кнопку Cancel
оказалась кнопка Ok
.
Кроме того, что кнопки необходимо вывести на экран, необходимо также знать о том, какая из кнопок была нажата пользователем (нажатие на кнопку в QMessageBox
закрывает диалоговое окно и возвращает управление родительскому приложению). Если используется один из упомянутых статических методов, то для этого необходимо использовать возвращаемое значение. Если создаётся экземпляр класса QMessageBox
, то можно использовать возвращаемое значение метода exec
или после его завершения метод clickedButton. В любом случае на выходе вы получите одно из приведенных выше значений перечисления QMessageBox.StandardButton
и простым сравнением, можно выяснить какая кнопка была нажата.
В качестве примера под спойлером ниже приводится диалоговое окно, которое задает пользователю вопрос, хочет ли он изменить заголовок главного окна приложения и предлагает варианты ответа Yes
и No
.
Код.
import sys
from PySide6.QtWidgets import *
class ChangeTitleQuestion(QMessageBox):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setText('Change the window title to "Hello, World!" string?')
self.setIcon(QMessageBox.Question)
self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
self.setDefaultButton(QMessageBox.No)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setMinimumWidth(300)
# buttons
push_button = QPushButton("Show question")
push_button.clicked.connect(self.show_question)
layout.addWidget(push_button)
def show_question(self):
message_box = ChangeTitleQuestion(parent=self)
ret = message_box.exec()
if ret == QMessageBox.Yes:
self.setWindowTitle("Hello, World!")
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Окно ввода данных. QInputDialog
#
Для ввода данных удобно использовать диалоговое окно QInputDialog. Как и в случае с QMessageBox
можно создать экземпляр класса, настроить его и вызвать метод exec
. Но во многих случаях удобнее использовать статические методы
getDouble — диалоговое окно для ввода действительного числа;
getInt — диалоговое окно для ввода целого числа;
getItem — диалоговое окно для выбора элемента из списка элементов;
getText — диалоговое окно для строки текста и getMultiLineText для ввода нескольких строк текста.
Эти методы всегда возвращают кортеж из двух значений: введенное пользователем значение и статус, который позволяет определить, нажал пользователь Ok
или Cancel
. Первое значение стоит использовать, только если статус не является None
.
Метод getText
возвращает первым значением введенную пользователем строку текста.
Метод getItem
принимает в качестве параметра items
список строк и возвращает первым значением элемент списка, выбранный пользователем.
Методы ввода чисел getInt
и getDouble
создают окна с spin box
и принимают параметры для него в качестве аргументов:
value
— значение при появлении;minValue
,maxValue
— минимальное и максимальное значение;step
— шаг.
Первым значением эти методы возвращают сразу числа.
В качестве примера под спойлером ниже приводится приложение, которое выводит синтетически сгенерированный зашумленный сигнал и позволяет пользователю выбирать заголовок графика (строка), среднеквадратичное отклонение (действительное число) и стиль графика (элемент списка, см. документацию по стилям).
Код.
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from PySide6.QtWidgets import *
class MPLGraph(FigureCanvasQTAgg):
def __init__(self):
self.fig = plt.figure(figsize=(2, 2), layout="tight")
self.ax = None
super().__init__(self.fig)
self.style = "default"
self.title = ""
self.noise_scale = 0.1
self.plot()
def plot(self):
with plt.style.context(self.style):
if self.ax:
self.fig.delaxes(self.ax)
self.ax = self.fig.add_subplot(111)
x = np.linspace(0, 1)
noise = np.random.normal(0, self.noise_scale, size=(len(x),))
y = x + noise
self.ax.plot(x, y)
self.ax.set_title(self.title)
self.ax.set_xlabel("t")
self.ax.set_ylabel("signal")
self.draw()
def set_style(self, style):
self.style = style
self.plot()
def set_title(self, title):
self.title = title
self.plot()
def set_noise_scale(self, scale):
self.noise_scale = scale
self.plot()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 600, 400)
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
self.setCentralWidget(widget)
# widgets
self.graph = MPLGraph()
set_title_button = QPushButton("Set title")
set_style_button = QPushButton("Choose style")
set_noise_scale_button = QPushButton("Set noice scale")
# layout
layout.addWidget(self.graph)
bottom_row = QHBoxLayout()
bottom_row.addWidget(set_title_button)
bottom_row.addWidget(set_style_button)
bottom_row.addWidget(set_noise_scale_button)
layout.addLayout(bottom_row)
# connections
set_title_button.clicked.connect(self.on_set_title_button_click)
set_style_button.clicked.connect(self.on_set_style_button_clicked)
set_noise_scale_button.clicked.connect(
self.on_set_noise_scale_button_click
)
def on_set_title_button_click(self):
title, ok = QInputDialog.getText(
self,
"Choose title",
"Choose the title for the figure",
)
if ok:
self.graph.set_title(title)
def on_set_style_button_clicked(self):
style_list = ['default'] + plt.style.available
style, ok = QInputDialog.getItem(
self,
"Choose style",
"Pick a style",
style_list
)
self.graph.set_style(style)
def on_set_noise_scale_button_click(self):
scale, ok = QInputDialog.getDouble(
self,
"Choose noise scale",
"Type in standard deviation of noise to be applied",
0.1,
0,
0.2,
decimals=2,
step=0.01
)
if ok:
self.graph.set_noise_scale(scale)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Выбор файла. QFileDialog
#
Предоставить пользователю возможность выбирать файл(ы)/директорию(и) проще всего файловым диалогом QFileDialog и его статическими методами, среди которых мы разберем getOpenFile, позволяющий выбрать один файл.
Этот метод принимает параметры
parent
— родительский виджет;caption
— заголовок файлового диалога;dir
— путь к папке, которая открыта изначально;filter
— фильтр допустимых файлов или их список;selectedFilter
— выбранный изначально фильтр;и др.
Параметр filter
позволяет отсеивать неподходящие файлы по их расширению. Например, если приложение ожидает таблицу, то подходящими расширениями могут считаться csv
файлы, а также Excel
таблицы — файлы с расширениями xls
и xlsx
. В таком случае можно указать в качестве фильтра строку "*.csv *.xls *.xlsx"
. Тогда файлы с расширениями отличными от перечисленных не будут показывать в диалоге выбора файлов.
Чтобы яснее дать пользователю, что ожидаются файлы-таблицы, можно в качестве параметра filter
передать строку "Tables (*.csv *.xls *.xlsx)"
, т.е. указать расширения в скобках, а перед ними указать подсказку пользователю.
Для изображений такая строка могла бы выглядеть "Images (*.png *.jpg *.xpm)"
, для файлов исходного кода python
— "Python source code (*.py)"
, для всех файлов — "All files (*)"
. Чтобы указать список фильтров, эти фильтры объединяются в одну строку с разделителем ;;
.
Возвращает этот метод кортеж из двух значений. Первое значение — путь к выбранному пользователем файлу в виде строки, второе — фильтр, при котором был выбран этот файл. Если пользователь нажимает кнопку cancel
, то в качестве пути возвращается пустая строка.
В качестве примера под спойлером ниже приводится приложение, которое выводит изображение, выбранное пользователем в файловом диалоге.
Код.
import os.path
import sys
from PySide6.QtGui import *
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
self.setCentralWidget(widget)
# buttons
self.label = QLabel("An image will be displayed here")
push_button = QPushButton("Choose Image")
layout.addWidget(self.label)
layout.addWidget(push_button)
# connections
push_button.clicked.connect(self.choose_image)
def choose_image(self):
path, _ = QFileDialog.getOpenFileName(
self,
caption="Pick an image",
dir=os.path.join("..", "images"),
filter="Images (*.png *.xpm *.jpg)"
)
if path:
self.label.setPixmap(QPixmap(path))
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()