2012-03-24 9 views
1

Ich habe (Arbeit) Anwendung mit ttk getan. Es verwendet ein selbst erstelltes Modul zum Anzeigen von comport-bezogenen Steuerelementen und eine Zeichenfläche, auf der einige Grafiken gezeichnet werden. Wenn ich eine Instanz meines Objekts kiste, wird ein Thread gestartet, in dem die serielle Eingabe verarbeitet und an eine Liste angehängt wird (eine Liste pro Diagramm). Wenn ich 3-6 Graphen habe, wird die Anwendung merklich langsam. Es hat auch ein paar Bugs, aber ich werde sie adressieren, wenn ich mit dem allgemeinen Konzept fertig bin.Python GUI (tkinter.ttk) Anwendung langsam

Dinge, die helfen können Sie mir helfen:

  • comport ist eine Instanz eines selbstgeschriebenen Objekt, das von LabelFrame und Serial.Serial leitet
  • für Koordinaten Graphen in einem Wörterbuch von Listen gespeichert sind, : self.graphs = {} self.graphs ['name1'] = [] Anzahl der gespeicherten Koordinaten ist bis zur Breite der Leinwand, also etwa 1000-2000 pro Graph. Haben sechs Graphen - multiplizieren bitte um 6
  • Mit jedem neuen Koordinaten ankommen ich pop (0) aus der Liste und append() die neue
  • ich vergaß, ich auch speichern Timing jedes neuen Satz von Koordinaten Koordinaten ankommen in einer separaten Liste
  • ich eine preiodic Anruffunktion verwenden, um die Listen zu verarbeiten: self.after (100, func = self.periodicCall) So alle 100ms ich löschen (ALL) von der Leinwand und ich jedes Diagramm zeichnen mit Linien. Also, wenn ich 1000 coords in 6 graps zeichne ich 6000 kleine Linien
  • plus einige Service-Info natürlich wie ein paar Herrscher

Also ich denke, die Idee ist klar. Ich möchte herausfinden, was der bessere Ansatz wäre. Ich habe gerade angefangen in Python und in der Programmierung, also frage ich nach deiner Entschuldigung für den Code, den ich posten werde und für den Schmerz in deinen Augen, den er verursachen wird. Ich habe keinen Programmierstil und möchte das beheben. Zumindest ein bisschen. Daher sind alle anderen Kommentare zu allem, was Sie im Code sehen, willkommen.

#------------------------------------------------------------------------------- 
# Name:  dataVisualizer 
# Purpose: 
# 
# Author:  dccharacter 
# 
# Created:  23.03.2012 
# Copyright: (c) dccharacter 2012 
# Licence:  <your licence> 
#------------------------------------------------------------------------------- 
#!/usr/bin/env python 

from tkinter import * 
from tkinter.ttk import * 
from robowidgets.serialPortGui import * 
import threading 
import re 
import atexit 
import random 
from datetime import datetime 
import time 

class dataVisualizer(LabelFrame): 
    def __init__(self, master, comport , cnf={}, **kw): 
     self.master = master 
     self.comport = comport 
     LabelFrame.__init__(self, *cnf, **kw) 

     self.messageVar = StringVar() 
     Label(self, text="Message format regexp:").pack() 
     self.messagePattern = Entry(self, width = 20, text = 234234, textvariable = self.messageVar); 
     self.messageVar.set(r'(-*\d+),(-*\d+),(-*\d+),(-*\d+),(-*\d+),(-*\d+)') 
     self.messagePattern.pack() 
     Button(self, text = "Pause", command = self.pause).pack() 
     self.pauseFlag = TRUE 

     self.canvWidth, self.canvHeight = 1000, 700 
     self.density = 1 ##width of pixel - the bigger, the wider graph 
     self.numOfDots = self.canvWidth//self.density 
     self.graphs = {} 
     self.graphs['name1']=[] 
     self.graphs['name2']=[] 
     self.graphs['name3']=[] 
     self.graphs['name4']=[] 
     self.graphs['name5']=[] 
     self.graphs['name6']=[] 
     self.timings = [] 
     self.zeroTiming = datetime.now() 
     self.colors = ['red', 'blue', 'green', 'orange', 'violet', 'black', 'cyan'] 

     self.canv = Canvas(self, width = self.canvWidth, height = self.canvHeight) 
     self.canv.pack() 

     self.thread = threading.Thread(target = self.workerThread) 
     self.thread.start() 

     self.serialData = [] 

     self.periodicCall() 

    def pause(self): 
     self.pauseFlag = ~self.pauseFlag 

    def redraw(self): 
     self.canv.delete(ALL) 

     colorIndex = 0 
     for graphName in self.graphs: 
      runningAverage = sum(self.graphs[graphName][-10:])//10 
      text = str(runningAverage) 
      self.canv.create_text(self.canvWidth-60, 20*(colorIndex+1), text = text, 
       fill = self.colors[colorIndex], anchor = W) 
      prev_xxx, prev_yyy = 0, 0 
      for yyy in self.graphs[graphName]: 
       self.canv.create_line(prev_xxx, prev_yyy, prev_xxx+self.density, self.canvHeight//2 - yyy, 
        width = 1.4, fill = self.colors[colorIndex]) 
       prev_xxx, prev_yyy = prev_xxx+self.density, self.canvHeight//2 - yyy 
      colorIndex = colorIndex + 1 
     self.drawMesh() 

    def drawMesh(self): 
     self.canv.create_rectangle(3, 3, self.canvWidth, 
      self.canvHeight, outline = 'black', width = 2) 
     self.canv.create_line(0, self.canvHeight/2, self.canvWidth, 
      self.canvHeight/2, fill="black", width = 1) 

     mouseX = self.canv.winfo_pointerx() - self.canv.winfo_rootx() 
     mouseY = self.canv.winfo_pointery() - self.canv.winfo_rooty() 

     if mouseY < 60: aaa = -1 
     else: aaa = 1 
     if mouseX > self.canvWidth - 200 : bbb = -12 
     else: bbb = 1 
     try: 
      self.canv.create_rectangle(mouseX + 10*bbb - 5, mouseY - 20*aaa +10, 
       mouseX + 10*bbb + 115, mouseY - 20*aaa - 30, outline = "black", 
       fill = "red") 
      self.canv.create_text(mouseX + 10*bbb, mouseY - 40*aaa, 
       text = "t="+str(self.timings[mouseX//self.density]), 
       anchor = W) 
      self.canv.create_text(mouseX + 10*bbb, mouseY - 20*aaa, 
       text = "value="+str(self.canvHeight//2 - mouseY), 
       anchor = W) 
     except IndexError: 
      pass 
     self.canv.create_line(mouseX, 0, mouseX, 
      self.canvHeight, fill="blue", dash = [4, 1, 2, 1], width = 1) 
     self.canv.create_line(0, mouseY, self.canvWidth, 
      mouseY, fill="blue", dash = [4, 1, 2, 1], width = 1) 


    def periodicCall(self): 
     self.redraw() 
     self.after(100, func=self.periodicCall) 

    def workerThread(self): 

     while (1): 
      try: 
       if self.comport.isOpen() and (self.pauseFlag == TRUE): 
        comLine = self.comport.readline() 
        if len(self.timings) == self.numOfDots: 
         self.timings.pop(0) 
        td = datetime.now() - self.zeroTiming 

        ## b'271;-3:-50\r\n' 
        parsedLine = re.search(self.messagePattern.get(), str(comLine)) 
        index = 1 
        if parsedLine: 
         self.timings.append(td) 
         for graphName in self.graphs: 
          if len(self.graphs[graphName]) == self.numOfDots: 
           self.graphs[graphName].pop(0) 
          try: 
           self.graphs[graphName].append(int(parsedLine.group(index))) 
          except IndexError: 
           self.graphs[graphName].append(0) 
          index = index + 1 
       else: 
        self.comport.flush(); 
        time.sleep(1) 
      except TclError: 
       self.thread._stop() 

def main(): 
    root = Tk() 
    mainWindow = Frame(root) 
    mainWindow.pack() 
    port = comPortWidget(mainWindow) 
    port.pack() 
    dv = dataVisualizer(mainWindow, port) 
    dv.pack() 
    root.mainloop() 

if __name__ == '__main__': 
    main() 

Und das Serienteil - auch kann hinken (zur Verzögerung, wenn ich Ports evey Sekunde oder so reenumerate verwendet ...)

#------------------------------------------------------------------------------- 
# Name:  robowidgets 
# Purpose: 
# 
# Author:  dccharacter 
# 
# Created:  10.03.2012 
# Copyright: (c) dccharacter 2012 
# Licence:  <your licence> 
#------------------------------------------------------------------------------- 
#!/usr/bin/env python 

import serial 
from serial.tools.list_ports_windows import comports 
from tkinter import * 
from tkinter.ttk import * 

class comPortWidget(LabelFrame, serial.Serial): 

    commonComPortSpeeds = ["1200", "2400", "4800", "9600", "14400", "19200", "38400", "57600", "115200"] 

    def __init__(self, master=None, cnf={}, **kw): 
     """Construct a comPortWidget widget with the parent MASTER. 

     STANDARD OPTIONS 

      borderwidth, cursor, font, foreground, 
      highlightbackground, highlightcolor, 
      highlightthickness, padx, pady, relief, 
      takefocus, text, background, class, colormap, container, 
      height, labelanchor, labelwidget, 
      visual, width 

     WIDGET-SPECIFIC OPTIONS 


     """ 
     self.master = master 
     LabelFrame.__init__(self, master, text="Serial settings", *cnf, **kw) 
     serial.Serial.__init__(self) 
     self.parent = master 
     self.draw() 

    def draw(self): 
     self.strVarComPort = StringVar() 
     self.comboComport = Combobox(self, 
      textvariable=self.strVarComPort) 

     self.comboComport.grid(row=0, column=1) 
     self.labelComportName = Label(self, text="Com port:") 
     self.labelComportName.grid(row=0, column=0) 

     self.strVarComSpeed = StringVar() 
     self.comboComSpeed = Combobox(self, 
      textvariable=self.strVarComSpeed, values=self.commonComPortSpeeds) 
     self.comboComSpeed.current(len(self.commonComPortSpeeds)-1) 
     self.comboComSpeed.grid(row=1, column=1) 
     self.labelComSpeed = Label(self, text="Com speed:") 
     self.labelComSpeed.grid(row=1, column=0) 

     self.buttonComOpen = Button(self, text="Open port", command=self.openPort) 
     self.buttonComOpen.grid(row=0, column=2) 
     self.buttonComClose = Button(self, text="Close port", command=self.closePort) 
     self.buttonComClose.grid(row=1, column=2) 
     self.buttonRefreshPorts = Button(self, text="Re", width=3, command=self.refreshComPortsCombo) 
     ##self.buttonRefreshPorts.grid(row=0, column=2) 

     self.refreshComPortsCombo() 

    def refreshComPortsCombo(self): 
     listComs = self.enumerateComPorts() 
     if not listComs: 
      listComs.append("No com ports found") 
      self.disableControls(~self.isOpen()) 
      self.buttonComClose.configure(state=DISABLED) 
     else: 
      self.disableControls(self.isOpen()) 
     self.buttonRefreshPorts.configure(state=NORMAL) 
     self.comboComport.config(values=listComs) 
     self.comboComport.current(len(listComs)-1) 
     ##self.after(500, func=self.refreshComPortsCombo) 

    def enumerateComPorts(self): 
     """ 
     Returns the list ofcom port names in the system or an empty list if 
     no ports found 
     """ 
     listComs = [] 
     for port, desc, hwid in sorted(comports()): 
      listComs.append(port) 
     return listComs 

    def openPort(self): 
     if self.isOpen(): 
      return 
     self.port = self.comboComport.get() 
     self.baudrate = int(self.comboComSpeed.get()) 
     self.timeout = 1 
     try: 
      self.open() 
      self.disableControls(self.isOpen()) 
     except IOError: 
      pass 

    def closePort(self): 
     if self.isOpen(): 
      self.flush() 
      self.close() 
      self.disableControls(self.isOpen()) 

    def disableControls(self, isConnected): 
     if isConnected: 
      self.labelComportName.configure(state=DISABLED) 
      self.labelComSpeed.configure(state=DISABLED) 
      self.comboComport.configure(state=DISABLED) 
      self.comboComSpeed.configure(state=DISABLED) 
      self.buttonComClose.configure(state=NORMAL) 
      self.buttonComOpen.configure(state=DISABLED) 
      self.buttonRefreshPorts.configure(state=DISABLED) 
     else: 
      self.labelComportName.configure(state=NORMAL) 
      self.labelComSpeed.configure(state=NORMAL) 
      self.comboComport.configure(state=NORMAL) 
      self.comboComSpeed.configure(state=NORMAL) 
      self.buttonComClose.configure(state=DISABLED) 
      self.buttonComOpen.configure(state=NORMAL) 
      self.buttonRefreshPorts.configure(state=NORMAL) 

def main(): 
    pass 

if __name__ == '__main__': 
    main() 

UPDATE: Ich tat, wie Brian empfohlen . Jetzt habe ich zwei Screen-Redraw-Funktionen. Der Unterschied zwischen ihnen besteht darin, dass zuerst alle Zeilen nach links verschoben werden, wobei rechts neue hinzugefügt werden und diejenigen, die von der Zeichenfläche fallen, gelöscht werden. Der zweite bewegt Linien nach links und setzt Elemente, die von der Leinwand nach rechts fallen, wieder ein (ohne neue zu erstellen). In Bezug auf meine ursprüngliche Variante gibt es eine enorme Verbesserung, aber ich sehe keinen großen Unterschied zwischen den beiden mit bloßem Auge - vielleicht hätte ich mehr Elemente, die ich hätte. Letzteres funktioniert jedoch besser für meine Anwendung, da ich nicht diejenigen verfolgen muss, die von der Klippe fallen.

Hier werden die Funktionen:

def drawGraph(self): ###needed for self.updateGraph2() only as it is creates the lines 
    for graphNum in range(0, self.numOfGraphs): 
     self.graphLines.append([]) 
     self.graphData.append([0,]*self.numOfDots) 
     for iii in range(0,self.numOfDots): 
      self.graphLines[graphNum].append(
       self.canv.create_line(0,0,0,0,fill=self.colors[graphNum], 
       width=1.2, tags=('graphLines', 'graph'+str(graphNum))) 
       ) 


def updateGraph2(self): 
    while not self.queue.empty(): 
     iTuple = self.queue.get() 
     self.canv.move('graphLines', -self.density,0) 
     for graphNum in range(0, self.numOfGraphs): 
      try: self.graphData[graphNum].append(iTuple[graphNum]) 
      except IndexError: 
       self.graphData[graphNum].append(0) 
      self.graphData[graphNum].pop(0) 
      self.graphLines[graphNum].append(self.graphLines[graphNum].pop(0)) 
      self.canv.coords(self.graphLines[graphNum][-1], 
       self.canv.winfo_width()-self.density, 
       int(int(self.graphData[graphNum][-2])+int(self.canv.winfo_height()//2)), 
       self.canv.winfo_width(), 
       int(int(self.graphData[graphNum][-1])+int(self.canv.winfo_height()//2)) 
       ) 

def updateGraph(self): 
    while not self.queue.empty(): 
     self.timingIndex = self.timingIndex + 1 
     self.canv.move('graphLines', -self.density, 0) 
     iTuple = self.queue.get() 
     for iii in range(0, len(iTuple)): 
      yyy = int(iTuple[iii])+self.canv.winfo_height()//2 
      if yyy < 0: yyy = 0 
      if yyy > self.canv.winfo_height(): yyy = self.canv.winfo_height() 
      prev_yyy = int(self.prevTuple[iii])+self.canv.winfo_height()//2 
      if prev_yyy < 0: prev_yyy = 0 
      if prev_yyy > self.canv.winfo_height(): prev_yyy = self.canv.winfo_height() 
      self.canv.create_line(
       self.canv.winfo_width()-self.density, prev_yyy, 
       self.canv.winfo_width(), yyy, 
       width = 1.4, fill = self.colors[iii], tags=('graphLines','graph'+str(iii))) 
     self.prevTuple = iTuple 

     self.canv.addtag_overlapping('todelete',-1,-1,-3,self.canv.winfo_height()+1) 
     self.canv.dtag('preserve','todelete') 
     self.canv.delete('todelete') 

Antwort

2

Mein Verständnis der Leinwand ist, dass die mehr Element-IDs, die zugeordnet wurden, wird desto langsamer. Es kann Zehntausende ohne viel Problem (und vielleicht sogar Hunderttausende) behandeln, aber wenn Sie alle 100ms 6000 Objekte erstellen und löschen, ist das wahrscheinlich Ihr Problem.Obwohl Sie die Elemente löschen, wirkt sich dies immer noch auf die Leistung aus, insbesondere wenn Sie 60.000 pro Sekunde erstellen.

Anstatt alle Elemente alle 100 ms zu löschen, verschieben Sie einfach die Elemente vom Bildschirm und merken sie sich. Verwenden Sie dann die Methode coords, um ihre Koordinaten für das neue Diagramm zu ändern.

+0

Danke Bryan, ich werde es versuchen. Ich denke, ich muss eine Liste mit allen Elementen haben, dann wiederhole ich jedes Update und ändere die Coords. – dccharacter

+0

Nein! Ich muss Tags lernen! – dccharacter

+0

Ich kann meinen Kopf nicht bekommen ... Ok, Tags sind klar und funktionieren. Ich kann einfach keinen Weg finden, mit all diesen Zeilen umzugehen. Wenn ich __init__ initialisiere ich Listen auf self.numOfDots Nullen und Linien zeichnen. Jetzt habe ich viele markierte Zeilen. Was ich im nächsten Update tun kann, ist, alle markierten Zeilen zu verschieben. Ich kann auch diejenigen finden, die von Leinwand fallen. Aber wie kann ich die Koordinaten einzeln ändern? Ich fühle, dass die Lösung nahe ist, aber nicht grep. Aber!!! Das scheint in der Tat viel schneller zu funktionieren. – dccharacter