Ein Mini-Editor
1. Aufgabe
Entwickeln Sie einen einfachen Editor mit dem Aussehen aus Abbildung 1:
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...