Startseite>Android-Smartphone als RasPi-Bildschirm
Aus Raspberry Pi Geek 12/2018

Android-Smartphone als RasPi-Bildschirm (Seite 3)

Bei Bedarf richten Sie darüber hinaus das Paket wx3.0-examples mit Beispielen in C++ ein. Da die Methodenaufrufe in C++ und Python fast identisch ausfallen, lassen sich die Beispiele recht einfach in die Python-Welt übertragen.

wxPython

Von der Grundarchitektur her unterscheidet sich ein wxPython-Programm nicht von anderen Programmen mit grafischer Oberfläche. Der Hauptteil besteht aus einer Endlosschleife, dem sogenannten Event-Loop.

Jede Interaktion mit dem Programm, etwa das Drücken eines Schalters, eine Texteingabe oder das Verschieben des Fensters löst ein Ereignis aus. Der Event-Loop verarbeitet das Ereignis, indem er es an entsprechende Python-Methoden weitergibt. Unser Beispielprogramm verfügt über drei Buttons, entsprechend gibt es drei Methoden für die Verarbeitung.

In Listing 3 sehen Sie die wichtigsten Abschnitte des Programms WxVlcdApp.py, den kompletten Code laden Sie aus dem Github-Repo des Autors herunter [8]. Der beschriebene Event-Loop findet sich in der letzten Zeile. Dort ruft das Programm die Methode MainLoop() der darüber definierten Applikationsklasse WxVlcdApp auf.

Sie erweitert die Klasse wx.App und besteht hier nur aus der einzelnen Methode OnInit(). Diese generiert die Oberfläche der Anwendung. Dafür gibt es wiederum eine eigene, ab Zeile 22 definierte Klasse AppFrame, die den Hauptteil des Programms ausmacht.

Der Konstruktor von AppFrame erzeugt als Erstes in der Methode create_controls() die grafischen Elemente (ab Zeile 34). Dazu verwenden wir einen BoxSizer(), wie man ihn aus anderen Toolkits unter dem Namen BoxLayout() oder ähnlich kennt.

Dieses Layoutelement richtet seine Kinder vertikal aus; im Beispiel sind das konkret die Textbox für die Ausgabe und die Schalterleiste. Die Parameter der Methode Add() steuern, welches Kind sich wie ausbreiten darf und wie es sich ausrichtet. Weitere Details dazu finden Sie in der Dokumentation zu wxPython. Analog dazu erzeugt die Methode create_buttons() (ab Zeile 44) die Schalterleiste. Hier landen drei Buttons in einem horizontalen BoxSizer().

Zusätzlich legen die Zeilen 50 bis 53 fest, welche Methoden das Programm beim Drücken der einzelnen Knöpfe ausführt. Die Erweiterung um zusätzliche Buttons erfolgt hier einfach durch Kopieren und Einfügen der relevanten Zeilen.

Listing 3

 

#!/usr/bin/python
import os, time, threading, subprocess, re, socket
import wx
[...]
# --- Hilfsklasse für asynchrones Update der Textbox
SYSCMD_EVENT_TYPE = wx.NewEventType()
EVT_SYSCMD = wx.PyEventBinder(SYSCMD_EVENT_TYPE, 1)
class SysCmdFinishEvent(wx.PyCommandEvent):
  """ wrap result of a system-command into an event """
  def __init__(self, ev_id, value=None):
    wx.PyCommandEvent.__init__(self, SYSCMD_EVENT_TYPE, ev_id)
    self._value = value
  def get_output(self):
    return self._value
# --- Hauptapplikationsrahmen
class AppFrame(wx.Frame):
  """ Application toplevel frame """
  # --- Constructor
  def __init__(self, parent, title):
    wx.Frame.__init__(self, parent, -1, title, size=(800, 480))
    #self.SetMenuBar(self.create_menu())
    #self.CreateStatusBar()
    self.create_controls()
    self.Bind(EVT_SYSCMD, self.on_syscmd_result)
    self.on_uptime(None)
  # --- Widgets erstellen
  def create_controls(self):
    """ create panel with all controls """
    vbox = wx.BoxSizer(wx.VERTICAL)
    vbox.Add(self.create_outputarea(self), 1, wx.ALL|wx.EXPAND, 10)
    vbox.Add(self.create_buttons(self), 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10)
    self.SetSizer(vbox)
[...]
  # --- Buttonleiste erstellen
  def create_buttons(self, parent):
    button_panel = wx.Panel(parent)
    sys_btn = wx.Button(button_panel, -1, "System")
    disk_btn = wx.Button(button_panel, -1, "Disk")
    net_btn = wx.Button(button_panel, -1, "Network")
    self.Bind(wx.EVT_BUTTON, self.on_uptime, sys_btn)
    self.Bind(wx.EVT_BUTTON, self.on_df, disk_btn)
    self.Bind(wx.EVT_BUTTON, self.on_ip, net_btn)
    hbox = wx.BoxSizer(wx.HORIZONTAL)
    hbox.Add(sys_btn, 0, wx.ALL, 10)
    hbox.Add(disk_btn, 0, wx.ALL, 10)
    hbox.Add(net_btn, 0, wx.ALL, 10)
    button_panel.SetSizer(hbox)
    return button_panel
  # --- Textbox für Ausgabe erstellen
  def create_outputarea(self, parent):
    text_panel = wx.Panel(parent)
    text_panel.SetBackgroundColour(wx.BLUE)
    self._output_text = wx.StaticText(text_panel, -1, "")
    self._output_text.SetForegroundColour(wx.WHITE)
    self._output_text.SetFont(wx.Font(14, wx.MODERN, wx.NORMAL, wx.BOLD))
    self._output_text.SetSize(self._output_text.GetBestSize())
    hbox = wx.BoxSizer(wx.HORIZONTAL)
    hbox.Add(self._output_text, 1, wx.EXPAND, 0)
    text_panel.SetSizer(hbox)
    return text_panel
  # --- Handler für den "uptime"-Button
  def on_uptime(self, evt):
    """ start asynchronous command """
    threading.Thread(target=self.get_uptime).start()
[...]
  # --- Handler für asynchrones Update der Textbox
  def on_syscmd_result(self, evt):
    self._output_text.SetLabelText(evt.get_output())
  # --- Exit-Handler
  def on_exit(self, evt):
    """ exit-handler """
    self.Close()
  # --- Abfrage uptime (asynchron ausgeführt)
  def get_uptime(self):
    hostname = self.read_hostname()
    tm     = time.strftime("%H:%M:%S")
    (uptime, idle) = self.read_uptime()
    load   = self.read_loadavg()
    output = """Hostname: %s
Time:     %s
Uptime:   %s
Idle:     %s
Load:     %s""" % (hostname, tm, uptime, idle, load)
    evt = SysCmdFinishEvent(-1, output)
    wx.PostEvent(self, evt)
[...]
  # --- Hostname auslesen
  def read_hostname(self):
    """ read hostname from /proc/sys/kernel/hostname """
    with open("/proc/sys/kernel/hostname") as f:
      hostname = f.read().split()
      f.close()
    return hostname[0]
[...]
# --- Hauptanwendungsklasse
class WxVlcdApp(wx.App):
  def OnInit(self):
    frame = AppFrame(None, "Virtual LCD")
    self.SetTopWindow(frame)
    frame.Show(True)
    return True
# --- Hauptprogramm
if __name__ == '__main__':
  wait_for_X11()
  app = WxVlcdApp(redirect=False)
  app.MainLoop()

Event-Verarbeitung

Unsere Button-Methoden rufen letztlich Systeminformationen ab und schreiben das Ergebnis in das Textfeld. Das erfolgt aber nicht direkt, weil während der Laufzeit der Methode die Anwendung quasi “einfriert”: MainLoop() kann in dieser Zeit auf Ereignisse der Oberfläche (Fenster verschieben, Button drücken und so weiter) nicht reagieren.

Die Button-Methode on_uptime() in den Zeilen ab 73 startet deshalb nur schnell einen Thread, der dann die eigentliche Arbeit asynchron in get_uptime() (ab Zeile 89) erledigt.

Wie bei allen grafischen Bibliotheken üblich darf aber nur der Hauptthread (also MainLoop()) die Oberfläche aktualisieren. Deshalb erzeugt und versendet der Thread am Ende ein selbst definiertes Event (ab Zeile 100). Dieser von uns SysCmdFinishEvent() genannte private Event enthält lediglich den anzuzeigenden Text, die Definition steht am Anfang des Listings.

Auch an dieses Event hat das Programm im Konstruktor von AppFrame eine Methode gebunden (Zeile 31), in der ein Update des Textfelds stattfindet (ab Zeile 80), und damit schließt sich der Kreis.

Möchten Sie automatisch ein Feld mit Statusinfos füllen, nutzen Sie dazu dasselbe Schema: Ein lang laufender Thread sendet einfach in festen Abständen ein Update-Event mit dem neuen Feldinhalt.

Integration

Unser Programm hat jetzt noch zwei Probleme: Startet es, solange der X-Server (noch) nicht läuft, dann stürzt es gleich wieder ab. Ebenso beendet es sich, sobald die X-Server-App nicht mehr läuft oder außer Reichweite ist.

Das erste Problem lösen Sie, indem Sie während des Programmstarts den X-Server testen und das Programm nur dann fortsetzen, wenn er reagiert (der Code fehlt in Listing 3 aus Platzgründen). Sobald die App startet, poppt auch schon die Oberfläche hoch. Das funktioniert nur dann problemlos, wenn Sie nicht mehrere RasPis an einem Smartphone verwenden.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 7 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
€0,99 – Kaufen
RASPBERRY PI GEEK KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS
Deutschland