Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[wxPython] StaticText et rafraîchissement

10 réponses
Avatar
Zarak
Bonjour,

J'ai un problème avec mon interface graphique wxpython. J'affiche un
StaticText
juste avant le lancement d'une tâche qui doit créer un fichier Excel. Souci
: le
StaticText n'a pas le temps de s'afficher totalement avant le lancement de
la tâche.

Même problème si je fais un time.sleep(n) après un StaticText, le sleep se
lance
avant l'affichage complet du texte, et c'est très moche.

Une solution à proposer, genre rafraîchissement d'écran ou autre ?

Merci de vos pythonesques lumières.

10 réponses

Avatar
Zarak
Plus joli :

Bonjour,

J'ai un problème avec mon interface graphique wxpython. J'affiche un
StaticText juste avant le lancement d'une tâche qui doit créer un fichier
Excel. Souci : le StaticText n'a pas le temps de s'afficher totalement avant
le lancement de la tâche.

Même problème si je fais un time.sleep(n) après un StaticText, le sleep
se lance avant l'affichage complet du texte, et c'est très moche.

Une solution à proposer, genre rafraîchissement d'écran ou autre ?

Merci de vos pythonesques lumières.
Avatar
NicolasP
Bonjour,

Le 09/02/2010 08:44, Zarak a écrit :
Plus joli :

Bonjour,

J'ai un problème avec mon interface graphique wxpython. J'affiche un
StaticText juste avant le lancement d'une tâche qui doit créer un fichier
Excel. Souci : le StaticText n'a pas le temps de s'afficher totalement
avant
le lancement de la tâche.

Même problème si je fais un time.sleep(n) après un StaticText, le sleep
se lance avant l'affichage complet du texte, et c'est très moche.

Une solution à proposer, genre rafraîchissement d'écran ou autre ?

Merci de vos pythonesques lumières.



Dans tous les cas, un time.sleep() ne peut absolument pas aider. Bien au contraire.
Il faut bien avoir en tête que l'interface graphique d'une application tourne dans un et unique thread. Si tu bloques ce thread avec un time.sleep(), c'est toute l'interface graphique qui est bloquée.
Dans ton code, lorsque tu "mets à jour" un StaticText, en réalité, tu ne fais que demander la mise à jour. La vraie mise à jour est faite à postériori, quand tu rends la main au système graphique, c'est à dire quand la fonction en cours d'exécution se termine.
Ce n'est donc pas de ce coté qu'il faut chercher une solution.

Comment est effectuée la tâche ?
Directement en appelant une fonction/méthode ?
En lançant une application/un thread ?

Nicolas
Avatar
Zarak
"NicolasP" a écrit dans le message de news: 4b7270e5$0$10476$

Dans tous les cas, un time.sleep() ne peut absolument pas aider. Bien au contraire.
Il faut bien avoir en tête que l'interface graphique d'une application tourne dans un et unique thread. Si tu bloques ce thread
avec un time.sleep(), c'est toute l'interface graphique qui est bloquée.
Dans ton code, lorsque tu "mets à jour" un StaticText, en réalité, tu ne fais que demander la mise à jour. La vraie mise à jour
est faiti5e à postériori, quand tu rends la main au système graphique, c'est à dire quand la fonction en cours d'exécution se
termine. Ce n'est donc pas de ce coté qu'il faut chercher une solution.

Comment est effectuée la tâche ?
Directement en appelant une fonction/méthode ?
En lançant une application/un thread ?



En fait, globalement, il s'agit d'une zone où sont affichés des messages de
statut, je veux simplement qu'il y ait 1 ou 2 secondes entre "connexion OK"
et "Attendez pendant la création du fichier Excel". Avec le sleep entre les
deux, le "connexion OK" s'affiche partiellement, ne disparaît pas vraiment
avant l'affichage de l'autre message, bref c'est très moche. Sans le sleep,
tout est ok, mais évidemment la transition est trop rapide.

Code source :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
time.sleep(2)
labelState.Destroy()
labelState = wx.StaticText(self, -1, pos=(30,395),label="Patientez pendant la génération du fichier Excel...")
Avatar
NicolasP
Le 10/02/2010 14:20, Zarak a écrit :
"NicolasP" a écrit dans le message de news:
4b7270e5$0$10476$

Dans tous les cas, un time.sleep() ne peut absolument pas aider. Bien
au contraire.
Il faut bien avoir en tête que l'interface graphique d'une application
tourne dans un et unique thread. Si tu bloques ce thread avec un
time.sleep(), c'est toute l'interface graphique qui est bloquée.
Dans ton code, lorsque tu "mets à jour" un StaticText, en réalité, tu
ne fais que demander la mise à jour. La vraie mise à jour est faiti5e
à postériori, quand tu rends la main au système graphique, c'est à
dire quand la fonction en cours d'exécution se termine. Ce n'est donc
pas de ce coté qu'il faut chercher une solution.

Comment est effectuée la tâche ?
Directement en appelant une fonction/méthode ?
En lançant une application/un thread ?



En fait, globalement, il s'agit d'une zone où sont affichés des messages de
statut, je veux simplement qu'il y ait 1 ou 2 secondes entre "connexion OK"
et "Attendez pendant la création du fichier Excel". Avec le sleep entre les
deux, le "connexion OK" s'affiche partiellement, ne disparaît pas vraiment
avant l'affichage de l'autre message, bref c'est très moche. Sans le sleep,
tout est ok, mais évidemment la transition est trop rapide.

Code source :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
time.sleep(2)
labelState.Destroy()
labelState = wx.StaticText(self, -1, pos=(30,395),label="Patientez
pendant la génération du fichier Excel...")




Comme je le disais dans mon précédent message, ça ne peut pas marcher.
Tu es sûr de vouloir faire poireauter l'utilisateur pour rien ?
Un moyen pour arriver à tes fins serait de déclencher un timer auquel tu passes une fonction de callback qui fera ce qui se trouve après le sleep(). Et tu vires le sleep() bien sûr.

Donc dans ton source actuel :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
init_timer_a_2_secondes(callback)


Dans la callback :
labelState.Destroy()
labelState = wx.StaticText(self, -1, pos=(30,395),label="Patientez pendant la génération du fichier Excel...")


Nicolas
Avatar
Zarak
"NicolasP" a écrit dans le message de news: 4b72dd39$0$4622$

Code source :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
time.sleep(2)
labelState.Destroy()
labelState = wx.StaticText(self, -1, pos=(30,395),label="Patientez
pendant la génération du fichier Excel...")




Comme je le disais dans mon précédent message, ça ne peut pas marcher.
Tu es sûr de vouloir faire poireauter l'utilisateur pour rien ?
Un moyen pour arriver à tes fins serait de déclencher un timer auquel tu passes une fonction de callback qui fera ce qui se >
trouve après le sleep(). Et tu vires le sleep() bien sûr.



Je viens de me rendre compte que le problème ne vient pas du "sleep",
c'est bel et bien que la fonction de génération du fichier Excel se lance
avant la fin de l'affichage du texte.

labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
labelState.SetForegroundColour("#00FF00")
labelState.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False, u'Comic Sans MS'))
GenerateExcelJanuary()

Une autre idée ?
Avatar
NicolasP
Le 11/02/2010 14:51, Zarak a écrit :
"NicolasP" a écrit dans le message de news:
4b72dd39$0$4622$

Code source :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
time.sleep(2)
labelState.Destroy()
labelState = wx.StaticText(self, -1, pos=(30,395),label="Patientez
pendant la génération du fichier Excel...")




Comme je le disais dans mon précédent message, ça ne peut pas marcher.
Tu es sûr de vouloir faire poireauter l'utilisateur pour rien ?
Un moyen pour arriver à tes fins serait de déclencher un timer auquel
tu passes une fonction de callback qui fera ce qui se > trouve après
le sleep(). Et tu vires le sleep() bien sûr.



Je viens de me rendre compte que le problème ne vient pas du "sleep",
c'est bel et bien que la fonction de génération du fichier Excel se lance
avant la fin de l'affichage du texte.

labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
labelState.SetForegroundColour("#00FF00")
labelState.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False,
u'Comic Sans MS'))
GenerateExcelJanuary()

Une autre idée ?



L'idée est toujours la même.
Tu ne peut pas faire quelque chose qui prend du temps à partir du thread qui gère l'interface graphique sous peine de geler l'interface graphique jusqu'à ce que le boulot soit fini.
Il faut que tu exécutes GenerateExcelJanuary() à partir d'un autre thread.

Ton code sera comme ceci :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
labelState.SetForegroundColour("#00FF00")
labelState.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False, u'Comic Sans MS'))
Creation et Lancement du threadtravailleur.

Tu n'as pas le choix. C'est la seule solution.

Nicolas
Avatar
Zarak
"NicolasP" a écrit dans le message de news: 4b742f3a$0$9106$

Ton code sera comme ceci :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
labelState.SetForegroundColour("#00FF00")
labelState.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False, u'Comic Sans MS'))
Creation et Lancement du threadtravailleur.

Tu n'as pas le choix. C'est la seule solution.

Nicolas



Ca marche, mais du coup ça amène d'autres problèmes. Le thread travailleur se lance,
le premier texte s'affiche entièrement, mais le fait que le thread principal continue ne
m'arrange pas vraiment, étant donné que quelque chose comme "Fichier terminé" doit
s'afficher seulement après la fin du travail, et d'autres soucis aussi.

J'utilise :
t = thread.start_new_thread(GenerateExcelFebruary,(self,))

Y'a-t-il une possibilité de mettre une condition sur la fin du thread travailleur, du genre
"if t.end()" ?
Avatar
NicolasP
Le 12/02/2010 10:07, Zarak a écrit :
"NicolasP" a écrit dans le message de news:
4b742f3a$0$9106$

Ton code sera comme ceci :
labelState = wx.StaticText(self, -1, pos=(80,395),label="Connexion OK")
labelState.SetForegroundColour("#00FF00")
labelState.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD, False,
u'Comic Sans MS'))
Creation et Lancement du threadtravailleur.

Tu n'as pas le choix. C'est la seule solution.

Nicolas



Ca marche, mais du coup ça amène d'autres problèmes. Le thread
travailleur se lance,
le premier texte s'affiche entièrement, mais le fait que le thread
principal continue ne
m'arrange pas vraiment, étant donné que quelque chose comme "Fichier
terminé" doit
s'afficher seulement après la fin du travail, et d'autres soucis aussi.

J'utilise :
t = thread.start_new_thread(GenerateExcelFebruary,(self,))

Y'a-t-il une possibilité de mettre une condition sur la fin du thread
travailleur, du genre
"if t.end()" ?




Admettons que tu mettes une condition genre "if t.end()" dans ton code. Où va tu la mettre ?
N'oublies pas qu'un système fenêtré est évènementiel. C'est à dire que le code que tu écris n'est appelé que lorsqu'il y a quelque chose à faire (un affichage, un bouton cliqué...).
Une fois ton thread lancé il tourne tout seul dans son coin. Comme un grand.Et l'interface graphique de son coté également.
Tu pourrais être tenté de faire un labelState.SetLabel() à partir du thread travailleur pour indiquer la fin du travail. C'est à ne pas faire. Si tu essayes, tu me diras surement que ça marche. Mais peut être pas. Ce qui est sûr c'est que les interfaces graphiques ne sont pas "thread safe" (il ne faut pas appeler des fonction des l'interface à partir d'autres threads). Si tu ne respectes pas ça, ton code marchera la pluspart du temps. Mais des fois il ne fera rien ou pas bien. Et des fois ça plantera carrément l'interface graphique.
Il faut donc établir un dialogue entre le thread travailleur et l'interface graphique.
Pour cela, il y a la classe wx.PyEvent. Ce qui est génial ici, c'est que tu peux passer presque n'importe quoi dans le message.
Un exemple étant toujours mieux que du blabla, en voici un fonctionnel:

# -*- coding: UTF-8 -*-
import wx
from threading import *
from VideoNgLogoRs232 import SendLogo


_EVT_RESULT_ID = 100


class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
"""Init Result Event."""
wx.PyEvent.__init__(self)
self.SetEventType(_EVT_RESULT_ID)
self.data = data


# Thread class that executes processing
class WorkerThread(Thread):
"""Worker Thread Class."""
def __init__(self, notify_window, port, image):
"""Init Worker Thread Class."""
Thread.__init__(self)
self._notify_window = notify_window
self.port = port
self.image = image
#self._want_abort = 0
# This starts the thread running on creation, but you could
# also make the GUI thread responsible for calling this
self.start()


def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
s = SendLogo(self.port, self.image, self.UpdateText, self.UpdateProgress)
if s.IsPortComOk() == False :
self.UpdateText ("Invalid communication port")
else :
error = s.Send()
if error == False :
self.UpdateText ("Success")
else :
self.UpdateText ("Failed")

wx.PostEvent(self._notify_window, ResultEvent({"Action" : "Done"}))


def UpdateText (self, text):
wx.PostEvent(self._notify_window, ResultEvent({"Action" : "UpdateText",
"Content" : text}))

def UpdateProgress (self, progress):
wx.PostEvent(self._notify_window, ResultEvent({"Action" : "UpdateProgress",
"Content" : progress}))

def abort(self):
"""abort worker thread."""
# Method for use by main thread to signal an abort
#self._want_abort = 1


class DialogLogoSend(wx.Dialog):
def __init__(self, parent, id, title, port, image):#, image):
wx.Dialog.__init__(self, parent, id, title, style = wx.CAPTION)

self.port = port
self.image = image

self.staticText = wx.StaticText(self, -1, u'Initialising dialog box and uploader')
self.Gauge = wx.Gauge(self, -1, 100, style=wx.GA_HORIZONTAL | wx.GA_SMOOTH)
self.button1 = wx.Button (self, wx.ID_OK, "OK")

# Main layout
vBox = wx.BoxSizer(wx.VERTICAL)
vBox.Add(self.staticText, flag=wx.EXPAND|wx.ALIGN_LEFT|wx.ALL, border)
vBox.Add(self.Gauge, flag=wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, border)
vBox.Add(self.button1, flag=wx.ALIGN_CENTER|wx.ALL, border)

self.SetSizerAndFit(vBox)

# Bind des boutons avec les fonctions
self.Bind(wx.EVT_BUTTON, self.OnOk, id=wx.ID_OK)
#self.Bind(wx.EVT_BUTTON, self.OnMessage, id=_EVT_RESULT_ID)
self.Connect(-1, -1, _EVT_RESULT_ID, self.OnMessage)

self.button1.Enable(False)

self.worker = WorkerThread(self, self.port, self.image)


def OnOk(self, event):
# On ne fait rien pour l'instant
event.Skip ()


def OnMessage (self, event):
data = event.data
if data["Action"] == "Done" :
self.button1.Enable(True)
elif data["Action"] == "UpdateText" :
self.staticText.SetLabel (data["Content"])
elif data["Action"] == "UpdateProgress" :
self.Gauge.SetValue (data["Content"])

Il s'agit d'une boite de dialogue qui affiche une barre de progression et un pourcentage ou un statut. Cette boite de dialogue crée et lance un thread travailleur.
SendLogo est une classe à moi qui envoie des données sur une liaison série. Dans cette classe, UpdateProgress et UpdateText sont utilisés pour mettre à jour la barre de progression lors de l'envoi des données. A la fin de l'envoi, le message "Done" est envoyé à la boite de dialogue pour que le bouton "ok" soit dégrisé et donc cliquable.
A noter que le format des données des messages est perso. Ce n'est pas imposé.

Nicolas
Avatar
Zarak
"NicolasP" a écrit dans le message de news: 4b755ebc$0$4685$

Il s'agit d'une boite de dialogue qui affiche une barre de progression et un pourcentage ou un statut. Cette boite de dialogue crée >et lance un thread travailleur.
SendLogo est une classe à moi qui envoie des données sur une liaison série. Dans cette classe, UpdateProgress et UpdateText >sont utilisés pour mettre à jour la barre de progression lors de l'envoi des données. A la fin de l'envoi, le message "Done" est >envoyé à la boite de dialogue pour que le bouton "ok" soit dégrisé et donc cliquable.
A noter que le format des données des messages est perso. Ce n'est pas imposé.



Ok, je mets ton script de tout côté, qui pourra me servir plus tard dans l'avancée du
projet. En attendant, étant donné la petitesse du problème (afficher un "traitement du fichier"
après un "connexion ok") je vais simplement afficher le deuxième en dessous du premier.
Grâce au thread travailleur, j'ai déjà résolu un problème, et j'ai réussi à faire une progression
en pourcentage qui marche bien.

Merci encore pour ton aide !
Avatar
NicolasP
Le 12/02/2010 16:11, Zarak a écrit :
"NicolasP" a écrit dans le message de news: 4b755ebc$0$4685$

Il s'agit d'une boite de dialogue qui affiche une barre de progression et un pourcentage ou un statut. Cette boite de dialogue crée>et lance un thread travailleur.
SendLogo est une classe à moi qui envoie des données sur une liaison série. Dans cette classe, UpdateProgress et UpdateText>sont utilisés pour mettre à jour la barre de progression lors de l'envoi des données. A la fin de l'envoi, le message "Done" est>envoyé à la boite de dialogue pour que le bouton "ok" soit dégrisé et donc cliquable.
A noter que le format des données des messages est perso. Ce n'est pas imposé.



Ok, je mets ton script de tout côté, qui pourra me servir plus tard dans l'avancée du
projet. En attendant, étant donné la petitesse du problème (afficher un "traitement du fichier"
après un "connexion ok") je vais simplement afficher le deuxième en dessous du premier.
Grâce au thread travailleur, j'ai déjà résolu un problème, et j'ai réussi à faire une progression
en pourcentage qui marche bien.



Regarde mon code maintenant que tu as le nez dedans. Tu verras que ce n'est pas compliqué et facilement. Même avec une petite application.
Et je répète que le thread travailleur n'a pas le droit d'appeler des fonctions d'affichage directement.

Merci encore pour ton aide !


Pas de quoi.