Как создать приложение с графическим интерфейсом на Python с помощью wxPython
Оглавление
- Начало работы с wxPython
- Создание скелета приложения
- Создание рабочего приложения
- Заключение
- Дальнейшее чтение
Существует множество наборов инструментов графического интерфейса пользователя (GUI), которые можно использовать с языком программирования Python. Основных три: Tkinter, wxPython и PyQt. Каждый из этих наборов инструментов работает в Windows, macOS и Linux, а PyQt имеет дополнительную возможность работать на мобильных устройствах.
Графический интерфейс пользователя - это приложение, в котором есть кнопки, окна и множество других виджетов, с помощью которых пользователь может взаимодействовать с вашим приложением. Хорошим примером может служить веб-браузер. В нем есть кнопки, вкладки и главное окно, в котором загружается все содержимое.
В этой статье вы узнаете, как построить графический интерфейс пользователя на Python с помощью набора инструментов wxPython GUI.
Вот темы, которые будут рассмотрены:
- Начало работы с wxPython
- Определение графического интерфейса
- Создание скелета приложения
- Создание рабочего приложения
Давайте начнем учиться!
Начало работы с wxPython
Инструментарий графического интерфейса wxPython представляет собой обертку на языке Python вокруг библиотеки C++ под названием wxWidgets. Первый выпуск wxPython состоялся в 1998 году, так что wxPython существует уже довольно давно. Основное отличие wxPython от других наборов инструментов, таких как PyQt или Tkinter, заключается в том, что wxPython использует реальные виджеты на родной платформе, когда это возможно. Благодаря этому приложения wxPython выглядят родными для операционной системы, на которой они запущены.
PyQt и Tkinter рисуют свои виджеты сами, поэтому они не всегда совпадают с родными виджетами, хотя PyQt очень близок к этому.
Это не значит, что wxPython не поддерживает пользовательские виджеты. На самом деле, в инструментарий wxPython входит множество пользовательских виджетов, а также десятки и десятки основных виджетов. На странице wxPython downloads есть раздел Extra Files, на который стоит обратить внимание.
Здесь вы можете скачать демонстрационный пакет wxPython. Это небольшое приложение, демонстрирующее подавляющее большинство виджетов, входящих в состав wxPython. Демонстрация позволяет разработчику просматривать код в одной вкладке и запускать его во второй. Вы даже можете редактировать и повторно запускать код в демонстрационной версии, чтобы увидеть, как ваши изменения влияют на работу приложения.
Установка wxPython
Для этой статьи вы будете использовать последнюю версию wxPython, а именно wxPython 4, также известную как релиз Phoenix. Версии wxPython 3 и wxPython 2 собраны только для Python 2. Когда Робин Данн, основной сопровождающий wxPython, создавал релиз wxPython 4, он отказался от многих псевдонимов и очистил много кода, чтобы сделать wxPython более питонизированным и более простым в сопровождении.
Если вы переходите с более старой версии wxPython на wxPython 4 (Phoenix), вам стоит обратиться к следующим ссылкам:
- Classic vs Phoenix
- wxPython Project Phoenix Руководство по миграции
Пакет wxPython 4 совместим как с Python 2.7, так и с Python 3.
Теперь вы можете использовать pip
для установки wxPython 4, что было невозможно в старых версиях wxPython. Вы можете сделать следующее, чтобы установить его на свою машину:
$ pip install wxpython
Примечание: В Mac OS X для успешного завершения установки вам потребуется установить компилятор, например XCode. В Linux также может потребоваться установка некоторых зависимостей, прежде чем программа установки pip
будет работать корректно.
Например, на Xubuntu мне понадобилось установить freeglut3-dev, libgstreamer-plugins-base0.10-dev и libwebkitgtk-3.0-dev, чтобы он установился.
К счастью, сообщения об ошибках, которые выводит pip
, помогают понять, чего не хватает, и вы можете использовать раздел prerequisites на странице wxPython Github, чтобы найти необходимую информацию, если вы хотите установить wxPython на Linux.
Для наиболее популярных версий Linux существует несколько колес Python, которые вы можете найти в разделе Extras Linux с версиями GTK2 и GTK3. Чтобы установить одно из этих колес, выполните следующую команду:
$ pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04/ wxPython
Убедитесь, что вы изменили приведенную выше команду в соответствии с вашей версией Linux.
Определение графического интерфейса
Как было сказано во введении, графический интерфейс пользователя (GUI) - это интерфейс, который рисуется на экране, чтобы пользователь мог с ним взаимодействовать.
Пользовательские интерфейсы имеют несколько общих компонентов:
- Главное окно
- Меню
- Панель инструментов
- Кнопки
- Ввод текста
- Метки
Все эти элементы известны под общим названием виджеты. Существует множество других общих виджетов и множество пользовательских виджетов, которые поддерживает wxPython. Разработчик берет виджеты и логически располагает их в окне, чтобы пользователь мог с ними взаимодействовать.
Циклы событий
Графический интерфейс пользователя работает, ожидая, пока пользователь что-то сделает. Это что-то называется событием. События происходят, когда пользователь что-то набирает, пока ваше приложение находится в фокусе, или когда пользователь использует мышь для нажатия кнопки или другого виджета.
Под прикрытием графического интерфейса работает бесконечный цикл, который называется event loop. Цикл событий просто ожидает наступления событий, а затем действует в соответствии с тем, что разработчик закодировал в приложении. Если приложение не перехватывает событие, оно фактически игнорирует его наступление.
Когда вы программируете графический пользовательский интерфейс, вы должны помнить, что вам нужно будет подключить каждый из виджетов к обработчикам событий, чтобы ваше приложение что-то сделало.
При работе с циклами событий необходимо помнить об особом моменте: они могут быть заблокированы. Если заблокировать цикл событий, графический интерфейс станет невосприимчивым и покажется пользователю застывшим.
Любой процесс, запускаемый в графическом интерфейсе и занимающий более четверти секунды, вероятно, следует запускать как отдельный поток или процесс. Это предотвратит зависание графического интерфейса и обеспечит пользователю лучшие впечатления.
В фреймворке wxPython есть специальные потокобезопасные методы, которые вы можете использовать для обратной связи с вашим приложением, чтобы сообщить ему о завершении потока или сообщить о его обновлении.
Давайте создадим скелет приложения, чтобы продемонстрировать, как работают события.
Создание скелета приложения
Скелет приложения в контексте GUI - это пользовательский интерфейс с виджетами, не имеющими обработчиков событий. Они полезны для создания прототипов. По сути, вы просто создаете графический интерфейс и представляете его на подпись заинтересованным лицам, прежде чем тратить много времени на логику бэкенда.
Начнем с создания Hello World
приложения с wxPython:
import wx
app = wx.App()
frame = wx.Frame(parent=None, title='Hello World')
frame.Show()
app.MainLoop()
Примечание: Пользователи Mac могут получить следующее сообщение: Этой программе необходим доступ к экрану. Пожалуйста, запускайте программу с Framework-сборкой python и только когда вы вошли в систему на главном экране вашего Mac. Если вы видите это сообщение и не работаете в виртуальной среде, то вам нужно запустить ваше приложение с pythonw вместо python. Если вы запускаете wxPython из виртуальной среды, то решение проблемы можно найти в wxPython wiki.
В этом примере у вас есть две части: wx.App
и wx.Frame
. wx.App
- это объект приложения wxPython, который необходим для запуска графического интерфейса. wx.App
запускает то, что называется .MainLoop()
. Это цикл событий, о котором вы узнали в предыдущем разделе.
Другой частью головоломки является wx.Frame
, которая создаст окно, с которым будет взаимодействовать пользователь. В данном случае вы сообщили wxPython, что у фрейма нет родителя и что его заголовок Hello World
. Вот как это выглядит при выполнении кода:
Примечание: Приложение будет выглядеть по-разному, если вы запустите его на Mac или Windows.
По умолчанию wx.Frame
включает в себя кнопки минимизации, максимизации и выхода в верхней части. Однако обычно вы не будете создавать приложение таким образом. Большинство кода wxPython потребует от вас подклассов wx.Frame
и других виджетов, чтобы вы могли использовать всю мощь инструментария.
Давайте уделим немного времени и перепишем ваш код в виде класса:
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Hello World')
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
Вы можете использовать этот код в качестве шаблона для своего приложения. Однако это приложение делает не так уж много, поэтому давайте немного узнаем о других виджетах, которые вы можете добавить.
Виджеты
В наборе инструментов wxPython есть более сотни виджетов на выбор. Это позволяет создавать богатые приложения, но попытка разобраться, какой виджет использовать, может оказаться нелегкой. Вот почему wxPython Demo полезен, так как в нем есть фильтр поиска, с помощью которого вы можете найти виджеты, которые могут быть применимы к вашему проекту.
Большинство GUI-приложений позволяют пользователю ввести текст и нажать кнопку. Давайте добавим эти виджеты:
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Hello World')
panel = wx.Panel(self)
self.text_ctrl = wx.TextCtrl(panel, pos=(5, 5))
my_btn = wx.Button(panel, label='Press Me', pos=(5, 55))
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
Когда вы запустите этот код, ваше приложение должно выглядеть так:
Первым виджетом, который вам нужно добавить, будет нечто под названием wx.Panel
. Этот виджет не обязателен, но рекомендуется. В Windows вы фактически обязаны использовать Панель, чтобы цвет фона рамки был правильного оттенка серого. Обход вкладок без Панели в Windows не работает.
Когда вы добавляете виджет панели в рамку, и панель является единственным дочерним элементом рамки, она автоматически расширяется, чтобы заполнить рамку собой.
Следующим шагом будет добавление wx.TextCtrl
на панель. Первым аргументом почти для всех виджетов является то, к какому родителю должен относиться виджет. В данном случае вы хотите, чтобы текстовый элемент управления и кнопка находились в верхней части панели, поэтому вы указываете именно родителя.
Вам также нужно указать wxPython, куда поместить виджет, что можно сделать, передав позицию через параметр pos
. В wxPython позицией начала отсчета является (0,0), то есть левый верхний угол родительского элемента. Таким образом, для текстового элемента управления вы сообщаете wxPython, что хотите расположить его левый верхний угол на расстоянии 5 пикселей от левого угла (x) и 5 пикселей от верхнего (y).
Затем добавьте кнопку на панель и задайте ей метку. Чтобы виджеты не перекрывались, для позиции кнопки нужно установить координату y равной 55.
Абсолютное позиционирование
Когда вы указываете точные координаты для позиции вашего виджета, техника, которую вы использовали, называется абсолютным позиционированием. Большинство наборов инструментов GUI предоставляют такую возможность, но на самом деле она не рекомендуется.
По мере усложнения приложения становится трудно отслеживать все расположения виджетов, а если нужно, то и перемещать их. Сброс всех этих позиций превращается в кошмар.
К счастью, все современные наборы инструментов графического интерфейса предоставляют решение этой проблемы, о котором вы узнаете далее.
Сайзеры (динамическое изменение размеров)
В инструментарий wxPython входят сайзеры, которые используются для создания динамических макетов. Они управляют размещением ваших виджетов и корректируют их при изменении размера окна приложения. В других наборах инструментов GUI сайзеры будут называться layouts, что и делает PyQt.
Ниже перечислены основные типы просекателей, которые чаще всего используются:
wx.BoxSizer
wx.GridSizer
wx.FlexGridSizer
Давайте добавим wx.BoxSizer
в ваш пример и посмотрим, сможем ли мы заставить его работать более красиво:
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Hello World')
panel = wx.Panel(self)
my_sizer = wx.BoxSizer(wx.VERTICAL)
self.text_ctrl = wx.TextCtrl(panel)
my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)
my_btn = wx.Button(panel, label='Press Me')
my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)
panel.SetSizer(my_sizer)
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
Здесь вы создаете экземпляр wx.BoxSizer
и передаете ему wx.VERTICAL
, который является ориентацией, в которой виджеты добавляются в sizer.
В этом случае виджеты будут добавляться вертикально, то есть по одному сверху вниз. Вы также можете установить ориентацию BoxSizer на wx.HORIZONTAL
. В этом случае виджеты будут добавляться слева направо.
Для добавления виджета в sizer используется .Add()
. Она принимает до пяти аргументов:
window
(виджет)proportion
flag
border
userData
Аргумент window
- это виджет, который будет добавлен, а proportion
задает, сколько места относительно других виджетов в sizer должен занимать данный виджет. По умолчанию это значение равно нулю, что указывает wxPython оставить виджет в его пропорции по умолчанию.
Третий аргумент - flag
. При желании вы можете передать несколько флагов, если разделите их символом трубы: |
. В инструментарии wxPython используется |
для добавления флагов с помощью серии побитовых ИЛИ.
В этом примере вы добавляете текстовый элемент управления с помощью флагов wx.ALL
и wx.EXPAND
. Флаг wx.ALL
сообщает wxPython, что вы хотите добавить границу со всех сторон виджета, а wx.EXPAND
заставляет виджеты расширяться настолько, насколько они могут в пределах sizer.
Наконец, у вас есть параметр border
, который указывает wxPython, сколько пикселей границы вы хотите видеть вокруг виджета. Параметр userData
используется только в тех случаях, когда вы хотите сделать что-то сложное с размерами виджета, и на самом деле довольно редко встречается на практике.
Добавление кнопки в sizer происходит точно так же. Однако, чтобы сделать все немного интереснее, я заменил флаг wx.EXPAND
на wx.CENTER
, чтобы кнопка была центрирована на экране.
Когда вы запустите эту версию кода, ваше приложение должно выглядеть следующим образом:
Если вы хотите узнать больше о сайзерах, в документации wxPython есть хорошая страница на эту тему.
Добавление события
Хотя визуально ваше приложение выглядит интереснее, на самом деле оно ничего не делает. Например, если вы нажмете на кнопку, ничего не произойдет.
Давайте дадим кнопке работу:
import wx
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Hello World')
panel = wx.Panel(self)
my_sizer = wx.BoxSizer(wx.VERTICAL)
self.text_ctrl = wx.TextCtrl(panel)
my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)
my_btn = wx.Button(panel, label='Press Me')
my_btn.Bind(wx.EVT_BUTTON, self.on_press)
my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)
panel.SetSizer(my_sizer)
self.Show()
def on_press(self, event):
value = self.text_ctrl.GetValue()
if not value:
print("You didn't enter anything!")
else:
print(f'You typed: "{value}"')
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()
Виджеты в wxPython позволяют прикреплять к ним привязки событий, чтобы они могли реагировать на определенные типы событий.
Примечание: В приведенном выше блоке кода используются f-строки. Вы можете прочитать о них в Python's F-String for String Interpolation and Formatting.
Вы хотите, чтобы кнопка что-то делала, когда пользователь на нее нажимает. Этого можно добиться, вызвав метод .Bind()
кнопки. .Bind()
принимает событие, к которому вы хотите привязаться, обработчик, который нужно вызвать при наступлении события, необязательный источник и несколько необязательных идентификаторов.
В этом примере вы привязываете объект button к событию wx.EVT_BUTTON
и указываете ему вызывать on_press()
при наступлении этого события.
Событие "срабатывает", когда пользователь выполняет действие, к которому вы привязались. В данном случае событие, которое вы установили, - это событие нажатия кнопки, wx.EVT_BUTTON
.
.on_press()
принимает второй аргумент, который вы можете назвать event
. Это сделано по соглашению. Вы можете назвать его как-то иначе, если захотите. Однако параметр event здесь указывает на то, что при вызове этого метода его вторым аргументом должен быть какой-либо объект события.
Внутри .on_press()
можно получить содержимое текстового элемента управления, вызвав его метод GetValue()
. Затем вы печатаете строку в stdout в зависимости от того, каково содержимое текстового элемента управления.
Теперь, когда вы разобрались с основами, давайте узнаем, как создать приложение, которое будет делать что-то полезное!
Создание рабочего приложения
Первый шаг при создании чего-то нового - понять, что именно вы хотите создать. В данном случае я взял на себя смелость принять это решение за вас. Вы узнаете, как создать редактор MP3-тегов! Следующий шаг при создании чего-то нового - выяснить, какие пакеты могут помочь вам в решении этой задачи.
Если вы сделаете поиск в Google по запросу Python mp3 tagging
, то обнаружите, что у вас есть несколько вариантов:
mp3-tagger
eyeD3
mutagen
Я попробовал несколько из них и решил, что eyeD3 имеет хороший API, который можно использовать, не заморачиваясь со спецификацией ID3 для MP3. Вы можете установить eyeD3 с помощью pip
, например, так:
$ pip install eyed3
При установке этого пакета на macOS вам может потребоваться установить libmagic
с помощью brew
. У пользователей Windows и Linux не должно возникнуть проблем с установкой eyeD3.
Разработка пользовательского интерфейса
Когда дело доходит до проектирования интерфейса, всегда приятно просто набросать, как, по вашему мнению, должен выглядеть пользовательский интерфейс.
Вы должны уметь делать следующее:
- Открыть один или несколько файлов MP3
- Отображение текущих MP3-тегов
- Редактирование тега MP3
В большинстве пользовательских интерфейсов для открытия файлов или папок используется меню или кнопка. Вы можете использовать для этого меню File. Поскольку вы, вероятно, захотите видеть теги для нескольких MP3-файлов, вам нужно будет найти виджет, который сможет сделать это в красивой манере.
Идеальным вариантом было бы что-то табличное со столбцами и строками, потому что в этом случае вы можете иметь маркированные столбцы для тегов MP3. В наборе инструментов wxPython есть несколько виджетов, которые подойдут для этого, и два из них - следующие:
wx.grid.Grid
wx.ListCtrl
В данном случае следует использовать wx.ListCtrl
, поскольку виджет Grid
является излишеством, да и, честно говоря, он немного сложнее. Наконец, вам нужна кнопка для редактирования тега выбранного MP3.
Теперь, когда вы знаете, чего хотите, вы можете нарисовать это:
Иллюстрация выше дает нам представление о том, как должно выглядеть приложение. Теперь, когда вы знаете, что хотите сделать, пришло время писать код!
Создание пользовательского интерфейса
Когда речь идет о написании нового приложения, существует множество различных подходов. Например, нужно ли следовать паттерну проектирования Model-View-Controller? Как разделить классы? По одному классу на файл? Таких вопросов много, и по мере приобретения опыта в проектировании GUI вы поймете, как на них отвечать.
В вашем случае вам действительно нужны только два класса:
- A
wx.Panel
класс - A
wx.Frame
класс
Можно было бы привести аргументы в пользу создания модуля типа контроллера, но для чего-то вроде этого он действительно не нужен. Можно также привести доводы в пользу того, чтобы поместить каждый класс в свой собственный модуль, но для компактности вы создадите один Python-файл для всего вашего кода.
Начнем с импорта и класса панели:
import eyed3
import glob
import wx
class Mp3Panel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
main_sizer = wx.BoxSizer(wx.VERTICAL)
self.row_obj_dict = {}
self.list_ctrl = wx.ListCtrl(
self, size=(-1, 100),
style=wx.LC_REPORT | wx.BORDER_SUNKEN
)
self.list_ctrl.InsertColumn(0, 'Artist', width=140)
self.list_ctrl.InsertColumn(1, 'Album', width=140)
self.list_ctrl.InsertColumn(2, 'Title', width=200)
main_sizer.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 5)
edit_button = wx.Button(self, label='Edit')
edit_button.Bind(wx.EVT_BUTTON, self.on_edit)
main_sizer.Add(edit_button, 0, wx.ALL | wx.CENTER, 5)
self.SetSizer(main_sizer)
def on_edit(self, event):
print('in on_edit')
def update_mp3_listing(self, folder_path):
print(folder_path)
Здесь вы импортируете пакет eyed3
, пакет Python glob
и пакет wx
для вашего пользовательского интерфейса. Далее вы подклассифицируете wx.Panel
и создаете свой пользовательский интерфейс. Для хранения данных о MP3-файлах вам понадобится словарь, который вы можете назвать row_obj_dict
.
Затем вы создаете wx.ListCtrl
и устанавливаете его в режим отчета (wx.LC_REPORT
) с утопленной границей (wx.BORDER_SUNKEN
). Элемент управления списком может принимать несколько других форм в зависимости от флага стиля, который вы передаете, но флаг отчета является наиболее популярным.
Для того чтобы ListCtrl
имел правильные заголовки, вам нужно вызвать .InsertColumn()
для каждого заголовка столбца. Затем вы указываете индекс столбца, его метку и ширину в пикселях, которую должен иметь столбец.
Последним шагом будет добавление кнопки Edit
, обработчика события и метода. Вы можете создать привязку к событию и оставить метод, который он вызывает, пока пустым.
Теперь нужно написать код для фрейма:
class Mp3Frame(wx.Frame):
def __init__(self):
super().__init__(parent=None,
title='Mp3 Tag Editor')
self.panel = Mp3Panel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = Mp3Frame()
app.MainLoop()
Этот класс намного проще, чем первый, поскольку все, что вам нужно сделать, это установить заголовок фрейма и инстанцировать класс панели, Mp3Panel
. Когда все будет готово, ваш пользовательский интерфейс должен выглядеть так:
Пользовательский интерфейс выглядит почти правильно, но у вас нет меню File. Это делает невозможным добавление MP3-файлов в приложение и редактирование их тегов!
Давайте исправим это сейчас.
Сделайте функционирующее приложение
Первым шагом в обеспечении работы приложения будет обновление приложения, чтобы в нем появилось меню File, потому что тогда вы сможете добавлять MP3-файлы в свое творение. Меню почти всегда добавляется в класс wx.Frame
, поэтому именно этот класс вам нужно модифицировать.
Примечание: Некоторые приложения отказались от использования меню в своих программах. Одним из первых это сделал Microsoft Office, добавив ленточную панель. В инструментарии wxPython есть пользовательский виджет, который можно использовать для создания лент в wx.lib.agw.ribbon
.
Другой тип приложений, которые в последнее время отказались от меню, - это веб-браузеры, такие как Google Chrome и Mozilla Firefox. Теперь они используют только панели инструментов.
Давайте узнаем, как добавить строку меню в наше приложение:
class Mp3Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None,
title='Mp3 Tag Editor')
self.panel = Mp3Panel(self)
self.create_menu()
self.Show()
def create_menu(self):
menu_bar = wx.MenuBar()
file_menu = wx.Menu()
open_folder_menu_item = file_menu.Append(
wx.ID_ANY, 'Open Folder',
'Open a folder with MP3s'
)
menu_bar.Append(file_menu, '&File')
self.Bind(
event=wx.EVT_MENU,
handler=self.on_open_folder,
source=open_folder_menu_item,
)
self.SetMenuBar(menu_bar)
def on_open_folder(self, event):
title = "Choose a directory:"
dlg = wx.DirDialog(self, title,
style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
self.panel.update_mp3_listing(dlg.GetPath())
dlg.Destroy()
Здесь вы добавляете вызов .create_menu()
внутри конструктора класса. Затем в самом .create_menu()
вы создадите экземпляр wx.MenuBar
и экземпляр wx.Menu
.
Чтобы добавить пункт меню в меню, вы вызываете функцию .Append()
экземпляра меню и передаете ей следующее:
- Уникальный идентификатор
- Метка для нового пункта меню
- Строка помощи
Next, you need to add the menu to the menubar, so you will need to call the menubar’s .Append()
. It takes the menu instance and the label for menu. This label is a bit odd in that you called it &File
instead of File
. The ampersand tells wxPython to create a keyboard shortcut of Alt+F to open the File
menu using just your keyboard.
Примечание: Если вы хотите добавить сочетания клавиш в свое приложение, то для их создания вам понадобится экземпляр wx.AcceleratorTable
. Подробнее о таблицах ускорителей вы можете прочитать в wxPython документации.
Чтобы создать привязку события, вам нужно вызвать self.Bind()
, который привязывает фрейм к wx.EVT_MENU
. Когда вы используете self.Bind()
для события меню, вам нужно не только указать wxPython, какой handler
использовать, но и к какому source
привязать обработчик.
Наконец, необходимо вызвать фрейм .SetMenuBar()
и передать ему экземпляр menubar, чтобы он был показан пользователю.
Теперь, когда меню добавлено в ваш фрейм, давайте рассмотрим обработчик события пункта меню, который снова приведен ниже:
def on_open_folder(self, event):
title = "Choose a directory:"
dlg = wx.DirDialog(self, title, style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
self.panel.update_mp3_listing(dlg.GetPath())
dlg.Destroy()
Поскольку вы хотите, чтобы пользователь выбрал папку, содержащую MP3-файлы, вам нужно использовать wx.DirDialog
из wxPython. wx.DirDialog
позволяет пользователю открывать только каталоги.
Вы можете задать заголовок диалога и различные флаги стиля. Чтобы показать диалог, вам нужно вызвать .ShowModal()
. При этом диалог будет отображаться модально, что означает, что пользователь не сможет взаимодействовать с вашим основным приложением, пока диалог отображается.
Если пользователь нажимает кнопку OK, вы можете получить информацию о выборе пути пользователем через .GetPath()
. Вы захотите передать этот путь в класс панели, что можно сделать здесь, вызвав .update_mp3_listing()
.
Наконец, необходимо закрыть диалог. Чтобы закрыть диалог, рекомендуется вызвать его .Destroy()
.
У диалогов есть метод .Close()
, но он, по сути, просто скрывает диалог, и он не уничтожается при закрытии приложения, что может привести к странным проблемам, например, к тому, что ваше приложение теперь не закрывается должным образом. Проще вызвать .Destroy()
на диалоге, чтобы предотвратить эту проблему.
Теперь давайте обновим ваш класс Mp3Panel
. Вы можете начать с обновления .update_mp3_listing()
:
def update_mp3_listing(self, folder_path):
self.current_folder_path = folder_path
self.list_ctrl.ClearAll()
self.list_ctrl.InsertColumn(0, 'Artist', width=140)
self.list_ctrl.InsertColumn(1, 'Album', width=140)
self.list_ctrl.InsertColumn(2, 'Title', width=200)
self.list_ctrl.InsertColumn(3, 'Year', width=200)
mp3s = glob.glob(folder_path + '/*.mp3')
mp3_objects = []
index = 0
for mp3 in mp3s:
mp3_object = eyed3.load(mp3)
self.list_ctrl.InsertItem(index,
mp3_object.tag.artist)
self.list_ctrl.SetItem(index, 1,
mp3_object.tag.album)
self.list_ctrl.SetItem(index, 2,
mp3_object.tag.title)
mp3_objects.append(mp3_object)
self.row_obj_dict[index] = mp3_object
index += 1
Здесь вы устанавливаете текущий каталог в указанную папку, а затем очищаете элемент управления списком. Это позволяет сохранить свежий элемент управления списком и отобразить только те MP3, над которыми вы работаете в данный момент. Это также означает, что вам нужно снова вставить все столбцы.
Далее нужно взять переданную папку и использовать glob
модуль Python для поиска MP3-файлов.
Затем вы можете перебирать MP3-файлы и превращать их в объекты eyed3
. Это можно сделать, вызвав .load()
из eyed3
. Предполагая, что MP3-файлы уже имеют соответствующие теги, вы можете добавить исполнителя, альбом и название MP3-файла в элемент управления списком.
Интересно, что метод добавления новой строки в объект управления списком заключается в вызове .InsertItem()
для первого столбца и SetItem()
для всех последующих столбцов.
Последним шагом будет сохранение объекта MP3 в словарь Python, row_obj_dict
.
Теперь нужно обновить обработчик события .on_edit()
, чтобы можно было редактировать теги MP3:
def on_edit(self, event):
selection = self.list_ctrl.GetFocusedItem()
if selection >= 0:
mp3 = self.row_obj_dict[selection]
dlg = EditDialog(mp3)
dlg.ShowModal()
self.update_mp3_listing(self.current_folder_path)
dlg.Destroy()
Первое, что вам нужно сделать, это получить выбор пользователя, вызвав элементы управления списком .GetFocusedItem()
.
Если пользователь ничего не выбрал в элементе управления списком, он вернет -1
. Предположив, что пользователь все-таки выбрал что-то, вы должны извлечь объект MP3 из словаря и открыть диалог редактора тегов MP3. Это будет пользовательский диалог, который вы будете использовать для редактирования тегов исполнителя, альбома и названия MP3-файла.
Как обычно, покажите диалог модально. Когда диалог закроется, будут выполнены две последние строки в .on_edit()
. Эти две строки обновят элемент управления списком так, чтобы он отображал текущую информацию о теге MP3, который только что отредактировал пользователь, и уничтожат диалог.
Создание диалогового окна редактирования
Последний кусочек головоломки - создание диалогового окна редактирования MP3-тегов. Для краткости мы не будем делать наброски этого интерфейса, поскольку он представляет собой ряд строк, содержащих метки и текстовые элементы управления. В текстовых элементах управления должна быть предварительно заполнена информация о существующем теге. Вы можете создать метку для текстовых элементов управления, создав экземпляры wx.StaticText
.
Когда вам нужно создать пользовательский диалог, класс wx.Dialog
- ваш друг. Вы можете использовать его для создания редактора:
class EditDialog(wx.Dialog):
def __init__(self, mp3):
title = f'Editing "{mp3.tag.title}"'
super().__init__(parent=None, title=title)
self.mp3 = mp3
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.artist = wx.TextCtrl(
self, value=self.mp3.tag.artist)
self.add_widgets('Artist', self.artist)
self.album = wx.TextCtrl(
self, value=self.mp3.tag.album)
self.add_widgets('Album', self.album)
self.title = wx.TextCtrl(
self, value=self.mp3.tag.title)
self.add_widgets('Title', self.title)
btn_sizer = wx.BoxSizer()
save_btn = wx.Button(self, label='Save')
save_btn.Bind(wx.EVT_BUTTON, self.on_save)
btn_sizer.Add(save_btn, 0, wx.ALL, 5)
btn_sizer.Add(wx.Button(
self, id=wx.ID_CANCEL), 0, wx.ALL, 5)
self.main_sizer.Add(btn_sizer, 0, wx.CENTER)
self.SetSizer(self.main_sizer)
Здесь вы хотите начать с подкласса wx.Dialog
и дать ему пользовательское название, основанное на названии MP3, который вы редактируете.
Далее вы можете создать используемый сайзер и виджеты. Чтобы упростить работу, можно создать вспомогательный метод .add_widgets()
для добавления виджетов wx.StaticText
в качестве строк с экземплярами текстового элемента управления. Единственным другим виджетом здесь является кнопка Save.
Далее напишем метод add_widgets
:
def add_widgets(self, label_text, text_ctrl):
row_sizer = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText(self, label=label_text,
size=(50, -1))
row_sizer.Add(label, 0, wx.ALL, 5)
row_sizer.Add(text_ctrl, 1, wx.ALL | wx.EXPAND, 5)
self.main_sizer.Add(row_sizer, 0, wx.EXPAND)
add_widgets()
принимает текст метки и экземпляр текстового элемента управления. Затем создается горизонтально ориентированный элемент BoxSizer
.
Далее вы создадите экземпляр wx.StaticText
, используя переданный текст для его параметра label. Вы также зададите его размер 50
пикселей в ширину, а высоту по умолчанию установите с помощью -1
. Поскольку метка должна располагаться перед текстовым элементом управления, вы сначала добавите виджет StaticText в BoxSizer, а затем добавите текстовый элемент управления .
Наконец, вы хотите добавить горизонтальный сайзер к вертикальному сайзеру верхнего уровня. Вкладывая сайзеры друг в друга, вы можете создавать сложные приложения.
Теперь вам нужно создать обработчик события on_save()
, чтобы вы могли сохранить изменения:
def on_save(self, event):
self.mp3.tag.artist = self.artist.GetValue()
self.mp3.tag.album = self.album.GetValue()
self.mp3.tag.title = self.title.GetValue()
self.mp3.tag.save()
self.Close()
Здесь вы устанавливаете теги на содержимое текстовых элементов управления, а затем вызываете eyed3
объекта .save()
. Наконец, вы вызываете .Close()
диалога. Причина вызова .Close()
здесь вместо .Destroy()
в том, что вы уже вызвали .Destroy()
в .on_edit()
вашего подкласса панели.
Теперь ваша заявка завершена!
Заключение
В этой статье вы узнали много нового о wxPython. Вы познакомились с основами создания GUI-приложений с помощью wxPython.
Теперь вы знаете больше о следующем:
- Как работать с некоторыми виджетами wxPython
- Как работают события в wxPython
- Как абсолютное позиционирование сравнивается с сайзерами
- Как создать скелет приложения
Наконец-то вы узнали, как создать работающее приложение, редактор тегов MP3. Вы можете использовать то, что узнали в этой статье, чтобы продолжить совершенствовать это приложение или, возможно, создать удивительное приложение самостоятельно.
Инструментарий графического интерфейса wxPython надежен и полон интересных виджетов, которые вы можете использовать для создания кроссплатформенных приложений. Вы ограничены только своим воображением.
Дальнейшее чтение
Если вы хотите узнать больше о wxPython, вы можете посмотреть следующие ссылки:
- Официальный сайт wxPython
- Zetcode's wxPython tutorial
- Мышь против Python Blog
Для получения дополнительной информации о том, что еще можно делать с помощью Python, вам стоит заглянуть в раздел What Can I Do with Python? Если вы хотите узнать больше о super()
Python, то Supercharge Your Classes With Python super() может быть как раз для вас.
Вы также можете скачать код для приложения-редактора MP3-тегов, которое вы создали в этой статье, если хотите изучить его более подробно.
Back to Top