Статьи

wxPython: создание «темного режима»


Однажды на работе мне сказали, что у нас есть запрос функции для одной из моих программ.
Они хотели «темный режим», когда они использовали мое приложение ночью, так как обычные цвета были яркими. Моя программа используется в ноутбуках в полицейских машинах, поэтому я мог понять их разочарование. Я потратил некоторое время на изучение этого вопроса и собрал в основном рабочий сценарий, которым я собираюсь поделиться со своими читателями. Конечно, если вы долгое время читаете, вы, вероятно, знаете, что я говорю о программе wxPython. Я пишу почти все мои графические интерфейсы, используя wxPython. В любом случае, давайте продолжим историю!

В темноту

Получить виджеты для изменения цвета в wxPython довольно легко. Вам нужны только два метода: SetBackgroundColour и SetForegroundColour, Единственная серьезная проблема, с которой я столкнулся, когда я делал это, заключалась в том, чтобы заставить мой виджет ListCtrl / ObjectListView правильно менять цвета. Вам нужно перебрать каждый ListItem и менять их цвета индивидуально. Я чередую цвета строк, чтобы сделать вещи интереснее. Другая проблема, с которой я столкнулся, — это восстановление цвета фона ListCtrl. Обычно вы можете установить цвет фона виджета на wx.NullColour (или wx.NullColor), и он вернется к цвету по умолчанию. Однако некоторые виджеты не работают таким образом, и вам действительно нужно указать цвет. Следует также отметить, что некоторые виджеты вообще не обращают внимания на SetBackgroundColour. Один такой виджет, который я нашел, это wx.ToggleButton.

Теперь вы знаете, что я знаю, поэтому давайте посмотрим на код, который я придумал, чтобы решить мою проблему:

import wx
try:
    from ObjectListView import ObjectListView
except:
    ObjectListView = False
 
#----------------------------------------------------------------------
def getWidgets(parent):
    """
    Return a list of all the child widgets
    """
    items = [parent]
    for item in parent.GetChildren():
        items.append(item)
        if hasattr(item, "GetChildren"):
            for child in item.GetChildren():
                items.append(child)
    return items
 
#----------------------------------------------------------------------
def darkRowFormatter(listctrl, dark=False):
    """
    Toggles the rows in a ListCtrl or ObjectListView widget.
    Based loosely on the following documentation:

http://objectlistview.sourceforge.net/python/recipes.html#recipe-formatter

    and http://objectlistview.sourceforge.net/python/cellEditing.html
    """
 
    listItems = [listctrl.GetItem(i) for i in range(listctrl.GetItemCount())]
    for index, item in enumerate(listItems):
        if dark:
            if index % 2:
                item.SetBackgroundColour("Dark Grey")
            else:
                item.SetBackgroundColour("Light Grey")
        else:
            if index % 2:
                item.SetBackgroundColour("Light Blue")
            else:
                item.SetBackgroundColour("Yellow")
        listctrl.SetItem(item)
 
#----------------------------------------------------------------------
def darkMode(self, normalPanelColor):
    """
    Toggles dark mode
    """
    widgets = getWidgets(self)
    panel = widgets[0]
    if normalPanelColor == panel.GetBackgroundColour():
        dark_mode = True
    else:
        dark_mode = False
    for widget in widgets:
        if dark_mode:
            if isinstance(widget, ObjectListView) or isinstance(widget, wx.ListCtrl):
                darkRowFormatter(widget, dark=True)
            widget.SetBackgroundColour("Dark Grey")
            widget.SetForegroundColour("White")
        else:
            if isinstance(widget, ObjectListView) or isinstance(widget, wx.ListCtrl):
                darkRowFormatter(widget)
                widget.SetBackgroundColour("White")
                widget.SetForegroundColour("Black")
                continue
            widget.SetBackgroundColour(wx.NullColor)
            widget.SetForegroundColour("Black")
    self.Refresh()
    return dark_mode

Этот код немного запутанный, но он выполняет свою работу. Давайте разберемся с этим немного и посмотрим, как это работает. Прежде всего, мы пытаемся импортировать ObjectListView, классный сторонний виджет, который упаковывает wx.ListCtrl и облегчает его использование. Однако сейчас он не является частью wxPython, поэтому вам нужно проверить его существование. Я просто установил его в False, если он не существует.

Функция GetWidgets принимает родительский параметр, который обычно представляет собой wx.Frame или wx.Panel, и проходит через все свои дочерние элементы, чтобы создать список виджетов, который затем возвращается к вызывающей функции. Основная функция — darkMode . Он также принимает два параметра, плохо называемый «self», который ссылается на родительский виджет, и цвет панели по умолчанию. Он вызывает GetWidgets, а затем использует условный оператор, чтобы решить, должен ли темный режим быть включен или нет. Затем он перебирает виджеты и соответственно меняет цвета. Когда это будет сделано, он обновит переданный родительский элемент и вернет bool, чтобы вы знали, включен ли темный режим.

Есть еще одна функция с именем darkRowFormatter , предназначенная только для установки цветов ListItems в виджете wx.ListCtrl или ObjectListView. Здесь мы используем понимание списка, чтобы создать список элементов wx.ListItems, которые мы затем перебираем, меняя их цвета. Чтобы действительно применить изменение цвета, нам нужно вызвать SetItem и передать ему экземпляр объекта wx.ListItem.

Испытание темного режима

Так что теперь вы, вероятно, задаетесь вопросом, как на самом деле использовать скрипт выше. Ну, этот раздел покажет вам, как это делается. Вот простая программа со списком управления и кнопкой переключения тоже!

import wx
import darkMode
 
########################################################################
class MyPanel(wx.Panel):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        self.defaultColor = self.GetBackgroundColour()
 
        rows = [("Ford", "Taurus", "1996", "Blue"),
                ("Nissan", "370Z", "2010", "Green"),
                ("Porche", "911", "2009", "Red")
                ]
        self.list_ctrl = wx.ListCtrl(self, style=wx.LC_REPORT)
 
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")
 
        index = 0
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row[0])
            self.list_ctrl.SetStringItem(index, 1, row[1])
            self.list_ctrl.SetStringItem(index, 2, row[2])
            self.list_ctrl.SetStringItem(index, 3, row[3])
            if index % 2:
                self.list_ctrl.SetItemBackgroundColour(index, "white")
            else:
                self.list_ctrl.SetItemBackgroundColour(index, "yellow")
            index += 1
 
        btn = wx.ToggleButton(self, label="Toggle Dark")
        btn.Bind(wx.EVT_TOGGLEBUTTON, self.onToggleDark)
        normalBtn = wx.Button(self, label="Test")
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL, 5)
        sizer.Add(normalBtn, 0, wx.ALL, 5)
        self.SetSizer(sizer)
 
    #----------------------------------------------------------------------
    def onToggleDark(self, event):
        """"""
        darkMode.darkMode(self, self.defaultColor)
 
########################################################################
class MyFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY,
                          "MvP ListCtrl Dark Mode Demo")
        panel = MyPanel(self)
        self.Show()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

Если вы запустите программу выше, вы должны увидеть что-то вроде этого:

Если вы нажмете кнопку ToggleButton, вы должны увидеть что-то вроде этого:

Обратите внимание, как на кнопку переключения не влиял метод SetBackgroundColour. Также обратите внимание, что заголовки столбцов элемента управления списком также не меняют цвета. К сожалению, wxPython не предоставляет доступ к заголовкам столбцов, поэтому нет способа манипулировать их цветом.

В любом случае, давайте на минутку посмотрим, как используется код темного режима. Сначала нам нужно его импортировать. В этом случае модуль называется darkMode . Чтобы вызвать его, нам нужно взглянуть на обработчик события ToggleButton:

darkMode.darkMode(self, self.defaultColor)

Как видите, все, что мы сделали, это вызвали darkMode.darkMode с объектом панели («self») и defaultColor, который мы установили в начале метода init wx.Panel. Это все, что мы должны были сделать тоже. Возможно, нам следует установить переменную, чтобы перехватить возвращаемое значение, но для этого примера нам все равно.

Завершение

Теперь мы закончили, и вы тоже можете создать «темный режим» для своих приложений. В какой-то момент я хотел бы обобщить это еще немного, чтобы превратить в скрипт смены цвета, где я могу передать ему любые цвета, которые я хочу. Что было бы действительно круто — это превратить его в миксин. Но это что-то на будущее. А пока наслаждайтесь!

Дальнейшее чтение

Исходный код

Источник: http://www.blog.pythonlibrary.org/2011/11/05/wxpython-creating-a-dark-mode/