这两天在学习写一个关于RichTextBox的自定义控件,把RichTextBox加上一个行号。要在WPF中自定义一个控件,使用UserControl与CustomControl都是可以的。UserControl在开发上简单快速,可以将已有的控件作为子元素拼成UserControl并修改外观,但对模板样式支持度不好。既然做WPF的,那CustomControl更加能体现风格,这个应用的也更加的广泛,所以我选择了CustomControl来自定义我的控件,虽然我不知道我这么做是否合适= =
首先先建立新的project然后自定义控件,会发现中间会有一个\Themes\Generic.xaml和一个CustomControl的后台cs文件。在xaml中有一个默认的style这是默认的样式在其中编写UI,在后台cs文件中编写逻辑。因为我只是要一个简单的RichTextBox类型的控件,所有我直接继承自RichTextBox,但在前面xaml中同一个控件定义它的 Template不能再用RichTextBox,只能使用 <ScrollViewer x:Name="ContentElement"/>不然就不能显示出来。
因为需要添加行号,所以在xaml中我加入了一个StackPanel,再通过override RichTextBox中的方法来往Panel中加入label来显示行号。因为在代码中需要找到所需要的元素,WPF控件通过名称定位他们所需要的元素,根据约定,这些名称以PART_开头,后跟元素名称(元素名称首字母大写)。就像这样: <StackPanel Width="50" Name="PART_StackPanel"/>
在初始化控件后,可以连接绑定表达式,WPF有一个专用的OnApplyTemplate()方法,如果需要在模板中查找元素并关联事件处理程序或添加数据绑定表达式就要重写方法。
public override void OnApplyTemplate() { base.OnApplyTemplate(); stackPanel = GetTemplateChild("PART_StackPanel") as StackPanel; }
建议为控件添加TemplatePart特性,以记录在控件模板中使用了哪些部件名称,以及每个部件使用了什么类型的控件:
[TemplatePart(Name = "PART_StackPanel", Type = typeof(StackPanel))] public class ClassicRichTextBox : RichTextBox {...}
然后只要重写RichTextBox的方法,往里面加自己想要的逻辑就可以了。这里的行号可以根据改变字体的大小而变动。
protected override void OnTextChanged(TextChangedEventArgs e) { base.OnTextChanged(e); LineNumbers(); } protected override void OnSelectionChanged(RoutedEventArgs e) { base.OnSelectionChanged(e); LineNumbers(); } private void LineNumbers() { stackPanel.Children.Clear(); int lineNumber = 1; var linePos = Document.ContentStart.GetLineStartPosition(0); while (true) { Label label = new Label(); label.Padding = new Thickness(0, 0, 5, 0); label.Margin = new Thickness(0); label.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Right; label.VerticalContentAlignment = System.Windows.VerticalAlignment.Center; var rect = linePos.GetCharacterRect(LogicalDirection.Backward); label.Height = rect.Height; label.Content = lineNumber.ToString(); stackPanel.Children.Add(label); int result; linePos = linePos.GetLineStartPosition(1, out result); if (result == 0) { break; } ++lineNumber; } } public StackPanel stackPanel;
当你写完自定义控件时,然后可以新建一个项目,然后需要将程序集和.NET名称空间映射到XAML命名控件,如下:
xmlns:control="clr-namespace:ClassicRichTextBoxControl;assembly=ClassicRichTextBoxControl"
这样就可以使用你的自定义控件了。
再PS一下:如果你在自定义控件的Themes文件夹下面新建一个xaml文件,来写他的内容的话,然后通过
<ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/ClassicRichTextBoxControl;component/themes/ClassicRichTextBox.xaml" /> </ResourceDictionary.MergedDictionaries>
这样提取资源的话,在同一个项目中是可以的,但是在不同的项目中就会坑爹的报错。必须写到Generic.xaml中写死。不能用相对路径= =。
最后附加下Demo:https://files.cnblogs.com/socialdk/RichTextBoxDemo.zip