我正在制作一个检查输入文本的程序。它的本质是有文本,有一个输入文本的字段,输入时检查输入的文本,如果输入正确的字符,则该字符为白色,否则为红色。我创建了这个的第一个实现,总体来说它是有效的,但我有几个问题想要解决(忽略 mvvm 的糟糕实现,正如我所说的选项一,我更关心输入):
1.在 richtextbox 中的文本更新方法中,我与视图交互,我想解决这个问题
2.因为我也使用文本框,如果我输入的字符多于文本中的字符,光标就会向右移动
3.在右侧输入文本时,理想情况下光标应该直接转到下一个单词的开头,但我会继续在右侧写入该单词,直到它变大并落入下一行。
查看代码:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Background="#323437">
<TextBlock x:Name="TextBlock"
Text="{Binding Text}"
Foreground="#646669"
FontSize="24"
TextWrapping="Wrap" Margin="220,146,139,146"/>
<TextBox Text="{Binding InputString, UpdateSourceTrigger=PropertyChanged}"
FontSize="24"
Background="Transparent"
Foreground="Transparent"
TextWrapping="Wrap"
BorderThickness="0"
Width="{Binding ActualWidth, ElementName=TextBlock}"
Height="{Binding ActualHeight, ElementName=TextBlock}" Margin="220,146,139,146"/>
<RichTextBox
x:Name="RichTextBox"
IsReadOnly="True"
FontSize="24"
IsHitTestVisible="False"
Width="{Binding ActualWidth, ElementName=TextBlock}"
Height="{Binding ActualHeight, ElementName=TextBlock}"
Background="Transparent"
BorderThickness="0" Margin="211,146,139,146"/>
</Grid>
模型视图:
public class MainViewModel : INotifyPropertyChanged
{
private string text = "asdhsgsgsdgss dgasaagasvbzbxzasdhsgs gsdgssdgasaa gasvbzbxzasdhs gsgsdgssdga saagas vbzbxz";
public string Text
{
get { return text; }
set { text = value; OnPropertyChanged(); }
}
private string inputString = string.Empty;
public string InputString
{
get { return inputString; }
set
{
if (inputString != value)
{
inputString = value;
OnPropertyChanged();
UpdateRichTextBox();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private FlowDocument _richTextContent;
public FlowDocument RichTextContent
{
get => _richTextContent;
set
{
_richTextContent = value;
OnPropertyChanged();
}
}
public void UpdateRichTextBox()
{
var window = System.Windows.Application.Current.MainWindow as MainWindow;
if (window != null)
{
window.RichTextBox.Document.Blocks.Clear();
var paragraph = new Paragraph();
for (int i = 0; i < inputString.Length; i++)
{
var run = new Run(text[i].ToString());
if (i < text.Length && inputString[i] == text[i])
{
run.Foreground = Brushes.White;
}
else
{
run.Foreground = Brushes.Red;
}
paragraph.Inlines.Add(run);
}
window.RichTextBox.Document.Blocks.Add(paragraph);
}
}
}
我尝试重新制作颜色更新方法,以便它将 run 添加到我将绑定到 richtextbox 的 flowdocument 属性,但是当我尝试这样做时,它给出了数据类型不匹配错误。
对于问题2和问题3都没有想法,所以希望得到你的帮助!
您不应该使用
RichTextBox
。如果您必须对文本进行操作,那么这是一个非常繁重的控制并且使用起来很麻烦。特别是在涉及实时文本搜索的随机内容场景的实时格式化中。
除此之外,修改视觉外观的完整逻辑属于视图而不是视图模型。当您发现自己在视图模型中处理控件时,您肯定知道您的设计已损坏。
您应该通过实现使用
FormattedText
呈现文本的自定义文本视图来手动呈现文本。这样您就可以将源文本、比较输入文本和比较结果绑定到文本视图控件。这实际上取决于文本比较的类型。如果涉及业务规则,则必须在模型中进行这种比较。如果这是纯文本比较,则完全可以在视图中实现。因为如果您比较模型中的文本,则必须向文本视图提供有关文本的元数据,以便它知道如何渲染某些字符。
下面的基本示例实现了视图中简单的文本比较。
您可以通过公开接受用于突出显示文本的画笔的依赖属性来进一步扩展该示例。
该示例还将文本视图的输入文本属性直接绑定到
TextBox
。您可能想绑定到视图模型类。
<local:TextCompareControl SourceText="{Binding TextValue}"
CompareText="{Binding ElementName=TextCopy, Path=Text}" />
<TextBox x:Name="TextCopy" />
TextCompareControl.cs
public class TextCompareControl : Control
{
public string SourceText
{
get => (string)GetValue(SourceTextProperty);
set => SetValue(SourceTextProperty, value);
}
public static readonly DependencyProperty SourceTextProperty = DependencyProperty.Register(
nameof(SourceText),
typeof(string),
typeof(TextCompareControl),
new FrameworkPropertyMetadata(
default(string),
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
OnSourceTextChanged));
private static void OnSourceTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> ((TextCompareControl)d).OnSourceTextChanged(e.OldValue as string, e.NewValue as string);
public string CompareText
{
get => (string)GetValue(CompareTextProperty);
set => SetValue(CompareTextProperty, value);
}
public static readonly DependencyProperty CompareTextProperty = DependencyProperty.Register(
nameof(CompareText),
typeof(string),
typeof(TextCompareControl),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.AffectsRender, OnCompareTextChanged));
private static void OnCompareTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> ((TextCompareControl)d).OnCompareTextChanged(e.OldValue as string, e.NewValue as string);
private FormattedText formattedSourceText;
private void OnSourceTextChanged(string? oldSourceText, string? newSourceText)
{
var typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch);
// You may want to register property changed callbacks for dependencies
// like ForeGHround, FontSize etc. to update the 'formattedSourceText' value
this.formattedSourceText = new FormattedText(
newSourceText,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
this.FontSize,
this.Foreground,
new NumberSubstitution(NumberCultureSource.Text, CultureInfo.CurrentCulture, NumberSubstitution.GetSubstitution(this)),
TextFormattingMode.Ideal,
VisualTreeHelper.GetDpi(this).PixelsPerDip);
}
private void OnCompareTextChanged(string? oldCompareText, string? newCompareText)
{
if (oldCompareText is null || newCompareText is null)
{
return;
}
// Restore original font color if input was deleted
if (newCompareText.Length < oldCompareText?.Length)
{
int deletionIndex = newCompareText.Length;
int deletionCount = oldCompareText.Length - newCompareText.Length;
this.formattedSourceText.SetForegroundBrush(this.Foreground, deletionIndex, deletionCount);
}
}
protected override Size MeasureOverride(Size constraint)
{
if (this.formattedSourceText is null)
{
return new Size(0, 0);
}
double desiredWidth = this.formattedSourceText.WidthIncludingTrailingWhitespace;
double desiredHeight = this.formattedSourceText.Height;
return new Size(desiredWidth, desiredHeight);
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (this.SourceText is null)
{
return;
}
for (int compareIndex = 0; compareIndex < this.CompareText.Length; compareIndex++)
{
char sourceCharacter = this.SourceText[compareIndex];
bool isMatch = sourceCharacter.Equals(this.CompareText[compareIndex]);
if (!isMatch && char.IsWhiteSpace(sourceCharacter))
{
this.formattedSourceText.SetTextDecorations(TextDecorations.Underline, compareIndex, 1);
}
Brush highlightBrush = isMatch ? Brushes.Green : Brushes.Red;
this.formattedSourceText.SetForegroundBrush(highlightBrush, compareIndex, 1);
}
var textAnchor = new Point(0, 0);
drawingContext.DrawText(this.formattedSourceText, textAnchor);
}
}