Gibt es eine systematische Strategie für das Entwerfen und Implementieren von GUIs?

18

Ich verwende Visual Studio, um eine GUI-Anwendung in C # zu erstellen. Die Toolbox dient als raffinierte Komponentenpalette, mit der ich Schaltflächen und andere Elemente (aus Gründen der Übersichtlichkeit sage ich "Schaltfläche", wenn ich "Steuerelement" meine) ganz einfach per Drag & Drop auf mein Formular ziehen kann. Dadurch ist das Erstellen statischer Formulare recht einfach. Ich habe jedoch zwei Probleme:

  • Das Erstellen der Schaltflächen ist an erster Stelle sehr aufwändig. Wenn ich ein Formular habe, das nicht statisch ist (dh Schaltflächen oder andere Steuerelemente werden zur Laufzeit gemäß den vom Benutzer ausgeführten Aktionen erstellt), kann ich die Palette überhaupt nicht verwenden. Stattdessen muss ich jede Schaltfläche manuell erstellen, indem ich den Konstruktor in der von mir verwendeten Methode aufrufe und dann manuell initialisiere, indem ich die Höhe, Breite, Position, Beschriftung, Ereignisbehandlungsroutine usw. der Schaltfläche festlege. Dies ist äußerst mühsam, da ich all diese kosmetischen Parameter erraten muss, ohne zu sehen, wie das Formular aussehen wird, und außerdem für jede Schaltfläche viele sich wiederholende Codezeilen generiert.
  • Es ist auch eine Menge Arbeit, die Tasten dazu zu bringen, etwas zu tun. Der Umgang mit Ereignissen in einer voll ausgestatteten Anwendung ist ein großer Schmerz. Die einzige Möglichkeit, wie ich dies tun kann, besteht darin, eine Schaltfläche auszuwählen, die Registerkarte "Ereignisse" in ihren Eigenschaften aufzurufen, auf das Ereignis zu klicken OnClick, damit es das Ereignis im FormCode des Ereignisses generiert, und dann den Text des Ereignisses auszufüllen. Da ich Logik und Präsentation trennen möchte, werden alle meine Event-Handler als einzeilige Aufrufe an die entsprechende Geschäftslogikfunktion ausgeführt. Wenn Sie dies jedoch für viele Schaltflächen verwenden (stellen Sie sich beispielsweise die Anzahl der in einer Anwendung wie MS Word vorhandenen Schaltflächen vor), wird der Code von my Formmit Dutzenden von Boilerplate-Event-Handler-Methoden verschmutzt , und es ist schwierig, dies beizubehalten.

Aus diesem Grund ist es sehr unpraktisch, ein GUI-Programm, das komplizierter als Hello World ist, für mich zu erstellen. Um es klar auszudrücken, ich habe keinerlei Probleme mit der Komplexität von Programmen, die ich schreibe und die nur über eine minimale Benutzeroberfläche verfügen. Ich habe das Gefühl, dass ich OOP mit einem angemessenen Maß an Kompetenz verwenden kann, um meinen Geschäftslogikcode ordentlich zu strukturieren. Aber wenn ich die GUI entwickle, stecke ich fest. Es ist so langweilig, dass ich das Rad neu erfinden möchte, und irgendwo gibt es ein Buch, in dem erklärt wird, wie man GUI richtig macht, das ich nicht gelesen habe.

Vermisse ich etwas? Oder akzeptieren alle C # -Entwickler nur die endlosen Listen von Handlern für sich wiederholende Ereignisse und den Code zum Erstellen von Schaltflächen?


Als (hoffentlich hilfreichen) Hinweis erwarte ich, dass eine gute Antwort über Folgendes spricht:

  • Verwenden von OOP-Techniken (z. B. das Factory-Muster), um die wiederholte Erstellung von Schaltflächen zu vereinfachen
  • Kombinieren Sie viele Ereignishandler zu einer einzigen Methode, die prüft Sender, welche Schaltfläche sie aufgerufen hat, und sich entsprechend verhält
  • XAML und Verwenden von WPF anstelle von Windows Forms

Sie müssen nicht haben , um eine dieser zu erwähnen, natürlich. Es ist nur meine beste Vermutung, welche Art von Antwort ich suche.

Super
quelle
Wie ich es sehe, wäre ja Factory ein gutes Muster, aber ich sehe viel mehr ein Model-View-Controller-Muster mit Befehlen, die an Steuerelemente gebunden sind, anstelle von Ereignissen direkt. WPF ist normalerweise MVVM, aber MVC ist gut möglich. Wir haben derzeit eine riesige Software in WPF mit MVC bei 90%. Alle Befehle werden an den Controller weitergeleitet und er kümmert sich um alles
Franck
Können Sie mit einem GUI-Editor nicht das dynamische Formular mit allen möglichen Schaltflächen erstellen und nur Dinge, die Sie nicht benötigen, dynamisch unsichtbar machen? Klingt nach viel weniger Arbeit.
Hans-Peter Störr
@hstoerr Aber es wäre schwierig, das Layout zum Laufen zu bringen. Viele Tasten würden sich überlappen.
Super

Antworten:

22

Es gibt verschiedene Methoden, die im Laufe der Jahre entwickelt wurden, um diese von Ihnen genannten Probleme zu lösen. Ich stimme zu, dass dies die beiden Hauptprobleme sind, mit denen sich UI-Frameworks in den letzten Jahren befassen mussten. Ausgehend von einem WPF-Hintergrund werden diese folgendermaßen angegangen:

Deklaratives Design statt Imperativ

Wenn Sie das sorgfältige Schreiben von Code zum Instanziieren von Steuerelementen und zum Festlegen ihrer Eigenschaften beschreiben, beschreiben Sie das zwingende Modell des Benutzeroberflächenentwurfs. Selbst mit dem WinForms-Designer verwenden Sie nur einen Wrapper über diesem Modell. Öffnen Sie die Datei Form1.Designer.cs, und Sie sehen den gesamten Code dort.

Mit WPF und XAML - und ähnlichen Modellen in anderen Frameworks, natürlich ab HTML - müssen Sie Ihr Layout beschreiben und das Framework die schwere Aufgabe übernehmen lassen, es zu implementieren. Sie verwenden intelligentere Steuerelemente wie Panels (Grid oder WrapPanel usw.), um die Beziehung zwischen UI-Elementen zu beschreiben. Es wird jedoch erwartet, dass Sie sie nicht manuell positionieren. Flexible Konzepte wie wiederholbare Steuerelemente wie ASP.NET Repeater oder WPF ItemsControl unterstützen Sie bei der Erstellung einer dynamisch skalierbaren Benutzeroberfläche, ohne wiederholten Code schreiben zu müssen. Auf diese Weise können dynamisch wachsende Datenelemente dynamisch als Steuerelemente dargestellt werden.

Mit den DataTemplates von WPF können Sie wieder deklarativ kleine Nuggets von UI-Güte definieren und sie Ihren Daten zuordnen. Beispielsweise kann eine Liste von Kundendatenobjekten an ein ItemsControl gebunden und eine andere Datenvorlage aufgerufen werden, je nachdem, ob es sich um einen regulären Mitarbeiter handelt (verwenden Sie eine Standardgitterzeilenvorlage mit Name und Adresse) oder um einen Hauptkunden, während eine andere Vorlage verwendet wird , mit einem Bild und leicht zugänglichen Schaltflächen angezeigt. Auch hier müssen Sie keinen Code in das jeweilige Fenster schreiben , sondern müssen nur intelligentere Steuerelemente verwenden, die den Datenkontext kennen. Auf diese Weise können Sie sie einfach an Ihre Daten binden und sie relevante Vorgänge ausführen lassen.

Datenbindung und Befehlstrennung

Wenn Sie sich mit der Datenbindung befassen, können Sie mit der Datenbindung von WPF (und auch in anderen Frameworks wie AngularJS) Ihre Absicht angeben, indem Sie ein Steuerelement (z. B. ein Textfeld) mit einer Datenentität (z. B. dem Namen des Kunden) verknüpfen und die Daten eingeben Der Rahmen übernimmt die Installation. Die Logik wird ähnlich behandelt. Anstatt Code-Behind-Event-Handler manuell mit Controller-basierter Geschäftslogik zu verbinden, verwenden Sie den Datenbindungsmechanismus, um das Verhalten eines Controllers (z. B. die Command-Eigenschaft einer Schaltfläche) mit einem Command-Objekt zu verknüpfen, das das Nugget der Aktivität darstellt.

Dieser Befehl kann von Windows gemeinsam genutzt werden, ohne dass die Ereignishandler jedes Mal neu geschrieben werden müssen.

Höhere Abstraktionsebene

Diese beiden Lösungen für Ihre beiden Probleme stellen eine Verschiebung auf eine höhere Abstraktionsebene dar als das ereignisgesteuerte Paradigma von Windows Forms, das Sie zu Recht als ermüdend empfinden.

Die Idee ist, dass Sie nicht jede einzelne Eigenschaft, die das Steuerelement besitzt, und jedes einzelne Verhalten ab dem Klicken auf die Schaltfläche im Code definieren möchten. Sie möchten, dass Ihre Steuerelemente und das zugrunde liegende Framework mehr Arbeit für Sie leisten und Sie abstraktere Konzepte der Datenbindung (die in WinForms vorhanden ist, aber bei weitem nicht so nützlich wie in WPF ist) und des Befehlsmusters zum Definieren von Verknüpfungen zwischen der Benutzeroberfläche berücksichtigen können und Verhaltensweisen, die es nicht erfordern, auf das Metall zu kommen.

Das MVVM- Muster ist Microsofts Ansatz für dieses Paradigma. Ich schlage vor, darüber nachzulesen.

Dies ist ein grobes Beispiel dafür, wie dieses Modell aussehen würde und wie Sie Zeit und Codezeilen sparen würden. Dies wird nicht kompiliert, aber es ist Pseudo-WPF. :)

Irgendwo in Ihren Anwendungsressourcen definieren Sie Datenvorlagen:

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

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

Nun verknüpfen Sie Ihren Hauptbildschirm mit einem ViewModel, einer Klasse, die Ihre Daten verfügbar macht. Angenommen, eine Auflistung von Customers (einfach a List<Customer>) und ein Command-Objekt (wieder eine einfache öffentliche Eigenschaft vom Typ ICommand). Über diesen Link können Sie Folgendes binden:

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

und die Benutzeroberfläche:

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

Und das ist es. Die Syntax der ListBox nimmt die Liste der Kunden aus dem ViewModel auf und versucht, sie auf der Benutzeroberfläche wiederzugeben. Aufgrund der beiden zuvor definierten DataTemplates wird die relevante Vorlage (basierend auf dem DataType, sofern PrimeCustomer vom Kunden erbt) importiert und als Inhalt der ListBox abgelegt. Keine Schleife, keine dynamische Erzeugung von Steuerelementen über Code.

In ähnlicher Weise verfügt der Button über eine bereits vorhandene Syntax, um sein Verhalten mit einer ICommand-Implementierung zu verknüpfen, die vermutlich die CustomersEigenschaft aktualisieren muss. Dadurch wird das Datenbindungsframework automatisch aufgefordert, die Benutzeroberfläche erneut zu aktualisieren.

Ich habe hier natürlich einige Abkürzungen vorgenommen, aber das ist das Wesentliche.

Avner Shahar-Kashtan
quelle
5

Zuerst empfehle ich diese Artikelserie: http://codebetter.com/jeremymiller/2007/07/26/the-build-your-own-cab-series-table-of-contents/ - es geht nicht auf die Detailprobleme ein Mit Ihrem Schaltflächencode erhalten Sie jedoch eine systematische Strategie zum Entwerfen und Implementieren Ihres eigenen Anwendungsframeworks, insbesondere für GUI-Anwendungen. Sie erfahren, wie Sie MVC, MVP, MVVM auf Ihre typische Forms-Anwendung anwenden.

Beantworten Sie Ihre Frage zum Thema "Umgang mit Ereignissen": Wenn viele Formulare mit Schaltflächen auf ähnliche Weise funktionieren, zahlt es sich wahrscheinlich aus, die Event-Handler-Zuweisung in Ihrem "Anwendungsframework" selbst zu implementieren. Anstatt die Klickereignisse mit dem GUI-Designer zuzuweisen, erstellen Sie eine Namenskonvention, wie ein "Klick" -Handler für eine Schaltfläche mit einem bestimmten Namen benannt werden muss. Zum Beispiel - für die Schaltfläche MyNiftyButton_ClickHandlerfür die Schaltfläche MyNiftyButton. Dann ist es nicht wirklich schwierig, eine wiederverwendbare Routine (unter Verwendung von Reflektion) zu schreiben, die über alle Schaltflächen eines Formulars iteriert, prüft, ob das Formular einen zugehörigen Klick-Handler enthält, und weist den Handler dem Ereignis automatisch zu. Sehen Sie hier ein Beispiel.

Und wenn Sie nur einen Ereignishandler für eine Reihe von Schaltflächen verwenden möchten, ist dies ebenfalls möglich. Da für jeden Ereignishandler der Absender übergeben wird und der Absender immer die Taste ist, die gedrückt wurde, können Sie den Geschäftscode über die Eigenschaft "Name" jeder Taste senden.

Für die dynamische Erstellung von Schaltflächen (oder anderen Steuerelementen): Sie können Schaltflächen oder andere Steuerelemente mit dem Designer in einem nicht verwendeten Formular erstellen, nur um eine Vorlage zu erstellen . Anschließend verwenden Sie Reflection ( ein Beispiel finden Sie hier ), um das Vorlagensteuerelement zu klonen und nur die Eigenschaften wie Position oder Größe zu ändern. Das ist wahrscheinlich viel einfacher, als zwei oder drei Dutzend Eigenschaften manuell im Code zu initialisieren.

Ich habe dies oben unter Berücksichtigung von Winforms geschrieben (genau wie Sie, denke ich), aber die allgemeinen Prinzipien sollten für andere GUI-Umgebungen wie WPF mit ähnlichen Fähigkeiten gelten.

Doc Brown
quelle
In der Tat in WPF möglich und es ist noch viel einfacher. Sie füllen einfach eine Liste von Befehlen (vorcodierte spezifische Funktionen) und legen die Quelle in Ihrem Fenster fest. Sie erstellen eine schöne einfache visuelle Vorlage und müssen lediglich eine Sammlung mit den gewünschten Befehlen füllen. Die großartige WPF-Bindung erledigt den Rest für Sie visuell.
Franck,