Tworzenie dialogu

From FreeCAD Documentation
Revision as of 05:35, 14 November 2023 by Kaktus (talk | contribs) (Created page with "W niektórych systemach program nazywa się pyuic4 zamiast pyuic. Spowoduje to po prostu konwersję pliku .ui na skrypt Pythona. Jeśli otworzymy plik mywidget.py, jego zawartość jest bardzo przejrzysta:")

Wprowadzenie

Na tej stronie pokażemy, jak zbudować prosty interfejs graficzny za pomocą Qt Designer, oficjalnego narzędzia Qt do projektowania interfejsów. Przekonwertujemy okno dialogowe na kod Python, a następnie będzie używane wewnątrz FreeCAD. Zakładamy, że użytkownik wie, jak edytować i uruchamiać Python.

W tym przykładzie cały interfejs jest zdefiniowany w środowisku Python. Chociaż jest to możliwe w przypadku małych interfejsów, w przypadku większych interfejsów zaleca się ładowanie utworzonych plików .ui bezpośrednio do programu.

Dwie ogólne metody tworzenia interfejsów, poprzez włączenie interfejsu do pliku Python lub poprzez użycie plików .ui.

Projektowanie okna dialogowego

W aplikacjach CAD zaprojektowanie dobrego interfejsu użytkownika (UI) jest bardzo ważne. Prawie wszystko, co użytkownik będzie robił, będzie odbywało się za pośrednictwem jakiegoś elementu interfejsu: czytanie okien dialogowych, naciskanie przycisków, wybieranie między ikonami itp. Dlatego bardzo ważne jest, aby dokładnie przemyśleć co chcesz zrobić, jak chcesz aby użytkownik postępował i jaki będzie przepływ pracy.

Istnieje kilka pojęć, które należy znać podczas projektowania interfejsu:

  • Modalne/niemodalne okna dialogowe: Modalne okno dialogowe pojawia się przed ekranem, zatrzymując działanie głównego okna, zmuszając użytkownika do odpowiedzi na okno dialogowe, podczas gdy niemodalne okno dialogowe nie przerywa pracy w głównym oknie. W niektórych przypadkach pierwsze rozwiązanie jest lepsze, w innych nie.
  • Określenie, co jest wymagane, a co opcjonalne: Upewnij się, że użytkownik wie, co musi zrobić. Oznacz wszystko odpowiednim opisem, użyj podpowiedzi itp.
  • Oddzielanie poleceń od parametrów: Zazwyczaj robi się to za pomocą przycisków i pól wprowadzania tekstu. Użytkownik wie, że kliknięcie przycisku spowoduje akcję, podczas gdy zmiana wartości w polu tekstowym zmieni gdzieś parametr. Jednak w dzisiejszych czasach użytkownicy zwykle dobrze wiedzą, co jest przyciskiem, co jest polem wejściowym itp. Zestaw narzędzi interfejsu, którego używamy, Qt, jest najnowocześniejszym zestawem narzędzi i nie będziemy musieli się zbytnio martwić o wyjaśnianie rzeczy, ponieważ będą one już bardzo zrozumiałe same w sobie.

Tak więc, teraz, gdy dobrze zdefiniowaliśmy, co będziemy robić, nadszedł czas, aby otworzyć projektanta QT. Zaprojektujmy bardzo proste okno dialogowe, takie jak to:

Następnie użyjemy tego okna dialogowego w programie FreeCAD, aby utworzyć ładną prostokątną płaszczyznę. Może się okazać, że nie jest to zbyt przydatne do tworzenia ładnych prostokątnych płaszczyzn, ale łatwo będzie je później zmienić, aby robić bardziej złożone rzeczy. Po otwarciu Qt Designer wygląda następująco:

Tworzenie okna dialogowego

Qt Designer jest bardzo prosty w użyciu. Na lewym pasku znajdują się elementy, które można przeciągnąć na widżet. Po prawej stronie znajdują się panele właściwości wyświetlające wszystkie rodzaje edytowalnych właściwości wybranych elementów. Zacznijmy więc od utworzenia nowego widżetu.

  1. Wybieramy "Dialog bez przycisków", ponieważ nie chcemy domyślnych przycisków OK/Cancel.
  2. Potrzebujemy Etykiety. Etykiety to proste ciągi tekstowe, które pojawiają się na widżecie, aby poinformować użytkownika końcowego. Jeśli wybierzesz etykietę, zauważysz, że po prawej stronie pojawi się kilka właściwości, które możesz modyfikować, takich jak: styl czcionki, wysokość itp. Przeciągnijmy więc 3 oddzielne etykiety na nasz widżet:
    • Jedna etykieta dla tytułu
    • Kolejna etykieta do napisania "Wysokość"
    • Kolejna etykieta do napisania "Szerokość"
  3. Potrzebujemy teraz LineEdits (a właściwie 2 z nich). Przeciągnij dwa z nich na widżet. LineEdits to pola tekstowe, które użytkownik końcowy może wypełnić. Potrzebujemy więc jednego LineEdit dla "Wysokość" i jednego dla "Szerokość". Tutaj również możemy edytować właściwości. Na przykład, dlaczego nie ustawić wartości domyślnej, powiedzmy na przykład: 1.00 dla każdego. W ten sposób, gdy użytkownik zobaczy okno dialogowe, obie wartości będą już wypełnione. Jeśli użytkownik będzie zadowolony, może bezpośrednio nacisnąć przycisk, oszczędzając cenny czas.
  4. Następnie dodajmy przycisk PushButton. Jest to przycisk, który użytkownik końcowy będzie musiał nacisnąć po wypełnieniu obu pól.

Uwaga: wybraliśmy tutaj bardzo proste kontrolki. Qt ma o wiele więcej opcji, na przykład można użyć Spinboxes zamiast LineEdits itp. Przyjrzyj się temu, co jest dostępne, zbadaj... na pewno będziesz miał inne pomysły.

To wszystko, co musimy zrobić w Qt Designer. Na koniec zmieńmy nazwy wszystkich elementów na prostsze, aby łatwiej było je zidentyfikować w naszych skryptach:

Konwersja naszego okna dialogowego do środowiska Python

Teraz zapiszmy gdzieś nasz widget. Zostanie on zapisany jako plik .ui, który z łatwością przekonwertujemy na skrypt Pythona za pomocą pyuic. Na Windowsie program pyuic jest dołączony do pyqt (do sprawdzenia), na linuksie prawdopodobnie będziesz musiał zainstalować go osobno z menedżera pakietów (na systemach opartych na debianie jest on częścią pakietu pyqt4-dev-tools). Aby dokonać konwersji, należy otworzyć okno terminala (lub okno wiersza polecenia w systemie Windows), przejść do miejsca, w którym zapisano plik .ui i wydać polecenie:

pyuic mywidget.ui > mywidget.py

W systemie Windows pyuic.py znajduje się w "C:\Python27\Lib\site-packages\PyQt4\uic\pyuic.py". Do konwersji należy utworzyć plik wsadowy o nazwie "compQt4.bat":

@"C:\Python27\python" "C:\Python27\Lib\site-packages\PyQt4\uic\pyuic.py" -x %1.ui > %1.py

W konsoli DOS wpisz bez rozszerzenia

compQt4 myUiFile

W systemie macOS można pobrać odpowiednią wersję (tę samą, która jest używana wewnętrznie w FreeCAD 0.19) QT i Pyside za pomocą następujących poleceń (wymagany program):

python3 -m pip install pyqt5
python3 -m pip install pySide2

Spowoduje to zainstalowanie uic w folderze "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/uic", a Designer w "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/Designer.app". Dla wygody można utworzyć link do uic w /usr/local/bin, aby móc go wywołać po prostu za pomocą uic -g python ... zamiast wpisywania całej ścieżki programu, oraz link do Designera, aby pobrać go w folderze Aplikacje na komputerze Mac za pomocą:

sudo ln -s /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/uic /usr/local/bin
ln -s /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/PySide2/Designer.app /Applications

W Linuksie: do zrobienia.

Ponieważ FreeCAD stopniowo odchodził od PyQt po wersji 0.13, na rzecz PySide (Wybierz instalację PySide budowanie PySide), aby plik był oparty na PySide, musisz teraz użyć:

pyside-uic mywidget.ui -o mywidget.py

W systemie Windows plik uic.py znajduje się w katalogu "C:\Python27\Lib\site-packages\PySide\scripts\uic.py". Aby utworzyć plik wsadowy "compSide.bat":

@"C:\Python27\python" "C:\Python27\Lib\site-packages\PySide\scripts\uic.py" %1.ui > %1.py

W konsoli DOS wpisz bez rozszerzenia:

compSide myUiFile

W Linuksie: do zrobienia.

W niektórych systemach program nazywa się pyuic4 zamiast pyuic. Spowoduje to po prostu konwersję pliku .ui na skrypt Pythona. Jeśli otworzymy plik mywidget.py, jego zawartość jest bardzo przejrzysta:

from PySide import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(187, 178)
        self.title = QtGui.QLabel(Dialog)
        self.title.setGeometry(QtCore.QRect(10, 10, 271, 16))
        self.title.setObjectName("title")
        self.label_width = QtGui.QLabel(Dialog)
        ...

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

   def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.title.setText(QtGui.QApplication.translate("Dialog", "Plane-O-Matic", None, QtGui.QApplication.UnicodeUTF8))
        ...

As you see it has a very simple structure: a class named Ui_Dialog is created, that stores the interface elements of our widget. That class has two methods, one for setting up the widget, and one for translating its contents, which is part of the general Qt mechanism for translating interface elements. The setup method simply creates, one by one, the widgets as we defined them in Qt Designer, and sets their options as we decided earlier. Then, the whole interface gets translated, and finally, the slots get connected (we'll talk about that later).

We can now create a new widget and use this class to create its interface. We can already see our widget in action, by putting our mywidget.py file in a place where FreeCAD will find it (in the FreeCAD bin directory, or in any of the Mod subdirectories), and, in the FreeCAD python interpreter, issue:

from PySide import QtGui
import mywidget
d = QtGui.QWidget()
d.ui = mywidget.Ui_Dialog()
d.ui.setupUi(d)
d.show()

And our dialog will appear! Note that our Python interpreter is still working, we have a non-modal dialog. So, to close it, we can (apart from clicking its close icon, of course) issue:

d.hide()

Making our dialog do something

Now that we can show and hide our dialog, we just need to add one last part: To make it do something! If you play a bit with Qt designer, you'll quickly discover a whole section called "signals and slots". Basically, it works like this: elements on your widgets (in Qt terminology, those elements are themselves widgets) can send signals. Those signals differ according to the widget type. For example, a button can send a signal when it is pressed and when it is released. Those signals can be connected to slots, which can be special functionality of other widgets (for example a dialog has a "close" slot to which you can connect the signal from a close button), or can be custom functions. The PyQt Reference Documentation lists all the qt widgets, what they can do, what signals they can send, etc...

What we will do here, is to create a new function that will create a plane based on height and width, and to connect that function to the pressed signal emitted by our "Create!" button. So, let's begin with importing our FreeCAD modules, by putting the following line at the top of the script, where we already import QtCore and QtGui:

import FreeCAD, Part

Then, let's add a new function to our Ui_Dialog class:

def createPlane(self):
    try:
        # first we check if valid numbers have been entered
        w = float(self.width.text())
        h = float(self.height.text())
    except ValueError:
        print("Error! Width and Height values must be valid numbers!")
    else:
        # create a face from 4 points
        p1 = FreeCAD.Vector(0,0,0)
        p2 = FreeCAD.Vector(w,0,0)
        p3 = FreeCAD.Vector(w,h,0)
        p4 = FreeCAD.Vector(0,h,0)
        pointslist = [p1,p2,p3,p4,p1]
        mywire = Part.makePolygon(pointslist)
        myface = Part.Face(mywire)
        Part.show(myface)
        self.hide()

Then, we need to inform Qt to connect the button to the function, by placing the following line just before QtCore.QMetaObject.connectSlotsByName(Dialog):

QtCore.QObject.connect(self.create,QtCore.SIGNAL("pressed()"),self.createPlane)

This, as you see, connects the pressed() signal of our create object (the "Create!" button), to a slot named createPlane, which we just defined. That's it! Now, as a final touch, we can add a little function to create the dialog, it will be easier to call. Outside the Ui_Dialog class, let's add this code:

class plane():
   def __init__(self):
       self.d = QtGui.QWidget()
       self.ui = Ui_Dialog()
       self.ui.setupUi(self.d)
       self.d.show()

(Python reminder: the __init__ method of a class is automatically executed whenever a new object is created!) Then, from FreeCAD, we only need to do:

import mywidget
myDialog = mywidget.plane()

That's all Folks... Now you can try all kinds of things, like for example inserting your widget in the FreeCAD interface (see the Code snippets page), or making much more advanced custom tools, by using other elements on your widget.

The complete script

This is the complete script, for reference:

# Form implementation generated from reading ui file 'mywidget.ui'
#
# Created: Mon Jun  1 19:09:10 2009
#      by: PyQt4 UI code generator 4.4.4
# Modified for PySide 16:02:2015 
# WARNING! All changes made in this file will be lost!

from PySide import QtCore, QtGui
import FreeCAD, Part 

class Ui_Dialog(object):
   def setupUi(self, Dialog):
       Dialog.setObjectName("Dialog")
       Dialog.resize(187, 178)
       self.title = QtGui.QLabel(Dialog)
       self.title.setGeometry(QtCore.QRect(10, 10, 271, 16))
       self.title.setObjectName("title")
       self.label_width = QtGui.QLabel(Dialog)
       self.label_width.setGeometry(QtCore.QRect(10, 50, 57, 16))
       self.label_width.setObjectName("label_width")
       self.label_height = QtGui.QLabel(Dialog)
       self.label_height.setGeometry(QtCore.QRect(10, 90, 57, 16))
       self.label_height.setObjectName("label_height")
       self.width = QtGui.QLineEdit(Dialog)
       self.width.setGeometry(QtCore.QRect(60, 40, 111, 26))
       self.width.setObjectName("width")
       self.height = QtGui.QLineEdit(Dialog)
       self.height.setGeometry(QtCore.QRect(60, 80, 111, 26))
       self.height.setObjectName("height")
       self.create = QtGui.QPushButton(Dialog)
       self.create.setGeometry(QtCore.QRect(50, 140, 83, 26))
       self.create.setObjectName("create")

       self.retranslateUi(Dialog)
       QtCore.QObject.connect(self.create,QtCore.SIGNAL("pressed()"),self.createPlane)
       QtCore.QMetaObject.connectSlotsByName(Dialog)

   def retranslateUi(self, Dialog):
       Dialog.setWindowTitle("Dialog")
       self.title.setText("Plane-O-Matic")
       self.label_width.setText("Width")
       self.label_height.setText("Height")
       self.create.setText("Create!")
       print("tyty")
   def createPlane(self):
       try:
           # first we check if valid numbers have been entered
           w = float(self.width.text())
           h = float(self.height.text())
       except ValueError:
           print("Error! Width and Height values must be valid numbers!")
       else:
           # create a face from 4 points
           p1 = FreeCAD.Vector(0,0,0)
           p2 = FreeCAD.Vector(w,0,0)
           p3 = FreeCAD.Vector(w,h,0)
           p4 = FreeCAD.Vector(0,h,0)
           pointslist = [p1,p2,p3,p4,p1]
           mywire = Part.makePolygon(pointslist)
           myface = Part.Face(mywire)
           Part.show(myface)

class plane():
  def __init__(self):
      self.d = QtGui.QWidget()
      self.ui = Ui_Dialog()
      self.ui.setupUi(self.d)
      self.d.show()

Więcej przykładów

Istotne odnośniki internetowe