Уведомления

Группа в Telegram: @pythonsu

#1 Янв. 12, 2012 10:48:34

Piton23
От:
Зарегистрирован: 2011-10-17
Сообщения: 139
Репутация: +  5  -
Профиль   Отправить e-mail  

GUI + thread

Привет всем.
Пишу программку GUI с использованием потоков.

Цель: необходимо чтобы при долгих вычислениях выводилась картинка (типа ждите идет загрузка). По завершении вычислений картинка закрывается.

Реализовал: 2 окна. Основной класс использует класс картинки в init. Далее основной класс продолжает свою работу в другом потоке, т.к. иначе картинку не показывалась б.

Проблема: Я не понимаю что происходит на 4 этапе. Главное окно перерисовывает панель? почему тогда все дочерние элементы (в данном случае контрол) не перерисовывает? Мб есть какие нить другие решения?
Я пробовал и update и refresh по завершении работы потока, но не помогает.

Вот картинка (gif) показывает что происходит после запуска, и на определенных этапах:




# -*- coding: utf-8 -*-

import wx
import wx.animate
import time
import threading

class LoaderImage(wx.Frame):
def __init__(self, img = ''):
style = ( wx.CLIP_CHILDREN | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.FRAME_SHAPED )
wx.Frame.__init__(self, None, title='LoaderImage', style = style)
#self.SetTransparent( 220 )
self.ag = wx.animate.GIFAnimationCtrl(self, -1, img, pos=(0, 0))
self.SetSize(self.ag.GetSize())
self.ag.GetPlayer().UseBackgroundColour(True)
self.ag.Play()
self.Center()
self.Show(True)

def close(self):
self.Close()


class Main(wx.Frame, threading.Thread):
def __init__(self):
threading.Thread.__init__(self)

wx.Frame.__init__(self, None, title='Main', pos = (30, 100), size = (350, 250))
self.panel = wx.Panel(self, -1)
self.panel.SetBackgroundColour('#A0A030')
self.Center()
self.Show(True)

# Создаем анимацию в главном потоке, иначе ругается на вызов self.ag.Play() в классе LoaderImage:
# PyAssertionError: ... in wxTimerBase::Start(): timer can only be started from the main thread
self.ani = LoaderImage("ajax-loader.gif")
# Затем мы делаем определенные действия, к примеру добавляем контрол на панель
# И выполняем это в другом потоке, иначе анимация не начнется, пока главный поток
# не завершится


# Запускаем поток по созданию контрола
self.start()

def run(self):
# см картинку 1
time.sleep(2)
self.text = wx.TextCtrl(self.panel, -1, "asd", pos = (50, 20))
# см картинку 2
time.sleep(2)
self.ani.close()
# см картинку 3
time.sleep(2)
# см рисунок 4, точнее скрин не с этого места, а когда поток прорабатывается, и программа
# так сказать переходит в режим ожидания (MainLoop)


app = wx.App(0)
f = Main()
app.MainLoop()
Кто чем может подсказать? куда копать?

Офлайн

#2 Янв. 12, 2012 11:52:09

nerijus
От:
Зарегистрирован: 2010-06-03
Сообщения: 93
Репутация: +  1  -
Профиль   Отправить e-mail  

GUI + thread

Piton23
Кто чем может подсказать? куда копать?
Даже не смотрел реализацию. Могу сказать только что работать прямо с GUI можно только с основного потока. Это правило существует для любой GUI системы (QT, WX, GTK, native api и т.д.). То есть всю работу можете делать в любом потоке, но для обновления GUI передавайте сигнал в основной поток и там уже рисуйте что хотите.



Офлайн

#3 Янв. 15, 2012 19:44:21

Piton23
От:
Зарегистрирован: 2011-10-17
Сообщения: 139
Репутация: +  5  -
Профиль   Отправить e-mail  

GUI + thread

Итак почитал немного про сигналы, поэкспериментировал.
Поскольку пишу под виндой, то доступны только эти типы сигналов

NSIG - имеет значение на единицу больше самого большого номера сигнала
SIGABRT - аварийное прерывание процесса
SIGBREAK - хз, но break видать то же с прерыванием связано
SIGFPE - ошибка при выполнении операции над числами с плавающей точкой
SIGILL - недопустимая инструкция
SIGINT - от терминала получен символ прерывания работы процесса (преобразуется в исключение KeyboardInterrupt, аля ctrl+c)
SIGSEGV - обращение к недопустимому адресу памяти (не могут обрабатыватся из программного кода python (из Python Подробный справочник 4 издание Дэвид Бэзлей))
SIGTERM - завершить работу процесса
SIG_DFL - обработчик сигнала, который вызывает обработчик по умолчанию

Пользовательские под виндой вроде нельзя делать. Даже если б был нужный тип сигнала, не до конца понял можно ли программно из возбуждать, как исключения (raise) чтоб обработчик сигнала с главного потока сработал.
В реали надо как раз в другом потоке не вычисления делать, а рисовать на окне множество кнопок, список кнопок (может достигать тысячи), а поскольку только в главном потоке, то пока в неведение … Но за подсказку все равно спасибо пойду дальше думать, искать и экспериментировать

Офлайн

#4 Янв. 15, 2012 21:03:19

nerijus
От:
Зарегистрирован: 2010-06-03
Сообщения: 93
Репутация: +  1  -
Профиль   Отправить e-mail  

GUI + thread

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

P.S. если когда нибудь работали с windows api, то это аналог sendmessage, который обрабатывается в главном потоке.



Офлайн

#5 Янв. 15, 2012 21:07:37

truporez
От:
Зарегистрирован: 2009-05-08
Сообщения: 266
Репутация: +  6  -
Профиль   Адрес электронной почты  

GUI + thread

С wx незнаком, однако найти решение оказалось несложно.



Отредактировано (Янв. 15, 2012 21:07:57)

Офлайн

#6 Янв. 17, 2012 21:23:33

Piton23
От:
Зарегистрирован: 2011-10-17
Сообщения: 139
Репутация: +  5  -
Профиль   Отправить e-mail  

GUI + thread

Вот он ключевой момент: wx.PostEvent(), вызывающий событие пользователя
спасибо: truporez за линк и nerijusза желание помочь
Все прекрасно прорисовывает, событие на ура срабатывает из второстепенных потоков. Пойду допиливать из быдла оформления в более менее человеческий вид.

Офлайн

#7 Янв. 24, 2012 11:13:28

Piton23
От:
Зарегистрирован: 2011-10-17
Сообщения: 139
Репутация: +  5  -
Профиль   Отправить e-mail  

GUI + thread

Да, хоть PostEvent() и позволяет подать сигнал в основной поток для прорисовки элементов, но при длительных рисованиях гифка также останавливается.

Решение всему этому wx.Yield(), с ним даже не потребовались дополнительные потоки. Из бесконечного цикла подаются сигналы на прорисовку новых элементов
Так же кому интересная будет эта тема могут взглянуть на статью http://wiki.wxpython.org/LongRunningTasks

Я ж в завершении темы приложу рабочий пример. Не оценивайте оформление gui (накидал быстро), цель - показать как можно долго рисовать и при этом на экран выводить картинку ожидания (что может пригодится при запуске программы, и в др. случаях чтоб избежать бликов и лагов)

Результат скрипта:




# -*- coding: utf-8 -*-
import time
import wx
import wx.animate
import random

# Button definitions
ID_START = wx.NewId()
ID_STOP = wx.NewId()

myEVENT = wx.NewEventType()
EVENT_USER = wx.PyEventBinder(myEVENT, 1)

class EventUser(wx.PyCommandEvent):
"""Event to signal that a count value is ready"""
def __init__(self, etype, eid, value=None):
"""Creates the event object"""
wx.PyCommandEvent.__init__(self, etype, eid)


class LoaderImage(wx.Frame):
def __init__(self, img = ''):
style = ( wx.CLIP_CHILDREN | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.FRAME_SHAPED )
wx.Frame.__init__(self, None, title='LoaderImage', style = style)
self.SetTransparent( 220 )
self.ag = wx.animate.GIFAnimationCtrl(self, -1, img, pos=(0, 0))
self.SetSize(self.ag.GetSize())
self.ag.GetPlayer().UseBackgroundColour(True)
self.Center()

def close(self):
self.Close()



# GUI Frame class that spins off the worker thread
class MainFrame(wx.Frame, wx.PyCommandEvent):
"""Class MainFrame."""
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'wxYield Test', size = (900, 700))

wx.Button(self, ID_START, 'Start', pos=(0,0))
wx.Button(self, ID_STOP, 'Stop', pos=(0,50))
self.status = wx.StaticText(self, -1, '', pos=(0,100))

self.Bind (wx.EVT_BUTTON, self.OnStart, id=ID_START)
self.Bind (wx.EVT_BUTTON, self.OnStop, id=ID_STOP)

self.Bind(EVENT_USER, self.onCreate)

self.data = ["one", "two", "three", "four", "five", "six"] * 120
self.current = 0
self.run = True
self.ani = LoaderImage("ajax-loader.gif")

self.panel = wx.Panel(self, -1, pos = (100, 5), size = (550, 500))
count = len(self.data)
self.sw = wx.ScrolledWindow(self.panel, size=(550, 500), style = wx.VSCROLL)
self.sw.SetScrollbars(1,1,275,(count + 1)*24/7 + 2)

def getRandColour(self):
r = hex(random.randint(0,15))[2:] + hex(random.randint(0,15))[2:]
g = hex(random.randint(0,15))[2:] + hex(random.randint(0,15))[2:]
b = hex(random.randint(0,15))[2:] + hex(random.randint(0,15))[2:]
return "#" + r + g + b

def onCreate(self, event):
if self.current == len(self.data):
self.run = False # Выход из бесконечного цикла Yield
self.ani.close()
return
x, y = self.current % 7, self.current // 7
pos = (x*74, y*24)
label = self.data[self.current]
btn = wx.Button(self.sw, -1, label, pos=pos)
btn.SetBackgroundColour(self.getRandColour())
self.current += 1


def OnStart(self, event):
self.ani.ag.Play()
self.ani.Show(True)
while self.run:
# Посылаем сигнал чтоб отрисовать кнопку
evt = EventUser(myEVENT, -1)
wx.PostEvent(self, evt)
# Притормаживаем гиф и отдаем основной поток на создание кнопки
wx.Yield()

def OnStop(self, event):
self.run = False
self.ani.close()


class MainApp(wx.App):
"""Class Main App."""
def OnInit(self):
"""Init Main App."""
self.frame = MainFrame(None,-1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True

if __name__ == '__main__':
app = MainApp(0)
app.MainLoop()
Примечание, замените адрес гифки, и должно все работать

Отредактировано (Янв. 24, 2012 11:15:53)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version