65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (4投票s)

2012年8月28日

CPOL

5分钟阅读

viewsIcon

21850

downloadIcon

422

目前的问题是如何反射 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

© . All rights reserved.