После написания статьи о чудесном пакете ConfigObj в своём блоге, один из моих читателей спросил о том, есть ли способ использовать файл конфигурации для генерирования диалога. Что же, я решил дать этой идее шанс. Для выполнения указаний из этой статьи вам нужно установить ConfigObj в дополнение к wxPython. Если у вас он даже не скачан, вам нужно использовать pip для его установки:
1 |
pip install configobj |
Теперь, когда он установлен, можем продолжать.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Файл конфигурации
Нам нужно создать что-то вроде файла конфигурации в wxPython, который мы сможем использовать для генерирования нашего диалога. Давайте взглянем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[Labels] server = Update Server: username = Username: password = Password: update interval = Update Interval: agency = Agency Filter: filters = "" [Values] server = http://www.someCoolWebsite/hackery.php username = "" password = "" update interval = 2 agency_choices = Include all agencies except, Include all agencies except, Exclude all agencies except filters = "" |
Данный файл конфигурации содержит 2 секции: ярлыки и значения. Секция ярлыков содержит ярлыки, который мы используем при создании управления для wx.StaticText. Секция значения содержит несколько примерных значений, которые мы можем использовать для корреспонденции между виджетами управления текстом и одним комбо-боксом.
Обратите внимание, что поле agency_choices является списком. Первый итем в списке будет опцией по умолчанию в комбо-боксе, а другие два итема будут реальным контентом виджета.
Создаём пользовательский интерфейс
Теперь вы знаете, что будет служить основой для нашего интерфейса, так что мы можем приступать к написанию кода. Когда код будет закончен, то вы увидите диалоговое окно, которое выглядит также, как на картинке выше. Давайте погрузимся в сам код, чтобы лучше его понять:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
import configobj import wx class PreferencesDialog(wx.Dialog): """ Создаёт и отображает диалог с параметрами, позволяющий пользователю изменить несколько настроек. """ def __init__(self): """ Инициализация диалога """ wx.Dialog.__init__(self, None, title='Preferences', size=(550,300)) self.createWidgets() def createWidgets(self): """ Создаёт и располагает виджеты в диалоге """ lblSizer = wx.BoxSizer(wx.VERTICAL) valueSizer = wx.BoxSizer(wx.VERTICAL) btnSizer = wx.StdDialogButtonSizer() colSizer = wx.BoxSizer(wx.HORIZONTAL) mainSizer = wx.BoxSizer(wx.VERTICAL) iniFile = "config.ini" self.config = configobj.ConfigObj(iniFile) labels = self.config["Labels"] values = self.config["Values"] self.widgetNames = values font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD) for key in labels: value = labels[key] lbl = wx.StaticText(self, label=value) lbl.SetFont(font) lblSizer.Add(lbl, 0, wx.ALL, 5) for key in values: print(key) value = values[key] if isinstance(value, list): default = value[0] choices = value[1:] cbo = wx.ComboBox(self, value=value[0], size=wx.DefaultSize, choices=choices, style=wx.CB_DROPDOWN|wx.CB_READONLY, name=key) valueSizer.Add(cbo, 0, wx.ALL, 5) else: txt = wx.TextCtrl(self, value=value, name=key) valueSizer.Add(txt, 0, wx.ALL|wx.EXPAND, 5) saveBtn = wx.Button(self, wx.ID_OK, label="Save") saveBtn.Bind(wx.EVT_BUTTON, self.onSave) btnSizer.AddButton(saveBtn) cancelBtn = wx.Button(self, wx.ID_CANCEL) btnSizer.AddButton(cancelBtn) btnSizer.Realize() colSizer.Add(lblSizer) colSizer.Add(valueSizer, 1, wx.EXPAND) mainSizer.Add(colSizer, 0, wx.EXPAND) mainSizer.Add(btnSizer, 0, wx.ALL | wx.ALIGN_RIGHT, 5) self.SetSizer(mainSizer) def onSave(self, event): """ Сохраняет значения на диске """ for name in self.widgetNames: widget = wx.FindWindowByName(name) if isinstance(widget, wx.ComboBox): selection = widget.GetValue() choices = widget.GetItems() choices.insert(0, selection) self.widgetNames[name] = choices else: value = widget.GetValue() self.widgetNames[name] = value self.config.write() self.EndModal(0) class MyApp(wx.App): """""" def OnInit(self): """Constructor""" dlg = PreferencesDialog() dlg.ShowModal() dlg.Destroy() return True if __name__ == "__main__": app = MyApp(False) app.MainLoop() |
Сначала мы превращаем wx.Dialog в подкласс и используем метод createWidgets. Этот метод прочтёт ваш файл конфигурации и использует данные из него для создания дисплея. Сразу же после прочтения файла конфигурации, мы loop over ключей в секции ярлыков, и создаём статичные текстовые контроли, если нужно. Далее, мы loop over значений в другой секции и используем условия, чтобы проверить тип виджета. В данном случае, нас интересуют только wx.TextCtrl и wx.Combobox. Как раз здесь ConfigObj и приходит к нам на помощь: он позволяет внести несколько записей для тайпкаста в файл конфигурации. Если вы используете configspec, вы можете сделать тайпкаст более зернистым, и это может стать направлением, которое я когда-то рассмотрю в своей статье. Учтите, что для текстового контроля и комбо-бокса, я задавал поле с именем. Это очень важно для сохранения данных, в чём мы и убедимся, когда захотим сделать код универсальным.
Как бы то ни было, в обеих петлях, мы используем вертикальные BoxSizers, чтобы держать наши виджеты. Вы можете захотеть сменить его на GridBagSizer и FlexGridSizer для специальных частей вашего интерфейса. Лично я предпочитаю BoxSizers. Я также использовал StdDialogButtonSizer для кнопок. Если вы используете корректные стандартные id для кнопок, то сайзер расположит их в правильном порядке на всех платформах. Он очень удобен, а также не требует никаких значений.
Следующий метод, который нас интересует, называется onSave. Здесь мы сохраняем всё, что ввёл пользователь. Ранее в программе, я захватывал имена виджетов из файла конфигурации, и как раз их мы сейчас и loop over. Мы вызываем wx.FindWindowByName для того, чтобы найти виджет по его имени. Затем мы снова используем isinstance, чтобы выяснить какого типа этот виджет. Как только это произошло, мы захватываем значение, которое содержится в виджете, с помощью GetValue и назначаем это значение в корректное поле в нашей конфигурации. Когда loop, закончится, мы записываем данные на диск. Последним шагом является вызов EndModal(0) для закрытия диалога, а затем, в свою очередь, и приложения.
Итоги
Теперь вы ознакомились с азами генерирования диалога из файла конфигурации. Я думаю, что использование чего-то вроде словаря с названиями типов виджетов (скорее всего в стрингах), позволит вам использовать данный скрипт с другими виджетами. Вы также заметите, что в данном примере напрочь отсутствует валидация! Это первое, что вам нужно сделать, расширяя этот фрагмент кода. Используйте своё воображение и сообщите мне, что у вас получилось.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»