Ein Mini-Editor

1. Aufgabe

Entwickeln Sie einen einfachen Editor mit dem Aussehen aus Abbildung 1:

Ein einfacher WPF-Editor.

Abbildung 1. Ein einfacher WPF-Editor.


Ziel der Aufgabe ist es, Routine im Umgang mit einfachen WPF-Standardsteuerelementen zu erlernen. Zum einen geht es um Bedienelemente wie etwa Schaltflächen (Klasse Button), Auswahlfelder (Klasse ComboBox), Textfelder (Klassen TextBox und TextBlock) usw., usw. Zum anderen sind Containerklassen wie z.B. StackPanel, Canvas oder auch Grid zu betrachten, mit deren Hilfe sich Standardsteuerelemente in ihrem umgebenden Containerobjekt platzieren lassen. Bei den Containerklassen ist insbesondere zu beachten, dass diese beliebig schachtelbar sind, also könnte man in der Zelle eines Gitters z.B. mehrere Steuerelemente in einem StackPanel-Objekt zusammenfassen usw.

Für die eigentliche Eingabe des Textes müssen Sie keine besonderen Vorkehrungen treffen, die Klasse TextBox ist hier der gesuchte Kandidat. Teilen Sie die Oberfläche des Hauptfensters (Containerobjekt Window) in mehrere Untercontainer auf, die einzelne Aufgaben des Editors übernehmen. Die Auswahl einer Farbe, um Hinter- und Vordergrundfarbe des Textfensters einstellen zu können, bietet sich hierfür an. In einem weiteren Containerobjekt stellen Sie alle auf Ihrem Rechner zur Verfügung stehenden Schriftarten in einem Auswahlfeld zusammen, um diese wiederum für das Textfenster Ihres Editors benutzen zu können. Anspruchsvollere Anwendungen können auch die Klasse RichTextBox verwenden. Weitere Anregungen zur Gestaltung Ihres Mini-Editors entnehmen Sie Abbildung 1.

2. Lösung

Die Gestaltung der Editor-Oberfläche mit XAML entnehmen Sie bitte Listing 1:

01: <Window
02:     x:Class="MiniEditor.MainWindow1"
03:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
04:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
05:     mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
06:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
07:     Title="Mini Text-Editor"
08:     Height="560" Width="800">
09:     <Grid>
10:         <!-- defining rows -->
11:         <Grid.RowDefinitions>
12:             <RowDefinition Height="Auto" />
13:             <RowDefinition Height="Auto" />
14:             <RowDefinition Height="*" />
15:         </Grid.RowDefinitions>
16: 
17:         <GroupBox Grid.Row="0"  Header="Colors" >
18:             <StackPanel Orientation="Horizontal">
19:                 <Grid Width="500">
20:                     <!-- defining columns -->
21:                     <Grid.ColumnDefinitions>
22:                         <ColumnDefinition Width="Auto" />
23:                         <ColumnDefinition Width="*" />
24:                     </Grid.ColumnDefinitions>
25: 
26:                     <!-- defining rows -->
27:                     <Grid.RowDefinitions>
28:                         <RowDefinition Height="Auto" />
29:                         <RowDefinition Height="Auto" />
30:                         <RowDefinition Height="Auto" />
31:                     </Grid.RowDefinitions>
32: 
33:                     <TextBlock Margin="5"  Grid.Row="0" Grid.Column="0"
34:                                VerticalAlignment="Center" Text="Red:"></TextBlock>
35:                     <TextBlock Margin="5"  Grid.Row="1" Grid.Column="0"
36:                                VerticalAlignment="Center" Text="Green:"></TextBlock>
37:                     <TextBlock Margin="5"  Grid.Row="2" Grid.Column="0"
38:                                VerticalAlignment="Center" Text="Blue:"></TextBlock>
39:                     <Slider Name="SliderRed" Orientation="Horizontal" Minimum="0"
40:                             Maximum="255" Margin="5" Grid.Row="0" Grid.Column="1"
41:                             ValueChanged="Slider_ValueChanged"></Slider >
42:                     <Slider Name="SliderGreen" Orientation="Horizontal" Minimum="0"
43:                             Maximum="255" Margin="5" Grid.Row="1" Grid.Column="1"
44:                             ValueChanged="Slider_ValueChanged"></Slider>
45:                     <Slider Name="SliderBlue" Orientation="Horizontal" Minimum="0"
46:                             Maximum="255" Margin="5" Grid.Row="2" Grid.Column="1"
47:                             ValueChanged="Slider_ValueChanged"></Slider>
48:                 </Grid>
49: 
50:                 <StackPanel Orientation="Vertical">
51:                     <RadioButton GroupName="ColorGroup"
52:                         Name="RadioButton_Fore" Margin="5"
53:                         IsChecked="False"
54:                         Checked="RadioButton_Checked">Foreground Color</RadioButton>
55:                     <RadioButton
56:                         Name="RadioButton_Back" Margin="5"
57:                         IsChecked="True"
58:                         Checked="RadioButton_Checked">Background Color</RadioButton>
59:                 </StackPanel>
60:             </StackPanel>
61:         </GroupBox>
62: 
63:         <GroupBox Grid.Row="1" Header="Fonts" Height="100">
64:             <Canvas>
65:                 <TextBlock
66:                     Canvas.Left="12" Canvas.Top="12" Text="Fonts:"
67:                     Height="23" Width="42" />
68:                 <ComboBox
69:                     Canvas.Left="60" Canvas.Top="12" Name="ComboBox_FontNames"
70:                     Width="120" Height="23"
71:                     SelectionChanged="ComboBox_FontNames_SelectionChanged" />
72: 
73:                 <TextBlock
74:                     Canvas.Left="240" Canvas.Top="12" Text="Size:"
75:                     Height="23" Width="42" />
76:                 <ComboBox
77:                     Canvas.Left="280" Canvas.Top="12" Name="ComboBox_FontSizes"
78:                     Width="120" Height="23"
79:                     SelectionChanged="ComboBox_FontSizes_SelectionChanged"  />
80:             </Canvas>
81:         </GroupBox>
82: 
83:         <TextBox Name="TextBox_MiniEditor" Grid.Row="2"  AcceptsReturn="True"
84:                  HorizontalScrollBarVisibility="Visible"
85:                  VerticalScrollBarVisibility="Visible"
86:                  Text="Hallo WPF :-)">
87:         </TextBox>
88:     </Grid>
89: </Window>

Beispiel 1. XAML-Markup zur Gestaltung des Mini-Editors.


Für die Code-Behind-Datei in Listing 1 gilt es einige Hinweise zu beachten: Um die Menge aller auf einem Rechner installierten Schriftarten zu ermitteln, gibt es an der Klasse Fonts die statische Klasseneigenschaft SystemFontFamilies. Diese wiederum stellt eine ICollection<T>- und damit letzten Endes eine IEnumerable<T>-Schnittstelle zur Verfügung, mit deren Hilfe man alle Schriftarten FontFamily- für FontFamily-Objekt traversieren kann. Unglücklicherweise liefert diese Aufzählung keine alphabetisch sortierte Liste von FontFamily-Objekten zurück. Wenn wir die einzelnen Namen der Schriftarten unsortiert in das ComboBox-Objekt eintragen, hinterlässt dies bei der Anwahl keinen guten Eindruck.

 

Aus diesem Grund bringen wir die Klassenmethode Array.Sort ins Spiel, die ein Array von Objekten sortiert. Nach welchem Kriterium? Es ist die IComparer<FontFamily>-Schnittstelle, die nun gefragt ist. Sie wird von FontFamily-Objekten nicht von Haus aus unterstützt, aus diesem Grund müssen wir ihre Implementierung nachreichen und tun dies im Kontext der Hilfsklasse FontsSortByNameHelper, die Sie gleich zu Beginn von Listing 1 in den Zeilen von 3 bis 9 vorfinden. Mit diesen Ergänzungen sollte nun die Initialisierungsmethode FillFontNamesComboBox verständlich geworden sein.

001: namespace MiniEditor
002: {
003:     class FontsSortByNameHelper : IComparer<FontFamily>
004:     {
005:         public int Compare (FontFamily f1, FontFamily f2)
006:         {
007:             return (f1.Source.CompareTo(f2.Source));
008:         }
009:     }
010: 
011:     public partial class MainWindow1 : Window
012:     {
013:         private Color foreColor;
014:         private Color backColor;
015: 
016:         public MainWindow1()
017:         {
018:             this.InitializeComponent();
019: 
020:             this.FillFontNamesComboBox();
021:             this.FillFontSizesComboBox();
022:             this.InitSliders(); 
023:         }
024: 
025:         // private helper method(s)
026:         private void InitSliders()
027:         {
028:             this.SliderRed.Value = 255;
029:             this.SliderGreen.Value = 255;
030:             this.SliderBlue.Value = 255;
031: 
032:             this.backColor = Colors.White;
033:             this.foreColor = Colors.Black;
034:         }
035: 
036:         private void FillFontNamesComboBox()
037:         {
038:             // retrieve collection of installed font objects
039:             ICollection<FontFamily> fontFamilies = Fonts.SystemFontFamilies;
040: 
041:             // copy enumeration of references into an array of references
042:             int numFonts = fontFamilies.Count;
043:             FontFamily[] fontFamiliesArray = new FontFamily[numFonts];            
044:             fontFamilies.CopyTo (fontFamiliesArray, 0);
045: 
046:             // sort font family names alphabetically
047:             FontsSortByNameHelper fontsComparer = new FontsSortByNameHelper();
048:             Array.Sort(fontFamiliesArray, fontsComparer);
049: 
050:             // fill combo box with font family names
051:             for (int i = 0; i < numFonts; i ++)
052:                 this.ComboBox_FontNames.Items.Add(fontFamiliesArray[i].Source);
053: 
054:             // set 'Arial' as default font
055:             this.ComboBox_FontNames.SelectedValue = "Arial";
056:         }
057: 
058:         private void FillFontSizesComboBox()
059:         {
060:             String[] fontSizes =
061:             {
062:                 "8", "10", "12", "14", "16", "18", "24", "32", "48", "64", "128"
063:             };
064: 
065:             // fill combo box with font family sizes
066:             for (int i = 0; i < fontSizes.Length; i++)
067:                 this.ComboBox_FontSizes.Items.Add(fontSizes[i]);
068: 
069:             // set default size
070:             this.ComboBox_FontSizes.SelectedValue = "32";
071:         }
072: 
073:         // event handlers
074:         private void Slider_ValueChanged
075:             (Object sender, RoutedPropertyChangedEventArgs<double> e)
076:         {
077:             // compute current color
078:             byte r = (byte)this.SliderRed.Value;
079:             byte g = (byte)this.SliderGreen.Value;
080:             byte b = (byte)this.SliderBlue.Value;
081: 
082:             Color c = Color.FromRgb(r, g, b);
083:             if ((bool) this.RadioButton_Fore.IsChecked)
084:             {
085:                 this.foreColor = c;
086:             }
087:             else if ((bool) this.RadioButton_Back.IsChecked)
088:             {
089:                 this.backColor = c;
090:             }
091: 
092:             SolidColorBrush brush = new SolidColorBrush(c);
093:             if ((bool)this.RadioButton_Back.IsChecked)
094:             {
095:                 this.TextBox_MiniEditor.Background = brush;
096:             }
097:             else
098:             {
099:                 this.TextBox_MiniEditor.Foreground = brush;
100:             }
101:         }
102: 
103:         private void ComboBox_FontSizes_SelectionChanged
104:             (Object sender, SelectionChangedEventArgs e)
105:         {
106:             String s = (String)this.ComboBox_FontSizes.SelectedItem;
107:             int size = Int32.Parse(s);
108:             this.TextBox_MiniEditor.FontSize = size;
109:         }
110: 
111:         private void ComboBox_FontNames_SelectionChanged
112:             (Object sender, SelectionChangedEventArgs e)
113:         {
114:             String s = (String) this.ComboBox_FontNames.SelectedItem;
115:             FontFamily f = new FontFamily(s);
116:             this.TextBox_MiniEditor.FontFamily = f;
117:         }
118: 
119:         private void RadioButton_Checked(Object sender, RoutedEventArgs e)
120:         {
121:             // synchronize sliders and corresponding color
122:             if ((bool) this.RadioButton_Fore.IsChecked)
123:             {
124:                 // need local copy of instance variable 'this.foreColor':
125:                 // (writing 'Slider.Value' property raises 'ValueChanged' event)
126:                 Color foreColor = this.foreColor;
127:                 this.SliderRed.Value = foreColor.R;
128:                 this.SliderGreen.Value = foreColor.G;
129:                 this.SliderBlue.Value = foreColor.B;
130:             }
131:             else if ((bool)this.RadioButton_Back.IsChecked)
132:             {
133:                 // need local copy of instance variable 'this.backColor':
134:                 // (writing 'Slider.Value' property raises 'ValueChanged' event)
135:                 Color backColor = this.backColor;
136:                 this.SliderRed.Value = backColor.R;
137:                 this.SliderGreen.Value = backColor.G;
138:                 this.SliderBlue.Value = backColor.B;
139:             }
140:         }
141:     }
142: }

Beispiel 2. Code-Behind-Datei zur Gestaltung des Mini-Editors.


Natürlich hinterlässt der Lösungsvorschlag aus Listing 1 und Listing 2 noch viele Freiheitsgrade und Möglichkeiten, um zu einer anspruchsvolleren Realisierung zu gelangen. Setzen Sie Ihrer Phantasie keine Grenzen und benutzen Sie die Ideen der Musterlösung als Ausgangspunkt für Ihre eigenen Weiterentwicklungen...