Visualisierung des BubbleSort-Algorithmus

1. Aufgabe

Die Visualisierung des BubbleSort-Algorithmus steht im Mittelpunkt dieser Aufgabe. Dazu betrachten wir ein Fensterobjekt, das unterlagert in einem spezialisierten Canvas-Objekt die unsortierten Werte eines Arrays mit horizontalen Balken darstellt, siehe Abbildung 1.

Visualisierung des BubbleSort-Algorithmus: Vor der Animation.

Abbildung 1. Visualisierung des BubbleSort-Algorithmus: Vor der Animation.


Ein Klick auf die Start-Schaltfläche startet eine Animation. Die sich verändernden Werte des Arrays werden im Verlauf des BubbleSort-Algorithmus jeweils durch grafisch entsprechend lange Balken visualisiert. Zwei Balken des Diagramms, deren korrespondierende Werte gerade verglichen werden, sind dabei in einer anderen Farbe besonders hervorzuheben. Am Ende des Algorithmus sollte man sich natürlich von seinem Erfolg überzeugen können, siehe Abbildung 2.

Visualisierung des BubbleSort-Algorithmus: Nach der Animation.

Abbildung 2. Visualisierung des BubbleSort-Algorithmus: Nach der Animation.


Wir schreiten nun in vier Teilschritten voran, um die Realisierung der Aufgabe in überschaubare Einheiten zu zergliedern:

Schritt 1: Oberfläche der Anwendung

Erstellen Sie zunächst mit XAML-Markup eine Spezialisierung der Klasse Window. Ordnen Sie in diesem Fensterobjekt im obereren Teil, zum Beispiel mit einem DockPanel-Objekt als Layoutcontainer, eine Reihe von Standardsteuerelementen wie Schaltflächen und Auswahlfelder an. Dazu wiederum verwenden Sie am besten einen StackPanel-Objekt als Layoutcontainer. Die Steuerelemente im StackPanel-Objekt dienen der Beeinflussung der Visualisierung der Animation, zum Beispiel um die Größe der Balken einstellen zu können oder zum Setzen der Anzahl der zu sortierenden Elemente usw. Die Oberfläche dieses Teils des Fensterobjekts sieht wie in Abbildung 3 gezeigt aus.

Toolbar der Multithreaded BubbleSort-Anwendung.

Abbildung 3. Toolbar der Multithreaded BubbleSort-Anwendung.


Die Funktion der einzelnen Bedienelemente ist weitestgehend selbsterklärend:

  • Schaltfläche Start – Starten einer Animation.

  • Schaltfläche Stop – Stoppen einer Animation.

  • Schaltfläche Shuffle – Zahlen des Arrays mischen.

  • Schaltfläche Reverse – Zahlen in absteigender Reihenfolge (zum Testen der Anwendung) anordnen.

  • Auswahlfeld Bar Height – Höhe eines Balkens.

  • Auswahlfeld Elements – Anzahl der zu sortierenden Elemente im Array festlegen.

  • Auswahlfeld Interval – Pause (in Millisekunden) zwischen dem Vergleich zweier Elemente.

Die grafische Animation mit der Darstellung der Balken ordnen wir in einer zweiten Klasse BubbleSortViewCanvas an, sie ist eine Spezialisierung der Canvas-Klasse. In Abbildung 1 erkennen wir ein Objekt dieses Typs im unteren Teil des Fensterobjekts. Fügen Sie in den Instanzvariablen der BubbleSortViewCanvas-Klasse ein int-Array hinzu, dessen Elemente sie mit zufällig erzeugten int-Zahlen belegen. Zufallszahlen können Sie mit Hilfe der Next-Methode aus der System.Random-Klasse generieren. Die Zufallswerte sollten einen bestimmten Maximalwert nicht überschreiten, der sich an einer sinnvollen Breite des Canvas-Objekts orientiert. Ergänzen Sie die BubbleSortViewCanvas-Klasse um die in Tabelle 1 beschriebenen Elemente:

Element

Beschreibung

Eigenschaft BarHeight

public int BarHeight { get; set; }

Definiert die Höhe eines Balkens in Pixel.

Eigenschaft Count

public int Count { get; set; }

Anzahl der zu sortierenden Elemente.

Methode Shuffle

public void Shuffle ();

Zahlen mischen.

Methode Reverse

public void Reverse ();

Zahlen in absteigender Reihenfolge (zum Testen der Anwendung) anordnen.

Tabelle 1. Eigenschaften und Methoden der Klasse BubbleSortViewCanvas zur Gestaltung der Oberfläche.


Stimmen Sie die Klasse BubbleSortViewCanvas so mit den anderen bislang entwickelten Programmteilen aufeinander ab, dass Sie die in Tabelle 1 beschriebenen Elemente testen können.

Schritt 2: Sortieren des Arrays

Wir bringen nun den BubbleSort-Algorithmus ins Spiel. Erweitern Sie die BubbleSortViewCanvas-Klasse um eine Sort-Methode mit der Schnittstelle

public void Sort ();

Testen Sie die Sort-Methode mit einer zusätzlichen Schaltfläche in der BubbleSortController-Klasse (in Abbildung 3 nicht dargestellt), bevor Sie sich der Animation zuwenden.

Schritt 3: Animation der Anwendung

Der Sortier-Vorgang ist nun mit Hilfe eines Threads animiert darzustellen. Zu diesem Zweck ist das Sortieren des Arrays in den Kontext eines separaten Threads auszulagern. Zusätzlich sollten Sie optional nach jedem Vergleich zweier Elemente des Arrays eine zeitliche Unterbrechung einlegen, um auf diese Weise zu einer ansprechenden Animation bei der Ausführung des Algorithmus zu gelangen.

Speziell für die Durchführung einer Animation stehen an dem BubbleSortViewCanvas-Objekt die drei in Tabelle 2 beschriebenen Eigenschaften bzw. Methoden Start, Stop und Interval zur Verfügung:

Element

Beschreibung

Methode Start

public void Start ();

Starten bzw. Fortfahren der Animation.

Methode Stop

public void Stop ();

Anhalten der Animation.

Eigenschaft Interval

public int Interval { get; set; }

Pause (in Millisekunden), die zwischen dem Vergleich zweier Elemente eingefügt wird. Der Wert 0 bedeutet, dass die Animation in der maximalen Geschwindigkeit abläuft.

Tabelle 2. Erweiterungen der Klasse BubbleSortViewCanvas für eine animierte Darstellung.


Klären Sie an Hand Ihrer Implementierung, ob der WPF-Dispatcher (UI-Thread) und der Sortier-Thread gleichzeitig auf dieselben Daten zugreifen dürfen. Entscheiden Sie, ob Sie zur Beseitigung des konkurrierenden Zugriffs die Hilfsmittel der Threadsynchronisation einsetzen müssen.

Schritt 4: Koordination von Visualisierung und Algorithmus

Zum Abschluss der Aufgabe ist eine Randbedingung zu beachten, die Sie – nebenbei bemerkt – mit minimalem Programmieraufwand erfüllen können: Jeder einzelne Schritt der Visualisierung ist grafisch darzustellen, oder anders formuliert: Es dürfen in der Visualisierung keine Schritte des Algorithmus verloren gehen. Es ist damit sicherzustellen, dass der BubbleSort-Algorithmus nicht schneller arbeitet, als die grafische Darstellung der einzelnen Phasen Zeit benötigt. Da die Ausführung des BubbleSort-Algorithmus im Kontext eines separaten Threads abläuft, stellen die Mechanismen der Thread-Koordination geeignete Hilfsmittel zur Verfügung, um dieser Anforderung gerecht zu werden.

2. Lösung

Zu Übungszwecken teilen wir die Anwendung auf drei Projekte wie folgt auf:

  • ein WPF User Control Library-Projekt für die Toolbar der Anwendung,

  • ein WPF Custom Control Library-Projekt zur Visualisierung des Algorithmus und

  • ein WPF Application-Projekt, das die zuvor genannten Komponenten instanziiert und ihr Zusammenspiel koordiniert.

Zunächst gehen wir auf die Visualisierung des Algorithmus näher ein. Um die vorgestellte Lösung in einem ersten Entwurf kurz zu halten, nehmen wir keine strukturelle Trennung zwischen der Darstellung des Algorithmus und dem zu sortierenden Feld vor. In einer umfangreicheren als auch flexibleren Realisierung würde man hier eine strikte Trennlinie einziehen.

Hinweis: Beim Anlegen eines WPF Custom Control Library-Projekts mit dem Visual Studio wird per Voreinstellung automatisch eine Control-Klasse als Basisklasse generiert. Diese ist für unsere Zwecke nicht so gut geeignet, wir können sie problemlos in der Code-Behind Datei in eine Canvas-Klasse abändern. Zusätzlich löschen wir den generierten Themes-Ordner im Projekt sowie den vorhandenen statischen Klassenkonstruktor der Control-Klasse. Nach diesen kleinen Modifikationen beginnen wir offensichtlich mit einer leeren Klasse in diesem Projekt. Sie heißt BubbleSorterViewControl, ihre vollständige Implementierung ist in Listing 1 gegeben:

001: namespace WpfBubbleSorterView
002: {
003:     public class BubbleSorterViewControl : Canvas
004:     {
005:         // array to sort
006:         private int[] numbers;
007: 
008:         // appearance
009:         private int barHeight;
010: 
011:         // threading utilities
012:         private Thread sorter;
013:         private bool isActive;
014: 
015:         // c'tor
016:         public BubbleSorterViewControl()
017:         {
018:             // needed for visual studio designer
019:             this.numbers = new int[0];
020:         }
021: 
022:         // public properties
023:         public int Interval { get; set; }
024:         public bool MonitorEnabled { get; set; }
025:         
026:         public int BarHeight
027:         {
028:             set
029:             {
030:                 if (this.barHeight != value)
031:                 {
032:                     this.barHeight = value;
033:                     this.UpdateHeightOfBarsOnDispatcherThread(value);
034:                 }
035:             }
036:         }
037: 
038:         public int Count
039:         {
040:             set
041:             {
042:                 if (this.numbers.Length != value)
043:                 {
044:                     this.AllocateArray(value);
045:                     this.UpdateBars();
046:                 }
047:             }
048:         }
049: 
050:         // public interface
051:         public void Start()
052:         {
053:             if (this.isActive)
054:                 return;
055: 
056:             // start animation
057:             ThreadStart ts = new ThreadStart(this.Sort);
058:             this.sorter = new Thread(ts);
059:             this.isActive = true;
060:             this.sorter.Start();
061:         }
062: 
063:         public void Stop()
064:         {
065:             // terminate thread
066:             if (this.isActive)
067:             {
068:                 this.isActive = false;
069: 
070:                 // wakeup calculation thread, if necessary
071:                 Monitor.Enter(this);
072:                 Monitor.Pulse(this);
073:                 Monitor.Exit(this);
074: 
075:                 // wait for end of calculation thread
076:                 this.sorter.Join();
077:             }
078:         }
079: 
080:         public void Shuffle()
081:         {
082:             // re-initialize array
083:             this.InitializeArray();
084:             this.UpdateBars();
085:         }
086: 
087:         public void Reverse()
088:         {
089:             this.ReverseArray();
090:             this.UpdateBars();
091:         }
092: 
093:         // private helper methods
094:         private void Sort()
095:         {
096:             for (int i = this.numbers.Length; --i >= 0; )
097:             {
098:                 bool ready = true;
099: 
100:                 for (int j = 0; j < i; j++)
101:                 {
102:                     // premature end of thread
103:                     if (!this.isActive)
104:                         return;
105: 
106:                     // swap two elements thread-safe
107:                     Monitor.Enter(this);
108:                     if (this.numbers[j] > this.numbers[j + 1])
109:                     {
110:                         int tmp = this.numbers[j];
111:                         this.numbers[j] = this.numbers[j + 1];
112:                         this.numbers[j + 1] = tmp;
113:                         ready = false;
114:                     }
115: 
116:                     // swap corresponding bars
117:                     this.UpdateBars(j, j+1);
118: 
119:                     // wait for end of UI updating
120:                     if (this.MonitorEnabled)
121:                         Monitor.Wait(this);
122:                     Monitor.Exit(this);
123: 
124:                     // sleep a short time
125:                     if (this.Interval > 0)
126:                         Thread.Sleep(this.Interval);
127:                 }
128: 
129:                 // need to change colors of lowest two bar
130:                 if (i < this.numbers.Length - 1)
131:                     this.UpdateBar(i+1, Colors.Black);
132:                 this.UpdateBar(i, Colors.Red);
133: 
134:                 if (ready) // array is sorted
135:                     break; 
136:             }
137: 
138:             this.isActive = false;
139:         }
140: 
141:         // array specific methods
142:         private void AllocateArray(int count)
143:         {
144:             // allocate new array
145:             this.numbers = new int[count];
146:             this.InitializeArray();
147:         }
148: 
149:         private void InitializeArray()
150:         {
151:             // random-numbers equally distributed between 1 and max
152:             for (int i = 0; i < this.numbers.Length; i++)
153:                 this.numbers[i] = 8 * (i + 1);
154: 
155:             Random r = new Random();
156:             for (int i = 0; i < this.numbers.Length; i++)
157:             {
158:                 int j = r.Next(i);
159:                 int tmp = this.numbers[i];
160:                 this.numbers[i] = this.numbers[j];
161:                 this.numbers[j] = tmp;
162:             }
163:         }
164: 
165:         private void ReverseArray()
166:         {
167:             for (int i = 0; i < this.numbers.Length; i++)
168:                 this.numbers[this.numbers.Length-1-i] = 8 * (i+1);
169:         }
170: 
171:         // ui helper methods
172:         private void UpdateBars()
173:         {
174:             this.Children.Clear();
175: 
176:             for (int i = 0; i < this.numbers.Length; i++)
177:             {
178:                 Rectangle r = new Rectangle();
179:                 r.Height = this.barHeight;
180:                 r.Width = this.numbers[i] * 2;
181:                 r.SetCurrentValue(Canvas.LeftProperty, 20.0);
182:                 r.SetCurrentValue(Canvas.TopProperty,
183:                     (double)20 + i * (this.barHeight + 4));
184:                 r.Fill = Brushes.Black;
185: 
186:                 this.Children.Add(r);
187:             }
188:         }
189: 
190:         private delegate void UpdateBarsHandler(int i, int j);
191: 
192:         private void UpdateBars(int i, int j)
193:         {
194:             if (this.Dispatcher.CheckAccess())
195:             {
196:                 this.UpdateBarOnDispatcherThread(i, Colors.Black);
197:                 this.UpdateBarOnDispatcherThread(j, Colors.Yellow);
198: 
199:                 // enable next calculation step of bubble sort algorithm
200:                 Monitor.Enter(this);
201:                 Monitor.Pulse(this);
202:                 Monitor.Exit(this);
203:             }
204:             else
205:             {
206:                 // execute 'UpdateBars' asynchronously on primary thread 
207:                 UpdateBarsHandler d =
208:                     new UpdateBarsHandler(this.UpdateBars);
209:                 Object[] args = new Object[] { i, j };
210:                 this.Dispatcher.BeginInvoke(d, args);
211:             }
212:         }
213: 
214:         private delegate void UpdateBarHandler(int i, Color c);
215: 
216:         private void UpdateBar(int i, Color c)
217:         {
218:             if (this.Dispatcher.CheckAccess())
219:             {
220:                 this.UpdateBarOnDispatcherThread(i, c);
221:             }
222:             else
223:             {
224:                 // execute 'UpdateBar' asynchronously on primary thread 
225:                 UpdateBarHandler d =
226:                     new UpdateBarHandler(this.UpdateBar);
227:                 Object[] args = new Object[] { i, c };
228:                 this.Dispatcher.BeginInvoke(d, args);
229:             }
230:         }
231: 
232:         private void UpdateBarOnDispatcherThread(int i, Color c)
233:         {
234:             Rectangle r = (Rectangle)this.Children[i];
235:             r.Height = this.barHeight;
236:             r.Width = this.numbers[i] * 2;
237: 
238:             r.SetCurrentValue(Canvas.LeftProperty, 20.0);
239:             r.SetCurrentValue(Canvas.TopProperty,
240:                 (double)20 + i * (this.barHeight + 4));
241: 
242:             SolidColorBrush b = new SolidColorBrush(c);
243:             r.Fill = b;
244:         }
245: 
246:         private void UpdateHeightOfBarsOnDispatcherThread(int height)
247:         {
248:             for (int i = 0; i < this.Children.Count; i++)
249:             {
250:                 Rectangle r = (Rectangle)this.Children[i];
251:                 r.Height = height;
252:                 r.SetCurrentValue (
253:                     Canvas.TopProperty,
254:                     (double)20 + i * (height + 4));
255:             }
256:         }
257:     }
258: }

Beispiel 1. Klasse BubbleSorterViewControl: Visualisierung des Algorithmus.


Wir gehen auf die hervorhebenswerten Stellen des Quellcodes von Listing 1 ein. In Zeile 6 ist die Instanzvariable numbers definiert, um die zu sortierenden Zahlen aufzunehmen. Prinzipiell könnte man mit dem Allokieren des Objekts solange warten, bis der Algorithmus gestartet wird. Da aber in Eigenschaften wie zum Beispiel Count bereits ein existierendes Objekt voraus gesetzt wird, muss man bereits im Konstruktor (Zeile 19) ein Feld anlegen. Wenn man mit dem Speicher sparsam umgehen möchte, tut es auch ein Feld der Länge Null.

Die spezielle Anforderung an das Programm, jeden einzelnen Visualisierungsschritt zur Anzeige zu bringen, stellt eine Variation des Erzeuger/Verbraucher-Problems dar. Der Erzeuger ist in Gestalt einer Sortiermethode gegeben, die sukzessive Zahlen in einem Array vertauscht. Der Verbraucher residiert in der Methode UpdateBarOnDispatcherThread (Zeilen 232 bis 244) eines Steuerelements, das den aktuellen Zustand der Arrayelemente während des Sortierens grafisch darstellt. Um das Sortieren und die grafische Darstellung aufeinander abzustimmen, bietet sich der Einsatz eines Monitorobjekts wie folgt an: Der sortierende Thread begibt sich nach jedem Tausch zweier Arrayelemente in den Interimsraum eines Monitorobjekts, damit das WPF-Grafiksystem genügend Zeit besitzt, die in ihrer Länge geänderten Balken neu zeichnen zu können. Nach dem Zeichnen – und ggf. einer kleinen Pause, um die Animation nicht zu schnell ablaufen zu lassen – benachrichtigt das Grafiksystem den Sortierthread, dass er zur Darstellung des nächsten Sortierschritts bereit ist.

Beachten Sie hierbei einen weiteren Clou in der vorgestellten Lösung: Das Sortieren des Arrays und das Zeichnen der Balken findet in unterschiedlichen Threads statt! Dieser Umstand ist nicht ohne weiteres aus dem Quellcode ersichtlich: Die Sort-Methode (ab Zeile 94) ist offensichtlich die Threadprozedur eines Threads, der in Zeile 60 gestartet wird. Der Aufruf von UpdateBars (Zeile 117) in dieser Methode wird im Kontext dieses Threads ausgeführt. Der Implementierung von UpdateBars kann man erst in den Zeilen 207 bis 210 entnehmen, dass diese dann in den Dispatcher-Thread der Anwendung verzweigt, also einen Threadwechsel vornimmt. Wäre dies nicht der Fall, könnten die zwei am Monitorobjekt gerufenen Methoden Wait und Pulse nicht kooperierend zusammenarbeiten, da diese zwingend in unterschiedlichen Threads agieren müssen.

Für Modifikationen an der Oberfläche des Canvas-Objekts wurden speziell die zwei Methoden UpdateBarOnDispatcherThread und UpdateHeightOfBarsOnDispatcherThread konzipiert. Sie können ohne Probleme auf alle Instrumente des grafischen Subsystems wie Rectangle-Objekte oder das Canvas-Containerobjekt zugreifen, ohne eine Ausnahme der Gestalt The calling thread cannot access this object because a different thread owns it zu verursachen.

Damit kommen wir zur Toolbar der Anwendung. Im Gegensatz zum WPF Custom Control Library-Projekt der Visualisierung besitzt ein WPF User Control Library-Projekt sowohl einen XAML- wie auch einen Code-Behind-Anteil. Die Hauptaufgabe der Toolbar besteht darin, unterschiedlichste Einstellungen, die in der Komponente eingestellt werden, nach außen zu melden. Dazu definieren wir zwei Ereignisse Action und ActionSettings:

public event SortActionEventHandler Action;
public event SortSettingsEventHandler ActionSettings;

Hierzu gibt es einige benutzerdefinierte Datentypen:

public enum SortActions { Start, Stop, InitRandom, InitReverse };
public enum SortSettings { Length, BarHeight, Interval, MonitorEnabled };

public delegate void SortActionEventHandler(SortActions action);
public delegate void SortSettingsEventHandler
    (SortSettings kind, SortSettingsEventArgs args);

public class SortSettingsEventArgs : EventArgs
{
    public SortSettingsEventArgs()
    {
        this.Count = -1;
        this.BarHeight = -1;
        this.Interval = -1;
        this.MonitorEnabled = false;
    }

    public int Count { get; set; }
    public int BarHeight { get; set; }
    public int Interval { get; set; }
    public bool MonitorEnabled { get; set; }
}

Nach diesen Vorbereitungen dürfte der Quellcode der Toolbar leicht verständlich sein, in Listing 2 finden Sie ihren XAML-Code vor:

01: <UserControl
02:     x:Class="WpfBubbleSorterController.BubbleSorterController"
03:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
04:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
05:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
06:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
07:     mc:Ignorable="d" Height="32" >
08:     
09:     <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" >
10:         <Button Name="ButtonStart" Margin="10, 5, 5, 5" Width="40"
11:                     Content="Start" Click="Button_Click"/>
12:         <Button Name="ButtonStop" Margin="5" Width="40"
13:                     Content="Stop" Click="Button_Click"/>
14:         <Button Name="ButtonShuffle" Margin="5" Width="50"
15:                     Content="Shuffle" Click="Button_Click"/>
16:         <Button Name="ButtonReverse" Margin="5, 5, 10, 5" Width="50"
17:                     Content="Reverse" Click="Button_Click"/>
18:         <TextBlock Text="BarHeight:" VerticalAlignment="Center"/>
19:         <ComboBox
20:             Name="ComboBox_BarHeight"
21:             Width="40" 
22:             Margin="10,5,10,5"
23:             SelectionChanged="ComboBox_SelectionChanged"
24:             SelectedIndex="1">
25:                 <ComboBoxItem>2</ComboBoxItem>
26:                 <ComboBoxItem>4</ComboBoxItem>
27:                 <ComboBoxItem>6</ComboBoxItem>
28:                 <ComboBoxItem>8</ComboBoxItem>
29:                 <ComboBoxItem>10</ComboBoxItem>
30:                 <ComboBoxItem>12</ComboBoxItem>
31:                 <ComboBoxItem>15</ComboBoxItem>
32:         </ComboBox>
33:         <TextBlock Text="Elements:" VerticalAlignment="Center"/>
34:         <ComboBox
35:             Name="ComboBox_Elements"
36:             Width="60"
37:             Margin="10,5,10,5" 
38:             SelectedIndex="2"
39:             SelectionChanged="ComboBox_SelectionChanged" >
40:                 <ComboBoxItem>16</ComboBoxItem>
41:             <ComboBoxItem>32</ComboBoxItem>
42:             <ComboBoxItem>48</ComboBoxItem>
43:             <ComboBoxItem>64</ComboBoxItem>
44:             <ComboBoxItem>128</ComboBoxItem>
45:         </ComboBox>
46:         <TextBlock Text="Interval:" VerticalAlignment="Center"/>
47:         <ComboBox
48:             Name="ComboBox_Interval" Width="60"
49:             SelectedIndex="1"
50:             Margin="10,5,10,5"
51:             SelectionChanged="ComboBox_SelectionChanged" >
52:                 <ComboBoxItem>0</ComboBoxItem>
53:                 <ComboBoxItem>1</ComboBoxItem>
54:                 <ComboBoxItem>100</ComboBoxItem>
55:                 <ComboBoxItem>500</ComboBoxItem>
56:                 <ComboBoxItem>1000</ComboBoxItem>
57:                 <ComboBoxItem>2000</ComboBoxItem>
58:         </ComboBox>
59:         <CheckBox
60:             Name="CheckBox_MonitorEnabled"
61:             Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" 
62:             Content="Using Monitor"
63:             Margin="20, 8, 5, 5"
64:             HorizontalAlignment="Right"
65:             IsChecked="False" />
66:     </StackPanel>
67: </UserControl>

Beispiel 2. Klasse BubbleSorterController: XAML-Anteil.


In der zweiten Zeile von Listing 2 finden Sie den Namen der dort definierten Klasse vor: BubbleSorterController aus dem Namensraum WpfBubbleSorterController. Die Implementierung dieser Klasse ist unvollständig oder partiell, wie es im .NET-Jargon heißt. Die Vervollständigung der BubbleSorterController-Klasse findet in der Code-Behind-Datei statt, siehe hierzu Listing 3:

001: namespace WpfBubbleSorterController
002: {
003:     public partial class BubbleSorterController : UserControl
004:     {
005:         // events
006:         public event SortActionEventHandler Action;
007:         public event SortSettingsEventHandler ActionSettings;
008: 
009:         // c'tor
010:         public BubbleSorterController()
011:         {
012:             InitializeComponent();
013:         }
014: 
015:         // properties
016:         public int BarHeight
017:         {
018:             get
019:             {
020:                 ComboBoxItem item =
021:                     (ComboBoxItem)this.ComboBox_BarHeight.SelectedItem;
022:                 String content = (String)item.Content;
023:                 return Int32.Parse(content);
024:             }
025:         }
026: 
027:         public int Length
028:         {
029:             get
030:             {
031:                 ComboBoxItem item =
032:                     (ComboBoxItem)this.ComboBox_Elements.SelectedItem;
033:                 String content = (String)item.Content;
034:                 return Int32.Parse(content);
035:             }
036:         }
037: 
038:         public int Interval
039:         {
040:             get
041:             {
042:                 ComboBoxItem item =
043:                     (ComboBoxItem)this.ComboBox_Interval.SelectedItem;
044:                 String content = (String)item.Content;
045:                 return Int32.Parse(content);
046:             }
047:         }
048: 
049:         public bool MonitorEnabled
050:         {
051:             get
052:             {
053:                 return (bool)this.CheckBox_MonitorEnabled.IsChecked;
054:             }
055:         }
056: 
057:         // private event handlers
058:         private void Button_Click(Object sender, RoutedEventArgs e)
059:         {
060:             if (this.Action != null)
061:             {
062:                 if (sender == this.ButtonStart)
063:                     this.Action(SortActions.Start);
064:                 else if (sender == this.ButtonStop)
065:                     this.Action(SortActions.Stop);
066:                 else if (sender == this.ButtonShuffle)
067:                     this.Action(SortActions.InitRandom);
068:                 else if (sender == this.ButtonReverse)
069:                     this.Action(SortActions.InitReverse);
070:             }
071:         }
072: 
073:         private void CheckBox_Checked(Object sender, RoutedEventArgs e)
074:         {
075:             SortSettingsEventArgs settings =
076:                 new SortSettingsEventArgs();
077:             settings.MonitorEnabled =
078:                 (bool)this.CheckBox_MonitorEnabled.IsChecked;
079:             this.ActionSettings(SortSettings.MonitorEnabled, settings);
080:         }
081: 
082:         private void ComboBox_SelectionChanged(
083:             Object sender, SelectionChangedEventArgs e)
084:         {
085:             SortSettingsEventArgs args = new SortSettingsEventArgs();
086: 
087:             if (sender == this.ComboBox_BarHeight)
088:             {
089:                 args.BarHeight = BarHeight;
090:                 if (this.ActionSettings != null)
091:                     this.ActionSettings(SortSettings.BarHeight, args);
092:             }
093:             else if (sender == this.ComboBox_Elements)
094:             {
095:                 args.Count = Length;
096:                 if (this.ActionSettings != null)
097:                     this.ActionSettings(SortSettings.Length, args);
098:             }
099:             else if (sender == this.ComboBox_Interval)
100:             {
101:                 args.Interval = Interval;
102:                 if (this.ActionSettings != null)
103:                     this.ActionSettings(SortSettings.Interval, args);
104:             }
105:         }
106:     }
107: }

Beispiel 3. Klasse BubbleSorterController: Code-Behind-Anteil.


Damit kommen wir zur eigentlichen WPF-Anwendung dieser Aufgabe. Im Wesentlichen sind in ihr eine BubbleSorterViewControl- und eine BubbleSorterController-Komponente instanziiert, platziert und miteinander verschaltet. Da diese zwei Komponenten – sinnvollerweise – in anderen Projekten realisiert sind, hat dies zur Folge, dass diese zunächst in der aktuellen WPF-Anwendung des Hauptfensters überhaupt nicht bekannt sind. Zu diesem Zweck müssen diese in der References-Liste des Projekts ergänzt werden, wie in Abbildung 5 gezeigt wird:

Bekanntmachung externer Komponenten in einem .NET-Projekt.

Abbildung 5. Bekanntmachung externer Komponenten in einem .NET-Projekt.


Die Anpassungen in der References-Liste des Projekts sind nicht ausreichend, um die externen Komponenten im aktuellen Projekt verwenden zu können. Zusätzlich müssen die Namensräume der externen Klassen wie auch ihre Bezüge zum korrespondieren .NET-Assembly im XAML-Code deklariert werden. Ein C/C++ Entwickler würde hier folgende Formulierungen wählen: Die Einstellungen in der References-Liste sind für den Linker erforderlich, die Deklarationen im C#- bzw. XAML-Code für den Compiler. Natürlich gibt es im Übersetzungsweg von C# keinen Linker im klassischen Stile von C/C++. Der Zugriff auf externe Bibliotheken sowohl durch den Compiler zur Übersetzungszeit wie auch durch die CLR zur Laufzeit weist aber viele Ähnlichkeiten auf. Die Informationen über den Aufbau verwendeter Klassen müssen für beide Tools immer verfügbar sein.

In Listing 4 finden Sie in den Zeilen 6 und 8 die Bekanntmachung der externen Komponenten BubbleSorterController und BubbleSorterView vor. Mit dem XAML-Schlüsselwort clr-namespace werden die Namensräume der externen Komponenten bekannt gemacht. Mit dem zweiten XAML-Schlüsselwort assembly wird spezifiziert, in welcher .NET-Assembly der Code der externen Komponenten residiert:

01: <Window
02:     x:Class="WpfBubbleSorterWindow.BubbleSorterWindow"
03:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
04:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
05:         
06:     xmlns:local1=
07:         "clr-namespace:WpfBubbleSorterController;assembly=BubbleSorterController"
08:     xmlns:local2=
09:         "clr-namespace:WpfBubbleSorterView;assembly=BubbleSorterView"
10:                 
11:     Title="Multithreading Bubble Sorter - Reloaded"
12:     Height="400" Width="750" Background="LightGray">
13:     
14:     <DockPanel LastChildFill="True">
15:         <Border BorderBrush="Black" BorderThickness="1"
16:                 DockPanel.Dock="Top" >
17:             <local1:BubbleSorterController
18:                 Name="BubbleSortController" Background="White">
19:             </local1:BubbleSorterController>
20:         </Border>
21:         <Border BorderBrush="Black" BorderThickness="1" >
22:             <local2:BubbleSorterViewControl
23:                 Name="BubbleSortView"
24:                 BarHeight="6" Count="16" Interval="100">
25:             </local2:BubbleSorterViewControl>
26:         </Border>
27:     </DockPanel>
28: </Window>

Beispiel 4. Klasse BubbleSorterWindow: XAML-Behind-Anteil.


Nun kennt die WPF-Anwendung des Hauptfensters die zwei externen Komponenten BubbleSorterController und BubbleSorterView, bis jetzt treten diese allerdings isoliert in Erscheinung. Um die beiden Komponenten zu verschalten, verwenden wir das Sprachmittel der Ereignisse. In den Zeilen 10 und 13 von Listing 5 werden an den zwei Ereignissen Action und ActionSettings entsprechende Ereignishandlermethoden angemeldet. Im Rumpf dieser Methoden werden Ereignisse in der BubbleSorterController-Komponente, also letzten Endes Einstellungen an den Parametern des Sortieralgorithmus, auf entsprechende Eigenschaften an der BubbleSorterView-Komponente delegiert:

01: namespace WpfBubbleSorterWindow
02: {
03:     public partial class BubbleSorterWindow : Window
04:     {
05:         public BubbleSorterWindow()
06:         {
07:             InitializeComponent();
08: 
09:             // connect to events
10:             this.BubbleSortController.Action +=
11:                 new SortActionEventHandler(
12:                     this.BubbleSortController_SortActionEvent);
13:             this.BubbleSortController.ActionSettings +=
14:                 new SortSettingsEventHandler(
15:                     this.BubbleSortController_SortSettingsEvent);
16: 
17:             // initialize view
18:             this.BubbleSortView.BarHeight =
19:                 this.BubbleSortController.BarHeight;
20:             this.BubbleSortView.Count =
21:                 this.BubbleSortController.Length;
22:             this.BubbleSortView.Interval =
23:                 this.BubbleSortController.Interval;
24:             this.BubbleSortView.MonitorEnabled =
25:                 this.BubbleSortController.MonitorEnabled;
26:         }
27: 
28:         private void BubbleSortController_SortActionEvent(SortActions action)
29:         {
30:             switch (action)
31:             {
32:                 case SortActions.Start:
33:                     this.BubbleSortView.Start();
34:                     break;
35: 
36:                 case SortActions.Stop:
37:                     this.BubbleSortView.Stop();
38:                     break;
39: 
40:                 case SortActions.InitReverse:
41:                     this.BubbleSortView.Reverse();
42:                     break;
43: 
44:                 case SortActions.InitRandom:
45:                     this.BubbleSortView.Shuffle();
46:                     break;
47:             }
48:         }
49: 
50:         private void BubbleSortController_SortSettingsEvent(
51:             SortSettings kind, SortSettingsEventArgs args)
52:         {
53:             switch (kind)
54:             {
55:                 case SortSettings.BarHeight:
56:                     this.BubbleSortView.BarHeight = args.BarHeight;
57:                     break;
58: 
59:                 case SortSettings.Length:
60:                     this.BubbleSortView.Count = args.Count;
61:                     break;
62: 
63:                 case SortSettings.Interval:
64:                     this.BubbleSortView.Interval = args.Interval;
65:                     break;
66: 
67:                 case SortSettings.MonitorEnabled:
68:                     this.BubbleSortView.MonitorEnabled =
69:                         args.MonitorEnabled;
70:                     break;
71:             }
72:         }
73: 
74:         protected override void OnClosed(EventArgs e)
75:         {
76:             base.OnClosed(e);
77: 
78:             this.BubbleSortView.Stop();
79:         }
80:     }
81: }

Beispiel 5. Klasse BubbleSorterWindow: Code-Behind-Anteil.


Weiterarbeit: Diese Lösung hat den Nachteil, dass die Darstellung der Daten eng mit den Daten gekoppelt ist. Es ist also nicht möglich, entweder einen anderen Algorithmus (Quick-Sort, etc.) wie auch eine andere grafische Darstellung der Daten (vertikale oder horizontale Balken etc.) zu wählen. Strukturieren Sie Ihre Lösung so um, dass diese Anforderungen ebenfalls Berücksichtigung finden.