Zeichnungen mit Line-Objekten

1. Aufgabe

Selbst mit Hilfe einer so einfachen grafischen Figur wie einer Linie lassen sich durchaus recht interessante Bilder erstellen, wie Abbildung 1 zeigt:

Nichts nur als Linien?

Abbildung 1. Nichts nur als Linien?


Das in Abbildung 1 gezeigte Bild ist recht einfach zu erstellen. Zunächst müssen Sie die vier Linien des umschließenden Quadrats zeichnen. Anschließend sind die Kanten jeweils in äquidistante Abschnitte zu unterteilen. Danach sind die so ermittelten Punkte von jeweils zwei benachbarten Kanten paarweise miteinander zu verbinden. Weitere Details zur Implementierung der Ausgabe finden Sie in den folgenden Teilschritten vor.

Teilschritt 1: Oberfläche

Erstellen Sie in diesem Schritt eine C#-WPF-Anwendung. Gestalten Sie die Oberfläche der Anwendung aus Abbildung 2 mit XAML-Direktiven. Für die Fläche mit den Linien verwenden Sie in diesem Teilschritt ein – noch leeres – Canvas-Steuerelement.

Gestaltung der Oberfläche mit XAML-Direktiven.

Abbildung 2. Gestaltung der Oberfläche mit XAML-Direktiven.

Teilschritt 2: Zeichnen der Linien im Canvas-Steuerelement

In diesem Teilschritt wenden wir uns dem Canvas-Steuerelement zu. Zunächst zeichnen Sie mit vier Line-Objekten den Umriss der Figur. Nun müssen Sie diese vier Linien in gleich lange Teilabschnitte unterteilen. Für die Anzahl dieser Teilabschnitte können Sie zunächst eine Konstante, zum Beispiel 10, oder eine Instanzvariable des umgebenden Fensterobjekts verwenden. Die Grenzpunkte dieser Teilabschnitte sind nun jeweils von zwei aneinander liegenden Umrisslinien miteinander zu verbinden.

In Abbildung 3 können Sie diese Gedanken am Beispiel von 10 Teilabschnitten betrachten. Bei 10 Teilabschnitten entstehen neun Grenzpunkte, für die neun Line-Objekte zu erzeugen sind, wobei Sie die

  • unterste Trennlinie der vertikalen Linie mit der ganz linken Trennlinie der horizontalen Line verbinden,

  • die zweitunterste Trennlinie der vertikalen Linie mit der zweiten Trennlinie von links der horizontalen Line verbinden,

  • usw. usw.

Unterteilung der Umrisslinien in Teilabschnitte mit entsprechenden Verbindungslinien.

Abbildung 3. Unterteilung der Umrisslinien in Teilabschnitte mit entsprechenden Verbindungslinien.


Die Verbindungslinien sind zu jeweils zwei benachbarten Umrisslinien zu zeichnen, also insgesamt vier Mal. Sollten Sie beim Zeichnen kleine grafische Ungenauigkeiten erkennen, dann müssen Sie an einigen Stellen in ihrem Programm den Datentyp double einsetzen. Die Länge eines Teilabschnitts (Division der Figurenbreite durch die Anzahl der Teilabschnitte) wird in einer double-Variablen genauer dargestellt.

Teilschritt 3: Anzahl der Unterteilungen

Das Aussehen der Figur hängt in entscheidendem Maße von der Anzahl der Unterteilungen der Randlinien in Teilabschnitte ab. Zu diesem Zweck gibt es im oberen Bereich der Anwendung ein Slider-Steuerelement, um die Anzahl der Unterteilungen einstellen zu können. Begrenzen Sie den maximalen Wert des Schiebebalkens auf 100. Nun sollte es auch möglich sein, Figuren mit sehr vielen Linien wie etwa in Abbildung 4 zu erzeugen.

Unterteilung der Umrisslinien in Teilabschnitte mit entsprechenden Verbindungslinien.

Abbildung 4. Unterteilung der Umrisslinien in Teilabschnitte mit entsprechenden Verbindungslinien.

Teilschritt 4: Größe der Figur

Im letzten Teilschritt passen wir die Figurgröße an die Größe des umgebenden Fensterobjekts an. Bei einer Größenänderung des Hauptfensters verändert sich natürlich auch die Größe des unterlagerten Canvas-Objekts. Fügen Sie folglich einen Ereignishandler für das SizeChanged-Ereignis am Canvas-Objekt hinzu. Diese Handlermethode wird immer dann aufgerufen, wenn Sie – in der Regel mit dem Mauszeiger – die Hauptfenstergröße verändern – und eben indirekt damit auch die Größe des unterlagerten Canvas-Objekts.

Am Canvas-Objekt gibt es zwei Eigenschaften ActualWidth und ActualHeight, die immer die aktuelle Breite und Höhe des Steuerelements enthalten. Mit Hilfe dieser zwei Werte können Sie nun eine möglichst große Figurengröße berechnen und die Figur entsprechend neu zeichnen.

2. Lösung

In Listing 1 und Listing 2 finden Sie die beiden Teile (XAML und Code-Behind) der Hauptklasse MainWindow vor:

01: <Window x:Class="WpfFancyLines.MainWindow"
02:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
03:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
04:         Title="Fancy Drawing - using Lines" Height="600" Width="600" >
05: 
06:     <DockPanel LastChildFill="True">
07: 
08:         <DockPanel DockPanel.Dock="Top" LastChildFill="True">
09:             <TextBlock
10:                 Width="110" Margin="5"
11:                 DockPanel.Dock="Left">Number of Points:</TextBlock>
12:             <Slider
13:                 Name="FancySliderNumPoints" Margin="5" Orientation="Horizontal"
14:                 ValueChanged="Slider_ValueChanged"
15:                 Minimum="2" Maximum="100"></Slider>
16:         </DockPanel>
17: 
18:         <TextBox Name="FancyTextBox" DockPanel.Dock="Bottom"></TextBox>
19:       
20:         <Canvas Margin="5" Name="FancyCanvasLines"
21:                 SizeChanged="FancyCanvasLines_SizeChanged" Background="LightGray">
22:         </Canvas>
23:         
24:     </DockPanel>
25: </Window>

Beispiel 1. Klasse MainWindow der Fancy Lines-Anwendung: XAML.


001: namespace WpfFancyLines
002: {
003:     public partial class MainWindow : Window
004:     {
005:         private const int LeftGap = 20;
006:         private const int UpperGap = 20;
007: 
008:         private int length;
009:         private int numPoints;
010: 
011:         public MainWindow()
012:         {
013:             this.InitializeComponent();
014: 
015:             // init instance variables
016:             this.numPoints = 20;
017:             this.FancySliderNumPoints.Value = this.numPoints;
018:         }
019: 
020:         private void DrawLines()
021:         {
022:             this.FancyCanvasLines.Children.Clear();
023: 
024:             // drawing edges of rectangle
025:             Line edgeUpper = new Line();
026:             edgeUpper.X1 = LeftGap;
027:             edgeUpper.Y1 = UpperGap;
028:             edgeUpper.X2 = LeftGap + this.length;
029:             edgeUpper.Y2 = UpperGap;
030:             edgeUpper.Stroke = new SolidColorBrush (Colors.Black);
031:             edgeUpper.StrokeThickness = 4;
032:             edgeUpper.StrokeStartLineCap = PenLineCap.Square;
033:             edgeUpper.StrokeEndLineCap = PenLineCap.Square;
034: 
035:             this.FancyCanvasLines.Children.Add(edgeUpper);
036:             Line edgeLower = new Line();
037:             edgeLower.X1 = LeftGap;
038:             edgeLower.Y1 = UpperGap + this.length;
039:             edgeLower.X2 = LeftGap + this.length;
040:             edgeLower.Y2 = UpperGap + this.length;
041:             edgeLower.Stroke = new SolidColorBrush (Colors.Black);
042:             edgeLower.StrokeThickness = 4;
043:             edgeLower.StrokeStartLineCap = PenLineCap.Square;
044:             edgeLower.StrokeEndLineCap = PenLineCap.Square;
045:             this.FancyCanvasLines.Children.Add(edgeLower);
046: 
047:             Line edgeLeft = new Line();
048:             edgeLeft.X1 = LeftGap;
049:             edgeLeft.Y1 = UpperGap;
050:             edgeLeft.X2 = LeftGap;
051:             edgeLeft.Y2 = UpperGap + this.length;
052:             edgeLeft.Stroke = new SolidColorBrush (Colors.Black);
053:             edgeLeft.StrokeThickness = 4;
054:             edgeLeft.StrokeStartLineCap = PenLineCap.Square;
055:             edgeLeft.StrokeEndLineCap = PenLineCap.Square; 
056:             this.FancyCanvasLines.Children.Add(edgeLeft);
057: 
058:             Line edgeRight = new Line();
059:             edgeRight.X1 = LeftGap + this.length;
060:             edgeRight.Y1 = UpperGap;
061:             edgeRight.X2 = LeftGap + this.length;
062:             edgeRight.Y2 = UpperGap + this.length;
063:             edgeRight.Stroke = new SolidColorBrush (Colors.Black);
064:             edgeRight.StrokeThickness = 4;
065:             edgeRight.StrokeStartLineCap = PenLineCap.Square;
066:             edgeRight.StrokeEndLineCap = PenLineCap.Square; 
067:             this.FancyCanvasLines.Children.Add(edgeRight);
068: 
069:             // connecting points on two adjacent edges of the square
070:             double distance = (double)this.length / (double)this.numPoints;
071: 
072:             // upper and left side of square
073:             for (int i = 1; i < this.numPoints; i++)
074:             {
075:                 Line line = new Line();
076:                 line.X1 = LeftGap + this.length - i * distance;
077:                 line.Y1 = UpperGap;
078:                 line.X2 = LeftGap;
079:                 line.Y2 = UpperGap + i * distance;
080:                 line.Stroke = new SolidColorBrush (Colors.Black);
081:                 line.StrokeThickness = 1;
082:                 this.FancyCanvasLines.Children.Add(line);
083:             }
084: 
085:             // upper and right side of square
086:             for (int i = 1; i < this.numPoints; i++)
087:             {
088:                 Line line = new Line();
089:                 line.X1 = LeftGap + i * distance;
090:                 line.Y1 = UpperGap;
091:                 line.X2 = LeftGap + this.length;
092:                 line.Y2 = UpperGap + i * distance;
093:                 line.Stroke = new SolidColorBrush (Colors.Black);
094:                 line.StrokeThickness = 1;
095:                 this.FancyCanvasLines.Children.Add(line);
096:             }
097: 
098:             // lower and left side of square
099:             for (int i = 1; i < this.numPoints; i++)
100:             {
101:                 Line line = new Line();
102:                 line.X1 = LeftGap;
103:                 line.Y1 = UpperGap + i * distance;
104:                 line.X2 = LeftGap + i * distance;
105:                 line.Y2 = UpperGap + this.length;
106:                 line.Stroke = new SolidColorBrush (Colors.Black);
107:                 line.StrokeThickness = 1;
108:                 this.FancyCanvasLines.Children.Add(line);
109:             }
110: 
111:             // lower and right side of square
112:             for (int i = 1; i < this.numPoints; i++)
113:             {
114:                 Line line = new Line();
115:                 line.X1 = LeftGap + i * distance;
116:                 line.Y1 = UpperGap + this.length;
117:                 line.X2 = LeftGap + this.length;
118:                 line.Y2 = UpperGap + this.length - i * distance;
119:                 line.Stroke = new SolidColorBrush (Colors.Black);
120:                 line.StrokeThickness = 1;
121:                 this.FancyCanvasLines.Children.Add(line);
122:             }
123:         }
124: 
125:         private void Slider_ValueChanged (
126:             Object sender, RoutedPropertyChangedEventArgs<double> e)
127:         {
128:             if (this.FancyCanvasLines == null)
129:                 return;
130: 
131:             this.numPoints = (int) this.FancySliderNumPoints.Value;
132: 
133:             this.FancyTextBox.Text =
134:                 String.Format("{0} Zwischenpunkte", this.numPoints - 1);
135: 
136:             this.DrawLines();
137:         }
138: 
139:         private void FancyCanvasLines_SizeChanged (
140:             Object sender, SizeChangedEventArgs e)
141:         {
142:             if (this.FancyCanvasLines.ActualHeight < this.FancyCanvasLines.ActualWidth)
143:                 this.length = (int)this.FancyCanvasLines.ActualHeight - 40;
144:             else
145:                 this.length = (int)this.FancyCanvasLines.ActualWidth - 40;
146: 
147:             this.DrawLines();
148:         }
149:     }
150: }

Beispiel 2. Klasse MainWindow der Fancy Lines-Anwendung: Code-Behind.