Artikel

Dezember 2003 | Artikel

Formulare nach Maß

(Link zum Artikel: http://www.it-republik.de/dotnet/artikel/0487)

Hübsche Schaltflächen und andere Steuerelemente in .NET

Text: von Christian Wenz
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Die Standard-UI-Elemente sind out, Eigenkreationen sind in! Mit einem kleinen Trick können auch in .NET-Anwendungen die vorhandenen Steuerelemente mit einem Neuanstrich versehen werden. Dazu müssen nicht immer komplett neue Elemente erstellt werden, sondern man kann auf Bestehendem aufsetzen.

Der wichtigste Kniff, der in diesem Artikel eingesetzt wird, lässt sich mit einem Stichwort zusammenfassen: selbstzeichnend. Die Steuerelemente, die erstellt werden, sind einfache Ableitungen von UserControl. Das Ereignis Paint wird abgefangen und durch eine eigene Zeichenmethode ersetzt. Das Control zeichnet sich also im wahrsten Sinne des Wortes selbst.

Projekt 1: Schaltfläche
Genug der Vorrede, es geht direkt an die Implementierung. Erstellen Sie zunächst ein neues Projekt in Visual Studio .NET (egal ob 2002 oder 2003). Als Projekttyp wird Windows-Steuerelementbibliothek verwendet. Als Programmiersprache kam diesmal C# zum Zuge (wie üblich als Folge eines Münzwurfs). Mit Visual Basic .NET lässt sich das Beispiel selbstredend analog realisieren.

Als erstes sollte die standardmäßig angelegte Klasse umbenannt werden: UserControl1 ist wahrlich kein selbsterklärender Name. Stattdessen verwenden wir SelbstzeichnendButton. Die Umbenennung können Sie in der Klassenansicht des Projekts vornehmen.

Dann kommen wir aber auch schon zum Hauptpunkt der Anwendung: Das Ereignis Paint muss abgefangen werden. Dazu wird die Methode OnPaint entweder direkt überschrieben oder eine neue Behandlungsmethode für das Paint-Ereignis angelegt. Der zweite Weg wird im Folgenden umgesetzt. Wählen Sie dazu das Control in Visual Studio .NET aus, schalten Sie im Eigenschaftenfenster auf die Ereignis-Ansicht und klicken Sie doppelt auf das leere, weiße Feld neben Paint. Visual Studio springt automatisch in die Code-Ansicht und hat eine entsprechende Methode angelegt:
  1. private void SelbstzeichnendButton_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
  2. {
  3. }
Für diejenigen, die nicht mit Visual Studio .NET arbeiten: Im Hintergrund hat Visual Studio lediglich eine weitere Anweisung erzeugt, mit der die Prozedur dem Ereignis zugeordnet wird:
  1. this.Paint += new System.Windows.Forms.PaintEventHandler(this.UserControl1_Paint);
Aber zurück zur Implementierung: Zunächst einmal wird die Schaltfläche gezeichnet. Die Maße können aus den Eigenschaften Width und Height ausgelesen werden:
  1. int breite = this.Width;
  2. int hoehe = this.Height;
Das normale Layout - das klassische Rechteck - ist eher langweilig, stattdessen wollen wir ein Rechteck mit abgerundeten Ecken erstellen. Dieses wird normalerweise mit vier Linien sowie vier Viertelkreisen nachgebaut. Wir wollen es aber als Pfad realisieren und machen es uns besonders einfach und beschränken uns nur auf vier Linien; den Rest ergänzt .NET automatisch:
  1. System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
  2. gp.AddLine(5, 0, breite - 6, 0);
  3. gp.AddLine(breite - 1, 5, breite - 1, hoehe - 6);
  4. gp.AddLine(breite - 6, hoehe - 1, 5, hoehe - 1);
  5. gp.AddLine(0, hoehe - 6, 0, 5);
  6. gp.CloseFigure();
Das Ganze wird dann mit einer Farbe gefüllt. Wir haben uns für Orange entschieden:
  1. e.Graphics.FillPath(
  2. new SolidBrush(Color.Orange),
  3. gp
  4. );
Die Schaltfläche soll zwei Zustände bekommen; der jeweilige Zustand wird in einer globalen Variablen außerhalb von SelbstzeichnendButton_Paint() abgelegt:
  1. public bool aktiv = false;
Innerhalb von SelbstzeichnendButton_Paint() wird der Variablenzustand dann abgefragt. Zunächst einmal soll die Schaltfläche schwarz umrandet werden. Der Pfad liegt bereits vor, er muss nur noch mit einem schwarzen Stift gezeichnet werden:
  1. if (aktiv)
  2. {
  3. e.Graphics.DrawPath(
  4. new Pen(Color.Black, 3),
  5. gp
  6. );
  7. }
Außerdem wird, in Abhängigkeit vom Status, ein Text auf der Schaltfläche ausgegeben:
  1. string text = (aktiv) ? "ein" : "aus";
Die Breite und Höhe des Texts werden ermittelt und dieser dann zentriert ausgegeben:
  1. Font f = new Font("Verdana", 16);
  2. float x = (breite - e.Graphics.MeasureString(text, f).Width) / 2;
  3. float y = (hoehe - e.Graphics.MeasureString(text, f).Height) / 2;
  4. e.Graphics.DrawString(text, f, Brushes.Wheat, x - 1, y - 1);
Das war es auch schon, zumindest in Hinblick auf das Zeichnen der Schaltfläche. Wie Sie gesehen haben, hält sich der Aufwand in Grenzen. Was bleibt, sind kleinere Aufräumarbeiten. Zunächst einmal sollten Sie den Zustand wechseln, wenn ein Mausklick auf die Schaltfläche erfolgt. Dazu klicken Sie im Eigenschafteninspektor doppelt auf den leeren Bereich neben Click und legen folgende Methode an:
  1. private void SelbstzeichnendButton_Click(object sender, System.EventArgs e)
  2. {
  3. aktiv = !aktiv;
  4. this.Invalidate();
  5. }
Der Aufruf von this.Invalidate() führt dazu, dass die Schaltfläche sofort neu gezeichnet wird und Sie die Änderung sehen können. Abschließend legen Sie noch für die Ereignisse MouseEnter und MouseLeave Methoden an, die den Mauszeiger verändern - das Handsymbol weist darauf hin, dass die Schaltfläche auch angeklickt werden kann:
  1. private void SelbstzeichnendButton_MouseEnter(object sender, System.EventArgs e)
  2. {
  3. this.Cursor = Cursors.Hand;
  4. }
  5. private void SelbstzeichnendButton_MouseLeave(object sender, System.EventArgs e)
  6. {
  7. this.Cursor = Cursors.Default;
  8. }
Projekt 2: Der Test
Das ganze soll jetzt natürlich auch sofort in einem Visual Studio .NET-Projekt getestet werden. Fügen Sie also in der Projektmappe eine neue Windows-Applikation hinzu, machen Sie diese zum Standardprojekt und ziehen Sie aus der Toolbox das gerade erstellte Control auf das Formular (siehe Abb. 2). Wie Ihnen vielleicht zuvor schon aufgefallen ist, wurde die Variable aktiv als public deklariert. Sinn und Zweck des Ganzen: Damit kann der Zustand der Schaltfläche (oder des Radio Buttons oder der Checkbox, je nach Layout) abgefragt werden. Genau das soll auch hier getan werden. Periodisch soll dieser Zustand überprüft und ausgegeben werden. Erstellen Sie dazu ein Label-Element namens label1. In diesem wird der Zustand periodisch ausgegeben.
Streng genommen kann sich der Zustand der Schaltfläche nur bei einem Mausklick ändern, man könnte also das Click-Ereignis abfangen oder überschreiben. Aus Demonstrationszwecken wird aber an dieser Stelle nicht dieser Weg gegangen, sondern ein allgemeiner Ansatz gewählt: Per Timer wird jede Zehntelsekunde der Zustand der Schaltfläche geprüft und ausgegeben. Dazu muss zunächst ein globaler Timer deklariert werden:
  1. private static System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
Beim Start der Anwendung wird dann dieser Timer initialisiert:
  1. private void Form1_Load(object sender, System.EventArgs e)
  2. {
  3. t.Tick += new EventHandler(Form1_UpdateZustand);
  4. t.Interval = 100;
  5. t.Start();
  6. }
Fehlt nur noch die Aktualisierung selbst, die wie oben angegeben in einer Methode Form1_UpdateZustand() durchgeführt werden muss. Aber auch dies stellt uns vor keine allzu großen Herausforderungen:
  1. private void Form1_UpdateZustand(Object o, EventArgs e)
  2. {
  3. label1.Text = "Zustand: " + selbstzeichnendButton1.aktiv;
  4. }
Und das war es auch schon! Sie haben in diesem Artikel gesehen, wie Sie schnell und einfach angepasste Schaltflächen wider jede Norm erstellen können. In Listings 1 und 2 finden Sie alle relevanten Codeteile abgedruckt. Auf der Heft-CD befinden sich die komplette Visual Studio .NET-Solution sowie auch die exe-Datei zum Ausprobieren.
Die hier gezeigten grafischen Effekte wurden bewusst sparsam gehalten, Ihrer eigenen Fantasie und Kreativität dagegen sind keine Grenzen gesetzt. Eine mögliche Erweiterungsmöglichkeit wurde bereits angedeutet: Radio Buttons und Checkboxen.
Gutes und schlechtes UI-Design
Jakob Nielsen ist als Usability-Kritiker gefürchtet. Einer seiner bekanntesten Meinungsäußerungen ist sein legendärer Artikel Flash: 99% Bad, der online unter http://www.useit.com/alertbox/20001029.html verfügbar ist. Als einen der Hauptkritikpunkte an der Flash-Technologie sieht er die grafikverliebten Designer, die für jede neue Flash-Anwendung eigene Schaltflächen erstellen und dabei außer Acht lassen, dass nicht jeder Nutzer das für ebenso selbstverständlich und intuitiv bedienbar hält wie der Flasher selbst.

Diese mahnenden Worte gelten natürlich für jede Art von Applikation mit Benutzeroberfläche. Also: Aller Liebe zu einem schmissigen Design zum Trotze: Übertreiben Sie es nicht und lassen Sie Unbeteiligte, Unvoreingenommene und Unvorgepictureete Ihr Design testen. Nur wenn diese Personen gut mit Ihren Schaltflächen und selbst erstellten Elementen zurecht kommen, schafft es wohl auch die breite Masse der Benutzer.

Listing 1 - Quellcode für das Steuerelement (Auszug)
  1. using System;
  2. using System.Collections;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Data;
  6. using System.Windows.Forms;
  7. namespace DotnetmagazinSelbstzeichnend
  8. {
  9. public class SelbstzeichnendButton : System.Windows.Forms.UserControl
  10. {
  11. private System.ComponentModel.Container components = null;
  12. public bool aktiv = false;
  13. public SelbstzeichnendButton()
  14. {
  15. InitializeComponent();
  16. }
  17. protected override void Dispose( bool disposing )
  18. {
  19. // ...
  20. }
  21. private void InitializeComponent()
  22. {
  23. this.Name = "SelbstzeichnendButton";
  24. this.Click += new System.EventHandler(this.SelbstzeichnendButton_Click);
  25. this.Paint += new System.Windows.Forms.PaintEventHandler(this.SelbstzeichnendButton_Paint);
  26. this.MouseEnter += new System.EventHandler(this.SelbstzeichnendButton_MouseEnter);
  27. this.MouseLeave += new System.EventHandler(this.SelbstzeichnendButton_MouseLeave);
  28. }
  29. private void SelbstzeichnendButton_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
  30. {
  31. int breite = this.Width;
  32. int hoehe = this.Height;
  33. System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
  34. gp.AddLine(5, 0, breite - 6, 0);
  35. gp.AddLine(breite - 1, 5, breite - 1, hoehe - 6);
  36. gp.AddLine(breite - 6, hoehe - 1, 5, hoehe - 1);
  37. gp.AddLine(0, hoehe - 6, 0, 5);
  38. gp.CloseFigure();
  39. e.Graphics.FillPath(
  40. new SolidBrush(Color.Orange),
  41. gp
  42. );
  43. if (aktiv)
  44. {
  45. e.Graphics.DrawPath(
  46. new Pen(Color.Black, 3),
  47. gp
  48. );
  49. }
  50. string text = (aktiv) ? "ein" : "aus";
  51. Font f = new Font("Verdana", 16);
  52. float x = (breite - e.Graphics.MeasureString(text, f).Width) / 2;
  53. float y = (hoehe - e.Graphics.MeasureString(text, f).Height) / 2;
  54. e.Graphics.DrawString(text, f, Brushes.Wheat, x - 1, y - 1);
  55. }
  56. private void SelbstzeichnendButton_Click(object sender, System.EventArgs e)
  57. {
  58. aktiv = !aktiv;
  59. this.Invalidate();
  60. }
  61. private void SelbstzeichnendButton_MouseEnter(object sender, System.EventArgs e)
  62. {
  63. this.Cursor = Cursors.Hand;
  64. }
  65. private void SelbstzeichnendButton_MouseLeave(object sender, System.EventArgs e)
  66. {
  67. this.Cursor = Cursors.Default;
  68. }
  69. }
  70. }
Listing 2 - Quellcode für die Demoapplikation (Auszug)
  1. using System;
  2. using System.Drawing;
  3. using System.Collections;
  4. using System.ComponentModel;
  5. using System.Windows.Forms;
  6. using System.Data;
  7. namespace DotnetmagazinSelbstzeichnendDemo
  8. {
  9. public class Form1 : System.Windows.Forms.Form
  10. {
  11. private DotnetmagazinSelbstzeichnend.SelbstzeichnendButton selbstzeichnendButton1;
  12. private System.Windows.Forms.Label label1;
  13. private System.ComponentModel.Container components = null;
  14. private static System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
  15. public Form1()
  16. {
  17. InitializeComponent();
  18. }
  19. protected override void Dispose( bool disposing )
  20. {
  21. // ...
  22. }
  23. private void InitializeComponent()
  24. {
  25. // ...
  26. }
  27. [STAThread]
  28. static void Main()
  29. {
  30. Application.Run(new Form1());
  31. }
  32. private void Form1_UpdateZustand(Object o, EventArgs e)
  33. {
  34. label1.Text = "Zustand: " + selbstzeichnendButton1.aktiv;
  35. }
  36. private void Form1_Load(object sender, System.EventArgs e)
  37. {
  38. t.Tick += new EventHandler(Form1_UpdateZustand);
  39. t.Interval = 100;
  40. t.Start();
  41. }
  42. }
  43. }


Anzeige

Kommentare

zurück zum Seitenanfang