В этой главе мы поговорим о:
- создании списков в различных стилях
- работа с элементами в списке
- реакции на выбор пользователем элемента из списка
- редактирование меток и сортировка списка
- создание большого списка (large list)
В этой главе мы поговорим о:
import os
import wx
try:
import qrcode
except ImportError:
qrcode = None
try:
import PyQRNative
except ImportError:
PyQRNative = None
########################################################################
class QRPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
self.photo_max_size = 240
sp = wx.StandardPaths.Get()
self.defaultLocation = sp.GetDocumentsDir()
img = wx.EmptyImage(240,240)
self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(img))
qrDataLbl = wx.StaticText(self, label="Текст для перевода в QR код:")
self.qrDataTxt = wx.TextCtrl(self, value="http://www.mousevspython.com", size=(200,-1))
instructions = "Имя файла с QR кодом"
instructLbl = wx.StaticText(self, label=instructions)
self.qrPhotoTxt = wx.TextCtrl(self, size=(200,-1))
browseBtn = wx.Button(self, label='Изменить место для сохранения')
browseBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
defLbl = "Сохраняется по умолчанию в: " + self.defaultLocation
self.defaultLocationLbl = wx.StaticText(self, label=defLbl)
qrcodeBtn = wx.Button(self, label="Создать QR при помощи qrcode")
qrcodeBtn.Bind(wx.EVT_BUTTON, self.onUseQrcode)
pyQRNativeBtn = wx.Button(self, label="Создать QR при помощи PyQRNative")
pyQRNativeBtn.Bind(wx.EVT_BUTTON, self.onUsePyQR)
# компоновщик
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
qrDataSizer = wx.BoxSizer(wx.HORIZONTAL)
locationSizer = wx.BoxSizer(wx.HORIZONTAL)
qrBtnSizer = wx.BoxSizer(wx.VERTICAL)
qrDataSizer.Add(qrDataLbl, 0, wx.ALL, 5)
qrDataSizer.Add(self.qrDataTxt, 1, wx.ALL|wx.EXPAND, 5)
self.mainSizer.Add(wx.StaticLine(self, wx.ID_ANY),
0, wx.ALL|wx.EXPAND, 5)
self.mainSizer.Add(qrDataSizer, 0, wx.EXPAND)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
locationSizer.Add(instructLbl, 0, wx.ALL, 5)
locationSizer.Add(self.qrPhotoTxt, 0, wx.ALL, 5)
locationSizer.Add(browseBtn, 0, wx.ALL, 5)
self.mainSizer.Add(locationSizer, 0, wx.ALL, 5)
self.mainSizer.Add(self.defaultLocationLbl, 0, wx.ALL, 5)
qrBtnSizer.Add(qrcodeBtn, 0, wx.ALL, 5)
qrBtnSizer.Add(pyQRNativeBtn, 0, wx.ALL, 5)
self.mainSizer.Add(qrBtnSizer, 0, wx.ALL|wx.CENTER, 10)
self.SetSizer(self.mainSizer)
self.Layout()
#----------------------------------------------------------------------
def onBrowse(self, event):
""""""
dlg = wx.DirDialog(self, "Выберите папку:",
style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.defaultLocation = path
self.defaultLocationLbl.SetLabel("Сохраняем в: %s" % path)
dlg.Destroy()
#----------------------------------------------------------------------
def onUseQrcode(self, event):
"""
https://github.com/lincolnloop/python-qrcode
"""
qr = qrcode.QRCode(version=1, box_size=10, border=4)
qr.add_data(self.qrDataTxt.GetValue())
qr.make(fit=True)
x = qr.make_image()
qr_file = os.path.join(self.defaultLocation, self.qrPhotoTxt.GetValue() + ".jpg")
img_file = open(qr_file, 'wb')
x.save(img_file, 'JPEG')
img_file.close()
self.showQRCode(qr_file)
#----------------------------------------------------------------------
def onUsePyQR(self, event):
"""
http://code.google.com/p/pyqrnative/
"""
qr = PyQRNative.QRCode(20, PyQRNative.QRErrorCorrectLevel.L)
qr.addData(self.qrDataTxt.GetValue())
qr.make()
im = qr.makeImage()
qr_file = os.path.join(self.defaultLocation, self.qrPhotoTxt.GetValue() + ".jpg")
img_file = open(qr_file, 'wb')
im.save(img_file, 'JPEG')
img_file.close()
self.showQRCode(qr_file)
#----------------------------------------------------------------------
def showQRCode(self, filepath):
""""""
img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
# масштабируем изображение
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.photo_max_size
NewH = self.photo_max_size * H / W
else:
NewH = self.photo_max_size
NewW = self.photo_max_size * W / H
img = img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
self.Refresh()
########################################################################
class QRFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="QR Code Viewer", size=(550,500))
panel = QRPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = QRFrame()
frame.Show()
app.MainLoop()
import PyQRNative
def makeQR(data_string,path,level=1):
quality={1: PyQRNative.QRErrorCorrectLevel.L,
2: PyQRNative.QRErrorCorrectLevel.M,
3: PyQRNative.QRErrorCorrectLevel.Q,
4: PyQRNative.QRErrorCorrectLevel.H}
size=3
while 1:
try:
q = PyQRNative.QRCode(size,quality[level])
q.addData(data_string)
q.make()
im=q.makeImage()
im.save(path,format="png")
break
except TypeError:
size+=1
Автор: Ishayahu Lastov
В этой главе рассказывается о том, как:
9.1 Работа с модальными диалогами
9.1.1 Как создать модальный диалог
import wx
class SubclassDialog(wx.Dialog):
def __init__(self): #инициируем диалог
wx.Dialog.__init__(self, None, -1, 'Dialog Subclass',size=(300, 100))
okButton = wx.Button(self, wx.ID_OK, "OK", pos=(15, 15))
okButton.SetDefault()
cancelButton = wx.Button(self, wx.ID_CANCEL, "Cancel",pos=(115, 15))
if __name__ == '__main__':
app = wx.PySimpleApp()
dialog = SubclassDialog()
result = dialog.ShowModal() #показываем модальный диалог
if result == wx.ID_OK:
print "OK"
else:
print "Cancel"
dialog.Destroy()
после чего приложению остаётся лишь корректно обработать значения кнопок.
9.1.2 Как вывести пользователю сообщение?
Листинг 9.2
import wx
if __name__ == "__main__":
app = wx.PySimpleApp()
# с использованием классов
dlg = wx.MessageDialog(None, "Is this explanation OK?",
'A Message Box',wx.YES_NO | wx.ICON_QUESTION)
retCode = dlg.ShowModal()
if (retCode == wx.ID_YES):
print "yes"
else:
print "no"
dlg.Destroy()
# с использованием функции
retCode = wx.MessageBox("Is this way easier?", "Via Function",
wx.YES_NO | wx.ICON_QUESTION)
| Стиль | Описание |
|---|---|
| wx.CANCEL | Добавляет кнопку «отменить» с идентификатором wx.ID_CANCEL |
| wx.NO_DEFAULT | Если установлен флаг wx.YES_NO, то кнопкой по умолчанию будет «нет» |
| wx.OK | Добавляет кнопку «да» с идентификатором wx.ID_OK |
| wx.YES_DEFAULT | Если установлен флаг wx.YES_NO, то кнопкой по умолчанию будет «да». Такая настройка применяется по умолчанию |
| wx.YES_NO | Добавляет кнопки «да» и «нет» со значениями wx.ID_OK и wx.ID_NO соответственно |
Вторая группа флагов отвечает за знак, который будет отображён рядом с текстом:
| Стиль | Описание |
|---|---|
| wx.ICON_ERROR | Знак ошибки |
| wx.ICON_EXCLAMATION | Восклицательный знак |
| wx.ICON_HAND | То же, что и wx.ICON_ERROR |
| wx.ICON_INFORMATION | Знак информационного сообщения (буква i) |
| wx.ICON_QUESTION | Вопросительный знак |
Использование функции wx.MessageBox()
9.1.3 Как получить короткий текст от пользователя?
Листинг9.3
import wx
if __name__ == "__main__":
app = wx.PySimpleApp()
dialog = wx.TextEntryDialog(None,
"What kind of text would you like to enter?",
"Text Entry", "Default Value", style=wx.OK|wx.CANCEL)
if dialog.ShowModal() == wx.ID_OK:
print "You entered: %s" % dialog.GetValue()
dialog.Destroy()
9.2 Использование стандартных диалогов
9.2.1 Диалог выбора файла
Листинг 9.6
import wx
import os
if __name__ == "__main__":
app = wx.PySimpleApp()
wildcard = "Python source (*.py)|*.py|"
"Compiled Python (*.pyc)|*.pyc|"
"All files (*.*)|*.*"
dialog = wx.FileDialog(None, "Choose a file", os.getcwd(),
"", wildcard, wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
print dialog.GetPath()
dialog.Destroy()
Выбор файла
Выбор папки
Листинг 9.7
import wx
if __name__ == "__main__":
app = wx.PySimpleApp()
dialog = wx.DirDialog(None, "Choose a directory:",
style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
if dialog.ShowModal() == wx.ID_OK:
print dialog.GetPath()
dialog.Destroy()
9.2.2 Диалог выбора шрифта
9.3 Создание мастеров
import wx
import wx.wizard
# создаём простую страницу
class TitledPage(wx.wizard.WizardPageSimple):
def __init__(self, parent, title):
wx.wizard.WizardPageSimple.__init__(self, parent)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
titleText = wx.StaticText(self, -1, title)
titleText.SetFont(
wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
self.sizer.Add(titleText, 0,
wx.ALIGN_CENTRE | wx.ALL, 5)
self.sizer.Add(wx.StaticLine(self, -1), 0,
wx.EXPAND | wx.ALL, 5)
if __name__ == "__main__":
app = wx.PySimpleApp()
# создаём экземпляр мастера
wizard = wx.wizard.Wizard(None, -1, "Simple Wizard")
# создаём страницы мастера
page1 = TitledPage(wizard, "Page 1")
page2 = TitledPage(wizard, "Page 2")
page3 = TitledPage(wizard, "Page 3")
page4 = TitledPage(wizard, "Page 4")
page1.sizer.Add(wx.StaticText(page1, -1,
"Testing the wizard"))
page4.sizer.Add(wx.StaticText(page4, -1,
"This is the last page."))
# задаём последовательность страниц
wx.wizard.WizardPageSimple_Chain(page1, page2)
wx.wizard.WizardPageSimple_Chain(page2, page3)
wx.wizard.WizardPageSimple_Chain(page3, page4)
# задаём размер мастера
wizard.FitToPage(page1)
# запускаем мастера
if wizard.RunWizard(page1):
print "Success"
Создание экземпляра wx.wizard.Wizard — первый шаг в использовании мастера. Конструктор класса выглядит так:
wx.wizard.Wizard(parent, id=-1, title=wx.EmptyString, bitmap=wx.NullBitmap, pos=wx.DefaultPosition)
В нашем примере parent, id, title и pos имеют тот же смысл, что и в конструкторе wx.Panel. Если передан аргумент bitmap, то он будет отображаться на каждой странице. Из доступных флагов стиля есть только wx.WIZARD_EX_HELPBUTTON, который отображает кнопку помощи. Это флаг расширенного стиля и его надо устанавливать используя двухступенчатый механизм, описанный в главе 8.
Обычно, для установки размера Вы будете использовать FitToSize(), как в нашем примере, но ничто не мешает Вам задать размер самому с помощью метода SetPageSize(), передав ему в качестве аргумента кортеж или экземпляр wx.Size. Для получения текущего размера используется метод GetPageSize(). В любом случае размер будет использоваться лишь для той части мастера, которая предназначена для отображения страниц, сам мастер будет большего размера (например, из-за кнопок «далее» и «назад» — прим. переводчика).
Контролировать работу мастера можно с помощью его же методов: метод GetCurrentPage() возвращает отображаемую страницу, если же мастер ещё не запущен он вернёт None. Определить, имеет ли текущая стра
ница следующую или предыдущую можно с помощью методов HasNextPage() и HasPrevPage(). Для запуска мастера используется метод RunWizard(), как видно из нашего листинга.
Мастер продуцирует командные события, которые Вы сами можете перехватывать и обрабатывать; они перечислены в таблице ниже. Каждое из этих событий относится к классу wx.wizard.WizardEvent, для которого доступно два метода. GetPage() вернёт нам страницу, на которой было создано событие, а GetDirection() вернёт True, если пользователь движется вперёд по мастеру, и False в другом случае.
| Событие | Описание |
|---|---|
| EVT_WIZARD_CANCEL | Создаётся, когда пользователь нажимает кнопку «отмена». Это событие может быть отменено с помощью метода Veto(). В таком случае диалог не завершит работу. |
| EVT_WIZARD_FINISHED | Создаётся, когда пользователь нажимает кнопку «готово» в конце мастера. |
| EVT_WIZARD_HELP | Создаётся, когда пользователь нажимает кнопку справки |
| EVT_WIZARD_PAGE_CHANGED | Создаётся после смены страницы для постобработки |
| EVT_WIZARD_PAGE_CHANGING | Создаётся когда пользователь нажал на кнопку изменения страницы, но страница ещё не изменилась. Это событие так же может быть отменено с помощью Veto(), например, если пользователь заполнил не все поля. |
9.4 Отображение подсказок при запуске
Листинг 9.12
import wx
if __name__ == "__main__":
app = wx.PySimpleApp()
provider = wx.CreateFileTipProvider("tips.txt", 0)
wx.ShowTip(None, provider, True)
Автор: Ishayahu Lastov
9.5 Использование валидаторов
9.5.1 Как с помощью валидатора убедиться, что я получил верные данные?
import wx
about_txt = """
Валидатор, используемый в этом примере проверяет наличие текста в
текстовых полях при нажатии на кнопку "ок" и не даёт Вам закрыть диалог,
если это условие не выполняется."""
# создаём подкласс валидатора
class NotEmptyValidator(wx.PyValidator):
def __init__(self):
wx.PyValidator.__init__(self)
def Clone(self):
"""
Обратите внимание, что каждый валидатор должен реализовать метод Clone().
"""
return NotEmptyValidator()
# метод проверки
def Validate(self, win):
textCtrl = self.GetWindow()
text = textCtrl.GetValue()
if len(text) == 0:
wx.MessageBox("This field must contain some text!", "Error")
textCtrl.SetBackgroundColour("pink")
textCtrl.SetFocus()
textCtrl.Refresh()
return False
else:
textCtrl.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
textCtrl.Refresh()
return True
def TransferToWindow(self):
return True
def TransferFromWindow(self):
return True
class MyDialog(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, -1, "Validators: validating")
# Создаём поля для ввода текста
about = wx.StaticText(self, -1, about_txt)
name_l = wx.StaticText(self, -1, "Name:")
email_l = wx.StaticText(self, -1, "Email:")
phone_l = wx.StaticText(self, -1, "Phone:")
# используем валидаторы
name_t = wx.TextCtrl(self, validator=NotEmptyValidator())
email_t = wx.TextCtrl(self, validator=NotEmptyValidator())
phone_t = wx.TextCtrl(self, validator=NotEmptyValidator())
# Используем стандартные ID кнопок
okay = wx.Button(self, wx.ID_OK)
okay.SetDefault()
cancel = wx.Button(self, wx.ID_CANCEL)
# размещаем виджеты с помощью координаторов
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(about, 0, wx.ALL, 5)
sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
fgs = wx.FlexGridSizer(3, 2, 5, 5)
fgs.Add(name_l, 0, wx.ALIGN_RIGHT)
fgs.Add(name_t, 0, wx.EXPAND)
fgs.Add(email_l, 0, wx.ALIGN_RIGHT)
fgs.Add(email_t, 0, wx.EXPAND)
fgs.Add(phone_l, 0, wx.ALIGN_RIGHT)
fgs.Add(phone_t, 0, wx.EXPAND)
fgs.AddGrowableCol(1)
sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)
btns = wx.StdDialogButtonSizer()
btns.AddButton(okay)
btns.AddButton(cancel)
btns.Realize()
sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
app = wx.PySimpleApp()
dlg = MyDialog()
dlg.ShowModal()
dlg.Destroy()
app.MainLoop()
9.5.2 Передача данных с помощью валидаторов
import wx
import pprint
about_txt = """
В данном примере валидатор используется для демонстрации
возможности автоматической передачи данных в и из текстового поля
при открытии и закрытии диалога."""
# объявляем валидатор
class DataXferValidator(wx.PyValidator):
def __init__(self, data, key):
wx.PyValidator.__init__(self)
self.data = data
self.key = key
def Clone(self):
"""
Будьте внимательны: каждый валидатор должен реализовать метод Clone()
"""
return DataXferValidator(self.data, self.key)
# не проверяем данные
def Validate(self, win):
return True
# вызывается при открытии диалога
def TransferToWindow(self):
textCtrl = self.GetWindow()
textCtrl.SetValue(self.data.get(self.key, ""))
return True
# вызывается при закрытии диалога
def TransferFromWindow(self):
textCtrl = self.GetWindow()
self.data[self.key] = textCtrl.GetValue()
return True
class MyDialog(wx.Dialog):
def __init__(self, data):
wx.Dialog.__init__(self, None, -1, "Validators: data transfer")
about = wx.StaticText(self, -1, about_txt)
name_l = wx.StaticText(self, -1, "Name:")
email_l = wx.StaticText(self, -1, "Email:")
phone_l = wx.StaticText(self, -1, "Phone:")
name_t = wx.TextCtrl(self,
validator=DataXferValidator(data, "name"))
email_t = wx.TextCtrl(self,
validator=DataXferValidator(data, "email"))
phone_t = wx.TextCtrl(self,
validator=DataXferValidator(data, "phone"))
okay = wx.Button(self, wx.ID_OK)
okay.SetDefault()
cancel = wx.Button(self, wx.ID_CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(about, 0, wx.ALL, 5)
sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
fgs = wx.FlexGridSizer(3, 2, 5, 5)
fgs.Add(name_l, 0, wx.ALIGN_RIGHT)
fgs.Add(name_t, 0, wx.EXPAND)
fgs.Add(email_l, 0, wx.ALIGN_RIGHT)
fgs.Add(email_t, 0, wx.EXPAND)
fgs.Add(phone_l, 0, wx.ALIGN_RIGHT)
fgs.Add(phone_t, 0, wx.EXPAND)
fgs.AddGrowableCol(1)
sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)
btns = wx.StdDialogButtonSizer()
btns.AddButton(okay)
btns.AddButton(cancel)
btns.Realize()
sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
app = wx.PySimpleApp()
data = { "name" : "Jordyn Dunn" }
dlg = MyDialog(data)
dlg.ShowModal()
dlg.Destroy()
wx.MessageBox("You entered these values:nn" +
pprint.pformat(data))
app.MainLoop()
9.5.3 Как проверять данные в процессе ввода?
import wx
import string
about_txt = """
Валидатор, используемый в данном примере проверяет "на лету" ввод данных
пользователем и не ждёт нажатия кнопки "ок". Первое поле не допускает
ввода цифр, во второе поле можно ввести всё, что угодно, а в третье -
всё, кроме букв.
"""
class CharValidator(wx.PyValidator):
def __init__(self, flag):
wx.PyValidator.__init__(self)
self.flag = flag
# связываем функцию с событием ввода символа
self.Bind(wx.EVT_CHAR, self.OnChar)
def Clone(self):
"""
Будьте внимательны: каждый валидатор должен реализовать метод Clone()
"""
return CharValidator(self.flag)
def Validate(self, win):
return True
def TransferToWindow(self):
return True
def TransferFromWindow(self):
return True
# обработчик ввода данных в виджет
def OnChar(self, evt):
key = chr(evt.GetKeyCode())
if self.flag == "no-alpha" and key in string.letters:
return
if self.flag == "no-digit" and key in string.digits:
return
evt.Skip()
class MyDialog(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, -1, "Validators: behavior
modification")
# Создаём поля для ввода текста
about = wx.StaticText(self, -1, about_txt)
name_l = wx.StaticText(self, -1, "Name:")
email_l = wx.StaticText(self, -1, "Email:")
phone_l = wx.StaticText(self, -1, "Phone:")
# добавляем валидатор
name_t = wx.TextCtrl(self, validator=CharValidator("no-digit"))
email_t = wx.TextCtrl(self, validator=CharValidator("any"))
phone_t = wx.TextCtrl(self, validator=CharValidator("no-alpha"))
okay = wx.Button(self, wx.ID_OK)
okay.SetDefault()
cancel = wx.Button(self, wx.ID_CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(about, 0, wx.ALL, 5)
sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
fgs = wx.FlexGridSizer(3, 2, 5, 5)
fgs.Add(name_l, 0, wx.ALIGN_RIGHT)
fgs.Add(name_t, 0, wx.EXPAND)
fgs.Add(email_l, 0, wx.ALIGN_RIGHT)
fgs.Add(email_t, 0, wx.EXPAND)
fgs.Add(phone_l, 0, wx.ALIGN_RIGHT)
fgs.Add(phone_t, 0, wx.EXPAND)
fgs.AddGrowableCol(1)
sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)
btns = wx.StdDialogButtonSizer()
btns.AddButton(okay)
btns.AddButton(cancel)
btns.Realize()
sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
app = wx.PySimpleApp()
dlg = MyDialog()
dlg.ShowModal()
dlg.Destroy()
app.MainLoop()