Esiste una sorta di strategia sistematica per la progettazione e l'implementazione di GUI?

18

Sto utilizzando Visual Studio per creare un'applicazione GUI in C #. La Casella degli strumenti funge da una tavolozza di componenti nifty che mi permette di trascinare facilmente pulsanti e altri elementi (per chiarezza dirò il pulsante ogni volta che intendo "controllo") sul mio modulo, il che rende le forme statiche abbastanza facili da fare. Tuttavia, ho incontrato due problemi:

  • Creare i pulsanti in primo luogo è molto lavoro. Quando ho un modulo che non è statico (cioè pulsanti o altri controlli vengono creati in fase di esecuzione in base a ciò che l'utente fa) non posso usa la palette a tutti. Devo invece creare manualmente ciascun pulsante, chiamando il costruttore nel metodo che sto utilizzando, quindi inizializzarlo manualmente specificando altezza, larghezza, posizione, etichetta, gestore di eventi e così via. Questo è estremamente noioso perché devo indovinare tutti questi parametri cosmetici senza essere in grado di vedere come sarà la forma, e genera anche molte linee di codice ripetitivo per ciascun pulsante.
  • Anche fare i pulsanti è un compito molto impegnativo. Affrontare gli eventi in un'applicazione completa è un enorme problema. L'unico modo che so come fare è selezionare un pulsante, andare alla scheda eventi nelle sue proprietà, fare clic sull'evento OnClick in modo che generi l'evento nel codice Form , quindi compilare il corpo dell'evento. Dal momento che voglio separare la logica e la presentazione, tutti i miei gestori di eventi finiscono per essere chiamate su una singola linea alla funzione di business logic appropriata. Ma usando questo per molti pulsanti (ad esempio, immagina il numero di pulsanti presenti in un'applicazione come MS Word) inquina il codice del mio Form con dozzine di metodi boilerplate event handler ed è difficile mantenerlo.

Per questo motivo, qualsiasi programma GUI più complicato di Hello World è molto poco pratico da realizzare per me. Per essere chiari, non ho alcun problema con la complessità dei programmi che scrivo che hanno un'interfaccia utente minima - mi sento in grado di utilizzare OOP con un discreto livello di competenza per strutturare in modo ordinato il mio codice di logica aziendale. Ma quando si sviluppa la GUI, sono bloccato. Sembra così noioso che mi sento come se stessi reinventando la ruota, e c'è un libro da qualche parte là fuori che spiega come fare GUI correttamente che non ho letto.

Mi manca qualcosa? Oppure tutti gli sviluppatori di C # accettano solo gli elenchi infiniti di gestori di eventi ripetitivi e codice di creazione di pulsanti?

Come suggerimento (spero utile), mi aspetto che una buona risposta parlerà di:

  • Uso delle tecniche OOP (come il modello predefinito) per semplificare la creazione ripetuta di pulsanti
  • Combinare molti gestori di eventi in un unico metodo che controlla Sender per capire quale pulsante lo ha chiamato e si comporta di conseguenza
  • XAML e utilizzo di WPF anziché di Windows Form

Non avere per menzionare nessuno di questi, naturalmente. È solo la mia ipotesi migliore su quale tipo di risposta sto cercando.

    
posta Superbest 13.01.2015 - 18:48
fonte

2 risposte

22

Ci sono diverse metodologie che si sono evolute nel corso degli anni per affrontare questi problemi che hai menzionato, che sono, sono d'accordo, i due problemi principali che i framework UI hanno dovuto affrontare negli ultimi anni. Provenendo da uno sfondo WPF, questi vengono affrontati come segue:

Progettazione dichiarativa, piuttosto che imperativa

Quando descrivi scrupolosamente la scrittura del codice per istanziare i controlli e impostare le loro proprietà, stai descrivendo il modello imperativo della progettazione dell'interfaccia utente. Anche con il progettista WinForms, stai semplicemente utilizzando un wrapper su quel modello: apri il file Form1.Designer.cs e vedi tutto quel codice seduto lì.

Con WPF e XAML - e modelli simili in altri framework, ovviamente, dall'HTML in poi - ci si aspetta che descriva il tuo layout e lasci che il framework faccia il grosso sforzo per implementarlo. Puoi usare controlli più intelligenti come Panels (Grid o WrapPanel, ecc.) Per descrivere la relazione tra gli elementi dell'interfaccia utente, ma l'aspettativa è che non li posizioni manualmente. Concetti flessibili come controlli ripetibili, come ASP.NET's Repeater o WPF's ItemsControl, aiutano a creare un'interfaccia utente scalabile in modo dinamico senza scrivere codice ripetuto, consentendo di rappresentare dinamicamente le entità di dati dinamicamente come controlli.

I DataTemplates di WPF ti consentono di definire, ancora una volta, in modo dichiarativo, piccole quantità di bontà dell'interfaccia utente e associarle ai tuoi dati; ad esempio, un elenco di oggetti dati cliente potrebbe essere associato a un oggetto ItemsControl e un modello di dati diverso richiamato a seconda che si tratti di un dipendente normale (utilizzare un modello di riga griglia standard con nome e indirizzo) o un cliente principale, mentre un modello diverso , con una foto e pulsanti di facile accesso sono visualizzati. Anche in questo caso, senza scrivere codice nella finestra specifica , i controlli più intelligenti sono a conoscenza del loro contesto di dati e quindi consentono semplicemente di associarli ai dati e consentire loro di eseguire operazioni pertinenti.

Associazione dati e separazione dei comandi

Toccando sull'associazione dati, il databinding di WPF (e, ancora, anche in altri framework, come AngularJS) consente di dichiarare il tuo intento collegando un controllo (ad esempio una casella di testo) con un'entità dati (ad esempio, il nome del cliente ) e lasciare che la struttura gestisca l'impianto idraulico. La logica è gestita allo stesso modo. Invece di collegare manualmente i gestori di eventi code-behind alla logica di business basata su controller, si utilizza il meccanismo Data Binding per collegare il comportamento di un controller (ad esempio la proprietà Command di un pulsante) a un oggetto Command che rappresenta il nugget dell'attività. / p>

Consente a questo comando di essere condiviso tra le finestre senza dover riscrivere i gestori di eventi ogni volta.

Livello di astrazione superiore

Entrambe queste soluzioni ai tuoi due problemi rappresentano un passaggio a un livello più alto di astrazione rispetto al paradigma event-driven di Windows Form che tu trovi giustamente noioso.

L'idea è che non si desidera definire, in codice, ogni singola proprietà del controllo e ogni singolo comportamento a partire dal clic del tasto e in poi. Volete che i vostri controlli e il framework sottostante facciano più lavoro per voi e permettiate di pensare in concetti più astratti di Data Binding (che esiste in WinForms, ma non è neanche lontanamente utile come in WPF) e il pattern Command per definire i collegamenti tra l'interfaccia utente e comportamenti che non richiedono scendere sul metal.

Il modello MVVM è l'approccio di Microsoft a questo paradigma. Vi suggerisco di leggerlo.

Questo è un esempio approssimativo di come apparirà questo modello e di come risparmierebbe tempo e linee di codice. Questo non verrà compilato, ma è pseudo-WPF. :)

Da qualche parte nelle risorse dell'applicazione, definite Modelli di dati:

<DataTemplate x:DataType="Customer">
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

<DataTemplate x:DataType="PrimeCustomer">
   <Image Source="GoldStar.png"/>
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

Ora colleghi la tua schermata principale a un ViewModel, una classe che espone i tuoi dati. Supponiamo, un insieme di Clienti (semplicemente un List<Customer> ) e un oggetto Comando (ancora una semplice proprietà pubblica di tipo ICommand ). Questo link consente l'associazione:

public class CustomersnViewModel
{
     public List<Customer> Customers {get;}
     public ICommand RefreshCustomerListCommand {get;}  
}

e l'interfaccia utente:

<ListBox ItemsSource="{Binding Customers}"/>
<Button Command="{Binding RefreshCustomerListCommand}">Refresh</Button>

E questo è tutto. La sintassi del ListBox catturerà l'elenco dei clienti da ViewModel e tenterà di renderli all'interfaccia utente. A causa dei due DataTemplate definiti in precedenza, verrà importato il modello pertinente (basato sul DataType, supposto che PrimeCustomer erediti dal Cliente) e lo inserisca come contenuto del ListBox. Nessun ciclo, nessuna generazione dinamica di controlli tramite codice.

Il pulsante, analogamente, ha una sintassi preesistente per collegare il suo comportamento a un'implementazione ICommand, che presumibilmente sa di aggiornare la proprietà Customers - richiamando il framework di associazione dei dati per aggiornare nuovamente l'interfaccia utente.

Ho preso alcune scorciatoie qui, ovviamente, ma questo è il succo di ciò.

    
risposta data 13.01.2015 - 21:34
fonte
5

Per prima cosa, raccomando questa serie di articoli: link - non affronta i problemi di dettaglio con il tuo codice pulsante, ma ti offre una strategia sistematica per la progettazione e l'implementazione del tuo framework applicativo, specialmente per le applicazioni GUI, che mostra come applicare MVC, MVP, MVVM alla tua tipica applicazione Forms.

Affrontare la tua domanda su "trattare gli eventi": se hai un sacco di moduli con pulsanti che funzionano in modo simile, probabilmente pagherà per implementare l'assegnazione del gestore di eventi nel tuo "quadro applicativo" da solo. Anziché assegnare gli eventi di clic utilizzando la finestra di progettazione della GUI, creare una convenzione di denominazione su come un gestore di "clic" deve essere denominato per un pulsante di un nome specifico. Ad esempio: per pulsante MyNiftyButton_ClickHandler per il pulsante MyNiftyButton . Quindi non è davvero difficile scrivere una routine riutilizzabile (utilizzando la reflection) che itera su tutti i pulsanti di un modulo, verificare se il modulo contiene un gestore di clic correlato e assegnare automaticamente il gestore all'evento. Vedi qui per un esempio.

E se vuoi solo utilizzare un solo gestore di eventi per un gruppo di pulsanti, sarà anche possibile. Poiché ogni gestore eventi riceve il mittente e il mittente è sempre il pulsante premuto, puoi inviare il codice aziendale utilizzando la proprietà "Nome" di ciascun pulsante.

Per la creazione dinamica di pulsanti (o altri controlli): puoi creare pulsanti o altri controlli con il designer su una forma inutilizzata solo allo scopo di essere un modello . Quindi, usi la riflessione (vedi qui per un esempio ) per clonare il controllo del modello e modificare solo le proprietà come posizione o dimensione. Probabilmente sarà molto più semplice che inizializzare manualmente due o tre dozzine di proprietà nel codice.

Ho scritto questo sopra avendo in mente Winform (proprio come te, credo), ma i principi generali dovrebbero applicarsi ad altri ambienti GUI come WPF con capacità simili.

    
risposta data 13.01.2015 - 20:48
fonte

Leggi altre domande sui tag