name
pyqt-dialogs
description
PyQt/PySide6 dialogs - QFileDialog, QMessageBox, QInputDialog, QColorDialog, custom QDialog patterns
metadata
author
version
tags
mte90
1.0.0
python
qt
pyqt
pyside
dialogs
ui
Standard and custom dialog patterns for PyQt/PySide6 applications.
from PySide6 .QtWidgets import QFileDialog
# Open single file
filename , _ = QFileDialog .getOpenFileName (
self ,
"Open File" ,
"/home/user" , # Starting directory
"Images (*.png *.jpg);;Text Files (*.txt);;All Files (*)"
)
if filename :
print (f"Selected: { filename } " )
# Save file
filename , _ = QFileDialog .getSaveFileName (
self ,
"Save File" ,
"/home/user/untitled.txt" ,
"Text Files (*.txt);;All Files (*)"
)
# Select directory
directory = QFileDialog .getExistingDirectory (
self ,
"Select Directory" ,
"/home/user" ,
QFileDialog .Option .ShowDirsOnly
)
# Open multiple files
files , _ = QFileDialog .getOpenFileNames (
self ,
"Open Files" ,
"/home/user" ,
"Images (*.png *.jpg)"
)
for f in files :
print (f )
# Options
options = QFileDialog .Option .DontUseNativeDialog # Use Qt dialog instead of OS dialog
filename , _ = QFileDialog .getOpenFileName (self , "Open" , "" , "" , options = options )
from PySide6 .QtWidgets import QMessageBox
# Question dialog
reply = QMessageBox .question (
self ,
"Confirm" ,
"Are you sure?" ,
QMessageBox .StandardButton .Yes | QMessageBox .StandardButton .No ,
QMessageBox .StandardButton .No
)
if reply == QMessageBox .StandardButton .Yes :
print ("User confirmed" )
# Information
QMessageBox .information (self , "Info" , "Operation completed successfully" )
# Warning
QMessageBox .warning (self , "Warning" , "This action cannot be undone" )
# Critical error
QMessageBox .critical (self , "Error" , "Failed to connect to server" )
# About
QMessageBox .about (self , "About" , "My App v1.0\n \n Copyright 2024" )
# About Qt
QMessageBox .aboutQt (self )
# Custom buttons
msg = QMessageBox (self )
msg .setWindowTitle ("Custom Dialog" )
msg .setText ("Continue?" )
msg .setIcon (QMessageBox .Icon .Question )
msg .addButton ("Yes" , QMessageBox .ButtonRole .YesRole )
msg .addButton ("No" , QMessageBox .ButtonRole .NoRole )
msg .addButton ("Cancel" , QMessageBox .ButtonRole .RejectRole )
result = msg .exec ()
print (f"Button role: { msg .buttonRole (msg .clickedButton ())} " )
from PySide6 .QtWidgets import QInputDialog , QLineEdit
# Get text
text , ok = QInputDialog .getText (
self ,
"Input" ,
"Enter name:" ,
QLineEdit .EchoMode .Normal ,
"Default value"
)
if ok and text :
print (f"Name: { text } " )
# Get integer
value , ok = QInputDialog .getInt (
self ,
"Input" ,
"Enter age:" ,
25 , # Default
0 , # Min
120 , # Max
1 # Step
)
if ok :
print (f"Age: { value } " )
# Get double
price , ok = QInputDialog .getDouble (
self ,
"Input" ,
"Enter price:" ,
0.0 ,
0.0 ,
1000.0 ,
2 # Decimals
)
if ok :
print (f"Price: ${ price :.2f} " )
# Get item from list
items = ["Option 1" , "Option 2" , "Option 3" ]
item , ok = QInputDialog .getItem (
self ,
"Select" ,
"Choose an option:" ,
items ,
0 , # Current index
False # Editable
)
if ok :
print (f"Selected: { item } " )
# Get multiline text
text , ok = QInputDialog .getMultiLineText (
self ,
"Input" ,
"Enter description:" ,
"Default\n text"
)
from PySide6 .QtWidgets import QColorDialog
from PySide6 .QtGui import QColor
# Get color
color = QColorDialog .getColor (
QColor (255 , 0 , 0 ), # Default color
self ,
"Select Color"
)
if color .isValid ():
print (f"Color: { color .name ()} " ) # "#ff0000"
widget .setStyleSheet (f"background-color: { color .name ()} ;" )
# With alpha
color = QColorDialog .getColor (
QColor (255 , 0 , 0 , 128 ),
self ,
"Select Color with Alpha" ,
QColorDialog .ColorDialogOption .ShowAlphaChannel
)
# Get color with options
options = (
QColorDialog .ColorDialogOption .ShowAlphaChannel |
QColorDialog .ColorDialogOption .NoButtons
)
color = QColorDialog .getColor (Qt .white , self , "Color" , options )
from PySide6 .QtWidgets import QFontDialog
from PySide6 .QtGui import QFont
# Get font
font , ok = QFontDialog .getFont (
QFont ("Arial" , 12 ), # Default font
self ,
"Select Font"
)
if ok :
print (f"Font: { font .family ()} , Size: { font .pointSize ()} " )
widget .setFont (font )
# With options
font , ok = QFontDialog .getFont (
QFont (),
self ,
"Select Font" ,
QFontDialog .FontDialogOption .MonospacedFonts
)
from PySide6 .QtWidgets import (
QDialog , QVBoxLayout , QHBoxLayout , QLabel ,
QLineEdit , QDialogButtonBox , QFormLayout
)
class InputDialog (QDialog ):
def __init__ (self , parent = None ):
super ().__init__ (parent )
self .setWindowTitle ("Input Dialog" )
self .setMinimumWidth (400 )
layout = QVBoxLayout (self )
# Form
form = QFormLayout ()
self .name_edit = QLineEdit ()
self .email_edit = QLineEdit ()
form .addRow ("Name:" , self .name_edit )
form .addRow ("Email:" , self .email_edit )
layout .addLayout (form )
# Buttons
buttons = QDialogButtonBox (
QDialogButtonBox .StandardButton .Ok |
QDialogButtonBox .StandardButton .Cancel
)
buttons .accepted .connect (self .accept )
buttons .rejected .connect (self .reject )
layout .addWidget (buttons )
def get_values (self ):
return {
"name" : self .name_edit .text (),
"email" : self .email_edit .text ()
}
def set_values (self , name = "" , email = "" ):
self .name_edit .setText (name )
self .email_edit .setText (email )
# Usage
dialog = InputDialog (self )
dialog .set_values ("John" , "john@example.com" )
if dialog .exec () == QDialog .DialogCode .Accepted :
values = dialog .get_values ()
print (f"Name: { values ['name' ]} , Email: { values ['email' ]} " )
class ValidatedDialog (QDialog ):
def __init__ (self , parent = None ):
super ().__init__ (parent )
self .setWindowTitle ("Validated Input" )
layout = QVBoxLayout (self )
# Input
form = QFormLayout ()
self .age_spin = QSpinBox ()
self .age_spin .setRange (0 , 120 )
self .email_edit = QLineEdit ()
form .addRow ("Age:" , self .age_spin )
form .addRow ("Email:" , self .email_edit )
layout .addLayout (form )
# Error label
self .error_label = QLabel ()
self .error_label .setStyleSheet ("color: red;" )
layout .addWidget (self .error_label )
# Buttons
self .buttons = QDialogButtonBox (
QDialogButtonBox .StandardButton .Ok |
QDialogButtonBox .StandardButton .Cancel
)
self .buttons .accepted .connect (self .try_accept )
self .buttons .rejected .connect (self .reject )
layout .addWidget (self .buttons )
def try_accept (self ):
if not self .validate ():
return
self .accept ()
def validate (self ):
import re
# Validate email
email = self .email_edit .text ()
if not email :
self .error_label .setText ("Email is required" )
return False
if not re .match (r"[^@]+@[^@]+\.[^@]+" , email ):
self .error_label .setText ("Invalid email format" )
return False
self .error_label .clear ()
return True
def get_values (self ):
return {
"age" : self .age_spin .value (),
"email" : self .email_edit .text ()
}
class SearchDialog (QDialog ):
"""Non-modal (modeless) dialog that stays open."""
searchRequested = Signal (str )
def __init__ (self , parent = None ):
super ().__init__ (parent )
self .setWindowTitle ("Search" )
self .setWindowFlags (
Qt .WindowType .Dialog |
Qt .WindowType .WindowCloseButtonHint
)
layout = QVBoxLayout (self )
self .search_edit = QLineEdit ()
self .search_edit .setPlaceholderText ("Enter search term..." )
self .search_edit .returnPressed .connect (self .search )
self .search_btn = QPushButton ("Search" )
self .search_btn .clicked .connect (self .search )
layout .addWidget (self .search_edit )
layout .addWidget (self .search_btn )
def search (self ):
term = self .search_edit .text ()
if term :
self .searchRequested .emit (term )
# Usage
search_dialog = SearchDialog (self )
search_dialog .searchRequested .connect (self .perform_search )
search_dialog .show () # Use show() instead of exec() for modeless
Use standard dialogs when possible - They're familiar and consistent
Provide sensible defaults - Pre-fill common values
Validate input before accepting - Show clear error messages
Use QDialogButtonBox - Ensures correct button ordering per platform
Set minimum size - Prevent dialogs from being too small
Consider modeless dialogs - For search, find/replace, etc.
# ✅ GOOD: Use QDialog for custom dialogs
class MyDialog (QDialog ):
def __init__ (self , parent = None ):
super ().__init__ (parent )
self .setWindowTitle ("Settings" )
self .setModal (True )
layout = QVBoxLayout ()
# ... widgets ...
self .setLayout (layout )
# Confirm/Cancel buttons
btn_box = QDialogButtonBox (
QDialogButtonBox .Ok | QDialogButtonBox .Cancel
)
btn_box .accepted .connect (self .accept )
btn_box .rejected .connect (self .reject )
layout .addWidget (btn_box )
def get_value (self ):
if self .result () == QDialog .Accepted :
return self .value
# Qt 6: add native buttons
# (replace QDialogButtonBox)
self .addNativeButton (QDialogButtonBox .StandardButton .Ok )
# Modal (blocks parent)
dialog = MyDialog (parent )
result = dialog .exec () # Blocks until closed
# Non-modal (doesn't block)
dialog = MyDialog (parent )
dialog .show () # Doesn't block
Best Practices (Extended)
# ✅ GOOD: Always have Cancel button
button_box = QDialogButtonBox (
QDialogButtonBox .Ok | QDialogButtonBox .Cancel
)
# ✅ GOOD: Use QDialogButtonBox for standard buttons
# Avoid manual QDialogButton instances
# ✅ GOOD: Pass parent to dialogs
dialog = QDialog (parent_window )
# ❌ BAD: Creating dialogs without parent
dialog = QDialog () # No parent = orphaned
class MultiPageDialog (QDialog ):
def __init__ (self ):
super ().__init__ ()
self .pages = [Page1 (), Page2 (), Page3 ()]
self .current_page = 0
self .setup_ui ()
def setup_ui (self ):
main_layout = QVBoxLayout ()
# Page navigation
page_layout = QHBoxLayout ()
prev_btn = QPushButton ("← Prev" )
next_btn = QPushButton ("Next →" )
prev_btn .clicked .connect (lambda : self .set_page (- 1 ))
next_btn .clicked .connect (lambda : self .set_page (1 ))
page_layout .addWidget (prev_btn )
page_layout .addWidget (next_btn )
self .page_container = QWidget ()
self .page_container_layout = QVBoxLayout (self .page_container )
self .page_container_layout .addWidget (self .pages [0 ])
main_layout .addLayout (page_layout )
main_layout .addWidget (self .page_container )
self .setLayout (main_layout )
def set_page (self , delta ):
self .current_page += delta
if 0 <= self .current_page < len (self .pages ):
self .page_container_layout .deleteWidget (self .pages [0 ])
self .page_container_layout .addWidget (self .pages [self .current_page ])
else :
self .current_page = max (0 , min (len (self .pages )- 1 , self .current_page ))