원본

더보기

서론

pyside2를 이용하여 windows에서 Gui 환경을 적용한 plc 모니터링 시스템을 만드는 중이다.

 

그런데 객체 지향 프로그래밍에 대한 기초가 잘 안잡혀 있어서 구조를 짜고 구현하는데 너무 애를 많이 먹었다...

 

하지만 결국 해결했다 ㅎ


문제

내가 가지고 있었던 문제는 상속 관계에 있었다.

 

프로젝트의 구조는 이러하다.

 

main.py -> monitor.py -> gui.py

                              -> communication.py


1. main.py에서 monitor 객체를 생성한다.

 

2. monitor class __init__()에서 Ui_monitor 객체를 생성하고 Ui_monitor.setupUi()라는 함수를 호출한다.

(pyqt designer를 이용해 짠 Ui파일을 불러오는 작업이다.)

 

이제 gui.py communication.py를 이용하여 Gui 이미지 생성 및 갱신하는 부분과 통신 부분을 구현할 생각이었다.

 

그런데 왠열.... 처음에는 mro Error(찾아보니 다이아몬드 형식으로 상속을 했을 때 발생하는 오류라고 한다.)가 발생하고, 이를 해결하니 Gui 창이 안바뀌고....

 

문제의 원인은 2가지였다.

1. 클래스 객체와 인스턴스 객체

2. super().__init__()

 

 1. 인스턴스 객체는 인스턴스... 즉, class를 이용하여 인스턴스를 생성하면 각 인스턴스마다 가지는 객체이다.

그래서 python3 부터 self.~~라는 변수명을 볼 수 있는데 이게 바로 인스턴스 객체이다.

 

하지만 내 ui객체(ui = Ui_monitor())는 단 하나만 존재해야 한다. 그래서 인스턴스 객체가 아닌 클래스 객체로 선언을 해줘야 한다. 어떤 Monitor 객체가 생성되도 내가 관리할 Gui 창은 하나이기 때문이다.

 

2. 문제는 pyside의 setupUi() 함수이다. setupUi()는 qtdesigner에서 만든 다양한 위젯들을 만들어주는 함수이다.

이놈은 자기 자신을 인자로 받아 처리하기 때문에 생성자에 선언하게 된다.

즉, 한 번 생성이 되고, 다시 생성이 될 경우(ex. 자식 객체가 super().__init__을 한 경우 등) main.py에서 show()를 통해 이미 출력되어 있는 창이 아닌 다른 인스턴스 객체들이 생성이 되어버린다.

 

그렇게 생성된 다른 인스턴스를 이용하여 값을 변경 시키려고 해봤자... 현재 떠있는 창에는 아무런 반응이 없을 수 밖에...ㅎ

 

즉, 클래스 변수로 객체를 선언하고, 자식 클래스에서 __init__을 통한 부모 객체를 추가적으로 생성시키지 않게 하면서 일단 문제를 해결하였다...


그런데 더 좋은 구조가 있는데 아직 부족해서 내가 못찾고 있는 게 아닌가 하는 생각이 든다.

뭐랄까.... 깔끔하지가 않다... 찜찜한 이느낌..ㅋ

 

수정

왠지 너무 찝찝해서 다시 열심히 조사를 하고 드디어 (일단) 가장 만족스러운 구조를 찾았다.

 

기본적인 생각은 동일하다.

 

GUI와 Serial 통신을 나누는 것....

 

class는 크게 2가지이다. monitor classsignalThread class

 

기본적인 상속 구조는 이런식이다.

Ui_monitor, QMainWindow(pyside2 class) -> monitor class

QThread -> signalThread class

 

그리고 내가 최대한 신경쓴 singalThread class에서 serial 통신을 이용하여 값을 받아 왔을 때 그 결과를 GUI에 어떻게 반영할 것인가이다.

 


01. 통신 결과를 GUI에 반영하기

communication.py

5 line : QThread Class를 상속받는다.

6 line : Pyside2.QtCore.Signal() 함수를 이용하여 사용자 Singal을 설정할 수 있다. Signal의 경우 Class 변수로 선언하고, Singal의 인자로 반환할 타입을 입력하면 데이터를 반환할 수 있다.

현재는 dic(dictionary) 타입을 넘겨주기 위해 작성하였다. 

8~14 line : QThread의 run() 함수를 오버라이딩한다. 실제 통신 결과를 data에 넣게 된다. 

10 line : with threading.Lock() Serial 통신이 동기적으로 움직일 수 있도록 구현하였다.

13 line : emit(data) 함수를 이용하여 data를 나중에 사용될 slot에 넘겨주면서 signal을 발생시키도록 하였다. 일종의 callback() 함수와 동일하게 동작한다고 보면 된다. serial 통신이 모두 완료가 되면 signal을 발생시키고, 그 signal과 연결된 slot을 수행하여 GUI를 수정할 것이다.

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Monitor(QMainWindow, Ui_monitor):
 
    def __init__(self):
        super(Monitor, self).__init__()
        self.setupUi(self)
 
        self.sig = signalThread()
        self.sig.start()
 
        #통신을 통해 한 스캔을 완료했을 경우 발생하는 signal이 finished이고, 해당 시그널이 발생하면 통신 결과를 Gui에 바로 반영할 수 있도록 update_info()를 호출
        #그리고 update_info()의 인자로 받아온 값들을 통해 gui에 바로 업데이트를 할 수 있다.
        self.sig.finished.connect(self.update_info)
 
        #click을 눌렀을 경우 데이터를 communication.py에 보내기
        data = {}
        data['send'= 'Good'
        self.buttonIn.clicked.connect(lambda: self.sig.sendSignal(data))
 
    @Slot(dict)
    def update_info(self, data):
        try:
            print('Update Info', data)
        except Exception as e:
            print(e)
            pass
cs

18~19 line : signalThread() 객체를 생성한 후 run Thread를 실행시켜주는 start()를 실행한다.

23 line : finished Signal을 self.update_info Slot과 연결시킨다.

31 line : finished 신호가 오면 그 결과를 Gui에 반영하는 함수이다. set~~가 여러개 들어갈 예정이다.

 

이렇게 되면 serial 통신은 signalThread()에서만 수행하게 되고 GUI 결과는 Monitor()에서 처리를 할 수 있게 된다.

 


02. 사용자 입력값(GUI에서 조작한 값)을 serial 통신으로 보내기

 

signalThread class

23
24
25
26
27
28
29
30
 
    @Slot()
    def sendSignal(self, data):
        with threading.Lock():
            print("Send: ", data)
 
 
 
 
 
 
cs

signalThread에 slot을 하나 생성해주었다. 이 slot은 monitor class에서 버튼을 클릭했을 때 호출이 될 예정이다.

 

monitor class

25
26
27
28
29
30
31
32
33
34
35
36
        #click을 눌렀을 경우 데이터를 communication.py에 보내기
        data = {}
        data['send'= 'Good'
        self.buttonIn.clicked.connect(lambda: self.sig.sendSignal(data))
 
    @Slot(dict)
    def update_info(self, data):
        try:
            print('Update Info', data)
        except Exception as e:
            print(e)
            pass
cs

26~28 line : 버튼을 lambda 함수를 이용하여 인자를 넘기면서 호출해주면 연결 끝이다.

+ Recent posts