Как сделать окно вывода логов в wx.TextCtrl — wxPython

Делая графическую обертку для какого-то инструмента wxPython нередко приходится сталкиваться с необходимостью вывода событий (простых логов) в окно wx.TextCtrl. Сегодня поговорим о том, как сделать окно вывода логов.

Особенности создания окна вывода логов

Поставленную задачу можно реализовать с помощью метода wx.TextCtrl.AppendText(msg), добавляющего в окно принятую строку msg к имеющимся. 

Сама база, с которой нам придется работать, выглядит вот так. 

import logging

import logging.config

import wx





class CustomConsoleHandler(logging.StreamHandler):

    """"""




    def __init__(self, textctrl):

        """"""

        logging.StreamHandler.__init__(self)

        self.textctrl = textctrl




    def emit(self, record):

        """Constructor"""

        msg = self.format(record)

        self.textctrl.WriteText(msg + "\n")

        self.flush()





class MyPanel(wx.Panel):

    """"""




    def __init__(self, parent):

        """Constructor"""

        wx.Panel.__init__(self, parent)

        self.logger = logging.getLogger("wxApp")




        self.logger.info("Test from MyPanel __init__")




        logText = wx.TextCtrl(

            self,

            style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)




        btn = wx.Button(self, label="Press Me")

        btn.Bind(wx.EVT_BUTTON, self.onPress)




        sizer = wx.BoxSizer(wx.VERTICAL)

        sizer.Add(logText, 1, wx.EXPAND|wx.ALL, 5)

        sizer.Add(btn, 0, wx.ALL, 5)

        self.SetSizer(sizer)




        txtHandler = CustomConsoleHandler(logText)

        self.logger.addHandler(txtHandler)




    def onPress(self, event):

        """

        On the press of a button, log some messages

        """

        self.logger.error("Error Will Robinson!")

        self.logger.info("Informational message")





class MyFrame(wx.Frame):

    """"""




    def __init__(self):

        """Constructor"""

        wx.Frame.__init__(self, None, title="Logging test")

        panel = MyPanel(self)

        self.logger = logging.getLogger("wxApp")

        self.Show()





def main():

    """

    Запускаем программу

    """

    dictLogConfig = {

        "version":1,

        "handlers":{

            "fileHandler":{

                "class":"logging.FileHandler",

                "formatter":"myFormatter",

                "filename":"test.log"

                },

            "consoleHandler":{

                "class":"logging.StreamHandler",

                "formatter":"myFormatter"

            }

            },

        "loggers":{

            "wxApp":{

                "handlers":["fileHandler", "consoleHandler"],

                "level":"INFO",

            }

            },




        "formatters":{

            "myFormatter":{

                "format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"

            }

        }

    }

    logging.config.dictConfig(dictLogConfig)

    logger = logging.getLogger("wxApp")




    logger.info("This message came from main!")




    app = wx.App(False)

    frame = MyFrame()

    app.MainLoop()





if __name__ == "__main__":

    main()

 Как видим, здесь использовался модуль logging.config. А метод dictConfig был добавлен в Python 2.7. Простыми словами настраивается хэндлер логгинга и форматтеры, и все, что не находится в словаре. Затем все это пропускается через logging.config.

Если вы попробуете выполнить данный код, то обнаружите, что первые несколько сообщений отправляются в stdout и в лог, но не в текстовый контроль. В конце панельного класса __init__ добавляется стандартный хэндлер, и здесь осуществляется перенаправление сообщений логгинга в текстовый контроль.

Вот как это будет выглядеть на компьютере под управлением Mac.Как сделать окно вывода логов в wx.TextCtrl - wxPython

Вы обнаружите, что можете увидеть данные о логгинге в stdout, а также в текстовом контроле на скриншоте. 

В терминале и файле лога должна появиться информация такого типа.

2016-11-30 14:04:35,026 — wxApp — INFO — This message came from main!

2 2016-11-30 14:04:35,026 — wxApp — INFO — Test from MyPanel __init__

3 2016-11-30 14:04:38,261 — wxApp — ERROR — Error Will Robinson!

Одним словом, это база того, как перенаправлять методы логгинга Python в виджет wxPython. Это может быть очень полезным в случае, если надо сохранить выходные данные из приложения в отдельный файл, а не только лишь просматривать их в режиме реального времени. А теперь поговорим о том, как перенаправить stdout в wx.TextCtrl.

Перенаправление stdout в wx.Text.Ctrl

Давайте возьмем frame, который указан ранее и удалим класс CustomConsoleHandler, а также весь код, который с ним связан. 

import wx

import random





class MyPanel(wx.Panel):




   def __init__(self, parent):

       """Constructor"""

        # создадим панель в которую помести окно вывода и кнопку для генерации событий

        wx.Panel.__init__(self, parent)




       self.logText = wx.TextCtrl(

           self,

            style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL | wx.TE_RICH)




       btn = wx.Button(self, label="Press Me")

       btn.Bind(wx.EVT_BUTTON, self.onPress)

       sizer = wx.BoxSizer(wx.VERTICAL)

       sizer.Add(self.logText, 1, wx.EXPAND | wx.ALL, 5)

       sizer.Add(btn, 0, wx.ALL, 5)

       self.SetSizer(sizer)




   def onPress(self, event):

       """

        Ивент который генерирует сообщение по нажатию на кнопку

        """

        random_list = ['ERROR: this is error\n', 'INFO: start program\n', 'DEBUG: debug info for developer\n']

       self.outputPrint(random.choice(random_list))




   def outputPrint(self, message):

       self.logText.AppendText(message)





class MyFrame(wx.Frame):

   def __init__(self):

       """Constructor"""

        wx.Frame.__init__(self, None, title="Logging test")

       panel = MyPanel(self)

       self.Show()

Теперь добавим метод, который будет выводить в окно логов определенный текст по нажатию кнопки. 

Здесь важно помнить, что после message необходимо добавить \n, чтобы перенести новую строку логов. Как следствие, получаем такой результат в окне вывода.

Как сделать окно вывода логов в wx.TextCtrl - wxPythonЕсть также возможность инвертировать лог путем добавления чекбокса wx.CheckBox и создав список, в котором можно хранить все сообщения. 

Тем не менее есть определенные сложности. Перед каждым вызовом метода надо осуществлять очистку TextCtrl немного по-другому. В ином случае сообщения будут дублироваться и спамить. 

А после цикла необходимо передвинуть курсор вверх на последнее сообщение инвертированного лога. И здесь на помощь придет wx.TextCtrl.SetInsertionPoint(0), помещающий курсор TextCtrl в требуемое положение.

А для инверсии необходимо проверить флажок с использованием метода IsChecked. В случае, если он равняется True, необходимо выполнить reversed имеющегося списка с сообщениями. Следовательно, будет осуществлено инвертирование вывода. 

# coding: utf-8

import wx

import random





class MyPanel(wx.Panel):




   def __init__(self, parent):

       """Constructor"""

        # создадим панель в которую поместить окно вывода, чебокс инвертировать и кнопку для генерации событий

        wx.Panel.__init__(self, parent)

       # здесь мы будем хранить все сообщения

        self.cache_msg = list()




       self.logText = wx.TextCtrl(

           self,

            style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL | wx.TE_RICH)




       btn = wx.Button(self, label="Press Me")

       btn.Bind(wx.EVT_BUTTON, self.onPress)

       # чебокс

        self.check_box_invert = wx.CheckBox(self, wx.ID_ANY, 'Invert log', wx.DefaultPosition, wx.DefaultSize, 0)




       sizer = wx.BoxSizer(wx.VERTICAL)

       # горизонтальный сайзер в который мы поместим кнопку и чекбокс, чтобы они были рядом

        hor_sizer = wx.BoxSizer(wx.HORIZONTAL)

       sizer.Add(self.logText, 1, wx.EXPAND | wx.ALL, 5)

       hor_sizer.Add(btn, 0, wx.ALL, 5)

       hor_sizer.Add(self.check_box_invert, 0, wx.ALL, 5)

       sizer.Add(hor_sizer, 0, wx.ALL, 5)

       self.SetSizer(sizer)




   def onPress(self, event):

       """

        Ивент который генерирует сообщение по нажатию на кнопку

        """

        random_list = ['ERROR: this is error\n', 'INFO: start program\n', 'DEBUG: debug info for developer\n']

       self.outputPrint(random.choice(random_list))




   def outputPrint(self, message):

       # сохраним наше сообщение в списке

        self.cache_msg.append(message)

       # очистим окно от старых сообщений

        self.logText.Clear()

       # в зависимости от состояния флажка одном сообщения в окне

        # если флажок установлен, то инвертируем лог (последнее сообщение будет вверху)

        if self.check_box_invert.IsChecked():

           for msg in reversed(self.cache_msg):

               # определим цвет сообщения

                color = self.outputColored(str(msg))

               # добавим его в окно

                self.logText.AppendText(msg)

               # окрасим вывод

                self.logText.SetForegroundColour(color)

           # перемещаем курсор в верхнее положение, иначе пользователь будет видеть нижнее сообщение

            # используем только если инвертировать лог включено

            self.logText.SetInsertionPoint(0)

       else:

           for msg in self.cache_msg:

               color = self.outputColored(str(msg))

               self.logText.AppendText(msg)

               self.logText.SetForegroundColour(color)




   @staticmethod

    # метод определяет цвет вывода

    def outputColored(message):

       return wx.RED if message.split()[0] == 'ERROR:' else wx.BLACK





class MyFrame(wx.Frame):

   def __init__(self):

       """Constructor"""

        wx.Frame.__init__(self, None, title="Logging test")

       panel = MyPanel(self)

       self.Show()





def main():

   app = wx.App(False)

   frame = MyFrame()

   app.MainLoop()





if __name__ == "__main__":

   main()

Помимо этого, есть возможность сделать вывод подходящего цвета в случае попадания в лог какого-то конкретного сообщения. Чтобы продемонстрировать это, добавим random и сгенерируем список событий, которые будут создавать наш лог. Также нужно будет использовать метод, определяющий цвет лога. 

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

Нюанс

Если логи будут приходить не из потока, а из процесса в бэкенде либо в ui, то само приложение может остановиться, ожидая ответа. Следовательно, данный метод может использоваться для логирования внутренних методов самого ui либо вынесения логов в отдельный поток.

Выводы

Чтобы вести маленькое количество логов, можно использовать AppendText. В ином случае необходимо подумать по поводу хендлера, поскольку описанный ранее метод – далеко не самый идеальный. Вывод в окне может быть окрашен любым цветом. Можно использовать как цвета от самого wx, но и от множества RGB.

ОфисГуру
Adblock
detector