Quelle est la "bonne" façon d'organiser le code GUI?
je travaille sur un programme GUI assez sophistiqué à déployer avec le compilateur MATLAB. (Il y a de bonnes raisons pour que MATLAB soit utilisé pour construire cette GUI, ce n'est pas le sujet de cette question. Je me rends compte que le GUI-building n'est pas un atout pour cette langue.)
il y a plusieurs façons de partager des données entre fonctions dans une interface graphique, ou même de passer des données entre interfaces graphiques dans une application:
-
setappdata/getappdata/_____appdata
- associé données arbitraires à une poignée -
guidata
- typiquement utilisé avec GUIDE; "stocker ou récupérer [S] données GUI" à une structure de poignées - Appliquer
set/get
l'opération de laUserData
la propriété d'un objet descripteur - utilise des fonctions imbriquées dans une fonction principale; émule essentiellement des variables de portée" globale".
- passer les données de part et d'autre parmi les sous-fonctions
La structure de mon code n'est pas la plus jolie. En ce moment, j'ai séparé le moteur de l'Avant (bon!) mais le code GUI est assez spaghetti-like. Voici un squelette d'une "activité", pour emprunter Android-parler:
function myGui
fig = figure(...);
% h is a struct that contains handles to all the ui objects to be instantiated. My convention is to have the first field be the uicontrol type I'm instantiating. See draw_gui nested function
h = struct([]);
draw_gui;
set_callbacks; % Basically a bunch of set(h.(...), 'Callback', @(src, event) callback) calls would occur here
%% DRAW FUNCTIONS
function draw_gui
h.Panel.Panel1 = uipanel(...
'Parent', fig, ...
...);
h.Panel.Panel2 = uipanel(...
'Parent', fig, ...
...);
draw_panel1;
draw_panel2;
function draw_panel1
h.Edit.Panel1.thing1 = uicontrol('Parent', h.Panel.Panel1, ...);
end
function draw_panel2
h.Edit.Panel2.thing1 = uicontrol('Parent', h.Panel.Panel2, ...);
end
end
%% CALLBACK FUNCTIONS
% Setting/getting application data is done by set/getappdata(fig, 'Foo').
end
j'ai déjà écrit du code où rien n'est imbriqué, donc j'ai fini par passer h
d'un endroit à l'autre (puisque les choses devaient être redessinées, mises à jour, etc) et setappdata(fig)
pour stocker des données réelles. En tout cas, j'ai garder de l' "activité" dans un seul fichier, et je suis sûr que cela va être un entretien de cauchemar dans l'avenir. Les Callbacks interagissent à la fois avec les données d'application et les objets de gestion graphique, ce qui est nécessaire, je suppose, mais qui empêche une ségrégation complète des deux "moitiés" de la base de code.
donc je suis à la recherche d'un peu d'organisation/GUI design aide ici. À savoir:
- y a-t-il une structure de répertoire que je devrais être utiliser pour organiser? (Rappels vs fonctions de dessin?)
- Quelle est la" bonne façon " d'interagir avec les données de L'interface graphique et de les séparer des données de l'application? (Quand je me réfère aux données GUI je veux dire
set/get
propriétés ting des objets de poignée). - Comment puis-je éviter de mettre toutes ces fonctions de dessin dans un fichier géant de milliers de lignes et passer encore efficacement à la fois l'application et les données GUI avant et en arrière? Est-ce possible?
- Y a-t-il une pénalité de rendement associée à l'utilisation constante de
set/getappdata
? - y a-t-il une structure que mon code d'arrière-plan (3 classes d'objets et un tas de fonctions d'aide) devrait prendre pour le rendre plus facile à maintenir d'un point de vue GUI?
Je ne suis pas un ingénieur logiciel par métier, je sais juste assez pour être dangereux, donc je suis sûr que ce sont des questions assez basiques pour les développeurs GUI chevronnés (dans n'importe quelle langue). Je me sens presque comme le absence d'une norme de conception D'interface graphique dans MATLAB (existe-t-il une norme?) interfère sérieusement avec ma capacité à mener à bien ce projet. Il s'agit d'un projet MATLAB qui est beaucoup plus important que tous ceux que j'ai jamais entrepris, et je n'ai jamais eu à beaucoup réfléchir à UIS compliqué avec des fenêtres à plusieurs chiffres, etc., avant.
5 réponses
comme @SamRoberts expliqué, le modèle Model–view–controller (MVC) est bien adapté comme une architecture pour concevoir des interfaces graphiques. Je suis d'accord qu'il n'y a pas beaucoup D'exemples MATLAB là-bas pour montrer une telle conception...
ci-dessous est un exemple complet mais simple que j'ai écrit pour démontrer une interface graphique basée sur MVC dans MATLAB.
-
le modèle représente un 1D fonction d'un certain signal
y(t) = sin(..t..)
. C'est un objet handle-class, de cette façon nous pouvons transmettre les données sans créer de copies inutiles. Il expose les propriétés observables, ce qui permet aux autres composants d'écouter les notifications de changement. -
le view présente le modèle comme un objet graphique linéaire. La vue contient également un curseur pour contrôler une des propriétés du signal, et écoute les notifications de changement de modèle. Je inclut également une propriété interactive qui est spécifique à la vue (pas le modèle), où la couleur de la ligne peut être contrôlée en utilisant le menu contextuel du clic droit.
-
le contrôleur est chargé d'initialiser tout et de répondre aux événements à partir de la vue et de mettre à jour correctement le modèle en conséquence.
notez que la vue et le contrôleur sont écrits comme réguliers fonctions, mais vous pouvez écrire des classes si vous préférez le code entièrement orienté objet.
c'est un peu de travail supplémentaire par rapport à la façon habituelle de concevoir des interfaces graphiques, mais l'un des avantages d'une telle architecture est la séparation des données de la couche de présentation. Cela rend le code plus propre et plus lisible, surtout lorsqu'on travaille avec des interfaces graphiques complexes, où la maintenance du code devient plus difficile.
ce design est très flexible car il vous permet de construisez plusieurs vues des mêmes données. Encore plus vous pouvez avoir des vues multiples simultanées , instanciez juste plus d'instances de vues dans le controller et voyez comment les changements dans une vue sont propagés à l'autre! Ceci est particulièrement intéressant si votre modèle peut être visuellement présenté de différentes façons.
en outre, si vous préférez, vous pouvez utiliser L'éditeur de GUIDE pour construire des interfaces au lieu d'ajouter des commandes par programmation. Dans une telle conception, nous utiliserait GUIDE seulement pour construire les composants de L'interface graphique en utilisant drag-and-drop, mais nous n'écririons aucune fonction de rappel. Nous ne nous intéresserons donc qu'au fichier .fig
produit, et ignorerons simplement le fichier .m
qui l'accompagne. Nous configurerions les callbacks dans la fonction view/class. C'est essentiellement ce que j'ai fait dans le composant de vue View_FrequencyDomain
, qui charge le FIG-file existant construit en utilisant GUIDE.
Model.m
classdef Model < handle
%MODEL represents a signal composed of two components + white noise
% with sampling frequency FS defined over t=[0,1] as:
% y(t) = a * sin(2pi * f*t) + sin(2pi * 2*f*t) + white_noise
% observable properties, listeners are notified on change
properties (SetObservable = true)
f % frequency components in Hz
a % amplitude
end
% read-only properties
properties (SetAccess = private)
fs % sampling frequency (Hz)
t % time vector (seconds)
noise % noise component
end
% computable dependent property
properties (Dependent = true, SetAccess = private)
data % signal values
end
methods
function obj = Model(fs, f, a)
% constructor
if nargin < 3, a = 1.2; end
if nargin < 2, f = 5; end
if nargin < 1, fs = 100; end
obj.fs = fs;
obj.f = f;
obj.a = a;
% 1 time unit with 'fs' samples
obj.t = 0 : 1/obj.fs : 1-(1/obj.fs);
obj.noise = 0.2 * obj.a * rand(size(obj.t));
end
function y = get.data(obj)
% signal data
y = obj.a * sin(2*pi * obj.f*obj.t) + ...
sin(2*pi * 2*obj.f*obj.t) + obj.noise;
end
end
% business logic
methods
function [mx,freq] = computePowerSpectrum(obj)
num = numel(obj.t);
nfft = 2^(nextpow2(num));
% frequencies vector (symmetric one-sided)
numUniquePts = ceil((nfft+1)/2);
freq = (0:numUniquePts-1)*obj.fs/nfft;
% compute FFT
fftx = fft(obj.data, nfft);
% calculate magnitude
mx = abs(fftx(1:numUniquePts)).^2 / num;
if rem(nfft, 2)
mx(2:end) = mx(2:end)*2;
else
mx(2:end -1) = mx(2:end -1)*2;
end
end
end
end
View_TimeDomain.m
function handles = View_TimeDomain(m)
%VIEW a GUI representation of the signal model
% build the GUI
handles = initGUI();
onChangedF(handles, m); % populate with initial values
% observe on model changes and update view accordingly
% (tie listener to model object lifecycle)
addlistener(m, 'f', 'PostSet', ...
@(o,e) onChangedF(handles,e.AffectedObject));
end
function handles = initGUI()
% initialize GUI controls
hFig = figure('Menubar','none');
hAx = axes('Parent',hFig, 'XLim',[0 1], 'YLim',[-2.5 2.5]);
hSlid = uicontrol('Parent',hFig, 'Style','slider', ...
'Min',1, 'Max',10, 'Value',5, 'Position',[20 20 200 20]);
hLine = line('XData',NaN, 'YData',NaN, 'Parent',hAx, ...
'Color','r', 'LineWidth',2);
% define a color property specific to the view
hMenu = uicontextmenu;
hMenuItem = zeros(3,1);
hMenuItem(1) = uimenu(hMenu, 'Label','r', 'Checked','on');
hMenuItem(2) = uimenu(hMenu, 'Label','g');
hMenuItem(3) = uimenu(hMenu, 'Label','b');
set(hLine, 'uicontextmenu',hMenu);
% customize
xlabel(hAx, 'Time (sec)')
ylabel(hAx, 'Amplitude')
title(hAx, 'Signal in time-domain')
% return a structure of GUI handles
handles = struct('fig',hFig, 'ax',hAx, 'line',hLine, ...
'slider',hSlid, 'menu',hMenuItem);
end
function onChangedF(handles,model)
% respond to model changes by updating view
if ~ishghandle(handles.fig), return, end
set(handles.line, 'XData',model.t, 'YData',model.data)
set(handles.slider, 'Value',model.f);
end
View_FrequencyDomain.m
function handles = View_FrequencyDomain(m)
handles = initGUI();
onChangedF(handles, m);
hl = event.proplistener(m, findprop(m,'f'), 'PostSet', ...
@(o,e) onChangedF(handles,e.AffectedObject));
setappdata(handles.fig, 'proplistener',hl);
end
function handles = initGUI()
% load FIG file (its really a MAT-file)
hFig = hgload('ViewGUIDE.fig');
%S = load('ViewGUIDE.fig', '-mat');
% extract handles to GUI components
hAx = findobj(hFig, 'tag','axes1');
hSlid = findobj(hFig, 'tag','slider1');
hTxt = findobj(hFig, 'tag','fLabel');
hMenu = findobj(hFig, 'tag','cmenu1');
hMenuItem = findobj(hFig, 'type','uimenu');
% initialize line and hook up context menu
hLine = line('XData',NaN, 'YData',NaN, 'Parent',hAx, ...
'Color','r', 'LineWidth',2);
set(hLine, 'uicontextmenu',hMenu);
% customize
xlabel(hAx, 'Frequency (Hz)')
ylabel(hAx, 'Power')
title(hAx, 'Power spectrum in frequency-domain')
% return a structure of GUI handles
handles = struct('fig',hFig, 'ax',hAx, 'line',hLine, ...
'slider',hSlid, 'menu',hMenuItem, 'txt',hTxt);
end
function onChangedF(handles,model)
[mx,freq] = model.computePowerSpectrum();
set(handles.line, 'XData',freq, 'YData',mx)
set(handles.slider, 'Value',model.f)
set(handles.txt, 'String',sprintf('%.1f Hz',model.f))
end
contrôleur.m
function [m,v1,v2] = Controller
%CONTROLLER main program
% controller knows about model and view
m = Model(100); % model is independent
v1 = View_TimeDomain(m); % view has a reference of model
% we can have multiple simultaneous views of the same data
v2 = View_FrequencyDomain(m);
% hook up and respond to views events
set(v1.slider, 'Callback',{@onSlide,m})
set(v2.slider, 'Callback',{@onSlide,m})
set(v1.menu, 'Callback',{@onChangeColor,v1})
set(v2.menu, 'Callback',{@onChangeColor,v2})
% simulate some change
pause(3)
m.f = 10;
end
function onSlide(o,~,model)
% update model (which in turn trigger event that updates view)
model.f = get(o,'Value');
end
function onChangeColor(o,~,handles)
% update view
clr = get(o,'Label');
set(handles.line, 'Color',clr)
set(handles.menu, 'Checked','off')
set(o, 'Checked','on')
end
dans le contrôleur ci-dessus, j'instancie deux vues distinctes mais synchronisées, représentant et réagissant aux changements dans le même modèle sous-jacent. Une vue montre le domaine Temps du signal, et une autre montre la représentation du domaine fréquence en utilisant FFT.
la UserData
propriété est une propriété utile, mais héritée, des objets MATLAB. La série de méthodes" AppData " (c.-à-d. setappdata
, getappdata
, rmappdata
, isappdata
, etc.) constituent une excellente alternative à l'approche relativement plus maladroite get/set(hFig,'UserData',dataStruct)
de L'OMI. En fait, pour gérer les données GUI, GUIDE emploie le guidata
fonction, qui est juste un enveloppeur pour les setappdata
/ getappdata
fonctions .
quelques avantages de L'approche AppData par rapport à la propriété 'UserData'
qui vient à l'esprit:
-
interface plus naturelle pour des propriétés hétérogènes multiples.
UserData
est limité à une seule variable , ce qui vous oblige à concevoir une autre couche d'oranisation des données (c.-à-d. une structure). Dites que vous voulez stocker une cordestr = 'foo'
et un tableau numériquev=[1 2]
. AvecUserData
, vous auriez besoin d'adopter un schéma de structure tel ques = struct('str','foo','v',[1 2]);
etset/get
le tout chaque fois que vous voulez l'une ou l'autre propriété (par exemples.str = 'bar'; set(h,'UserData',s);
). Avecsetappdata
, le processus est plus direct (et efficace):setappdata(h,'str','bar');
. -
interface protégée avec l'espace de stockage sous-jacent.
alors que
'UserData'
est juste une propriété graphique de poignée régulière, le la propriété contenant les données de l'application n'est pas visible, bien qu'elle soit accessible par son nom ('ApplicationData', mais ne le faites pas!). Vous devez utilisersetappdata
pour modifier les propriétés D'AppData existantes, ce qui vous empêche d'écraser accidentellement le contenu entier de'UserData'
tout en essayant de mettre à jour un seul champ. En outre, avant de définir ou d'obtenir une propriété AppData, vous pouvez vérifier l'existence d'une propriété nommée avecisappdata
, ce qui peut aider avec le traitement des exceptions (par exemple lancer un processus de callback avant de définir les valeurs d'entrée) et gérer l'état de L'interface graphique ou les tâches qu'elle régit (par exemple inférer l'état d'un processus par l'existence de certaines propriétés et mettre à jour l'interface graphique de manière appropriée).
une différence importante entre les propriétés 'UserData'
et 'ApplicationData'
est que 'UserData'
est par défaut []
(un tableau vide), tandis que 'ApplicationData'
est nativement une structure. Cette différence, en collaboration avec le le fait que setappdata
et getappdata
n'ont pas d'implémentation de m-file (ils sont intégrés), suggère que définir une propriété nommée avec setappdata
ne pas nécessite de réécrire le contenu entier de la structure de données . (Imaginez une fonction MEX qui effectue une modification en place d'un champ struct - une opération MATLAB est capable de mettre en œuvre en maintenant une struct comme la représentation de données sous-jacente de la 'ApplicationData'
Gérer les graphiques propriété.)
la fonction guidata
est un wrapper des fonctions AppData, mais elle est limitée à une seule variable, comme 'UserData'
. Cela signifie que vous devez écraser toute la structure de données contenant tous vos champs de données pour mettre à jour un seul champ. Un avantage déclaré est que vous pouvez accéder aux données à partir d'un rappel sans avoir besoin de la poignée de chiffre réelle, mais en ce qui me concerne, ce n'est pas un gros avantage si vous êtes à l'aise avec la déclaration suivante:
hFig = ancestor(hObj,'Figure')
aussi, comme indiqué par MathWorks , Il ya des problèmes d'efficacité:
L'économie de grandes quantités de données dans la structure "poignées" peut parfois causer un ralentissement considérable, surtout si GUIDATA est souvent appelé dans les diverses sous-fonctions de l'interface graphique. Pour cette raison, il est recommandé d'utiliser uniquement la structure "poignées" stocker des descripteurs d'objets graphiques. Pour d'autres types de données, SETAPPDATA et GETAPPDATA doivent être utilisés pour les stocker en tant que données d'application.
cette affirmation appuie mon assertion que le 'ApplicationData'
entier n'est pas réécrit en utilisant setappdata
pour modifier une seule propriété nommée. (D'autre part, guidata
place la structure handles
dans un champ de 'ApplicationData'
appelé 'UsedByGUIData_m'
, il est donc clair pourquoi guidata
devrait réécrire toutes les données de L'interface graphique lorsqu'une propriété est changée).
les fonctions imbriquées nécessitent très peu d'efforts (aucune structure ou fonction auxiliaire nécessaire), mais elles limitent évidemment la portée des données à L'interface graphique, ce qui rend impossible pour d'autres interfaces graphiques ou fonctions d'accéder à ces données sans retourner les valeurs à l'espace de travail de base ou à une fonction d'appel commune. Évidemment cela vous empêche de séparer les sous-fonctions en fichiers séparés, quelque chose que vous pouvez facilement faire avec 'UserData'
ou AppData aussi longtemps que vous passez la poignée de chiffre.
en résumé, si vous choisissez d'utiliser les propriétés de poignée pour stocker et passer des données, il est possible d'utiliser à la fois "15197092020" pour gérer les poignées Graphiques (pas de grandes données) et setappdata
/ getappdata
pour les données réelles du programme. ils ne s'écraseront pas depuis guidata
fait un spécial 'UsedByGUIData_m'
champ dans ApplicationData
pour la structure handles
(sauf si vous faites l'erreur d'utiliser cette propriété vous-même!). Juste pour réitérer, ne pas accéder directement ApplicationData
.
cependant, si vous êtes à l'aise avec OOP, il peut être plus propre à mettre en œuvre la fonctionnalité GUI via une classe , avec des poignées et d'autres données stockées dans les variables membres plutôt que les propriétés de poignée, et les callbacks dans les méthodes qui peuvent exister dans fichiers séparés dans le dossier de classe ou de paquet . Il y a un bel exemple sur MATLAB Central File Exchange . Cette présentation démontre comment la transmission des données est simplifiée avec une classe puisqu'il n'est plus nécessaire d'obtenir et de mettre à jour constamment guidata
(les variables membres sont toujours à jour). Toutefois, il y a la tâche supplémentaire de gérer le nettoyage à la sortie, que la soumission accomplit en fixant le chiffre closerequestfcn
, qui appelle alors la fonction delete
de la classe. La soumission est très semblable à l'exemple du GUIDE.
tels sont les faits saillants que je vois, mais beaucoup plus de détails et des idées différentes sont discuté par MathWorks . Voir aussi cette réponse officielle à UserData
vs. guidata
vs. setappdata/getappdata
.
Je ne suis pas d'accord que MATLAB n'est pas bon pour la mise en œuvre (même complexe) GUIs - il est parfaitement bien.
cependant, ce qui est vrai est que:
- il n'y a pas d'exemples dans la documentation de MATLAB sur la façon de mettre en œuvre ou d'organiser une application GUI complexe
- tous les exemples de documentation de l'utilisation simple des interfaces graphiques qui ne se basent pas bien du tout sur des interfaces graphiques complexes
- en particulier, GUIDE (le outil intégré pour la génération automatique de code GUI) génère du code terrible qui est un exemple terrible à suivre si vous mettez en œuvre quelque chose vous-même.
à cause de ces choses, la plupart des gens ne sont exposés à MATLAB GUIs soit très simple ou vraiment horrible, et ils finissent par penser MATLAB n'est pas adapté pour faire des GUIs.
d'après mon expérience, la meilleure façon de mettre en œuvre une interface graphique complexe à MATLAB est la même que vous le feriez dans une autre langue-suivre un modèle bien utilisé comme MVC (model-view-controller).
cependant, il s'agit d'un modèle orienté objet, donc vous devez d'abord vous mettre à l'aise avec la programmation orientée objet dans MATLAB, et en particulier avec l'utilisation d'événements. Utiliser une organisation orientée objet pour votre application devrait signifier que toutes les techniques désagréables que vous mentionnez ( setappdata
, guidata
, UserData
, fonction imbriquée établissement de la portée et transmission de plusieurs données copies) ne sont pas nécessaires, car toutes les choses pertinentes sont disponibles en tant que propriétés de classe.
le meilleur exemple que je connaisse de ce que MathWorks a publié est dans cet article de MATLAB Digest. Même cet exemple est très simple, mais il vous donne une idée de comment commencer, et si vous regardez dans le modèle MVC il devrait devenir clair comment l'étendre.
de plus, je fais habituellement un usage intensif de dossiers package pour organiser grands codebases à MATLAB, pour s'assurer qu'il n'y a pas de conflits de noms.
un dernier conseil-utilisez le GUI Layout Toolbox , de MATLAB Central. Il rend de nombreux aspects du développement de GUI beaucoup plus facile, en particulier la mise en œuvre du comportement de redimensionnement automatique, et vous donne plusieurs éléments D'interface utilisateur supplémentaires à utiliser.
Espère que ça aide!
Edit: in MATLAB R2016a MathWorks introduced AppDesigner, un nouveau cadre D'interface graphique destiné à remplacer progressivement le GUIDE.
AppDesigner représente une rupture majeure avec les approches précédentes de création D'interfaces graphiques dans MATLAB de plusieurs façons (plus profondément, les fenêtres de figures sous-jacentes générées sont basées sur une toile HTML et JavaScript, plutôt que Java). Il s'agit d'une nouvelle étape sur la voie entamée par L'introduction de Handle Graphics 2 dans R2014b, et qui évoluera sans aucun doute au cours des prochaines versions.
mais un impact D'AppDesigner sur la question posée est qu'il génère beaucoup meilleur code que GUIDE fait - il assez propre, orienté objet, et adapté pour former la base d'un modèle MVC.
je suis très mal à l'aise avec la façon dont le GUIDE produit des fonctions. (pensez à des cas où vous aimeriez appeler une gui d'une autre)
je vous suggère fortement d'écrire votre objet de code orienté en utilisant des classes de poignée. De cette façon, vous pouvez faire des étoffes de fantaisie (par exemple ce ) et de ne pas se perdre. Pour organiser le code, vous avez les répertoires +
et @
.
Je ne pense pas que la structuration du GUI-code soit fondamentalement différente du non-GUI-code.
rassemble des choses qui vont ensemble, à un endroit quelconque.
Comme helper-fonctions qui pourraient aller dans un répertoire util
ou helpers
. Selon le contenu, peut-être en faire un paquet.
personnellement, je n'aime pas la philosophie" one function one M-file " de certains MATLABS. Mettre une fonction comme:
function pushbutton17_callback(hObject,evt, handles)
some_text = someOtherFunction();
set(handles.text45, 'String', some_text);
end
dans un fichier séparé n'a tout simplement aucun sens, quand il n'y a aucun scénario que vous appelleriez ça d'un autre endroit que de votre propre interface graphique.
vous pouvez toutefois construire l'interface graphique elle-même de manière modulaire, en créant par exemple certains composants en passant simplement le conteneur parent:
handles.panel17 = uipanel(...);
createTable(handles.panel17); % creates a table in the specified panel
cela simplifie également les tests de certains sous-Composants - vous pouvez simplement appeler createTable
sur une figure vide et tester certaines fonctionnalités de la table sans charger l'application complète.
juste deux articles supplémentaires que j'ai commencé à utiliser lorsque mon application est devenue de plus en plus grande:
utilisent des auditeurs sur les callbacks, ils peuvent simplifier considérablement la programmation GUI.
si vous disposez de données vraiment importantes (telles que celles provenant d'une base de données, etc.) il pourrait être utile de mettre en œuvre une la classe handle détient ces données. Stocker cette poignée quelque part dans le guidata/appdata améliore considérablement les performances get/setappdata.
Edit:
Auditeurs sur les rappels:
A pushbutton
en est un mauvais exemple. Appuyer sur un bouton déclenche généralement seulement sur une certaine action, ici les rappels sont très bien imho.
Un avantage principal dans mon cas, par exemple, était que le changement programmatique de texte / listes popup ne déclenche pas les callbacks, alors que les auditeurs sur leur propriété String
ou Value
sont déclenchés.
un autre exemple:
S'il y a une propriété centrale (par exemple une source d'inputdata) sur laquelle dépendent plusieurs composants de l'application, alors utiliser des écouteurs est très pratique pour s'assurer que tous les composants sont avisés si la propriété change. Chaque nouveau composant "intéressé" dans cette propriété peut simplement ajouter son propre écouteur, donc il n'y a pas besoin pour modifier centralement le callback. Cela permet une conception beaucoup plus modulaire des composants GUI et rend l'ajout/le retrait de ces composants plus facile.