2012-03-24 70 views
1

我已經用ttk完成了(工作)應用程序。它使用自行創建的模塊來顯示comport相關的控件,並在其上繪製一些圖形。當我創建我的對象的一個​​實例時,它啓動一個線程,在這個線程中,進程串行輸入並將其附加到列表中(每個圖表一個列表)。當我有3-6個圖表時,應用程序明顯變慢。它也有一些錯誤,但是當我完成一般概念時,我會對它們進行處理。Python GUI(tkinter.ttk)應用程序緩慢

東西,可以幫助你幫助我:

  • COMPORT是從 LabelFrame和Serial.Serial
  • 座標圖存儲在列表的字典派生的自寫對象的實例: self.graphs = {} self.graphs ['name1'] = []存儲的座標數量 取決於畫布寬度,因此每個圖形大約有1000-2000個。有六個 圖 - 請6
  • 乘以每一個新的座標到達我彈出(0)從列表中 的append()的新座標
  • 我忘了,每一個新的座標集我也商店時機到來 在一個單獨的列表
  • 我使用preiodic呼叫函數來處理該列表:self.after(100, FUNC = self.periodicCall)因此每100ms我從畫布 刪除(ALL)和I繪製每個圖形與這些線。所以,如果我有1000個COORDS在6個graps ,我畫6000條小線
  • 加當然一些服務信息,如少數統治者

所以我想這個想法是清楚的。我想弄清楚什麼是更好的方法。我只是一個在Python中開始編程以及編程,所以我要求你爲我將要發佈的代碼尋求借口,併爲你的眼睛會產生痛苦。我沒有任何編程風格,我想修復它。至少有點。所以對代碼中的任何內容的任何其他評論都是值得歡迎的。

#------------------------------------------------------------------------------- 
# 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() 

和串行部分 - 可能會滯後,以及(用於滯後,當我用重新枚舉端口埃維秒左右......)

#------------------------------------------------------------------------------- 
# 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() 

更新:我一樣布賴恩建議。現在我有兩個屏幕重繪功能。它們之間的區別在於,首先將所有行移到左側,向右側添加新的並刪除從畫布上脫落的行。第二個將線條向左移動並重新部署從畫布向右(不創建新的)的元素。就我的初始版本而言,這些都有很大的改進,但我沒有看到肉眼看到兩者之間的巨大差異 - 如果我有更多的元素,我可能會這麼做。後者雖然更適合我的應用程序,因爲我不必追蹤那些從懸崖上掉下來的人。

下面的功能:

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') 

回答

2

我的畫布的理解是,已分配了更多的元素ID,慢就愈大。它可以處理成千上萬的問題(甚至可能有數千個),但是如果每100ms創建和刪除6000個項目,那很可能是您的問題。即使您正在刪除這些項目,它仍然會影響性能,尤其是當您創建每秒60,000次時。

而不是每100ms刪除所有項目,只需將項目移出屏幕並記住它們,然後通過使用coords方法更改其對新圖形的座標來重用它們。

+0

謝謝布萊恩,我會試試看。我想我必須列出所有的項目,而不是每次更新和更改座標時重複它。 – dccharacter 2012-03-25 08:02:22

+0

不!我必須學習標籤! – dccharacter 2012-03-25 08:03:38

+0

不能讓我的頭在這...好吧,標籤是清晰的,工作。我無法想出一個辦法來處理所有這些線。當我__init__初始化列表self.numOfDots零和畫線。現在我有很多標籤行。我在下一次更新中可以做的是移動所有標籤行。我也可以找到那些畫布的秋天。但是我怎麼能一個接一個地修改這些座標呢?我覺得這個解決方案很接近,但不能理解它。但!!!這確實看起來工作方式更快 – dccharacter 2012-03-25 09:57:07