使用跟踪行为技术反射 RichTextBox 显示样式






4.36/5 (4投票s)
目前的问题是如何反射 RichTextBox 显示控件的样式。应用了通常称为“行为”的技术。
引言
正如我在上一篇文章中所承诺的,我现在将发布 RichTextBox 控件样式重塑。应用了通常称为“行为”的技术。
动机
为什么会陷入如此简单的任务?根本原因是什么?答案也很简单——为了让你的生活更轻松。顺便说一句,也能解决其他问题。
什么在起作用?
为什么?
- 代码的纯净性——所有依赖项都集中在一个地方。
- 代码、XAML 的可读性。
- 不使用后台代码(code-behind)。
- 添加辅助方法。
- 字体样式生成器
- 字体大小生成器
- 字体颜色生成器
- 字体背景颜色生成器
- 生成表示原子样式的类
- 集成事件改变样式
- 复杂原子样式的分解
- 与宿主完全分离
- 作为外部引用存在,通过识别添加和移除。
- 灵活性和可扩展性。
- 可测试性——一旦测试了行为,就可以确保绝对不会出现任何问题。此外,解耦代码还提供了测试标准模型和呈现器而非控件状态的自由。
一句话,这是最佳实践。
此外,还可以解决协同行为问题,特别是 ToggleButtom
控件和 ComboBox
。当我们希望通过 ToggleBox 控件反射状态时,就会出现这个问题。此控件在使用代码中的 IsChecked
更改时在视觉上不会改变。我们需要创建单独的后台代码或额外的属性呈现器代码,通过 XAML 代码绑定控件并显示其状态。这很不方便。破坏了良好实践和 (或) MVVM 模式的应用。为什么 View Model 需要知道粗体字体当前的状态?你需要这个知识。分析不应该在呈现器中进行。为此,我们使用单独的任务。
我认为这是经过深思熟虑的。值得为之奋斗。
此方法有效
基础与文章 https://codeproject.org.cn/Articles/437175/Autocompletion-with-RichTextBox-in-WPF-as-Behavior 中描述的相同。
图片精灵感谢:http://www.gentleface.com/free_icon_set.html。
我们从识别假设开始
- 工具栏控件应具有呈现状态的可能性
- 所有操作都必须在不使用后台代码的情况下完成。
- 不想使用 ViewModel 中的 ToolBar 控件链接。
- ToolBar 控件仅是视图中内部呈现逻辑的一部分,不脱离 Xaml。
- 呈现应在样式更改后立即发生。
- 语法不应限制适用于 ToolBar 控件的状态呈现。这意味着,要显示原子的当前状态,你可以应用各种控件,包括内置控件和通过仅应用
IValueConverter
转换器添加的控件。 - 行为可能不依赖于除宿主(RichTextBox 显示)以外的任何其他对象。
- 当事件更改发生后,行为就能生效。
- 该行为能够拍摄当前位于指示器位置的状态照片。
已做出假设,原型是视觉化的,然后转化为代码。
我是如何做的
正如之前建议的,整个基础是 Behavior<DependencyObject>
,这里是 RichTextBox。
完成标准任务
订阅 RichTextBox 控件的事件。
#region Attached and Detaching
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
this.AssociatedObject.PreviewKeyUp += new KeyEventHandler(AssociatedObject_PreviewKeyUp);
this.AssociatedObject.GotFocus += new RoutedEventHandler(AssociatedObject_GotFocus);
}
void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
prologAdapter();
}
void AssociatedObject_PreviewKeyUp(object sender, KeyEventArgs e)
{
prologAdapter();
}
void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
prologAdapter();
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.PreviewMouseLeftButtonUp -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
this.AssociatedObject.PreviewKeyUp -= new KeyEventHandler(AssociatedObject_PreviewKeyUp);
this.AssociatedObject.GotFocus-=new RoutedEventHandler(AssociatedObject_GotFocus);
}
#endregion
序言只是一个初步检查,看看我们是否能够在此基础上工作。
// Test whether we starting
void prologAdapter()
{
TextPointer tp = this.AssociatedObject.CaretPosition;
if (_flagPointer != null && tp.CompareTo(_flagPointer) == 0)
return;
else
_flagPointer = tp;
startAdapter();
}
如果可以,那么这就是开始。
// started Work
void startAdapter()
{
coreReset();
this.AssociatedObject.Dispatcher.BeginInvoke(new Action(() =>
{
#region Assets
_font_background = this.AssociatedObject.Selection.GetPropertyValue(Run.BackgroundProperty) as SolidColorBrush;
_font_foreground = this.AssociatedObject.Selection.GetPropertyValue(Run.ForegroundProperty) as SolidColorBrush;
_fontFamily = this.AssociatedObject.Selection.GetPropertyValue(Run.FontFamilyProperty) as FontFamily;
_fontSize = (double)this.AssociatedObject.Selection.GetPropertyValue(Run.FontSizeProperty);
_fontStretch = (FontStretch)this.AssociatedObject.Selection.GetPropertyValue(Run.FontStretchProperty);
_fontStyle = (FontStyle)this.AssociatedObject.Selection.GetPropertyValue(Run.FontStyleProperty);
_fontWeight = (FontWeight)this.AssociatedObject.Selection.GetPropertyValue(Run.FontWeightProperty);
_lineHeight = (double)this.AssociatedObject.Selection.GetPropertyValue(Paragraph.LineHeightProperty);
_isHyphenationEnabled = (bool)this.AssociatedObject.Selection.GetPropertyValue(Paragraph.IsHyphenationEnabledProperty);
_textDecorations = this.AssociatedObject.Selection.GetPropertyValue(Run.TextDecorationsProperty) as TextDecorationCollection;
_textIndent = (double)this.AssociatedObject.Selection.GetPropertyValue(Paragraph.TextIndentProperty);
_textAlign = (TextAlignment)this.AssociatedObject.Selection.GetPropertyValue(Paragraph.TextAlignmentProperty);
#endregion // assets
#region Color
coreFontColor();
#endregion
#region TextAlignment
coreTextAlignment();
#endregion
#region FontWeight
coreFontWeight();
#endregion
#region FontStyle
coreFontStyle();
#endregion
#region Font type
OnChangeProperty("FontFamily");
#endregion
#region FontSize
coreFontSize();
#endregion
#region TextDecorations
coreTextDecorations();
#endregion
if (this.FlippinFotoAtChangeDataInStyle) OnFoto(null);
}));
}
在这段代码中,我们读取了我们想要了解的关于样式的所有信息。样式被存储在下一个变量中。我现在将不再深入探讨具体的细节和讨论。我只注意到这里有多种数据类型,有些可以被允许为 null 而不受任何后果。按区域划分的资产是一系列函数,支持我们的数据崩溃分析并将其分解为原子。这里不展示所有内容,只展示一个主要的粒子反应器。
解析字体粗细的有趣方法
void coreFontWeight()
{
this._fontWeight_toggleNormal_Bold = _fontWeight > FontWeights.SemiBold;
if (_fontWeight < FontWeights.Bold)
{
if (_fontWeight < FontWeights.Normal)
{
if (FontWeight.Compare(_fontWeight, FontWeights.ExtraLight) == 0) this._fontWeight_200_ExtraLight = true;
else if (FontWeight.Compare(_fontWeight, FontWeights.ExtraLight) < 0) this._fontWeight_100_Thin = true;
else
if (FontWeight.Compare(_fontWeight, FontWeights.UltraLight) <= 0)
{
this._fontWeight_250_UltraLight = true;
}
else { this._fontWeight_300_Light = true; }
}
else
{
if (_fontWeight < FontWeights.Medium)
{
if (FontWeight.Compare(_fontWeight, FontWeights.Normal) == 0)
{
this._fontWeight_350_Normal = true;
}
else
{ this._fontWeight_400_Regular = true; }
}
else
{
if (FontWeight.Compare(_fontWeight, FontWeights.Medium) < 0)
{
this._fontWeight_550_DemiBold = true;
}
else if (FontWeight.Compare(_fontWeight, FontWeights.Medium) == 0)
{
this._fontWeight_500_Medium = true;
}
else if (FontWeight.Compare(_fontWeight, FontWeights.Medium) > 0)
{
this._fontWeight_600_SemiBold = true;
}
}
}
}
else
{
if (_fontWeight < FontWeights.Black)
{
if (FontWeight.Compare(_fontWeight, FontWeights.ExtraBold) == 0)
{
this._fontWeight_750_ExtraBold = true;
}
else // (FontWeight.Compare(_fontWeight, FontWeights.ExtraBold) < 0)
{
this._fontWeight_700_Bold = true;
}
}
else if (FontWeight.Compare(_fontWeight, FontWeights.Black) == 0)
{
this._fontWeight_850_Black = true;
}
else if (FontWeight.Compare(_fontWeight, FontWeights.ExtraBold) < 0)
{
this._fontWeight_800_UltraBold = true;
}
else
{
if (FontWeight.Compare(_fontWeight, FontWeights.Heavy) < 0)
{
this._fontWeight_900_ExtraBlack = true;
}
else if (FontWeight.Compare(_fontWeight, FontWeights.ExtraBlack) == 0)
{
this._fontWeight_900_Heavy = true;
}
else
{
this._fontWeight_950_UltraBlack = true;
}
}
}
OnChangeProperty("FontWeight_ToggleNormal_Bold");
OnChangeProperty("FontWeight_ExtraLight");
OnChangeProperty("FontWeight_Thin");
OnChangeProperty("FontWeight_UltraLight");
OnChangeProperty("FontWeight_Light");
OnChangeProperty("FontWeight_Normal");
OnChangeProperty("FontWeight_Regular");
OnChangeProperty("FontWeight_DemiBold");
OnChangeProperty("FontWeight_Medium");
OnChangeProperty("FontWeight_SemiBold");
OnChangeProperty("FontWeight_ExtraBold");
OnChangeProperty("FontWeight_Bold");
OnChangeProperty("FontWeight_Black");
OnChangeProperty("FontWeight_UltraBold");
OnChangeProperty("FontWeight_ExtraBlack");
OnChangeProperty("FontWeight_Heavy");
OnChangeProperty("FontWeight_UltraBlack");
接受了 17 种粗体名称。然而,实际上只有 9 个实际值。该过程按顺序选择中值并与模式进行比较,直到找到所需的大小。从 fontWeight
指定的值中选择适当的值。
- 第一行设置了一个开关,表示是否为粗体或正常,这在大多数应用程序中已经足够了,而且我们也会主要使用它,而不会进行太多细微的调整。
- 接下来寻找粗细的正确标记并将其标记为 true。此时值得一提的是,在每次传递之前,控件数据都会被重置。
- 最后,我们按顺序报告事件的所有属性。
这就是该类在检测属性方面的运行原理。
更改状态可视化控件
几行前我写道,灯光不会反映程序代码部分的状态。
<ToggleButton x:Name="text_boldButton"
Style="{StaticResource toolBarToggleButton}"
Command="EditingCommands.ToggleBold"
IsChecked="{Binding FontWeight_ToggleNormal_Bold}"
Width="23"
Content="{DynamicResource text_boldImage}" />
例如:
void setToggleButton()
{
this.text_boldButton.IsChecked = true;
}
如果尝试将值赋给 object.SetValue
函数,将不会产生效果。
private void insertImageButton_Click(object sender, RoutedEventArgs e)
{
this.text_boldButton.SetValue(IsChecked, true);
}
或
private void insertImageButton_Click(object sender, RoutedEventArgs e)
{
this. text_boldButton.SetValue(IsCheckedProperty, true);
}
生成
Error(错误)
“The name 'IsChecked' does not exist in the current context”
和
this.text_boldButton.IsChecked.Value= true;
Error(错误)
Property or indexer 'System.Nullable<bool>.Value' cannot be assigned to -- it is read only
剩下要创建一个属性
- 附加 DependencyProperty
- 独立 DependencyProperty
- 后台代码中的属性通过添加
INotifyPropertyChanged
接口实现。 - 呈现器(ViewModel)中的属性。
然而,我希望在检索宿主类行为的类中应用这种奇迹。这不需要在代码(名称)中充斥着 xaml.cs,或者脏乱的 MVVM 或 MVP 模式。即使是逻辑。当 View Model(呈现器)中需要这种知识时,我使用这里引用的另一个适配器功能。
#region FotoCommand
private ICommand _fotoCommand;
public ICommand FotoCommand
{
get { return _fotoCommand ?? (_fotoCommand = new BehaviorDelegateCommand(OnFoto, CanOnFoto)); }
set { _fotoCommand = value; }
}
protected void OnFoto(object parametr)
{
FotoStyleFlippinEventHandler handler = FotoStyleFlippinChanged;
if (handler != null) handler(
this,
new FotoStyleFlippinEventArgs(GetStateAllStyleFactory())
);
}
protected bool CanOnFoto(object parametr)
{
return true;
}
/// <summary>
/// Factory for foto actual all data
/// </summary>
/// <returns>AddBehavior.FotoStyleFlippin</returns>
public FotoStyleFlippin FotoStyleFlippin
{
get
{
OnFoto(null);
return GetStateAllStyleFactory();
}
}
private FotoStyleFlippin GetStateAllStyleFactory()
{
FotoStyleFlippin f = new FotoStyleFlippin
{
TextAlignment = this.TextAlignment,
TextAlignment_Left = this.TextAlignment_Left,
TextAlignment_Right = this.TextAlignment_Right,
TextAlignment_Center = this.TextAlignment_Center,
TextAlignment_Justify = this.TextAlignment_Justify,
FontBackground = this.FontBackground,
FontForeground = this.FontForeground,
FontFamily = this.FontFamily,
FontSize = this.FontSize,
FontStretch = this.FontStretch,
FontStretch_1_UltraCondensed = this.FontStretch_1_UltraCondensed,
FontStretch_2_ExtraCondensed = this.FontStretch_2_ExtraCondensed,
FontStretch_3_Condensed = this.FontStretch_3_Condensed,
FontStretch_4_SemiCondensed = this.FontStretch_4_SemiCondensed,
FontStretch_5_Medium = this.FontStretch_5_Medium,
FontStretch_6_SemiExpanded = this.FontStretch_6_SemiExpanded,
FontStretch_7_Expanded = this.FontStretch_7_Expanded,
FontStretch_8_ExtraExpanded = this.FontStretch_8_ExtraExpanded,
FontStretch_9_UltraExpanded = this.FontStretch_9_UltraExpanded,
FontWeight = this.FontWeight,
FontWeight_Thin = this.FontWeight_Thin,
FontWeight_ExtraLight = this.FontWeight_ExtraLight,
FontWeight_UltraLight = this.FontWeight_UltraLight,
FontWeight_Light = this.FontWeight_Light,
FontWeight_Normal = this.FontWeight_Normal,
FontWeight_Regular = this.FontWeight_Regular,
FontWeight_Medium = this.FontWeight_Medium,
FontWeight_DemiBold = this.FontWeight_DemiBold,
FontWeight_SemiBold = this.FontWeight_SemiBold,
FontWeight_Bold = this.FontWeight_Bold,
FontWeight_ExtraBold = this.FontWeight_ExtraBold,
FontWeight_UltraBold = this.FontWeight_UltraBold,
FontWeight_Black = this.FontWeight_Black,
FontWeight_Heavy = this.FontWeight_Heavy,
FontWeight_ExtraBlack = this.FontWeight_ExtraBlack,
FontWeight_UltraBlack = this.FontWeight_UltraBlack,
FontStyle = this.FontStyle,
FontStyle_Normal = this.FontStyle_Normal,
FontStyle_Italic = this.FontStyle_Italic,
FontStyle_Oblique = this.FontStyle_Oblique,
Typography = this.Typography,
LineHeight = this.LineHeight,
IsHyphenationEnabled = this.IsHyphenationEnabled,
TextIndent = this.TextIndent
};
if (wantReflect)
f.PropertyChanged += delegate { reflection(f); };
return f;
}
private void reflection(FotoStyleFlippin f)
{
// for implementation
throw new NotImplementedException();
}
public event FotoStyleFlippinEventHandler FotoStyleFlippinChanged;
public bool FlippinFotoAtChangeDataInStyle { get; set; }
private bool wantReflect = false;
#endregion
在 ViewModel 中,你可以应用与我们的适配器的链接。
namespace Wpf.ImpressDemo
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AddBehavior;
using System.Windows.Controls;
public class DemoMV
{
#region Ctor
public DemoMV()
{
var x = App.Current.MainWindow as MainWindow;
x.Loaded += delegate
{
var b = (App.Current.MainWindow.Content as Grid)
.Children.OfType<ToolBarTray>()
.FirstOrDefault(
tbt => tbt.ToolBars.Where(
n => n.DataContext != null && (n.DataContext
as ImpressStyleOverCaretAdapter) != null) != null)
.ToolBars.FirstOrDefault(tb =>
(tb.DataContext as ImpressStyleOverCaretAdapter) != null)
.DataContext as ImpressStyleOverCaretAdapter;
if (b != null)
}
b.FotoStyleFlippinChanged += new FotoStyleFlippinEventHandler(b_FotoStyleFlippinChanged);
b.FlippinFotoAtChangeDataInStyle = true;
}
// or
// var fotoStyle = b.FotoStyleFlippin;
};
}
void b_FotoStyleFlippinChanged(object sender, FotoStyleFlippinEventArgs e)
{
var fotoStyle = e.FotoStyleFlippin;
// Processed
}
#endregion
}
}
因为
<toolbar datacontext="{Binding ElementName=behDemoRTB}" />
DemoVM 类的实例已连接到 MainView 中的项目。其他一些事情我将留给实现和思考,尽管示例运行良好并满足了之前提出的任务。在此示例中,颜色仅在适配器生成的列表中识别。值得注意的是,有两个点调用了负责 PropertyChanged
的函数。所谓的“小”和“大” onPropertyChanged
。这是由于分离了接收和发送属性中的数据更改。
<RichTextBox x:Name="demoRTB"
TabIndex="0">
<i:Interaction.Behaviors>
<beh:ImpressStyleOverCaretAdapter x:Name="behDemoRTB"></beh:ImpressStyleOverCaretAdapter>
</i:Interaction.Behaviors>
<FlowDocument>
<Paragraph FontSize="24" TextAlignment="Center">
<Run FontWeight="Bold">SKUTNIK</Run>
<LineBreak />
<Run FontSize="32" FontFamily="Jing Jing">Andrzej</Run>
</Paragraph>
</FlowDocument>
</RichTextBox>
附加功能
- 字体样式生成器
public IEnumerable<FontFamily> FontItemsSource { get { return Fonts.SystemFontFamilies; } }
public double[] FontsSize { get { return new double[] { 6, 7, 8, 9, 10,
10.5, 11, 11.5, 12, 13, 14, 16, 18, 20, 24, 28, 32, 40, 48, 64, 72, 128 }; } }
private static IEnumerable<Rectangle> ColorsItemsForFontForeground()
{
List<Rectangle> m_colorForSetColor = new List<Rectangle>();
// Generate the color swatch
int scale = 75;
for (int r = 0; r < 255; r += scale )
{
for (int g = 0; g < 255; g += scale )
{
for (int b = 0; b < 255; b += scale )
{
m_colorForSetColor.Add(new Rectangle
{
Width = 16,
Height = 16,
Margin = new Thickness(2),
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 1,
Fill = new SolidColorBrush(Color.FromArgb(255, (byte)r, (byte)g, (byte)b))
});
}
}
}
return m_colorForSetColor;
}
在 XAML 代码中
<ToolBar DataContext="{Binding ElementName=behDemoRTB}">
<ComboBox x:Name="fontTypeComboBox"
Height="23"
ItemsSource="{Binding FontItemsSource, AsyncState=true}"
Width="80"
Margin="5 5 1 5"
SelectedIndex="0"
SelectedValue="{Binding FontFamily}"
VerticalContentAlignment="Center"
Padding="10 0 0 0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
FontFamily="{Binding Source}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="fontSizesComboBox"
ItemsSource="{Binding FontsSize}"
Height="23"
Width="40"
SelectedIndex="6"
SelectedValue="{Binding FontSize}" />
<ComboBox x:Name="fontColorsComboBox"
Height="23"
Width="40"
SelectedValue="{Binding FontForegroundRect}"
ItemsSource="{Binding GenerateColorForSetColorForeground}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="200" />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
…
</ToolBar>
摘要
本文比给定的主题要短得多。好吧,我决定不深入探讨完整的实验,这是最好的适配器方式,因为它包含知识。所以,那些中间名字是实验家的人是这样说的。
此致 Andrzej Skutnik