1. Übersicht
Die Erstellung von benutzerdefinierten Widgets ermöglicht es, den Qt Designer-Editor zu verwenden, um ein benutzerdefiniertes Widget zu platzieren, anstatt dies manuell in einer Handler-Datei zu tun.
Ein nützliches benutzerdefiniertes Widget wäre eine großartige Möglichkeit, zu LinuxCNC beizutragen.
1.1. Widgets
Widget is the general name for the UI objects such as buttons and labels in PyQt.
Es gibt auch spezielle Widgets für LinuxCNC zur Erleichterung der Integration.
Alle diese Widgets können mit Qt Designer Editor platziert werden - so dass man das Ergebnis sehen kann, bevor man das Panel in LinuxCNC lädt.
1.2. Qt Designer
Qt Designer is a WYSIWYG (What You See is What You Get) editor for placing PyQt widgets.
Die ursprüngliche Absicht war es, die grafischen Widgets für Programme zu erstellen.
Wir nutzen es, um Bildschirme und Panels für LinuxCNC zu bauen.
In Qt Designer, auf der linken Seite des Editors, finden Sie drei Kategorien von LinuxCNC Widgets:
-
Nur HAL Widgets.
-
LinuxCNC-Controller-Widgets.
-
Dialog-Widgets.
Damit Qt Designer benutzerdefinierte Widgets zu seinem Editor hinzufügen kann, muss ein Plugin zum richtigen Ordner hinzugefügt werden.
1.3. Initialisierungsprozess
QtVCP macht extra setup für Widgets, die Spezialisierungen (Unterklassen) von _HALWidgetBase
sind, auch bekannt als "HAL-ified" Widgets.
Dies umfasst:
-
Injizieren wichtiger Variablen,
-
Aufruf einer extra Setup-Funktion
-
Aufruf einer schließenden Aufräumfunktion beim Herunterfahren.
Diese Funktionen werden nicht aufgerufen, wenn der Qt Designer Editor die Widgets anzeigt.
Wenn QtVCP einen Bildschirm aus der .ui-Datei erstellt:
-
Es wird nach allen HAL-ifizierten Widgets gesucht.
-
Er findet das Widget
ScreenOptions
, um Informationen zu sammeln, die es in die anderen Widgets einspeisen muss -
Er instanziiert jedes Widget und ruft, wenn es ein HAL-ifiziertes Widget ist, die Funktion
hal_init()
auf.
hal_init()
ist in der Basisklasse definiert und sie:-
Fügt Variablen wie die Einstellungsdatei zu jedem HAL-ifizierten Widget hinzu.
-
Ruft
+_hal_init()+
für das Widget auf.
+_hal_init()+
erlaubt es dem Widget-Designer, Einstellungen vorzunehmen, die den Zugriff auf zusätzliche Variablen erfordern.
-
Hier finden Sie eine Beschreibung der zusätzlichen Variablen, die in "HAL-ifizierte" Widgets eingefügt werden:
-
self.HAL_GCOMP
-
Die HAL-Komponenten-Instanz
-
self.HAL_NAME
-
Der Name dieses Widgets als String
-
self.QT_OBJECT_
-
Diese Objektinstanz des Widgets
-
self.QTVCP_INSTANCE_
-
Die übergeordnete Ebene des Bildschirms
-
self.PATHS_
-
Die Instanz der QtVCP-Pfadbibliothek
-
self.PREFS_
-
Die optionale Instanz der Einstellungsdatei
-
self.SETTINGS_
-
Die
Qsettings
Objektinstanz
1.4. Aufräum-Prozess
Wenn QtVCP geschlossen wird, ruft es die +_hal_cleanup()+
Funktion auf allen HAL-ifizierten Widgets auf.
Die Basisklasse erstellt eine leere Funktion +_hal_cleanup()+
, die in der Unterklasse des benutzerdefinierten Widgets neu definiert werden kann.
Damit können Sie z. B. Präferenzen aufzeichnen usw.
Diese Funktion wird nicht aufgerufen, wenn der Qt Designer Editor die Widgets anzeigt.
2. Benutzerdefinierte HAL-Widgets
HAL-Widgets sind die einfachsten, um ein Beispiel zu zeigen.
Die Datei qtvcp/widgets/simple_widgets.py
enthält viele reine HAL-Widgets.
Sehen wir uns einen Ausschnitt aus simple_widgets.py
an:
Hier importieren wir die Bibliotheken, die unsere Widget-Klasse benötigt.
#!/usr/bin/env python3 ############################### # Imports ############################### from PyQt5 import QtWidgets # <1> from qtvcp.widgets.widget_baseclass \ import _HalWidgetBase, _HalSensitiveBase # <2> import hal # <3>
In diesem Fall benötigen wir Zugang zu:
-
Die QtWidgets-Bibliothek von PyQt,
-
LinuxCNCs HAL-Bibliothek, und
-
QtVCP’s widget
baseclass
's_HalSensitiveBase
für automatisches HAL-Pin-Setup und zum Deaktivieren/Aktivieren des Widgets (auch bekannt als Eingangsempfindlichkeit).
Es gibt auch_HalToggleBase
, und_HalScaleBase
Funktionen in der Bibliothek._HalToggleBase
, und_HalScaleBase
.
Hier ist ein angepasstes Widget (engl. custom widget), das auf dem QGridLayout
Widget von PyQt basiert.
QGridLayout
erlaubt es,:
-
Objekte in einem Raster zu positionieren.
-
Alle darin enthaltenen Widgets zu aktivieren/deaktivieren, basierend auf einem HAL-Pin-Status.
###################### # WIDGET ###################### class Lcnc_GridLayout(QtWidgets.QWidget, _HalSensitiveBase): # <1> def __init__(self, parent = None): # <2> super(GridLayout, self).__init__(parent) # <3>
Zeile für Zeile:
-
Dies definiert den Klassennamen und die Bibliotheken, von denen sie erbt.
Diese Klasse, genanntLcnc_GridLayout
, erbt die Funktionen vonQWidget
und+_HalSensitiveBase+
.
+_HalSensitiveBase+
ist eine Unterklasse von+_HalWidgetBase+
, der Basisklasse der meisten QtVCP-Widgets, d.h. sie hat alle Funktionen von+_HalWidgetBase+
plus die Funktionen von+_HalSensitiveBase+
.
Sie fügt die Funktion hinzu, um das Widget basierend auf einem HAL-Eingangs-BIT-Pin zu aktivieren oder zu deaktivieren. -
Dies ist die Funktion, die aufgerufen wird, wenn das Widget zum ersten Mal erstellt wird (d.h. instanziiert wird) - das ist ziemlich standardmäßig.
-
Diese Funktion initialisiert die
Super
-Klassen unseres Widgets.
Super" bedeutet einfach die vererbten Basisklassen, d.h.QWidget
und_HalSensitiveBase
.
Außer dem Namen des Widgets wird sich nichts ändern.
3. Benutzerdefinierte Controller-Widgets mit STATUS
Widget, die mit LinuxCNC-Controller interagieren sind nur ein wenig komplizierter und sie erfordern einige extra Bibliotheken.
In diesem reduzierten Beispiel werden wir Eigenschaften hinzufügen, die im Qt Designer geändert werden können.
Dieses LED-Anzeige-Widget reagiert auf wählbare Zustände der LinuxCNC-Steuerung.
#!/usr/bin/env python3 ############################### # Imports ############################### from PyQt5.QtCore import pyqtProperty from qtvcp.widgets.led_widget import LED from qtvcp.core import Status ########################################### # **** instantiate libraries section **** # ########################################### STATUS = Status() ########################################## # custom widget class definition ########################################## class StateLED(LED): def __init__(self, parent=None): super(StateLED, self).__init__(parent) self.has_hal_pins = False self.setState(False) self.is_estopped = False self.is_on = False self.invert_state = False def _hal_init(self): if self.is_estopped: STATUS.connect('state-estop', lambda w:self._flip_state(True)) STATUS.connect('state-estop-reset', lambda w:self._flip_state(False)) elif self.is_on: STATUS.connect('state-on', lambda w:self._flip_state(True)) STATUS.connect('state-off', lambda w:self._flip_state(False)) def _flip_state(self, data): if self.invert_state: data = not data self.change_state(data) ######################################################################### # Qt Designer properties setter/getters/resetters ######################################################################## # invert status def set_invert_state(self, data): self.invert_state = data def get_invert_state(self): return self.invert_state def reset_invert_state(self): self.invert_state = False # machine is estopped status def set_is_estopped(self, data): self.is_estopped = data def get_is_estopped(self): return self.is_estopped def reset_is_estopped(self): self.is_estopped = False # machine is on status def set_is_on(self, data): self.is_on = data def get_is_on(self): return self.is_on def reset_is_on(self): self.is_on = False ####################################### # Qt Designer properties ####################################### invert_state_status = pyqtProperty(bool, get_invert_state, set_invert_state, reset_invert_state) is_estopped_status = pyqtProperty(bool, get_is_estopped, set_is_estopped, reset_is_estopped) is_on_status = pyqtProperty(bool, get_is_on, set_is_on, reset_is_on)
3.1. Im Abschnitt "Imports"
Hier importieren wir die Bibliotheken, die unsere Widget-Klasse benötigt.
#!/usr/bin/env python3 ############################### # Imports ############################### from PyQt5.QtCore import pyqtProperty # <1> from qtvcp.widgets.led_widget import LED # <2> from qtvcp.core import Status # <3>
Wir importieren
-
pyqtProperty
, damit wir mit dem Qt Designer-Editor interagieren können, -
LED
, weil unser benutzerdefiniertes Widget darauf basiert, -
Status
weil es uns Statusmeldungen von LinuxCNC gibt.
3.2. Im Abschnitt Bibliotheken instanziieren
Hier erstellen wir die Bibliotheksinstanz Status
:
########################################### # **** instantiate libraries section **** # ########################################### STATUS = Status()
Typischerweise haben wir die Bibliothek außerhalb der Widget-Klasse instanziiert, so dass der Verweis auf sie global ist - was bedeutet, dass Sie nicht self.
davor verwenden müssen.
Konventionell verwenden wir große Buchstaben im Namen für globale Verweise.
3.3. Im Abschnitt "Benutzerdefinierte Widget-Klassendefinition"
Dies ist das Herzstück unseres benutzerdefinierten Widgets.
class StateLed(LED): # <1> def __init__(self, parent=None): # <2> super(StateLed, self).__init__(parent) # <3> self.has_hal_pins = False # <4> self.setState(False) # <5> self.is_estopped = False self.is_on = False self.invert_state = False
-
Definiert den Namen unseres benutzerdefinierten Widgets und von welcher anderen Klasse es erbt.
In diesem Fall erben wirLED
- ein QtVCP-Widget, das eine Statusleuchte darstellt. -
Typisch für die meisten Widgets - wird aufgerufen, wenn das Widget zum ersten Mal erstellt wird.
-
Typisch für die meisten Widgets - ruft den Initialisierungscode des übergeordneten (Super-)Widgets auf.
Dann setzen wir einige Attribute:
-
Geerbt von
Lcnc_Led
- wir setzen ihn hier, damit kein HAL-Pin erstellt wird. -
Geerbt von
Lcnc_led
- wir setzen es, um sicherzustellen, dass die LED aus ist.
Die anderen Attribute sind für die auswählbaren Optionen unseres Widgets.
def _hal_init(self): if self.is_estopped: STATUS.connect('state-estop', lambda w:self._flip_state(True)) STATUS.connect('state-estop-reset', lambda w:self._flip_state(False)) elif self.is_on: STATUS.connect('state-on', lambda w:self._flip_state(True)) STATUS.connect('state-off', lambda w:self._flip_state(False))
Diese Funktion verbindet STATUS
(LinuxCNC-Statusmeldungsbibliothek) mit unserem Widget, so dass die LED je nach dem gewählten Status des Controllers ein- oder ausgeschaltet wird.
Wir haben zwei Zustände, zwischen denen wir wählen können: is_estopped
oder is_on
.
Je nachdem, welcher Zustand aktiv ist, wird unser Widget mit den entsprechenden STATUS-Meldungen verbunden.
+_hal_init()+
is called on each widget that inherits +_HalWidgetBase+
, when QtVCP first builds the screen.
You might wonder why it’s called on this widget since we didn’t have +_HalWidgetBase+
in our class definition (class Lcnc_State_Led(Lcnc_Led):
) - it’s called because Lcnc_Led
inherits +_HalWidgetBase+
.
In dieser Funktion haben Sie Zugang zu einigen zusätzlichen Informationen (obwohl wir sie in diesem Beispiel nicht verwenden):
-
self.HAL_GCOMP
-
Die HAL-Komponenten-Instanz
-
self.HAL_NAME
-
Der Name dieses Widgets als String
-
self.QT_OBJECT_
-
dieses Widgets PyQt-Objekt als Instanz
-
self.QTVCP_INSTANCE_
-
Die übergeordnete Ebene des Bildschirms
-
self.PATHS_
-
Die _Instanz der Pfadbibliothek von QtVCP
-
self.PREFS_
-
die Instanz einer optionalen Präferenzdatei
-
self.SETTINGS_
-
das
Qsettings
Objekt
Wir könnten diese Informationen nutzen, um HAL-Pins zu erstellen oder Bildpfade nachzuschlagen usw.
STATUS.connect('state-estop', lambda w:self._flip_state(True))
Schauen wir uns diese Zeile genauer an:
-
STATUS
ist ein sehr verbreitetes Thema beim Erstellen von Widgets.
STATUS
verwendet dasGObject
-Nachrichtensystem, um Nachrichten an Widgets zu senden, die sich dafür registrieren.
Diese Zeile ist der Registrierungsprozess. -
state-estop
ist die Nachricht, auf die wir hören und auf die wir reagieren wollen. Es sind viele Nachrichten verfügbar. -
lambda w:self._flip_state(True)` ist das, was passiert, wenn die Nachricht abgefangen wird.
Die Lambda-Funktion akzeptiert die Widget-Instanz (w
), die GObject ihr sendet und ruft dann die Funktionself._flip_state(True)
auf.
Lambda wurde verwendet, um das (w
) Objekt vor dem Aufruf der Funktionself._flip_state
zu strippen.
Es erlaubte auch die Verwendung, umself._flip_state()
den Zustand True zu senden.
def _flip_state(self, data): if self.invert_state: data = not data self.change_state(data)
Dies ist die Funktion, die den Zustand der LED tatsächlich umschaltet.
Sie wird aufgerufen, wenn die entsprechende STATUS-Meldung akzeptiert wird.
STATUS.connect('current-feed-rate', self._set_feedrate_text)
Die aufgerufene Funktion sieht wie folgt aus:
def _set_feedrate_text(self, widget, data):
in dem das Widget und alle Daten von der Funktion akzeptiert werden müssen.
3.3.1. Im Abschnitt Designer Properties Setter/Getters/Resetters
######################################################################### # Qt Designer properties setter/getters/resetters ######################################################################## # invert status def set_invert_state(self, data): self.invert_state = data def get_invert_state(self): return self.invert_state def reset_invert_state(self): self.invert_state = False # machine is estopped status def set_is_estopped(self, data): self.is_estopped = data def get_is_estopped(self): return self.is_estopped def reset_is_estopped(self): self.is_estopped = False # machine is on status def set_is_on(self, data): self.is_on = data def get_is_on(self): return self.is_on def reset_is_on(self): self.is_on = False
Dies ist die Art und Weise, wie Qt Designer die Attribute des Widgets setzt.
Dies kann auch direkt im Widget aufgerufen werden.
3.3.2. Im Abschnitt "Designereigenschaften"
####################################### # Qt Designer properties ####################################### invert_state_status = pyqtProperty(bool, get_invert_state, set_invert_state, reset_invert_state) is_estopped_status = pyqtProperty(bool, get_is_estopped, set_is_estopped, reset_is_estopped) is_on_status = pyqtProperty(bool, get_is_on, set_is_on, reset_is_on)
Dies ist die Registrierung von Eigenschaften in Qt Designer.
Der Eigenschaftsname:
-
ist der Text, der in Qt Designer verwendet wird,
-
kann nicht mit den Attributen identisch sein, die sie darstellen.
Diese Eigenschaften werden im Qt Designer in der Reihenfolge angezeigt, in der sie hier erscheinen.
4. Benutzerdefinierte Controller-Widgets mit Aktionen
Hier ist ein Beispiel für ein Widget, welches das Benutzerreferenzsystem festlegt.
Es ändert sich:
-
den Zustand der Maschinensteuerung mit Hilfe der Bibliothek
ACTION
, -
ob die Schaltfläche mit Hilfe der
STATUS
-Bibliothek angeklickt werden kann oder nicht.
import os import hal from PyQt5.QtWidgets import QWidget, QToolButton, QMenu, QAction from PyQt5.QtCore import Qt, QEvent, pyqtProperty, QBasicTimer, pyqtSignal from PyQt5.QtGui import QIcon from qtvcp.widgets.widget_baseclass import _HalWidgetBase from qtvcp.widgets.dialog_widget import EntryDialog from qtvcp.core import Status, Action, Info # Instanziieren Sie die Bibliotheken mit einer globalen Referenz. # STATUS gibt uns Statusmeldungen von LinuxCNC # INFO enthält INI-Details # ACTION gibt Befehle an LinuxCNC STATUS = Status() INFO = Info() ACTION = Aktion() class SystemToolButton(QToolButton, _HalWidgetBase): def __init__(self, parent=None): super(SystemToolButton, self).__init__(parent) self._joint = 0 self._last = 0 self._block_signal = False self._auto_label_flag = True SettingMenu = QMenu() for system in('G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1', 'G59.2', 'G59.3'): Button = QAction(QIcon('exit24.png'), system, self) Button.triggered.connect(self[system.replace('.','_')]) SettingMenu.addAction(Button) self.setMenu(SettingMenu) self.dialog = EntryDialog() def _hal_init(self): if not self.text() == '': self._auto_label_flag = False def homed_on_test(): return (STATUS.machine_is_on() and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED)) STATUS.connect('state-off', lambda w: self.setEnabled(False)) STATUS.connect('state-estop', lambda w: self.setEnabled(False)) STATUS.connect('interp-idle', lambda w: self.setEnabled(homed_on_test())) STATUS.connect('interp-run', lambda w: self.setEnabled(False)) STATUS.connect('all-homed', lambda w: self.setEnabled(True)) STATUS.connect('not-all-homed', lambda w, data: self.setEnabled(False)) STATUS.connect('interp-paused', lambda w: self.setEnabled(True)) STATUS.connect('user-system-changed', self._set_user_system_text) def G54(self): ACTION.SET_USER_SYSTEM('54') def G55(self): ACTION.SET_USER_SYSTEM('55') def G56(self): ACTION.SET_USER_SYSTEM('56') def G57(self): ACTION.SET_USER_SYSTEM('57') def G58(self): ACTION.SET_USER_SYSTEM('58') def G59(self): ACTION.SET_USER_SYSTEM('59') def G59_1(self): ACTION.SET_USER_SYSTEM('59.1') def G59_2(self): ACTION.SET_USER_SYSTEM('59.2') def G59_3(self): ACTION.SET_USER_SYSTEM('59.3') def _set_user_system_text(self, w, data): convert = { 1:"G54", 2:"G55", 3:"G56", 4:"G57", 5:"G58", 6:"G59", 7:"G59.1", 8:"G59.2", 9:"G59.3"} if self._auto_label_flag: self.setText(convert[int(data)]) def ChangeState(self, joint): if int(joint) != self._joint: self._block_signal = True self.setChecked(False) self._block_signal = False self.hal_pin.set(False) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value)
5. Stylesheet-Eigenschaftsänderungen auf der Grundlage von Ereignissen
Es ist möglich, Widgets bei Ereignisänderungen neu zu gestalten.
Sie müssen das Widget explizit _"polieren", damit PyQt den Stil wiederherstellt.
Dies ist eine relativ teure Funktion und sollte daher sparsam verwendet werden.
Dieses Beispiel setzt eine "isHomed"-Eigenschaft basierend auf dem "homed"-Zustand von LinuxCNC und verwendet diese wiederum, um Stylesheet-Eigenschaften zu ändern:
class HomeLabel(QLabel, _HalWidgetBase): def __init__(self, parent=None): super(HomeLabel, self).__init__(parent) self.joint_number = 0 # for stylesheet reading self._isHomed = False def _hal_init(self): super(HomeLabel, self)._hal_init() STATUS.connect('homed', lambda w,d: self._home_status_polish(int(d), True)) STATUS.connect('unhomed', lambda w,d: self._home_status_polish(int(d), False)) # ishomed-Eigenschaft aktualisieren # Widget polieren, damit Stylesheet die Änderung der Eigenschaft sieht # einige Stylesheets färben den Text bei home/unhome def _home_status_polish(self, d, state): if self.joint_number = d: self.setProperty('isHomed', state) self.style().unpolish(self) self.style().polish(self) # Qproperty getter and setter def getisHomed(self): return self._isHomed def setisHomed(self, data): self._isHomed = data # Qproperty isHomed = QtCore.pyqtProperty(bool, getisHomed, setisHomed)
Hier ist ein Beispiel-Stylesheet zum Ändern der Textfarbe basierend auf dem Home-Status.
In diesem Fall wird jedes Widget, das auf dem obigen HomeLabel-Widget basiert, die Textfarbe ändern.
Normalerweise würden Sie bestimmte Widgets mit HomeLabel #specific_widget_name[homed=true]
auswählen:
HomeLabel[homed=true] {
color: green;
}
HomeLabel[homed=false] {
color: red;
}
6. Verwenden von Stylesheets zum Ändern benutzerdefinierter Widget-Eigenschaften
class Label(QLabel): def __init__(self, parent=None): super(Label, self).__init__(parent) alternateFont0 = self.font # Qproperty getter and setter def getFont0(self): return self.aleternateFont0 def setFont0(self, value): self.alternateFont0(value) # Qproperty styleFont0 = pyqtProperty(QFont, getFont0, setFont0)
Beispiel-Stylesheet, das eine benutzerdefinierte Widget-Eigenschaft festlegt.
Label{
qproperty-styleFont0: "Times,12,-1,0,90,0,0,0,0,0";
}
7. Widget-Plugins
Wir müssen unsere benutzerdefinierten Widgets registrieren, damit Qt Designer sie verwenden kann.
Hier sind ein paar typische Beispiele.
Sie müssten zu qtvcp/plugins/
hinzugefügt werden.
Dann müsste qtvcp/plugins/qtvcp_plugin.py
angepasst werden, um sie zu importieren.
7.1. Beispiel für ein Raster (engl. grid)-Layout
#!/usr/bin/env python3 from PyQt5 import QtCore, QtGui from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin from qtvcp.widgets.simple_widgets import Lcnc_GridLayout from qtvcp.widgets.qtvcp_icons import Icon ICON = Icon() #################################### # GridLayout #################################### class LcncGridLayoutPlugin(QPyDesignerCustomWidgetPlugin): def __init__(self, parent = None): QPyDesignerCustomWidgetPlugin.__init__(self) self.initialized = False def initialize(self, formEditor): if self.initialized: return self.initialized = True def isInitialized(self): return self.initialized def createWidget(self, parent): return Lcnc_GridLayout(parent) def name(self): return "Lcnc_GridLayout" def group(self): return "LinuxCNC - HAL" def icon(self): return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('lcnc_gridlayout'))) def toolTip(self): return "HAL enable/disable GridLayout widget" def whatsThis(self): return "" def isContainer(self): return True def domXml(self): return '<widget class="Lcnc_GridLayout" name="lcnc_gridlayout" />\n' def includeFile(self): return "qtvcp.widgets.simple_widgets"
7.2. SystemToolbutton Beispiel
#!/usr/bin/env python3 from PyQt5 import QtCore, QtGui from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin from qtvcp.widgets.system_tool_button import SystemToolButton from qtvcp.widgets.qtvcp_icons import Icon ICON = Icon() #################################### # SystemToolButton #################################### class SystemToolButtonPlugin(QPyDesignerCustomWidgetPlugin): def __init__(self, parent = None): super(SystemToolButtonPlugin, self).__init__(parent) self.initialized = False def initialize(self, formEditor): if self.initialized: return self.initialized = True def isInitialized(self): return self.initialized def createWidget(self, parent): return SystemToolButton(parent) def name(self): return "SystemToolButton" def group(self): return "LinuxCNC - Controller" def icon(self): return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('systemtoolbutton'))) def toolTip(self): return "Button for selecting a User Coordinate System" def whatsThis(self): return "" def isContainer(self): return False def domXml(self): return '<widget class="SystemToolButton" name="systemtoolbutton" />\n' def includeFile(self): return "qtvcp.widgets.system_tool_button"
7.3. Erstellen eines Plugins mit einem Dialogfeld "MenuEntry"
Es ist möglich, einen Eintrag in den Dialog einzufügen, der erscheint, wenn Sie mit der rechten Maustaste auf das Widget im Layout klicken.
So können Sie beispielsweise Optionen auf bequemere Weise auswählen.
Dies ist das Plugin, das für action buttons verwendet wird.
#!/usr/bin/env python3 import sip from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin, \ QPyDesignerTaskMenuExtension, QExtensionFactory, \ QDesignerFormWindowInterface, QPyDesignerMemberSheetExtension from qtvcp.widgets.action_button import ActionButton from qtvcp.widgets.qtvcp_icons import Icon ICON = Icon() Q_TYPEID = { 'QDesignerContainerExtension': 'org.qt-project.Qt.Designer.Container', 'QDesignerPropertySheetExtension': 'org.qt-project.Qt.Designer.PropertySheet', 'QDesignerTaskMenuExtension': 'org.qt-project.Qt.Designer.TaskMenu', 'QDesignerMemberSheetExtension': 'org.qt-project.Qt.Designer.MemberSheet' } #################################### # ActionBUTTON #################################### class ActionButtonPlugin(QPyDesignerCustomWidgetPlugin): # Die Methode __init__() wird nur verwendet, um das Plugin einzurichten und seine # initialisierte Variable zu definieren. def __init__(self, parent=None): super(ActionButtonPlugin, self).__init__(parent) self.initialized = False # Die Methoden initialize() und isInitialized() erlauben es dem Plugin, # alle benötigten Ressourcen einzurichten, # wobei sichergestellt wird, dass dies nur einmal für jedes # Plugin geschehen kann. def initialize(self, formEditor): if self.initialized: return manager = formEditor.extensionManager() if manager: self.factory = ActionButtonTaskMenuFactory(manager) manager.registerExtensions(self.factory, Q_TYPEID['QDesignerTaskMenuExtension']) self.initialized = True def isInitialized(self): return self.initialized # Diese Factory-Methode erstellt neue Instanzen unseres benutzerdefinierten Widgets def createWidget(self, parent): return ActionButton(parent) # Diese Methode gibt den Namen der benutzerdefinierten Widget-Klasse zurück. def name(self): return "ActionButton" # Gibt den Namen der Gruppe in der Widgetbox von Qt Designer zurück def group(self): return "LinuxCNC - Controller" # Gibt das Icon zurück def icon(self): return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('actionbutton'))) # Gibt eine Kurzbeschreibung des Tooltips zurück def toolTip(self): return "Aktionsschaltflächen-Widget" # Gibt eine kurze Beschreibung des benutzerdefinierten Widgets zur Verwendung in einer # "Was ist das?"-Hilfemitteilung für das Widget. def whatsThis(self): return "" # Gibt True zurück, wenn das benutzerdefinierte Widget als Container für andere Widgets fungiert; def isContainer(self): return False # Gibt eine XML-Beschreibung einer benutzerdefinierten Widget-Instanz zurück, welche die # Standardwerte für seine Eigenschaften beschreibt. def domXml(self): return '<widget class="ActionButton" name="actionbutton" />\n' # Gibt das Modul zurück, das die benutzerdefinierte Widget-Klasse enthält. Es kann # einen Modulpfad enthalten. def includeFile(self): return "qtvcp.widgets.action_button" class ActionButtonDialog(QtWidgets.QDialog): def __init__(self, widget, parent = None): QtWidgets.QDialog.__init__(self, parent) self.widget = widget self.previewWidget = ActionButton() buttonBox = QtWidgets.QDialogButtonBox() okButton = buttonBox.addButton(buttonBox.Ok) cancelButton = buttonBox.addButton(buttonBox.Cancel) okButton.clicked.connect(self.updateWidget) cancelButton.clicked.connect(self.reject) layout = QtWidgets.QGridLayout() self.c_estop = QtWidgets.QCheckBox("Estop Action") self.c_estop.setChecked(widget.estop ) layout.addWidget(self.c_estop) layout.addWidget(buttonBox, 5, 0, 1, 2) self.setLayout(layout) self.setWindowTitle(self.tr("Set Options")) def updateWidget(self): formWindow = QDesignerFormWindowInterface.findFormWindow(self.widget) if formWindow: formWindow.cursor().setProperty("estop_action", QtCore.QVariant(self.c_estop.isChecked())) self.accept() class ActionButtonMenuEntry(QPyDesignerTaskMenuExtension): def __init__(self, widget, parent): super(QPyDesignerTaskMenuExtension, self).__init__(parent) self.widget = widget self.editStateAction = QtWidgets.QAction( self.tr("Set Options..."), self) self.editStateAction.triggered.connect(self.updateOptions) def preferredEditAction(self): return self.editStateAction def taskActions(self): return [self.editStateAction] def updateOptions(self): dialog = ActionButtonDialog(self.widget) dialog.exec_() class ActionButtonTaskMenuFactory(QExtensionFactory): def __init__(self, parent = None): QExtensionFactory.__init__(self, parent) def createExtension(self, obj, iid, parent): if not isinstance(obj, ActionButton): return None if iid == Q_TYPEID['QDesignerTaskMenuExtension']: return ActionButtonMenuEntry(obj, parent) elif iid == Q_TYPEID['QDesignerMemberSheetExtension']: return ActionButtonMemberSheet(obj, parent) return None