2012-03-24 3 views
1

У меня есть (рабочее) приложение, сделанное с помощью ttk. Он использует самоподготовленный модуль для отображения элементов управления, связанных с comport, и холста, который рисует на нем несколько графиков. Когда я разбиваю экземпляр моего объекта, он запускает поток, в котором обрабатывается последовательный ввод и добавляет его в список (по одному списку на граф). Когда у меня есть 3-6 графиков, приложение становится заметно медленным. У него также есть несколько ошибок, но я буду их присылать, когда я покончу с общей концепцией.Приложение Python GUI (tkinter.ttk) slow

Вещи, которые могут помочь вам помочь мне:

  • согласоваться является экземпляром самостоятельной написанный объект, производный от LabelFrame и Serial.Serial
  • координаты для графики хранятся в словаре списков : self.graphs = {} self.graphs ['name1'] = [] количество сохраненных координат соответствует ширине холста, примерно 1000-2000 на граф. Есть шесть графики - пожалуйста, умножить на 6
  • С каждой новой системе координат прибывающий я поп (0) из списка и Append() новая координата
  • Я забыл, я также магазин синхронизации каждого нового набора координат прибытия в отдельном списке
  • я использую preiodic вызов функции для обработки списков: self.after (100, функ = self.periodicCall) Таким образом, каждые 100 мс я удалить (все) из холста и я рисую каждый граф с набор линий. Так что, если у меня есть 1000 Coords в 6 Грапс, я рисую 6000 маленькие линии
  • Plus некоторую информацию службы, конечно, такие, как несколько правителей

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

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

И последовательная часть - может отставать, а также (используется для лаг, когда я использовал для reenumerate портов Evey второй или так ...)

#------------------------------------------------------------------------------- 
# 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: Я сделал, как советовал Брайан , Теперь у меня есть две функции перерисовки экрана. Разница между ними заключается в том, что сначала перемещается все строки влево, добавляя новое вправо и удаляя те, которые падают с холста. Второй перемещает строки влево и повторно развертывает элементы, которые падают с холста вправо (без создания новых). Есть огромное улучшение с любым из них в отношении моего первоначального варианта, но я не вижу большой разницы между двумя невооруженным глазом - майне, если бы у меня было больше элементов, которые я хотел бы. Последний, хотя лучше работает для моего приложения, так как мне не нужно отслеживать тех, кто падает со скалы.

Здесь функции:

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

Мое понимание холста является то, что чем больше идентификаторов элементов, которые были выделены, тем медленнее он получает. Он может обрабатывать десятки тысяч без особых проблем (и, возможно, даже 100 тысяч), но если вы создаете и удаляете 6000 элементов каждые 100 мс, это, скорее всего, ваша проблема.Даже при том, что вы удаляете элементы, это все равно влияет на производительность, особенно когда вы создаете 60 000 в секунду.

Вместо того чтобы удалять все предметы каждые 100 мс, просто перемещайте элементы с экрана и помните их, а затем повторно используйте их с помощью метода coords, чтобы изменить их координаты для нового графика.

+0

Спасибо Брайан, я попробую. Я предполагаю, что мне нужно будет иметь список всех элементов, а затем перебирать каждое обновление и изменять координаты. – dccharacter

+0

Нет! Мне нужно узнать теги! – dccharacter

+0

Не могу получить мою голову, это ... Хорошо, теги ясны и работают. Я просто не могу понять способ обработки всех этих строк. Когда я __init__, я инициализирую списки в self.numOfDots нули и рисую строки. Теперь у меня много помеченных строк. Что я могу сделать в следующем обновлении, это переместить все помеченные строки. Я также могу найти те, которые падают на холст. Но как я могу конкретно изменить координаты тех, один за другим? Я чувствую, что решение близко, но не может его обработать. Но!!! Это действительно работает быстрее. – dccharacter

Смежные вопросы