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

WPF TextBox 中的拼写建议

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (19投票s)

2007年2月19日

CPOL

4分钟阅读

viewsIcon

143769

downloadIcon

1902

探讨一种直观的纠正 TextBox 中拼写错误的方法。

Screenshot - SmartTextBox.png

引言

本文介绍了一个 TextBox 的子类,它提供了帮助用户轻松修复拼写错误的功能。SmartTextBox 通过弹出可自定义的“建议”列表来扩展 WPF 内置的拼写检查支持。建议列表可以通过 F1 键手动显示,也可以通过一个简单的函数调用以编程方式显示。当显示建议列表时,其高度会短暂地进行动画处理,为用户提供微妙的视觉提示。

SmartTextBox 可以为 WPF 应用程序带来额外的便利和专业感,这是大多数用户非常看重的。当应用程序允许用户输入供他人阅读的笔记或评论时,使用它可能会非常合适。

背景

WPF 为 TextBoxRichTextBox 中的文本拼写检查提供了支持。SpellCheck 类暴露了用于拼写检查的主要 API。截至 WPF v1,拼写检查支持仍不具备全部功能,但预计在后续版本中会更加成熟。

例如,只支持少数几种语言(我记得只有英语、德语和法语),并且您无法使用自定义词典。有关拼写检查支持的更多信息,请在此处阅读更多内容。

TextBox 控件已集成到 WPF 的拼写检查功能中。启用拼写检查时,拼写错误的单词下方会显示一条红色波浪线。另外,如果您右键单击拼写错误的单词,ContextMenu 将提供拼写建议,如下所示。

SmartTextBox 的功能是对此标准功能的补充。

使用 SmartTextBox

API

SmartTextBox 提供了几个公共成员,可用于控制其行为和外观。这是它的公共 API:

属性

  • AreSuggestionsVisible - 如果建议列表当前已显示,则返回 true
  • IsCurrentWordMisspelled - 如果插入符号索引处的单词拼写错误,则返回 true
  • SuggestionListBoxStyle - 获取/设置应用于显示拼写建议的 ListBoxStyle。这是一个依赖属性。

方法

  • GetSpellingError() - 返回当前插入符号索引处单词的 SpellingError,如果当前单词未拼写错误,则返回 null
  • HideSuggestions() - 隐藏建议列表并将焦点返回到输入区域。如果建议列表尚未显示,则不执行任何操作。
  • ShowSuggestions() - 显示插入符号索引处单词的建议列表。如果当前单词未拼写错误,则此方法不执行任何操作。

基础知识

您可以在 XAML 中像创建其他元素一样创建 SmartTextBox 的实例:

<jas:SmartTextBox>I contian a typo!</jas:SmartTextBox>

默认情况下,拼写检查功能是启用的。如果您需要禁用该功能,可以通过多种方式实现:

this.smartTextBox.SpellCheck.IsEnabled = false;

SpellCheck.SetIsEnabled( this.smartTextBox, false );

或者(在 XAML 中):

<jas:SmartTextBox SpellCheck.IsEnabled="false" />

样式化建议列表

拼写建议列表显示在 SmartTextBox附加层 (adorner layer) 中。列表本身是一个 ListBox 控件,它位于一个附加元素 (Adorner) 中。如果要为建议列表设置样式,请将 SmartTextBoxSuggestionListBoxStyle 依赖属性设置为一个 Style

例如,下面是演示应用程序中使用的 Style,正如文章顶部的截图所示:

<Style x:Key="SuggestionListBoxStyle" TargetType="ListBox">
  <Setter Property="BitmapEffect">
    <Setter.Value>
      <DropShadowBitmapEffect ShadowDepth="1" Softness="0.5" />
    </Setter.Value>
  </Setter>
  <Setter Property="BorderBrush" Value="Gray" />
  <Setter Property="ItemContainerStyle">
    <Setter.Value>
      <Style TargetType="ListBoxItem">
        <Setter Property="Border.BorderBrush" Value="LightGray" />
        <Setter Property="Border.BorderThickness" Value="0,0,0,0.5" />
        <Setter Property="FontWeight" Value="Bold" />
        <Setter Property="Margin" Value="2,4" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
      </Style>
    </Setter.Value>
  </Setter>
</Style>

<Style TargetType="jas:SmartTextBox">
  <Setter Property="AcceptsReturn" Value="True" />
  <Setter Property="BorderBrush" Value="Gray" />
  <Setter Property="Margin" Value="4" />
  <Setter
    Property="SuggestionListBoxStyle"
    Value="{StaticResource SuggestionListBoxStyle}" />
  <Setter Property="TextWrapping" Value="Wrap" />
</Style>

工作原理

本文的其余部分将讨论 SmartTextBox 的工作原理。您无需阅读更多内容即可在应用程序中使用此控件。

附加元素 (Adorner) 与弹出窗口 (Popup)

当我第一次设计这个类时,我尝试将建议 ListBox 托管在一个弹出窗口 (Popup) 中。在使用 Popup 时遇到了几个问题,所以我决定使用一个自定义的附加元素 (Adorner) 来托管建议。

当窗口移动时,Popup 并不会“跟随”SmartTextBox。如果我将窗口状态更改为“最大化”或“还原”,Popup 会恢复到正确的位置,但当窗口移动时不会。Adorner 没有这个问题,因为它们不是像 Popup 那样的独立顶层窗口。

此外,在使用 Popup 时,我还遇到了一些与输入焦点相关的令人沮丧的问题。当我使用附加元素 (adorner) 方法时,这些问题就消失了。

我所知道的使用附加元素 (adorner) 显示建议列表的唯一缺点是它们可能会被裁剪。如果包含 SmartTextBox 的窗口底部非常靠近一个拼写错误的单词,则建议列表可能不完全可见。但我认为这不是一个大问题。这是我所指的:

显示建议列表

当插入符号位于一个拼写错误的单词中且用户按下 F1 键时,SmartTextBox 需要显示一个拼写建议列表。这通过以下(大大简化的)代码实现:

protected override void OnPreviewKeyDown( KeyEventArgs e )
{
 base.OnPreviewKeyDown( e );
 if( e.Key == Key.F1 )
 {
  this.AttemptToShowSuggestions();
  if( this.AreSuggestionsVisible )
   this.suggestionList.SelectedIndex = 0;
 }
}

void AttemptToShowSuggestions()
{
 if( this.AreSuggestionsVisible )
  return;

 // If there is no spelling error, there is no

 // need to show the list of suggestions.

 SpellingError error = this.GetSpellingError();
 if( error == null )
  return;

 this.suggestionList.ItemsSource = error.Suggestions;
 this.ShowSuggestions();
}

public SpellingError GetSpellingError()
{
 int idx = this.FindClosestCharacterInCurrentWord();
 return idx < 0 ? null : base.GetSpellingError( idx );
}

public void ShowSuggestions()
{
 if( this.AreSuggestionsVisible || !this.IsCurrentWordMisspelled )
  return;

 AdornerLayer layer = AdornerLayer.GetAdornerLayer( this );
 if( layer == null )
  return;

 // Position the adorner beneath the misspelled word.

 int idx = this.FindBeginningOfCurrentWord();
 Rect rect = base.GetRectFromCharacterIndex( idx );
 this.adorner.SetOffsets( rect.Left, rect.Bottom );

 // Add the adorner into the adorner layer.

 layer.Add( this.adorner );

 // Since the ListBox might have a new set of items but has not

 // rendered yet, we force it to calculate its metrics so that

 // the height animation has a sensible target value.

 this.suggestionList.Measure(
    new Size( Double.PositiveInfinity, Double.PositiveInfinity ) );
 this.suggestionList.Arrange(
    new Rect( new Point(), this.suggestionList.DesiredSize ) );

 // Animate the ListBox's height to the natural value.

 DoubleAnimation anim = new DoubleAnimation();
 anim.From = 0.0;
 anim.To = this.suggestionList.ActualHeight;
 anim.Duration = new Duration( TimeSpan.FromMilliseconds( 200 ) );
 anim.FillBehavior = FillBehavior.Stop;
 this.suggestionList.BeginAnimation( ListBox.HeightProperty, anim );

 this.areSuggestionsVisible = true;
}

修复拼写错误

当用户选择了一个建议单词来替换拼写错误时,SmartTextBox 需要更新文本并隐藏建议列表。大部分繁重的工作由 SpellingError 类处理,如下所示:

void suggestionList_PreviewKeyDown( object sender, KeyEventArgs e )
{
 if( this.suggestionList.SelectedIndex < 0 )
  return;

 if( e.Key == Key.Escape )
 {
  this.HideSuggestions();
 }
 else if( e.Key == Key.Space || e.Key == Key.Enter || e.Key == Key.Tab )
 {
  this.ApplySelectedSuggestion();
  // Mark the event as handled so that the keystroke

  // does not propogate to the TextBox.

  e.Handled = true;
 }
}

void ApplySelectedSuggestion()
{
 if( !this.AreSuggestionsVisible || this.suggestionList.SelectedIndex < 0 )
  return;

 SpellingError error = this.GetSpellingError();
 if( error != null )
 {
  string correctWord = this.suggestionList.SelectedItem as string;
  error.Correct( correctWord );
  base.CaretIndex = this.FindEndOfCurrentWord();
  base.Focus();
 }
 this.HideSuggestions();
}

public void HideSuggestions()
{
 if( !this.AreSuggestionsVisible )
  return;

 this.suggestionList.ItemsSource = null;

 AdornerLayer layer = AdornerLayer.GetAdornerLayer( this );
 if( layer != null )
  layer.Remove( this.adorner );

 base.Focus();
 this.areSuggestionsVisible = false;
}

修订历史

  • 2007 年 2 月 19 日 - 创建文章。
  • 2007 年 2 月 25 日 - 修复了一个涉及依赖属性值继承到 SmartTextBox 附加层中显示的 ListBox 的错误。Zhou Yong(又名 Sheva)和Ian Griffiths 帮助找到了解决方案。Sheva 在此处写了博客。该问题在 WPF 论坛的此帖中得到解决。我从本文中删除了关于元素视觉树与其附加层之间关系的错误陈述。我还更新了文章的源代码下载。
© . All rights reserved.