2017-04-14 138 views
0

我一直在研究這個話題在過去的幾天,還沒有找到適合我的應用程序的答案。我正在python 2.7中用PyQt4構建一個GUI來與機器人進行交互。我需要一個線程(main)來運行使用QT Designer創建的GUI和另一個線程(worker)來連續地向機器人傳輸數據和從機器人傳輸數據。我想在無限工作線程中使用在GUI(主線程)中輸入的數據。在我看到的所有教程中,數據只從工作人員發送到主線程;像通常的「加載欄」示例一樣。Python PyQt4線程化,共享變量

這是我到目前爲止。該程序從QT Designer導入GUI輸出,並在按下控制檯時將值輸出到控制檯。我也有一個無限的線程設置,打印「你好」的控制檯,它的工作。爲了讓我指出正確的方向,當點擊名爲「按鈕」的按鈕被點擊時,你能告訴我如何將無限打印「hello」改成別的東西嗎?我熟悉C/C++,但這是我的第一個Python項目,任何幫助都是值得讚賞的。

from PyQt4 import QtGui, QtTest, QtCore # Import the PyQt4 module we'll need 
import sys # We need sys so that we can pass argv to QApplication 

import gui # Import the GUI output from QT Designer 


class GUI(QtGui.QMainWindow, gui.Ui_MainWindow): # This class is to interact   with the GUI 
def __init__(self): 
    super(self.__class__, self).__init__() 
    self.setupUi(self) 
    self.threadclass = ThreadClass() 
    self.threadclass.start() # start thread class 

    ######### Binds GUI widgets with functions ##### 
    self.connectbutton.clicked.connect(self.pushbutton) # Call function "pushbutton" when pressed 
    self.motor_up.clicked.connect(self.motorup) # Call function motorup when pressed 
    self.motor_down.clicked.connect(self.motordown) # Call function motorup when pressed 
    self.motor_speed_slider.valueChanged.connect(self.slider) # Call function slider 
    self.auto_manual_check.stateChanged.connect(self.checkbox) # Call function checkbox 
    self.Kp_box.valueChanged.connect(self.kp_entry) 
    self.Ki_box.valueChanged.connect(self.ki_entry) 
    self.Kd_box.valueChanged.connect(self.kd_entry) 


    ######### Functions to run when GUI event is encountered 

def pushbutton(self): # stuff to do if connect button is pressed    ## THIS BUTTON 
    self.connectlabel.setText("Connected") 
    QtTest.QTest.qWait(2000)   
    self.connectlabel.setText("Disconnected") 
    self.emit(SIGNAL("text(Qstring)"),"it worked") 

def slider(self): # Stuff to do when slider is changed 
    print(self.motor_speed_slider.value()) # Print to monitor 

def motorup(self): # Run if motor up is clicked 
    print("motor up") 

def motordown(self): # Run if motor down is clicked 
    print("motor down") 

def checkbox(self): # Run if checkbox is changed 
    if self.auto_manual_check.isChecked(): 
     print("is checked") 
    else: 
     print("unchecked") 

def kp_entry(self): # Run if kp is changed 
    print(self.Kp_box.value()) 

def ki_entry(self): # Run if ki is changed 
    print(self.Ki_box.value()) 

def kd_entry(self):# Run if kd is changed 
    print(self.Kd_box.value()) 



class ThreadClass(QtCore.QThread): # Infinite thread to communicate with robot 
def __init__(self, parent = None): 
    super(ThreadClass, self).__init__(parent) 

def run(self): 
    while 1:   # Runs continuously 
     print "hello" #       CHANGE THIS WHEN pushbutton IS PRESSED 




def main(): # Function to run the main GUI 
app = QtGui.QApplication(sys.argv) # A new instance of QApplication 
form = GUI()     # We set the form to be our ExampleApp 
form.show()       # Show the form 
app.exec_()       # and execute the app 


if __name__ == '__main__':    # if we're running file directly and not importing it 
main()        # run the main function 

編輯: 謝謝你的快速,詳盡的答覆。在對您的解決方案進行實驗後,我認爲我越來越接近了,但是我沒有讓他們爲我的應用程序工作,我想我的初步問題可能不清楚。我基本上試圖在線程之間傳遞變量,實際的打印只是爲了顯示變量已成功傳輸。我認爲這個精簡的代碼將清除它。

from PyQt4 import QtGui, QtTest, QtCore # Import the PyQt4 module we'll need 
import sys # We need sys so that we can pass argv to QApplication 

import gui # Import the GUI output from QT Designer 


class GUI(QtGui.QMainWindow, gui.Ui_MainWindow):   # This class is to interact with the GUI 
def __init__(self): 
    super(self.__class__, self).__init__() 
    self.setupUi(self) 
    self.threadclass = ThreadClass() 
    self.threadclass.start() 
    self.Kp_box.valueChanged.connect(self.kp_entry)  # Call function kp_entry if box value is changed 

def kp_entry(self):          # Run if kp is changed     
    print(self.Kp_box.value())       # I WANT TO SEND THE BOX VALUE TO THE WORKER THREAD   

class ThreadClass(QtCore.QThread):       # Infinite thread to communicate with robot 
def __init__(self, parent = None): 
    super(ThreadClass, self).__init__(parent) 

def run(self): 
    while 1:    
     print self.Kp_box.value()      #CONTINUOUSLY PRINT THE UPDATED BOX VALUE  


def main():        # Run the main GUI 
    app = QtGui.QApplication(sys.argv) # A new instance of QApplication 
    form = GUI()      # We set the form to be our ExampleApp 
    form.show()       # Show the form 
    app.exec_()       # and execute the app 

if __name__ == '__main__':    # if we're running file directly and not importing it 
main()        # run the main function 

所有我想要做的就是讓用戶更新在GUI中的值,然後將該值傳遞給工作線程。在這種情況下,我有一個輸入框,以便用戶可以更改號碼。然後我需要程序將該號碼傳送給與機器人通信的工作線程。現在代碼不起作用,我儘可能接近它,以更好地展示我正在嘗試做什麼。

如果有幫助,這是我的GUI here

+0

[this](http://stackoverflow.com/a/35534047/1994235)答案可能很有用(有一個解釋如何擴展信號/插槽連接以包含其他參數,以便它們是發給工人)。 –

回答

0

有幾個方法可以做到這一點。第一個(簡單的),一個是把一行代碼在你的方法pushbutton

def pushbutton(self): 
    self.threadclass.got_a_click() 

添加相應的方法和實例變量ThreadClass,並改變運行方式:

def __init__(self): 
    super(ThreadClass, self).__init__(parent) 
    self.flag = False 

def got_a_click(self): 
    self.flag = True 

def run(self): 
    while not self.flag: 
     print "hello" 
    do_something_useful() 

這可能夠好。請注意,方法got_a_click在主循環上下文中運行,並且run方法在第二個QThread上下文中。

更復雜的方法是使用Qt將信號發送到其他線程中的插槽。爲此,您必須在輔助線程中運行QEventLoop。 QThread基類已經爲此設置了:它有一個只包含事件循環的run方法。因此,爲了讓QThread子類運行一個事件循環,不要重載run方法。這個QThread只會在信號被髮送到其中一個插槽之前一直坐在那裏無所事事。當發生這種情況時,插槽將在輔助線程中執行。代碼如下所示:

class ThreadClass(QtCore.QThread): 
    def __init__(self, parent = None): 
     super(ThreadClass, self).__init__(parent) 
     self.moveToThread(self) 

    def onButtonPress(self): 
     pass # Your code here 

此行添加到的GUI構造: self.connectbutton.clicked.connect(self.threadclass.onButtonPress)

每次單擊該按鈕,在二級線程上下文onButtonPress運行。

請注意ThreadClass構造函數中的語句self.moveToThread(self)。這個非常重要。它告訴Qt Signal/Slot機制,通過ThreadClass線程中的事件循環來調用此對象中的所有Slots。沒有它,插槽onButtonPress將在主線程的上下文中運行,即使其代碼位於ThreadClass

需要注意的另一個缺陷是直接調用onButtonPress。 不會使用Signal/Slot機制,onButtonPress將像任何普通的Python函數一樣執行。例如,如果您從現有函數pushbutton調用onButtonPress,它將運行在主線程上下文中。爲了獲得所需的穿線行爲,必須使Qt信號發射到onButtonPress插槽。

下面是你重新解釋的問題的答案。下面的代碼片段工作,你打算:

from PySide import QtGui, QtTest, QtCore # Import the PySide module we'll need 
import sys 

class GUI(QtGui.QMainWindow):   # This class is to interact with the GUI 
    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.Kp_box = QtGui.QSpinBox(self) 
     self.threadclass = ThreadClass(self) 
     self.threadclass.start() 
     self.Kp_box.valueChanged.connect(self.kp_entry)  # Call function kp_entry if box value is changed 

    def kp_entry(self):          # Run if kp is changed     
     print("Main", self.Kp_box.value())       # I WANT TO SEND THE BOX VALUE TO THE WORKER THREAD   

class ThreadClass(QtCore.QThread):       # Infinite thread to communicate with robot 
    def __init__(self, main_window): 
     self.main_window = main_window 
     super(ThreadClass, self).__init__(main_window) 

    def run(self): 
     while 1:    
      print(self.main_window.Kp_box.value())      #CONTINUOUSLY PRINT THE UPDATED BOX VALUE  


def main():        # Run the main GUI 
    app = QtGui.QApplication(sys.argv) # A new instance of QApplication 
    form = GUI()      # We set the form to be our ExampleApp 
    form.show()       # Show the form 
    app.exec_()       # and execute the app 

if __name__ == '__main__':    # if we're running file directly and not importing it 
    main()        # run the main function 

我從你的代碼改變什麼:

  1. 我用PySide不PyQt的,但他們大多是兼容的(或至少 他們應該是)。我不得不改變第一個導入 聲明;你將不得不改回它。
  2. 要刪除對GUI工具的依賴關係,我將主窗口替換爲只有一個SpinBox的主窗口 。這證明了您的多線程問題的基本特徵 ,並將其從您的其他應用程序需求中解耦出來。
  3. 您的輔助線程無法訪問SpinBox,因爲它沒有引用它的 變量。我懷疑你是 擁有的大問題。它通過將主窗口對象傳遞給ThreadClass構造函數並將其存儲在實例變量中來修復。

在任何情況下,任何東西都不會從一個線程轉移到另一個線程。這段代碼簡單地使用了這樣的事實:即使程序是多線程的,Python程序中的所有變量都存在於相同的內存空間中。 [如果程序使用多個進程,則情況並非如此。]

這個小程序沒有提供任何優雅的關閉方式。

+0

謝謝保羅爲你的好帖子!我已經嘗試過使用您的解決方案,並取得了有限的成功。我已經能夠告訴工作者線程在按下GUI按鈕時運行打印方法,但不能共享變量。爲了更好地說明我的目標,我編輯了我的初步迴應。 –

+0

根據您的重做問題重新解決了我的問題:-) –