Flexible & erweiterte Progressbar in Matlab mit Event-Listener
In Matlab fängt man mit Progressbars eigentlich recht simpel an. Und dann wirds größer und man will Informationen unterbringen, zum Beispiel wieviel Zeit vergangen ist, wieviel noch verbleibt, diverse Zwischenstände. Und wenn erst einmal für eine Funktion eine solche komplexe Waitbar erstellt hat, möchte man sie auch weiterverwenden, austauschen, konfigurieren, etc.
Wie man das recht gut, objekt-orientiert und elegant mit Event-Listeners in Matlab realisieren kann, versuche ich mal im Folgenden an Hand eines simplen Beispiels zu demonstrieren…
Matlab bringt bereits eine recht einfache, aber gute Fortschrittsanzeige mit, die mit waitbar aufgerufen wird, und für jede Iteration aktualisiert werden kann.
Man hätte also in etwa mit folgenden Code eine schnelle, schlanke Lösung
for r=1:obj.rounds obj.performance = obj.computePerformance; waitbar(r/obj.rounds) end
Für einfache, wirklich kurze Programme funktioniert das sehr gut. Aber man wird schon relativ häufig feststellen, dass Programme sehr schnell unübersichtlich und groß werden. Und hier wird man schnell einige Nachteile dieses Vorgehens feststellen:
- Man erweitert die Waitbar um weitere Inhalte (z.B. abgelaufene Zeit) und hat recht komplexen Code. Dabei möchte man diese Progress-Bar vielleicht auch für andere Vorgänge nutzen – man möchte aber redundanten Code vermeiden
- Man möchte die Waitbar gegen eine andere austauschen, oder statt einer GUI-basierten Fortschrittsanzeige eine textuelle Ausgabe, wenn man die Funktion per Kommandozeile auruft – Oder beides gleichzeitig, oder komplett deaktivieren…
- Die Funktion, die die Schleife ausführt soll eigentlich nicht GUI-Dinge, Benutzerausgaben abwickeln. Außerdem macht der zusätzliche Code die Funktion unleserlich – Es scheint irgendwie intuitiv nicht richtig so, es sollte gekappselt sein.
Was man hier möchte ist eine seperate Funktion, die benachrichtigt wird bei jedem Schleifendurchlauf, um einen Fortschrittsbalken (welcher Art auch immer) zu aktualisieren. Und die eigentlich Funktion hat nichts weiter mit der Realisierung zu tun, außer ein solches notify zu senden.
Das sind Events und Eventlistener. Und sowas gibts schon in Matlab – sofern man objekt-orientiert entwickelt. Folgendes Beispiel demonstriert die Funktionsweise – der Cycler ist ein Objet, dass eine bestimmte Operation iteriert, und eine Performance berechnet:
classdef cycler < handle
properties (SetAccess = protected)
rounds = 20;
end
properties (GetAccess = public)
performance = 0;
actualRound;
end
events
cycle;
end
methods
function rounds = getRoundCount(obj)
rounds = obj.rounds;
end
function runCycles(obj)
for r=1:obj.rounds
obj.performance = obj.computePerformance;
obj.actualRound = r;
notify(obj,'cycle');
end
end
function perf = computePerformance(obj)
% some magic calculation
pause(0.35)
perf = rand;
end
end
endZunächst kommt eine Reihe von Properties. Zum Beispiel soll auch die aktuell laufende Runde in einer Variable abgelegt werden, die man zum Beispiel auch für die Progressbar als zusätzliche Information nutzen könnte.
Interessanter sind hier events. In diesem Fall gibt es nur ein Event: cycle. Es soll nach jeder Iteration ein Event cycle gesendet werden. Und das ganze passiert in der Methode runCycles. Diese Schleife sieht genauso aus wie in dem simplen Beispiel oben, aber mit dem Unterschied:
notify(obj,'cycle');
Hier wird einfach nur ein notify auf dem Objekt aufgerufen.
Nur schauen wir uns an, wie man eine solche Waitbar realisieren kann:
classdef CyclerProgressBar < handle
properties (SetAccess = protected)
Start;
h;
end
methods
function obj = CyclerProgressBar(cycler_obj)
obj.h = waitbar(0, 'Please Wait', 'Name', 'Running Cycles');
posi = get(obj.h,'Position');
posi(4) = posi(4) + 100;
set(obj.h,'Position',posi);
addlistener(cycler_obj, 'cycle',@(src,evtdata)handleEvnt(obj,src,evtdata));
obj.Start = tic;
end
function delete(obj)
delete(obj.h);
end
function handleEvnt(obj,src,evtdata)
timer = toc(obj.Start);
obj.h = waitbar(src.actualRound/src.getRoundCount, obj.h, ...
sprintf('Validation Round %d (%d)\n\n Performance:\nThis iteration: %3.1f %%\n\nTime: \nRemaining: %4.0f sec. - Elapsed: %4.0f sec.\n\n',...
src.actualRound, src.getRoundCount, ...
src.performance*100,...
(timer/src.actualRound)*(src.getRoundCount-src.actualRound), timer));
end
end
endDer Konstruktor erstellt direkt die in Matlab gewohnte Waitbar (man könnte dies evlt. auch später machen oder über eine seperate Funktion). Um nun in der Waitbar mehrere Zeilen Text unterbringen zu können, vergrößert man diese mittels GUI-Properties:
posi = get(obj.h,'Position'); posi(4) = posi(4) + 100; set(obj.h,'Position',posi);
Der Konstruktor bekommt direkt das Objekt mit übergeben, auf das er horchen soll. Ein bißchen tricky war in diesem die Funktion addListener zu modifizieren, damit die Progressbar sich aus dem Objekt, auf das es horcht, weitere Informationen (wie z.B. Performance) ziehen kann und diese mit ausgibt. Wenn man einen Blick in die Beispiele der Matlab-Doku wirft, findet man meist so etwas hier:
addlistener(toggle_button_obj,'ToggledState',@RespondToToggle.handleEvnt);
Was ich eigentlich wollte, ist das Objekt (oder etwas anderes) mit übergeben. Hierfür macht man sich die Funktionaltität anonymer Funktionen zu nutze:
addlistener(cycler_obj, 'cycle',@(src,evtdata)handleEvnt(obj,src,evtdata));
Nun kann die Funktion handleEvent das Objekt, dass das notify ausführt, mit übergeben bekommen.
in Timer mit wird mittels tic zum einen Timer gestartet und die Startzeit abgelegt, damit wäre der Konstruktor fertig.
Bei jedem notify wird nun handleEvent aufgerufen. Dabei checkt man mit toc die abelaufene Zeit. Und nun kann ich mit Hilfe von sprintf und weil ich das Waitbar-Fenster vergrößert habe, jede Menge Informationen ausgeben, wie Uhrzeit und Performance.
Der EventListener muss nun nur noch registiert werden:
demoCycler = cycler; demoBar = CyclerProgressBar(demoCycler); demoCycler.runCycles; delete(demoBar);
Denkbar wären, wie bereits zuvor erwähnt, nun auch weitere Progressbars, die anderen Text enthalten, oder eine zusätzliche textuelle Ausgabe (was ich mal aus Faulheit hier jetzt nicht mehr gecodet habe
).
Sicherlich sind Eventlistener in Matlab nicht so flexibel wie in Java, doch sie funktionieren wie in diesem Beispiel für einfach Dinge recht gut.
Das Beispiel zum Download:
AdvancedWaitbarSample (107)
Ähnliche Beiträge:
Beschriftete Balkendiagramme in Matlab
Zeilenumbruch in Tooltips – Matlab GUI
Neuronale Netze und Matlab
