Я получаю очень много электронных писем от людей, изучающих Python и wxPython. В одном из них меня попросили рассказать, как создать интерфейс, в котором фоновое изображение на панели будет размещено под кнопками с помощью Tkinter или wxPython. Сначала я проверил возможно ли это в Tkinter и выяснил, что виджет PhotoImage поддерживает только два формата: gif и pgm (по крайней мере пока я не установил Python Imaging Library). Поэтому, я решил дать wxPython шанс. И вот, что у меня получилось.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Плохой пример
Погуглив немного, я нашёл способ в одном из постов на форуме. Он показался мне вполне рабочим. Я воспроизведу для вас один из вариантов его использования ниже:
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 |
import wx # Создаём фоновое изображение на панели wxPython # И делаем так, чтобы кнопки отображались поверх него class Panel1(wx.Panel): """ A subclass of wx.Panel """ def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) try: # Выберите изображение, которое хотите разместить # Вы можете загружать .jpg .png .bmp или # .gif файлы image_file = 'roses.jpg' bmp1 = wx.Image( image_file, wx.BITMAP_TYPE_ANY ).ConvertToBitmap() # Левый верхний угол изображения закрепляется на панели # координаты (0, 0) self.my_bitmap = wx.StaticBitmap( self, -1, bmp1, (0, 0)) # отображаем несколько деталей относительно изображения str1 = "%s %dx%d" % ( image_file, bmp1.GetWidth(), bmp1.GetHeight() ) parent.SetTitle(str1) except IOError: print("Image file %s not found" % image_file) raise SystemExit if __name__ == "__main__": app = wx.App(False) my_frame = wx.Frame( None, -1, "An image on a panel", size=(350, 400) ) panel = Panel1(my_frame, -1) my_frame.Show(True) app.MainLoop() |
Первой моей мыслью, когда я увидел это было: «Это наверняка будет выглядеть плохо». Почему я так решил?
Что же, парень, который запостил это использовал wx.StaticBitmap в качестве родителя для кнопки. Виджет StaticBitmap НЕ является контейнером, как тот же виджет Panel или Frame, поэтом я решил, его использование не будет хорошей идеей.
Я спросил Роберта Дана используя IRC канал #wxPython, о том, что он думает по поводу этой ситуации. Он сказал, что, если бы я сделал так, как показано на примере выше, у меня скорей всего появились бы проблемы со смещением и прочим, и порекомендовал мне использовать EVT_ERASE_BACKGROUND с элементами простого рисования. Учитывая то, что Роберт Дан создал wxPython, я последовал его совету и отказался от этого прошлого варианта.
Примечание: Когда я запустил этот код на Windows 7 с помощью wxPython Phoenix, у меня были жёсткие проблемы с тем, чтобы нарисовать виджет, а также с тем, чтобы отменить этот процесс. Так что используйте данный метод на свой страх и риск!
Финальное решение
Следуя указаниям Роберта Дана, я дополнил этот код:
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 |
import wx class MainPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent=parent) self.frame = parent sizer = wx.BoxSizer(wx.VERTICAL) hSizer = wx.BoxSizer(wx.HORIZONTAL) for num in range(4): label = "Button %s" % num btn = wx.Button(self, label=label) sizer.Add(btn, 0, wx.ALL, 5) hSizer.Add((1,1), 1, wx.EXPAND) hSizer.Add(sizer, 0, wx.TOP, 100) hSizer.Add((1,1), 0, wx.ALL, 75) self.SetSizer(hSizer) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) def OnEraseBackground(self, evt): """ Добавляем фоновое изображение. """ dc = evt.GetDC() if not dc: dc = wx.ClientDC(self) rect = self.GetUpdateRegion().GetBox() dc.SetClippingRect(rect) dc.Clear() bmp = wx.Bitmap("big_cat.jpg") dc.DrawBitmap(bmp, 0, 0) class MainFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, size=(600,450)) panel = MainPanel(self) self.Center() class Main(wx.App): def __init__(self, redirect=False, filename=None): wx.App.__init__(self, redirect, filename) dlg = MainFrame() dlg.Show() if __name__ == "__main__": app = Main() app.MainLoop() |
Вот пример скриншота того, как я сделал фоновым изображением фото большого кота, сделанное мной прошлым летом.
Главный фрагмент кода, на который стоит обратить внимание:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def OnEraseBackground(self, evt): """ Добавляем фоновое изображение. """ dc = evt.GetDC() if not dc: dc = wx.ClientDC(self) rect = self.GetUpdateRegion().GetBox() dc.SetClippingRect(rect) dc.Clear() bmp = wx.Bitmap("big_cat.jpg") dc.DrawBitmap(bmp, 0, 0) |
Я копировал его из демо-версии ColourDB.py, которое доступно в демо-версии wxPython и слегка дополнил, чтобы он работал в моём приложении. Проще говоря, вы биндите панельEVT_ERASE_BACKGROUND и в этом хендлере, вы захватываете контекст устройства (DC), который в данном случае является панелью (я так думаю). Я называю это методом Clear, так как в моём настоящем приложении я использовал изображение с применением прозрачности, таким образом позволяя фону проступать.
Очистив его, я избавился от подтёков. Как бы то ни было, проверяем, существует ли DC или он пуст (так как я не уверен в этом до конца), и, если же нет – обновляет область (или так называемую «грязную зону» — часть изображения, которая пропадает после того, как на окно приложения накладывается другое). Затем я захватил своё изображение и использовал DrawBitmap, чтобы применить его в качестве фона. Это выглядит забавно, и я сам до конца не понимаю, что происходит, но это работает.
Также я нашёл ещё один метод, который я не испытывал в этой статье: http://www.5etdemi.com/blog/archives/2006/06/making-a-panel-with-a-background-in-wxpython/
Не стесняйтесь попробовать оба и выяснить какой именно метод подходит именно вам. Он похож на метод Робина Дана, в котором он использует DC, но не тот же его тип, который использовал я.
Итоги
Теперь вы получили все необходимые знания для того, чтобы добавить фоновое изображение на панель приложения. Я замечаю, что люди постоянно спрашивают о том, как это сделать, поэтому считаю данную тему очень важной. Вы можете использовать эти знания для создания Splash Screen. Несмотря ни на что, поиграйте с этим кодом сами, вдруг вы найдёте способ получше.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»