PyQt 예제 2.2.3

이번 글은 Smart 대화 상자에 대한 것이다.

Smart 대화 상자는 Standard 대화 상자처럼 값들을 초기값 인자로 넘겨주긴 하지만, Standard 대화 상자와는 다르게 사용자와 직접 상호작용하면서 직접 위젯들을 업데이트 시켜준다.

그렇기 때문에 Smart 대화 상자는 Modal 형식이 아닌 Modeless 대화 상자로 작동하기 때문에, 대화 상자가 열렸더라도 부모의 윈도우를 조절할 수 있다.

그리고 Standard 대화 상자와 마찬가지로 위젯 수준과 폼 수준의 확인(validation) 모두 가능하다.


먼저, Smart 대화 상자를 호출하는 부분을 보겠다.
def SmartCall(self):
        def update():
            self.textLabel.setText(self.values["label"])
            self.comboBoxLabel.setText(self.values["comboBox"])
            self.slider.setValue(self.values["slider"])

        self.values = {"label" : self.textLabel.text(), 
                        "comboBox" : self.comboBoxLabel.text(),
                        "slider" : self.slider.value()}
        dialog = SmartDialog(self.values, self)
        self.connect(dialog, SIGNAL("changed"), update)
        dialog.show()

self.values 부분부터 보면, 이 부분은 Standard 대화 상자와 비슷하다. 그리고 대화 상자의 호출 역시 비슷하게 이루어진다. 다만, 차이가 있다면, 지난 번에서는 함수의 지역 변수로 할당해서 호출했지만, 이번에는 클래스의 멤버 변수로 할당을 했다.

즉, SmartCall 메소드가 종료되더라도 self.values는 남아 있고, SmartDialog 에서는 이 변수에 계속 접근이 가능하다는 것이다.

그리고 self.connect를 통해서 대화 상자가 변경될 때마다 (changed 시그널이 보내질 때마다) update 메소드가 호출되도록 했다.
(좀 더 깔끔하게 작성을 한다면 update 함수는 클래스의 메소드로 포함되어 위젯을 업데이트 하도록 해야 할 것이다. 그러나 여기서는 Smart 메소드에만 종속되는 것이기 때문에 위와 같이 메소드 내에 포함시켜서 작성을 하였다.)

그리고 show 메소드를 호출하면서 대화상자를 띄우도록 했다. show 메소드를 호출하게 되면 대화 상자는 modeless 로 나타나게 된다.

그러면 이제 대화 상자 부분을 보겠다.
class SmartDialog(QDialog):
    def __init__(self, arg, parent=None):
        super().__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)

        textEditLabel = QLabel("Main 레이블 변경: ")
        self.textLineEdit = QLineEdit(arg["label"])
        
        valueEditLabel = QLabel("Main 슬라이더 변경: ")
        self.valueLineEdit = QLineEdit(str(arg["slider"]))
        self.valueLineEdit.setInputMask("900")

        comboBoxLabel = QLabel("Combo Box(&C): ")
        self.comboBox = QComboBox()
        comboBoxLabel.setBuddy(self.comboBox)
        for item in ["item1", "item2", "item3", "item4", "item5"]:
            self.comboBox.addItem(item)
        comboBoxIndex = self.comboBox.findText(arg["comboBox"])
        if comboBoxIndex == -1:
            comboBoxIndex = 0
        self.comboBox.setCurrentIndex(comboBoxIndex)

        self.result = arg

        widgetLayout = QFormLayout()
        widgetLayout.addRow(textEditLabel, self.textLineEdit)
        widgetLayout.addRow(valueEditLabel, self.valueLineEdit)
        widgetLayout.addRow(comboBoxLabel, self.comboBox)

        applyButton = QPushButton("적용(&A)")
        cancelButton = QPushButton("취소")
        buttonBox = QDialogButtonBox()
        buttonBox.addButton(applyButton, QDialogButtonBox.ApplyRole)
        buttonBox.addButton(cancelButton, QDialogButtonBox.RejectRole)
        applyButton.setDefault(True)
        self.connect(applyButton, SIGNAL("clicked()"), self.apply)
        self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))

        layout = QVBoxLayout()
        layout.addLayout(widgetLayout)
        layout.addWidget(buttonBox)

        self.setLayout(layout)
        self.setWindowTitle("Smart Dialog")

    def apply(self):
        class OutOfRangeNumberError(Exception): pass

        try:
            value = int(self.valueLineEdit.text())
            if value < 0 or value > 100:
                raise OutOfRangeNumberError("This value is out of range "
                                            "from 0 to 100.")
        except ValueError:
            QMessageBox.warning(self, "Value is Empty",
                                    "This may not be empty")
            self.valueLineEdit.selectAll()
            self.valueLineEdit.setFocus()
            return
        except OutOfRangeNumberError as e:
            QMessageBox.warning(self, "Out of Range of number", str(e))
            self.valueLineEdit.selectAll()
            self.valueLineEdit.setFocus()
            return

        self.result["label"] = self.textLineEdit.text()
        self.result["comboBox"] = self.comboBox.currentText()
        self.result["slider"] = value
        self.emit(SIGNAL("changed"))

먼저, Standard 대화 상자와 가장 다른 점을 보면, self.setAttribute(Qt.WA_DeleteOnClose) 코드가 추가되었다는 것이다.

이것은 대화 상자가 닫히면 이를 제거하라는 의미를 가진다. 이전까지는 대화 상자를 exec로 실행을 했기 때문에 대화 상자가 닫히고, 메소드의 지역을 빠져나가게 되면 자동으로 소멸이 되었지만, 여기서는 show를 통해 호출을 했기 때문에 지역을 빠져나가더라도 대화 상자가 계속 유지되게 된다.

즉, 대화 상자를 닫아도, 제거가 되는 것이 아니라, 단순히 숨겨지게 된다. 따라서, 이 속성을 추가하지 않고 대화 상자를 계속 호출하면 똑같은 대화 상자를 계속 열게 되므로 메모리 낭비가 발생하게 된다.

따라서 이를 해결하기 위해 self.setAttribute(Qt.WA_DeleteOnClose) 코드를 추가해서 대화 상자가 닫히면, 이를 숨기기보다는 삭제하라고 지정해주는 것이다.

그 다음으로, 대화 상자 위젯의 배치는 Standard 와 비슷하게 이루어진다. 다만, self.result 의 경우, 지난 번에는 객체를 copy 했지만, 이번에는 받은 객체를 그대로 가지고 있도록 했다.

그리고 Standard 대화 상자의 경우 확인과 취소 버튼을 사용했지만, 여기서는 적용과 취소 버튼을 사용한다. Smart 대화 상자의 경우 사용자의 작동에 따라서 직접 객체를 업데이트 해주기 때문에 적용 버튼을 통해서 부모 윈도우의 위젯이 업데이트 되도록 하기 때문이다.

그런데 self.valueLineEdit 에서 self.valueLineEdit.setInputMask("900") 이라는 코드가 추가된 것을 볼 수 있는데, 이는 위젯 수준에서 확인(validation)을 수행하는 것이다.

이전까지는 확인 단추를 누를 때, 폼 수준에서 확인을 시도 했지만, 여기서는 위젯 자체에서 확인을 하게 된다. self.valueLineEdit.setInputMask("900")의 의미는 입력된 문자의 각 자리를 제한해두는 것이다. 9는 "ASCII digit required. 0-9"의 의미로, 0~9의 숫자만 가능하다. 0은 "ASCII digit permitted but not required."의 의미로 숫자가 허용되지만 반드시 필요하다는 것은 아니다.

즉, 3자리 숫자를 입력할 수 있다는 의미를 가지게 된다. 그 외의 문자들은 입력이 불가능하다. 따라서 위젯 자체에서 숫자만 입력 가능하도록 했기 때문에 위젯 수준의 검증이 되는 것이다.

이를 활용해서 전화번호의 경우 999-9990-9999 의 inputmask를 주게 되면, 휴대폰 전화번호를 받을 수 있게 된다.

이와 비슷하게, 어떤 문자 조합만을 받는 것을 만든다면, setValidator 라는 메소드를 통해서 유효한 문자만 받게 할 수도 있다. 그리고 setMaxLength를 통해서는 글자의 최대 길이를 제한할 수도 있다.


이제 apply 메소드를 살펴보자. Standard 대화 상자에서는 getResult 라는 메소드가 있었지만, 여기서는 결과를 바로 적용하기 때문에 이러한 메소드가 필요없다.

apply 메소드 역시, Standard 대화 상자의 accept 메소드와 비슷하게, 위젯의 값을 검증하고, 유효할 경우에 이를 저장하도록 되어 있다. 그리고 마지막에는 시그널을 발생시키도록 해두었는데, 이것은 처음의 코드에서 changed 시그널을 받으면 update 메소드를 호출하도록 했기 때문에, 적용 단추가 눌렸을 경우에 대화 상자의 값을 부모 윈도우의 위젯들에게 업데이트 하도록 한다.

댓글 없음:

댓글 쓰기