Barrierefreie Anwendungen erstellen mit Qt

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Die Informationen in der Qt Dokumentation sagen , dass in "Description" nur Informationen über das Aussehen des Widgets stehen sollen. Dann würden die Reader unter Windows funktionieren, wie sie sollen.

Gibt es denn irgendwo ein Dokument, was ein Screenreader auswerten muss? Irgend ein Standard? Und möchte ein Benutzer überhaupt immer eine lange Version hören?
"OK" in einem Dialog macht ja mehr Sinn als "Dieser Knopf bestätigt den Dialog mit der Antwort OK".
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

Das Entscheidende kommt danach:
The description is primarily used to provide greater context for vision-impaired users, but is also used for context searching or other applications.
Die Sache ist die, bei Java und C# heißen die Eigenschaften genau gleich und dort wird auch immer AccessibleDescription genutzt um ein Bedienelement zu beschreiben.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Aber das aus anderen Sprachen und Tookits abzuleiten halte ich für schwierig.
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

Folgender Code:

Code: Alles auswählen

#*******************************************************************************
#*              Mit Python und Qt eine Screenreadertaugliche Anwendung         *
#*******************************************************************************

import os
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize

class MyWindow(QMainWindow):
    def __init__(self):
        # Konstruktor von QMainWindow aufrufen
        super().__init__()

        # Fenstergröße und Titel einstellen
        self.setMinimumSize(QSize(300, 100))
        self.setWindowTitle('Barrierefreiheit mit Qt')
        self.setGeometry(50,50,300,100)

        def BetriebbsystemAbfragen():
            os_info = sys.platform
            print("Betriebssytem: " + os_info)

        def HalloAnwender():
            print("Hallo Markus!")

        vButtonBetriebssystem = QPushButton("Hallo Betriebssystem",self)
        vButtonBetriebssystem.setGeometry(20,0,130,30)
        vButtonBetriebssystem.setAccessibleName("Es wird ausgegeben wie das Betriebssystem heißt")
        vButtonBetriebssystem.setToolTip("Es wird ausgegeben wie das Betriebssystem heißt")
        vButtonBetriebssystem.clicked.connect(BetriebbsystemAbfragen)
        vButtonBetriebssystem.show()

        vButtonAnwender = QPushButton("Hallo Anwender",self)
        vButtonAnwender.setGeometry(20, 40, 130, 30)
        vButtonAnwender.setAccessibleName("Der Anwender wird begrüßt")
        vButtonAnwender.setToolTip("Der Anwender wird begrüßt")
        vButtonAnwender.clicked.connect(HalloAnwender)
        vButtonAnwender.show()

app = QtWidgets.QApplication([])
win = MyWindow()
win.show()
app.exec_()
Hat unter Windows 10 mit dem Screenreader NVDA funktioniert und unter Ubuntu mit dem Screenreader Orca.

Wenn Ihr als Python-Experten sagt, dass das eine gute Lösung ist, dann lasse ich es jetzt auf sich beruhen!
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

Hier: https://doc.qt.io/qt-5/qwidget.html#acc ... ption-prop

Die Definition bestätigt meine Behauptung.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Bleibt die Frage, warum du dem Benutzer einen ewig langen Beschreibungstext zumuten möchtest.
Ich gehöre nicht zur Zielgruppe, aber mich würde es nerven, wenn jeder Buttob mit dem Tooktext versehen wäre, statt mit einer knackigen Beschriftung.
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

sparrow hat geschrieben: Samstag 30. Mai 2020, 22:45 Bleibt die Frage, warum du dem Benutzer einen ewig langen Beschreibungstext zumuten möchtest.
Ich gehöre nicht zur Zielgruppe, aber mich würde es nerven, wenn jeder Buttob mit dem Tooktext versehen wäre, statt mit einer knackigen Beschriftung.
Weil der Benutzer blind ist und sich alles was Du sehen kannst muß ein blinder Mensch sich im Kopf vorstellen können.

In diesem Youtube-Video zeige ich wie ein blinder Mensch meinen selbst entwickelten JavaFX-Texteditor nutzen kann:
https://www.youtube.com/watch?v=3j1YWrb-zOE
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PythonMarlem: Aber auch blinde Menschen lernen eine Anwendung im Laufe der Zeit kennen, und genau deswegen gibt es ja eigentlich auch *beide* Möglichkeiten, einen kurzen prägnanten Namen und eine ausführlichere Beschreibung.

So wie ich das verstehe ist das, jetzt mal auf Menüs bezogen das hörbare Äquivalent zu dem Namen des Menüpunkts (Name) und der Beschreibung was der Menüpunkt macht oder was das Untermenü enthält, was Anwendungen üblicherweise in der Statuszeile anzeigen (Beschreibung).

Ein weiterer Grund warum der Name kurz und knackig sein sollte, ist das der ja nicht nur zum vorgelesen werden gedacht ist, sondern umgekehrt auch für Spracherkennung um die jeweiligen Steuerlemente zu aktivieren. Da will man ja a) keine Romane erzählen müssen, die b) vielleicht gar nicht so gut von der Spracherkennung erkannt werden, und c) in vielen Fällen dann auch gar nicht so formuliert sind, dass sich das ”natürlich” anhört wenn man das als ”Kommando” spricht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

@PythonMarlem: Ich habe mich früher mal mit dem Thema beschäftigt, als ich noch sehr aktiv in der Linux Community war.
Vielleicht solltest du den Kontakt mit entsprechenden Communities suchen. Angenommen du hast ein Dialogfeld, und die Knöpfe "OK" und "Abbrechen". Mächtest du dann, dass das System "OK-Button" und "Abbrechen-Button" vorliest oder "Dieser OK Button schließt den Dialog und beendet ihn" und "Dieser Button schließt den Dialog und bricht die Aktion ab"? Klar kann man die Information verfügbar machen. Macht man in einem Tooltipp ja auch. Aber doch nicht als Bezeichnung.
Und wie man welche Information abgerufen bekommt, nämlich normalerweise den Namen und _optional_ die Beschreibung, sollte über das Werkzeug - also den Screenreader - abgefrühstückt werden.
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

Danke für Eure Hinweise.
Meine Demo-Anwendung ist fertig. Vielen Dank für Eure tolle Unterstützung.

Hier nochmal der Code für alle diejengen die wissen wollen wie man mit Python und Qt ein Programm entwickelt,
welches für blinde und sehbehinderte Menschen bedienbar ist.
Ich habe gestern Abend noch den Style Guide for Python Code umgesetzt! Falls ich was vergessen habe, bitte sagen!

Code: Alles auswählen

"""
**********************************************************************************************
* Mit Python und Qt eine Anwendung entwickeln die für Blinde und Sehbehinderte bedienbar ist *
**********************************************************************************************

Folgende Barrierefreie Kriterien demonstriert diese Anwendung:

1. Screenreadertauglichkeit
Screenreader ist eine Software die den Bildschirminhalt vorliest Blinde und auch
einige Sehbehinderte Menschen können nur Software bedienen die dem Screenreader
Texte übermitteln welche die Bedienelemente beschreiben.

In Java und C# werden Texte für Screenreader in der Eigenschaft "AccessibleDescription"
hinterlegt. Diese Eigenschaft gibt es auch in Python. Leider wird sie im Betriebssystem
Windows von den Screenreadern NVDA und Jaws nicht ausgelesen.
Deswegen werden die Texte für Screenreader in der Eigenschaft "AccessibleName" hinterlegt.

Die Software wurde erfolgreich mit folgenden Screenreadern getestet:
NVDA, Betriebssystem Windows
Jaws, Betriebssystem Windows
Orca, Betriebssystem Ubuntu

2. Tastaturbedienbarkeit
Alle Bedienelemente sind per Tabulatortaste erreichbar. Die Schalter können per Entertaste ausgeführt.
Die Tastaturbedienbarkeit ist für blinde und sehbehinderte Menschen wichtig.
Zur Tastaturbedienbarkeit gehört auch das Beschriftung mit Eingabefelder verknüpft sind.
Das geht über die Labelmethode setBuddy.

3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
Damit Menschen mit einer Sehbehinderung erkennen welches Bedienelement aktiv ist,
muss eine Software dies deutlich sichtbar machen. Meine Lieblingsmethode ist,
dass aktive Bedienelement bekommt die Hintergrundfarbe Gelb.

"""

import os
import sys

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize


# 3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
class QAcccessibilityButton(QPushButton):
    def focusInEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: yellow")

    def focusOutEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: #E1E1E1")


# 3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
class QAcccessibilityEdit(QLineEdit):
    def focusInEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: yellow")

    def focusOutEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: white")


class MyWindow(QWidget):
    def __init__(self):
        # Konstruktor von QMainWindow aufrufen
        super().__init__()
        self.button_betriebssystem = QAcccessibilityButton("Name des &Betriebssystem", self)
        self.button_anwender = QAcccessibilityButton("Hallo &Anwender", self)
        self.label_vor_nachname = QLabel("&Vor-und Nachname:", self)
        self.edit_vor_nachname = QAcccessibilityEdit(self)
        self.programm_beenden = QAcccessibilityButton("&Ende", self)
        self.initMe()

    def initMe(self):
        # Fenstergröße und Titel einstellen
        self.setMinimumSize(QSize(450, 350))
        self.setWindowTitle('Barrierefreiheit mit Python und Qt')
        self.showNormal()

        # self.button_betriebssystem.setGeometry(100, 10, 170, 60)
        self.button_betriebssystem.setToolTip("Es wird ausgegeben wie das Betriebssystem heißt")
        self.button_betriebssystem.setDefault(True)
        self.button_betriebssystem.clicked.connect(self.betriebssystem_abfragen)
        self.button_betriebssystem.show()

        self.button_anwender.setToolTip("Der Anwender wird begrüßt")
        self.button_anwender.setDefault(True)
        self.button_anwender.clicked.connect(self.OnClick)
        self.button_anwender.show()

        self.edit_vor_nachname.setToolTip("Geben Sie Vor-und Nachname ein")
        self.edit_vor_nachname.setText("Markus Lemcke")
        self.edit_vor_nachname.show()

        self.label_vor_nachname.show()
        # 2. Tastaturbedienbarkeit
        self.label_vor_nachname.setBuddy(self.edit_vor_nachname)

        self.programm_beenden.setToolTip("Programm Beenden")
        self.programm_beenden.setDefault(True)
        self.programm_beenden.clicked.connect(self.close)
        self.programm_beenden.show()

    def betriebssystem_abfragen(self):
        os_info = sys.platform
        print("Betriebssytem: " + os_info)

    def OnClick(self):
        Text = "Guten Tag " + self.edit_vor_nachname.text()
        print(Text)

    def barrierefreiheit_setzen(self):
        self.button_betriebssystem.setAccessibleName("Es wird ausgegeben wie das Betriebssystem heißt")
        self.button_anwender.setAccessibleName("Der Anwender wird begrüßt")
        self.edit_vor_nachname.setAccessibleName("Geben Sie bitte Vor-und Nachname ein")
        self.programm_beenden.setAccessibleName("Progamm wird beendet")

        #Die Positionen wird in Zeilen und Spalten angegben
        self.layout_barrierefrei = QGridLayout()
        self.layout_barrierefrei.addWidget(self.button_betriebssystem, 0, 0)
        self.layout_barrierefrei.addWidget(self.button_anwender, 1, 0)
        self.layout_barrierefrei.addWidget(self.label_vor_nachname,2,0)
        self.layout_barrierefrei.addWidget(self.edit_vor_nachname,2,1)
        self.layout_barrierefrei.addWidget(self.programm_beenden,3,0)
        self.setLayout(self.layout_barrierefrei)

app = QtWidgets.QApplication([])
win = MyWindow()
win.show()
win.barrierefreiheit_setzen()
app.exec_()
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Aus der Hüfte:

Es macht keinen Sinn eine Funktion .barrierefreiheit_setzen() in einer Klasse zu haben, die grundlegend wichtig für das Layout der Klasse ist. Und die dann auch noch von Außen aufzurufen. Das gehört in __init__().
Überhaupt verteilst du die gesamte Erstellung des Layouts unnötigerweise auf verschiedene Funktionen. __init__() (da sollte alles stehen), __initMe__() (das initialisiert wohl die Millennium Edition von Windows) und barrierefreiheit_setzen().
Und dann reißen diese Funktionen auch noch die logischen Zusammenhänge des Codes auseinander - also genau das, was man nicht will. Da wird in Methode 1 ein Widget an einen Namen gebunden, in Methode 2 werden Eigenschaften dieser Widgets gesetzt und in Methode 3 kommen dann noch einmal Eigenschaften. Das ist hinterher doch kaum zu warten. Wenn man schon Objekte erstellt, dann hält man deren Informationen zusammen. Sonst sucht man im Quellcode ewig, bis man alle Stellen gefunden hat, die ein Objekt verändern.

Warum rufst du denn für jedes Element .show() auf? Das sieht komisch aus. Und soviel ich weiß sind weder Label noch Buttons standardmäßig versteckt.

Warum gibt es das Prefix My bei Window? Gibt es auch ein YourWindow? Oder HerWindow?

Der Designer leitet das Hauptfenster immer vom QMainWindow ab. Die Tutorials, die ich kenne, auch.

Das Umfärben von Widgets, wenn etwas Focus hat, sollte durch Themes/Stylesheets erfolgen und nicht auf diese hart codierte Art. Das hat man dir hier auch schon gesagt.

Was du als .setAccessibleName() setzt ist das, was schon im ToolText steht. Das könntet du auch deutlich einfacher haben.
Und nochmal: Das macht Qt schon automatisch. Der ToolText ist der Fallback wenn .setAccessibleDescription() nicht gesetzt ist, die Beschriftungstext der Fallback wenn .setAccessibleName() nicht gesetzt ist.
Qt macht also schon alles richtig. Du reparierst das kaputt.

OnClick folgt nicht den Python-Namenskonventionen für eine Funktion und der Name beschreibt nicht wirklich, was passiert.

Dein Kommentar ist falsch. "In Python" gibt es keine Eigenschaft "AccessibleDescription". In Qt schon.
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

Zitat:
Es macht keinen Sinn eine Funktion .barrierefreiheit_setzen() in einer Klasse zu haben,
die grundlegend wichtig für das Layout der Klasse ist. Und die dann auch noch von Außen
aufzurufen. Das gehört in __init__().

Anmerkung von Markus:
__initMe__() machen die Udemy-Dozenten und ich habe es eben übernommen.
Funktion barrierefreiheit_setzen() ist weg und der Code ist in __init__().

Zitat:
Warum rufst du denn für jedes Element .show()

Anmerkung von Markus:
Habe ich von Udemy übernommen, aber hast recht, wird nicht benötigt.

Zitat:
Warum gibt es das Prefix My bei Window? Gibt es auch ein YourWindow? Oder HerWindow?

Anmerkung von Markus:
Habe ich von Udemy übernommen. Habe es geändert in MainWindow.

Zitat:
Das Umfärben von Widgets, wenn etwas Focus hat, sollte durch Themes/Stylesheets erfolgen und nicht auf diese hart codierte Art.
Das hat man dir hier auch schon gesagt.

Anmerkung von Markus:
Was ist an meiner Lösung so furchtbar?
Ich wollte mir einfach das Googeln nach Themes/Stylesheet ersparen und bin glücklich
dass ich eine Lösung habe die funktioniert.

Zitat:
Was du als .setAccessibleName() setzt ist das, was schon im ToolText steht.
Das könntet du auch deutlich einfacher haben.

Anmerkung von Markus:
ich habe hier sehr viel Zeit investiert mit Testen unter Windows und Ubuntu.
Meine Lösung funktioniert auf beiden Betriebssystemen.
Außerdem ist Tooltips Usability und AccessibleName Accessibility.

Zitat:
OnClick folgt nicht den Python-Namenskonventionen für eine Funktion und der Name
beschreibt nicht wirklich, was passiert.

Anmerkung von Markus:
heißt jetzt benutzergruessen.

Zitat:
Dein Kommentar ist falsch. "In Python" gibt es keine Eigenschaft "AccessibleDescription".
In Qt schon.

Anmerkung von Markus:
Hast recht, ist verbessert,.

Danke für das ausführliche Feedback.

Hier der verbesserte Code:

Code: Alles auswählen

"""
**********************************************************************************************
* Mit Python und Qt eine Anwendung entwickeln die für Blinde und Sehbehinderte bedienbar ist *
**********************************************************************************************

Folgende Barrierefreie Kriterien demonstriert diese Anwendung:

1. Screenreadertauglichkeit
Screenreader ist eine Software die den Bildschirminhalt vorliest Blinde und auch
einige Sehbehinderte Menschen können nur Software bedienen die dem Screenreader
Texte übermitteln welche die Bedienelemente beschreiben.

In Java und C# werden Texte für Screenreader in der Eigenschaft "AccessibleDescription"
hinterlegt. Diese Eigenschaft gibt es auch in Qt. Leider wird sie im Betriebssystem
Windows von den Screenreadern NVDA und Jaws nicht ausgelesen.
Deswegen werden die Texte für Screenreader in der Eigenschaft "AccessibleName" hinterlegt.

Die Software wurde erfolgreich mit folgenden Screenreadern getestet:
NVDA, Betriebssystem Windows
Jaws, Betriebssystem Windows
Orca, Betriebssystem Ubuntu

2. Tastaturbedienbarkeit
Alle Bedienelemente sind per Tabulatortaste erreichbar. Die Schalter können per Entertaste ausgeführt.
Die Tastaturbedienbarkeit ist für blinde und sehbehinderte Menschen wichtig.
Zur Tastaturbedienbarkeit gehört auch das Beschriftung mit Eingabefelder verknüpft sind.
Das geht über die Labelmethode setBuddy.

3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
Damit Menschen mit einer Sehbehinderung erkennen welches Bedienelement aktiv ist,
muss eine Software dies deutlich sichtbar machen. Meine Lieblingsmethode ist,
dass aktive Bedienelement bekommt die Hintergrundfarbe Gelb.

"""

import os
import sys

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize


# 3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
class QAcccessibilityButton(QPushButton):
    def focusInEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: yellow")

    def focusOutEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: #E1E1E1")


# 3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
class QAcccessibilityEdit(QLineEdit):
    def focusInEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: yellow")

    def focusOutEvent(self, QFocusEvent):
        self.setStyleSheet("background-color: white")


class MainWindow(QWidget):
    def __init__(self):
        # Konstruktor von QMainWindow aufrufen
        super().__init__()
        self.button_betriebssystem = QAcccessibilityButton("Name des &Betriebssystem", self)
        self.button_anwender = QAcccessibilityButton("Hallo &Anwender", self)
        self.label_vor_nachname = QLabel("&Vor-und Nachname:", self)
        self.edit_vor_nachname = QAcccessibilityEdit(self)
        self.programm_beenden = QAcccessibilityButton("&Ende", self)

        #Texte für Screenreader setzen
        self.button_betriebssystem.setAccessibleName("Es wird ausgegeben wie das Betriebssystem heißt")
        self.button_anwender.setAccessibleName("Der Anwender wird begrüßt")
        self.edit_vor_nachname.setAccessibleName("Geben Sie bitte Vor-und Nachname ein")
        self.programm_beenden.setAccessibleName("Progamm wird beendet")

        #Die Positionen wird in Zeilen und Spalten angegben
        self.layout_barrierefrei = QGridLayout()
        self.layout_barrierefrei.addWidget(self.button_betriebssystem, 0, 0)
        self.layout_barrierefrei.addWidget(self.button_anwender, 1, 0)
        self.layout_barrierefrei.addWidget(self.label_vor_nachname,2,0)
        self.layout_barrierefrei.addWidget(self.edit_vor_nachname,2,1)
        self.layout_barrierefrei.addWidget(self.programm_beenden,3,0)
        self.setLayout(self.layout_barrierefrei)

        # Fenstergröße und Titel einstellen
        self.setMinimumSize(QSize(450, 350))
        self.setWindowTitle('Barrierefreiheit mit Python und Qt')
        self.showNormal()

        # self.button_betriebssystem.setGeometry(100, 10, 170, 60)
        self.button_betriebssystem.setToolTip("Es wird ausgegeben wie das Betriebssystem heißt")
        self.button_betriebssystem.setDefault(True)
        self.button_betriebssystem.clicked.connect(self.betriebssystem_abfragen)

        self.button_anwender.setToolTip("Der Anwender wird begrüßt")
        self.button_anwender.setDefault(True)
        self.button_anwender.clicked.connect(self.benutzergruessen)

        self.edit_vor_nachname.setToolTip("Geben Sie Vor-und Nachname ein")
        self.edit_vor_nachname.setText("Markus Lemcke")

        # 2. Tastaturbedienbarkeit
        self.label_vor_nachname.setBuddy(self.edit_vor_nachname)

        self.programm_beenden.setToolTip("Programm Beenden")
        self.programm_beenden.setDefault(True)
        self.programm_beenden.clicked.connect(self.close)

    def betriebssystem_abfragen(self):
        os_info = sys.platform
        print("Betriebssytem: " + os_info)

    def benutzergruessen(self):
        Text = "Guten Tag " + self.edit_vor_nachname.text()
        print(Text)


app = QtWidgets.QApplication([])
win = MainWindow()
app.exec_()

Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PythonMarlem: Achtung: Grundlage von diesem Beitrag ist der Code im vorletzen Beitrag von Dir. Hat ein bisschen länger gedauert…

Mir fehlt da die She-Bang-Zeile.

Boxen aus Zeichen in Kommentaren und Docstrings sind zwar im ersten Moment nett anzusehen, aber nicht sehr wartungsfreundlich. In Docstrings hat man zusätzlich das Problem, dass weder reStructuredText noch Markdown damit etwas anfangen können.

`os` wird importiert, aber nirgends verwendet.

Sternchen-Importe sind Böse™. Das macht Programme unübersichtlich weil man nicht mehr so leicht nachvollziehen kann welcher Name woher kommt. Ausserdem besteht die Gefahr von Namenskollisionen. Das die Module `QtCore` und `QtWidgets` importiert werden und dann aber auch noch mal Objekte *aus* diesen beiden Modulen direkt importiert werden, macht die Sache auch nicht übersichtlicher. Wann geht man denn dann über das Modul und wann wird direkt importiert? Insbesondere bei `QtWidgets` wo ja der Sternchen-Import verwendet wird, frage ich mich was da dann der Grund sein sollte über das Modul zu gehen, wo man doch alle Namen schon direkt zur Verfügung hat?

Der Code-Block am Ende des Moduls gehört auch in eine Funktion, konventionell `main()` genannt, und davor geschützt ausgeführt zu werden wenn man das Modul importiert statt es als Programm auszuführen.

`QApplication` erwartet die Argumente von der Kommandozeile, denn Qt kann davon welche auswerten.

Zu sparrow's Anmerkungen das `barrierefreiheit_setzen()` nicht von aussen aufgerufen werden sollte: Das setzt auch noch ein neues Attribut auf dem Objekt. Auch das sollte nicht sein. Nachdem die `__init__()`-Methode durchgelaufen ist, sollten alle Attribute existieren und das Objekt in einem benutzbaren Zustand sein, denn genau das ist die Aufgabe der `__init__()`-Methode.

`layout_barrierefrei` sollte aber wohl eher auch gar kein Attribut sein, denn das wird nirgends sonst benötigt.

Neben den ganzen unnötigen `show()`-Aufrufen ist auch `showNormal()` Unsinn. Das Fenster wurde vorher ja gar nicht angezeigt, also auch nicht minimiert oder maximiert. Zudem gehört das Anzeigen von sich selbst nicht in die `__init__()` weil man so dem Code der das Widget erstellt, die Möglichkeit nimmt zu entscheiden ob und wann das Widget angezeigt werden soll.

Das Vorgeben der minimalen Fenstergrösse macht nicht wirklich Sinn. Das sieht mir nach dem Versuch aus, dass für die anfängliche Fenstergrösse zu missbrauchen.

Und auch `setDefault()` ist ein ganz klarer Missbrauch. Das ist dafür gedacht *einen* Default-Button in Dialogen festzulegen, nicht dazu um auf *allen* Buttons in einem Fenster gesetzt zu werden, nur damit man die Eingabetaste zum aktivieren verwenden kann. Das hat ja auch noch andere Auswirkungen. Und man kann das dann nicht mehr dazu verwenden wofür es eigentlich gedacht ist: bei mehreren Buttons in einem Dialog einen speziell ausgwählten davon per Tastatur aktivieren zu können, ohne das man ihn auswählen/fokussieren muss.

Das mit den Fokusfarben funktioniert bei mir (Linux) bei den Buttons nicht. Das Gelb blitzt ganz kurz auf und wird dann durch ein Blau ersetzt.

Das Farbe setzen hat den leicht komischen Effekt, dass Buttons die einmal den Fokus hatten, danach eine andere Hintergrundfarbe haben. Würde wohl auch für das Texteingabefeld gelten wenn da die normale Farbe nicht zufällig auch weiss wäre.

Aber noch schlimmer ist, dass die Hintergrundfarbe auch auf den Tooltips gesetzt wird. Die bei mir standardmässig eine dunkle Hintergrundfarbe mit weisser Schrift haben. Beim Texteingabefeld also wenn das den Fokus hat, weisse Schrift auf gelbem Hintergrund. Schlecht bis gar nicht lesbar. Wenn es dann den Fokus verliert, weisse Schrift auf weissem Hintergrund. Garantiert gar nicht mehr lesbar.

Die Fokus-Handler in den beiden Klassen rufen auch die ursprüngliche Implementierung auf der Basisklasse gar nicht auf. Falls die irgend etwas wichtiges macht, dann fällt das einfach unter den Tisch.

Für die beiden supersimplen Button-Aktionen würde ich keine Methoden definieren. Das bekommt man auch in einem ``lambda``-Ausdruck bequem unter.

Was PEP8 angeht ist `Text` in `OnClick()` wie eine Klasse geschrieben. Ich sehe aber auch nicht so wirklich wofür man da überhaupt einen Namen braucht.

Bei den Event-Handlern ist das Argument `QFocusEvent` falsch geschrieben. Zudem noch verwirrend, da das der Typ ist den das Argumnet hat, denn eine `QFocusEvent`-Klasse gibt es in Qt ja tatsächlich. Konvention bei unbenutzten Argumentnamen oder lokalen Namen ist es entweder nur `_` las Namen zu verwenden, oder den einen Unterstrich als Präfix zu verwenden.

In `barrierefreiheit_setzen()` ist die Leerzeichensetzung nach Kommas nicht konsistent.

Fazit: Es wird ganz viel wirklich falsch gemacht, nur um auf biegen und brechen ein Ergebnis zu bekommen, das nur für Leute funktioniert, die bestimmte Screenreader einsetzen. Und das auch nur wenn nur der Reader-Teil von Accessibility-Software benutzt wird. Sollte ein Anwender dann auch Sprachsteuerung verwenden wollen, verhindert der Missbrauch vom AccessibilityName das auch noch. Und das mit der Farbe bei Buttons funktioniert auch noch nicht mal überall. Für sehende Menschen macht das, zumindest bei mir, nur Probleme.

Das mag hart klingen, aber IMHO ist das komplett unbrauchbar.

Trotzdem noch mal den Zwischenstand nach der ganzen Kritik:

Code: Alles auswählen

#!/usr/bin/env python3
"""
==================================================
   Mit Python und Qt eine Anwendung entwickeln   
  die für Blinde und Sehbehinderte bedienbar ist 
==================================================

Folgende barrierefreie Kriterien demonstriert diese Anwendung:

1. Screenreadertauglichkeit

   Screenreader ist eine Software die den Bildschirminhalt vorliest Blinde und
   auch einige Sehbehinderte Menschen können nur Software bedienen die dem
   Screenreader Texte übermitteln welche die Bedienelemente beschreiben.

   In Java und C# werden Texte für Screenreader in der Eigenschaft
   "AccessibleDescription" hinterlegt. Diese Eigenschaft gibt es auch in Python.
   Leider wird sie im Betriebssystem Windows von den Screenreadern NVDA und Jaws
   nicht ausgelesen. Deswegen werden die Texte für Screenreader in der
   Eigenschaft "AccessibleName" hinterlegt.

   Die Software wurde erfolgreich mit folgenden Screenreadern getestet:
   
   * NVDA, Betriebssystem Windows
   * Jaws, Betriebssystem Windows
   * Orca, Betriebssystem Ubuntu

2. Tastaturbedienbarkeit

   Alle Bedienelemente sind per Tabulatortaste erreichbar. Die Schalter können
   per Entertaste ausgeführt. Die Tastaturbedienbarkeit ist für blinde und
   sehbehinderte Menschen wichtig. Zur Tastaturbedienbarkeit gehört auch das
   Beschriftung mit Eingabefelder verknüpft sind. Das geht über die Methode
   `Qlabel.setBuddy()`.

3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus

   Damit Menschen mit einer Sehbehinderung erkennen welches Bedienelement aktiv
   ist, muss eine Software dies deutlich sichtbar machen. Meine Lieblingsmethode
   ist, dass aktive Bedienelement bekommt die Hintergrundfarbe Gelb.
"""
import sys

from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import (
    QApplication,
    QGridLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QWidget,
)


# 3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
class QAcccessibilityButton(QPushButton):
    def focusInEvent(self, event):
        super().focusInEvent(event)
        self.setStyleSheet("background-color: yellow")

    def focusOutEvent(self, event):
        super().focusOutEvent(event)
        self.setStyleSheet("background-color: #E1E1E1")


# 3. Visuelle und programmgesteuerte Anzeige der Position des Tastaturfokus
class QAcccessibilityEdit(QLineEdit):
    def focusInEvent(self, event):
        super().focusInEvent(event)
        self.setStyleSheet("background-color: yellow")

    def focusOutEvent(self, event):
        super().focusOutEvent(event)
        self.setStyleSheet("background-color: white")


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Barrierefreiheit mit Python und Qt")

        layout = QGridLayout()

        self.button_betriebssystem = QAcccessibilityButton(
            "Name des &Betriebssystem", self
        )
        self.button_betriebssystem.setToolTip(
            "Es wird ausgegeben wie das Betriebssystem heißt"
        )
        self.button_betriebssystem.setDefault(True)
        layout.addWidget(self.button_betriebssystem, 0, 0)

        self.button_anwender = QAcccessibilityButton("Hallo &Anwender", self)
        self.button_anwender.setToolTip("Der Anwender wird begrüßt")
        layout.addWidget(self.button_anwender, 1, 0)

        self.label_vor_nachname = QLabel("&Vor- und Nachname:", self)
        layout.addWidget(self.label_vor_nachname, 2, 0)

        self.edit_vor_nachname = QAcccessibilityEdit(self)
        self.edit_vor_nachname.setToolTip("Geben Sie Vor- und Nachname ein")
        self.edit_vor_nachname.setText("Markus Lemcke")
        layout.addWidget(self.edit_vor_nachname, 2, 1)

        self.programm_beenden_button = QAcccessibilityButton("&Ende", self)
        self.programm_beenden_button.setToolTip("Programm Beenden")
        layout.addWidget(self.programm_beenden_button, 3, 0)

        self.setLayout(layout)

        self.label_vor_nachname.setBuddy(self.edit_vor_nachname)

        self.button_betriebssystem.clicked.connect(
            lambda: print("Betriebssytem:", sys.platform)
        )
        self.button_anwender.clicked.connect(
            lambda: print("Guten Tag", self.edit_vor_nachname.text())
        )
        self.programm_beenden_button.clicked.connect(self.close)


def main():
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Du benutzt doch schon Style-Sheets. Der Vorteil das global zu setzen ist, dass Du nicht extra Klassen für alles ableiten mußt. Dass man das dann auch global nach den eigenen Wünschen einstellen kann. Dass die Styles effizient in C++ implementiert sind und nicht per Python-Callback jeweils aktualisiert werden müssen.

*-Importe sind böse. Auch bei Qt. Zusammengehörige Dinge sollten auch zusammen stehen. Du setzt z.B. die Button-Eigenschaften über die gesamte __init__ verteilt.
Fenster sollten sich nicht selbst anzeigen, das ist Aufgabe des Aufrufers. Die letzten Zeilen gehören auch in eine Funktion, die üblicherweise main genannt wird.
Es sollte eigentlich nur einen Button geben, der setDefault(True) hat.

Code: Alles auswählen

import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QWidget, QPushButton, QLabel, QLineEdit, QGridLayout
from PyQt5.QtCore import QSize

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.button_betriebssystem = QPushButton("Name des &Betriebssystem", self)
        self.button_betriebssystem.setAccessibleName("Es wird ausgegeben wie das Betriebssystem heißt")
        self.button_betriebssystem.setToolTip("Es wird ausgegeben wie das Betriebssystem heißt")
        self.button_betriebssystem.clicked.connect(self.betriebssystem_abfragen)
        
        self.button_anwender = QPushButton("Hallo &Anwender", self)
        self.button_anwender.setAccessibleName("Der Anwender wird begrüßt")
        self.button_anwender.setToolTip("Der Anwender wird begrüßt")
        self.button_anwender.clicked.connect(self.benutzergruessen)
        
        self.edit_vor_nachname = QLineEdit(self)
        self.edit_vor_nachname.setAccessibleName("Geben Sie bitte Vor-und Nachname ein")
        self.edit_vor_nachname.setToolTip("Geben Sie Vor-und Nachname ein")
        self.edit_vor_nachname.setText("Markus Lemcke")
        
        self.label_vor_nachname = QLabel("&Vor-und Nachname:", self)
        self.label_vor_nachname.setBuddy(self.edit_vor_nachname)

        self.programm_beenden = QPushButton("&Ende", self)
        self.programm_beenden.setAccessibleName("Progamm wird beendet")
        self.programm_beenden.setToolTip("Programm Beenden")
        self.programm_beenden.setDefault(True)
        self.programm_beenden.clicked.connect(self.close)

        #Die Positionen wird in Zeilen und Spalten angegben
        self.layout_barrierefrei = QGridLayout()
        self.layout_barrierefrei.addWidget(self.button_betriebssystem, 0, 0)
        self.layout_barrierefrei.addWidget(self.button_anwender, 1, 0)
        self.layout_barrierefrei.addWidget(self.label_vor_nachname,2,0)
        self.layout_barrierefrei.addWidget(self.edit_vor_nachname,2,1)
        self.layout_barrierefrei.addWidget(self.programm_beenden,3,0)
        self.setLayout(self.layout_barrierefrei)

    def betriebssystem_abfragen(self):
        os_info = sys.platform
        print("Betriebssytem: " + os_info)

    def benutzergruessen(self):
        Text = "Guten Tag " + self.edit_vor_nachname.text()
        print(Text)

def main():
    app = QtWidgets.QApplication([])
    app.setStyleSheet("""
QPushButton:focus { background-color: yellow }
QLineEdit:focus { background-color: yellow }
    """);
    win = MainWindow()
    win.showNormal()
    app.exec_()

if __name__ == '__main__':
    main()
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

__blackjack__ hat geschrieben: Mittwoch 3. Juni 2020, 10:13 Zitat:
Und auch `setDefault()` ist ein ganz klarer Missbrauch. Das ist dafür gedacht *einen*
Default-Button in Dialogen festzulegen, nicht dazu um auf *allen* Buttons in einem
Fenster gesetzt zu werden, nur damit man die Eingabetaste zum aktivieren verwenden kann.

Anmerkung von Markus:
Lösung ist von Stackoverflow: https://stackoverflow.com/questions/118 ... -enter-key
Das Problem ist, dass wenn ich hier Fragen stelle, keine Antwort bekomme und dann Googeln muß,
müßt ihr auch damit leben wenn ich Eure Meinung nach die falsche Lösung ergoogelt habe!
Grundsätzlich bin ich als Python-Anfänger froh, wenn ich überhaupt eine Lösung ergoogle!
Aber wie wäre es wenn Du mir jetzt die richtige Lösung verrätst!

Zitat:
Das mit den Fokusfarben funktioniert bei mir (Linux) bei den Buttons nicht. Das Gelb blitzt ganz kurz auf und wird dann durch ein Blau ersetzt.

Anmerkung von Markus:
Unter Ununtu funktioniert es. Da ich es unter Linux nicht testen kann, kann auch niemand erwarten dass es unter Linux funktioniert.
In meinem Kommentar steht drin, dass ich es unter Ununtu getestet habe.

Zitat:
Bei den Event-Handlern ist das Argument `QFocusEvent` falsch geschrieben.
Zudem noch verwirrend, da das der Typ ist den das Argumnet hat, denn eine
`QFocusEvent`-Klasse gibt es in Qt ja tatsächlich.

Habe es verbessert!
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ubuntu ist Linux.
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

__deets__ hat geschrieben: Mittwoch 3. Juni 2020, 15:06 Ubuntu ist Linux.
Aus Wikipedia:
Ubuntu, auch Ubuntu Linux, ist eine Linux-Distribution
Es gibt sehr viel Linux-Distributionen, Versionen usw.
Ich habe auf meiner Virtuellen Machine nur Ubuntu und kann somit andere Distributionen und Versionen von Linux nicht testen!
Deswegen habe ich in der Python-Datei geschrieben, dass ich es unter Ubuntu getestet habe.
Ich habe nicht geschrieben, dass ich es unter Linux getestet habe.
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@PythonMarlem: Die richtige Lösung ist nicht gegen den Strom zu schwimmen. Zum aktivieren von Schaltflächen ist die Leertaste vorgesehen und man kann eine Schaltfläche zur Defaultschaltfläche machen, die per Eingabetaste aktiviert werden kann und das auch ohne das sie den Fokus haben muss. Die beiden Tasten haben verschiedene Aufgaben und Du willst die eine davon unbedingt entwerten, so dass beide effektiv das gleiche machen. Mit einem unschönen Hack, denn `setDefault()` kann sich nicht nur auf das Verhalten auswirken, sondern auch auf die Darstellung der Buttons, denn der Defaultbutton wird auch grafisch unter vielen Themes anders hervorgehoben als normale Buttons. Und wenn Du alle zu Defaultbuttons machst, kann man halt auch keinen Defaultbutton mehr haben. Was aber eine sinnvolle Funktionalität ist.

Ich verwende auch Ubuntu, genauer Kubuntu, aber das sollte eigentlich egal sein. Und bei mir funktioniert das mit dem Gelb nicht. Es ist aber auch egal denn Du machst da etwas falsch. In der Dokumentation zum setzen der Hintergrundfarbe von `QPushButton` steht eine Warnung die hier genau zutrifft und die zur Folge hat, dass man auch deutlich mehr Stylesheet schreiben muss, wenn man die Hintergrundfarbe gesetzt haben will, was wiederum sehr in das Theme eingreift das der Benutzer ausgewählt hat.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

Zitat:
@PythonMarlem: Die richtige Lösung ist nicht gegen den Strom zu schwimmen.

Antwort von Markus:
ohje, hast recht. Das mit der Leertaste habe ich nicht gewußt(peinlich).
Habe setDefault() entfernt.

Zitat:
Ich verwende auch Ubuntu, genauer Kubuntu, aber das sollte eigentlich egal sein.

Antwort von Markus:
Anscheinend gibt es einen Unterschied weil bei mir funktioniert es und bei Dir nicht!
PythonMarlem
User
Beiträge: 90
Registriert: Dienstag 19. Mai 2020, 19:17
Wohnort: Dußlingen
Kontaktdaten:

PythonMarlem hat geschrieben: Mittwoch 3. Juni 2020, 17:07 In der Dokumentation zum setzen der Hintergrundfarbe von `QPushButton` steht eine Warnung
Aus der Doku:
https://doc.qt.io/qt-5/stylesheet-examples.html
Antworten