web-dev-qa-db-fra.com

Quel est le moyen "droit" d'organiser le code de l'interface graphique?

Je travaille sur un programme d'interface graphique assez sophistiqué à déployer avec Matlab Compiler. (Il y a de bonnes raisons que Matlab est utilisée pour construire cette interface graphique, ce n'est pas le point de cette question. Je réalise que le bâtiment de l'interface graphique n'est pas un costume fort pour cette langue.)

Il existe de nombreuses façons de partager des données entre les fonctions d'une interface graphique ou même passer des données entre Guisttes dans une application:

  • setappdata/getappdata/_____appdata - associez des données arbitraires à une poignée
  • guidata - généralement utilisé avec guide; "Stocker [S] ou récupérer [S] Data GUI" à une structure de poignées
  • Appliquer une opération de set/get Sur la propriété UserData d'un objet de la poignée
  • Utiliser des fonctions imbriquées dans une fonction principale; essentiellement émule des variables de scopage "globalement".
  • Passer les données entre les deux sous-fonctionnements

La structure de mon code n'est pas la plus jolie. En ce moment, j'ai le moteur séparé de l'avant (bon!) Mais le code de l'interface graphique est assez spaghetti. Voici un squelette d'une "activité", d'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é, alors j'ai fini par passer h bilan et versions partout (puisque les choses devaient être redessinées, mises à jour, etc.) et setappdata(fig) pour stocker les données réelles . En tout cas, j'ai gardé une "activité" dans un seul fichier et je suis sûr que cela va être un cauchemar de maintenance à l'avenir. Les rappels interagissent avec des données d'application et des objets de la poignée graphique, que je suppose que je suppose que c'est nécessaire, mais cela empêche une ségrégation complète des deux "moitiés" de la base de code.

Je suis donc à la recherche d'une aide de conception organisationnelle/interface graphique ici. À savoir:

  • Existe-t-il une structure de répertoire que je devrais utiliser pour organiser? (Callbacks vs dessin fonctions?)
  • Quelle est la "bonne façon" d'interagir avec les données de l'interface graphique et de le conserver séparé des données d'application? (Lorsque je me réfère à des données d'interface graphique, je veux dire set/get Ting Propriétés des objets de la poignée).
  • Comment éviter de mettre toutes ces fonctions de dessin dans un fichier géant de milliers de lignes et transmettez-vous toujours efficacement les données d'application et d'interface graphique? Est-ce possible?
  • Y a-t-il une pénalité de performance associée à l'utilisation constante set/getappdata?
  • Y a-t-il une structure mon code arrière (3 classes d'objets et une bande de fonctions d'assistance) devraient-ils prendre pour faciliter la maintenance d'une perspective d'une interface graphique?

Je ne suis pas un ingénieur logiciel de commerce, je sais juste assez pour être dangereux, alors je suis sûr que ce sont des questions relativement fondamentales pour les développeurs d'interface graphique chevronnée (dans n'importe quelle langue). J'ai presque l'impression que l'absence d'une norme de conception de l'interface graphique à Matlab (n'existe-t-elle-t-elle?) Est sérieusement interférant avec ma capacité à compléter ce projet. C'est un projet MATLAB qui est beaucoup plus massif que tout ce que j'ai jamais entrepris, et je n'ai jamais eu à donner beaucoup de pensée à des UIS compliquées avec plusieurs fenêtres figurant, etc., avant.

27
Dang Khoa

Je ne suis pas d'accord que Matlab n'est pas bon pour la mise en œuvre (même complexe) Guis - c'est parfaitement bien.

Cependant, ce qui est vrai est que:

  1. Il n'y a pas d'exemples dans la documentation MATLAB de la manière de mettre en œuvre ou d'organiser une application d'interface graphique complexe
  2. Tous les exemples de documentation de Simple Guis utilisent des modèles qui ne font pas partie du tout à des Guis complexes
  3. En particulier, le guide (l'outil intégré pour la génération automatique de code d'interface graphique) génère un nouveau code 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 qu'à des gites de matlab très simples ou très horribles, et ils finissent par penser que Matlab ne convient pas à la fabrication de Guisse.

Dans mon expérience, la meilleure façon de mettre en œuvre une interface graphique complexe à Matlab est la même chose que possible dans une autre langue - suivez un modèle bien utilisé tel que MVC (contrôleur de vue de modèle).

Cependant, il s'agit d'un modèle orienté objet, vous devrez donc d'abord vous mettre à l'aise avec une programmation orientée objet dans Matlab, et en particulier avec l'utilisation d'événements. L'utilisation d'une organisation orientée objet pour votre demande devrait signifier que toutes les techniques méchantes que vous mentionnez (setappdata, guidata, UserData, cadre de fonction imbriquée et en passant par la suite. Les copies de données) ne sont pas nécessaires, car toutes les activités pertinentes sont disponibles en tant que propriétés de classe.

Le meilleur exemple que je connaisse que Mathworks a publié est dans cet article de Matlab Digest. Même cet exemple est très simple, mais cela vous donne une idée de la façon de partir et si vous regardez dans le modèle MVC, il devrait devenir clair comment l'étendre.

De plus, j'utilise généralement une utilisation intensive de dossiers d'emballage pour organiser de grandes échelles de code dans Matlab, afin de vous assurer qu'il n'y a aucun problème de nom.

Un dernier conseil - Utilisez la Boîte à outils de présentation GUI , de Matlab Central. Cela facilite beaucoup les aspects du développement de l'interface graphique, en particulier la mise en œuvre d'un comportement automatique de redimensionnement automatique et vous donne plusieurs éléments d'interface utilisateur supplémentaires à utiliser.

J'espère que cela pourra aider!


EDIT: Dans MATLAB R2016A Mathworks introduit 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 bâtiment d'interface graphique de Matlab de plusieurs manières (la plus profondément, les fenêtres de figure sous-jacente générée sont basées sur une toile HTML et JavaScript plutôt que Java). C'est une autre étape le long d'une route initiée par l'introduction de la poignée graphique 2 dans R2014B et évoluera sans doute plus loin sur les versions futures.

Mais un impact de l'AppDesigner sur la question posée est qu'il génère beaucoup meilleur code que guide a fait - c'est assez propre, orienté objet et approprié pour former la base d'un motif MVC.

7
Sam Roberts

Je suis très inconfortable avec le guide de la manière dont le guide produit des fonctions. (Pensez aux cas où vous souhaitez appeler une interface graphique d'une autre)

Je vous suggère fortement d'écrire votre code objet orienté à l'aide de classes de la poignée. De cette façon, vous pouvez faire des trucs fantaisistes (par exemple this ) et ne pas vous perdre. Pour organiser le code, vous avez le + et @ répertoires.

2
bdecaf

Je ne pense pas que la structuration de GUI-CODE est fondamentalement différente du code non-GUI.

Mettez des choses qui appartiennent ensemble à un endroit. Comme des fonctions d'assistance qui pourraient entrer dans un util ou helpers _ répertoire. Selon le contenu, procédez peut-être à un colis.


Personnellement, je n'aime pas la philosophie "One Fonction One M-Fichier" Certaines personnes Matlab ont. 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é, il n'a tout simplement aucun sens, quand il n'y a pas de scénario que vous appelleriez cela de quelque part d'ailleurs alors de votre propre interface graphique.


Vous pouvez toutefois construire l'interface graphique elle-même de manière modulaire, par ex. Créer 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, j'ai commencé à utiliser lorsque mon application est devenue de plus en plus grande:

Utilisez les auditeurs sur les rappels, ils peuvent simplifier considérablement la programmation de l'interface graphique.

Si vous avez des données vraiment importantes (telles que partir d'une base de données, etc.), il peut être intéressant de mettre en vaut la mise en œuvre d'une classe de la poignée qui détient ces données. Stocker cette poignée quelque part dans le Guidata/Appdata améliore considérablement la performance GET/SETAppData.

Edit :

Les auditeurs sur Callbacks:

Un pushbutton est mauvais exemple. Appuyez habituellement un bouton uniquement sur certaines mesures, les rappels sont bien ihoo. Un avantage principal dans mon cas par exemple Était-ce que des listes de texte/popup modifiées par programmation ne déclenchent pas de rappel, tandis que les auditeurs sur leur String ou Value sont déclenchés.

Un autre exemple:

S'il y a une propriété centrale (par exemple, comme une source d'INPUTDATA) que plusieurs composants de l'application dépendent, l'utilisation d'auditeurs est très pratique pour assurer que tous les composants sont notifiés si la propriété change. Chaque nouveau composant "intéressé" dans cette propriété peut simplement ajouter son propre auditeur, il n'est donc pas nécessaire de modifier au centre de la modification du rappel. Cela permet une conception beaucoup plus modulaire des composants d'interface graphique et facilite l'ajout/suppression de tels composants.

1
sebastian