我正在创建一个粗略的页面来检查输入是否正确。关键是你输入文本,如果它与原始文本匹配,那么你的字符是白色的,否则是红色的。问题是我使用textblock
c来显示文本textwrapping
,如果单词不适合,则会将其转移到新行,但是当输入我的单词时,一半保留在同一行,一半保留在新行(由于wrappanel
),并且我需要正确的传输,而发生这种情况是因为我以某种方式注意到该人输入了当前行的最后一个单词,然后会从文本中删除该行,但我不知道该怎么做,我附上问题的代码和截图如下:
正如你在这里看到的,这个词bloomed
一半在上面,一点在下面。为了防止这种情况,我想以某种方式捕获一个人输入了当前行的最后一个单词(如示例中所示freshly
)的事实,然后通过删除当前行并在集合中清除它来更新文本。
查看代码:
<Grid Background="#282a36">
<TextBlock Text="{Binding OriginalText}"
FontFamily="Consolas"
Foreground="Gray"
FontSize="16"
IsHitTestVisible="False"
TextWrapping="Wrap"/>
<TextBox Text="{Binding UserInput, UpdateSourceTrigger=PropertyChanged}"
FontSize="16"
FontFamily="Consolas"
Background="Transparent"
Foreground="Transparent"
BorderBrush="Transparent"
TextWrapping="Wrap"/>
<ItemsControl ItemsSource="{Binding ColoredUserInput}" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Character}"
Foreground="{Binding Color}"
FontSize="16"
FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
视图模型代码:
public class MainViewModel : INotifyPropertyChanged
{
private string _originalText = "The curious cat explored every corner of the garden, chasing butterflies and sniffing at freshly bloomed flowers. Meanwhile, the old oak tree stood tall, its branches swaying gently in the summer breeze. Birds chirped happily overhead, creating a symphony of natural sounds. The sun painted the sky in shades of orange and pink as evening approached.";
private string _userInput;
private ObservableCollection<ColoredCharacter> _coloredUserInput;
public MainViewModel()
{
ColoredUserInput = new ObservableCollection<ColoredCharacter>();
}
public string OriginalText
{
get => _originalText;
set
{
_originalText = value;
OnPropertyChanged();
}
}
public string UserInput
{
get => _userInput;
set
{
_userInput = value;
OnPropertyChanged();
UpdateColoredUserInput();
}
}
public ObservableCollection<ColoredCharacter> ColoredUserInput
{
get => _coloredUserInput;
set
{
_coloredUserInput = value;
OnPropertyChanged();
}
}
private void UpdateColoredUserInput()
{
ColoredUserInput.Clear();
for (int i = 0; i < OriginalText.Length; i++)
{
if (i < UserInput.Length)
{
var character = OriginalText[i];
var color = UserInput[i] == character ? Brushes.White : Brushes.Red;
ColoredUserInput.Add(new ColoredCharacter { Character = character.ToString(), Color = color });
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ColoredCharacter
{
public string Character { get; set; }
public Brush Color { get; set; }
}
因为我上面说过页面是原始的(它仅用于创建我将来要传输到项目的内容),那么这不是我在大多数情况下了解的纯代码,但如果这里有一些非常糟糕的东西,我会很高兴知道!
更新:根据我的想法,我自己将通过该方法在文本中创建连字符,并简单地UpdateColoredUserInput
检查下一个字符是否存在并且等于\n
,然后删除当前行,但是出现了新问题创建此方法是因为\n
需要插入这些方法来检查它是否能按字符将新单词放入当前行(这里您需要弄清楚如何根据字体大小和宽度进行计算,textblock
一般来说这听起来不像一个非常好的方法,但至少它看起来有点不像神奇地(或通过formattedtext
)找出这个词是否适合的方法)。
让我向您展示我正在谈论的一个例子。这不是您问题的答案,而是一种替代方案。
我们需要当前按下的按钮。在这里,您可以像以前一样通过文本字段进行操作,但这是一个会产生其他拐杖的拐杖,您只需要其中的当前按钮。我应该怎么办?有 2 个选项:
通过“互动”
下载 NuGet 包
Microsoft.Xaml.Behaviors.Wpf
在 XAML 的开头包含它:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
接下来,对所需的对象(例如,窗口)执行类似的操作:
正如您所看到的,我们与特定事件绑定,当触发时将调用命令。好吧,因为我们需要这个命令的数据,所以我们
EventArgs
向它传递参数。命令本身将是标准的,参数是KeyEventArgs
。这种方法的缺点是它不完全是 MVVM,因为KeyEventArgs
即使Key
这样也System.Windows.Input
清楚地向我们暗示这是一个视图层。如果这对您来说并不重要,那么这个选择就相当不错了。 PS 该事件KeyDown
不会给出本地化字母(这是您需要的),因此最好使用PreviewTextInput
,它会立即给出当前布局的本地化文本。除了参数类型之外,绑定将类似。通过接口
MVVM 是将所有内容划分为松散连接的层,这反过来意味着我们可以与 View 进行通信,就像 View 可以与 VM 进行通信一样,但这必须通过抽象、接口小心地完成。
我们创建一个接口来描述接收按下的按钮的逻辑,例如,如下所示:
接下来,在窗口构造函数中,或者更好的是,在窗口事件中
Loaded
,我们检查“DataContext
这是一个接口吗?”如果是,那么我们订阅所需的事件并将必要的数据传输到虚拟机。接下来,我们从这个接口继承VM类并实现它,这实际上是传输数据的另一种选择。
有了按下的符号,我们就可以继续前进,即编写界面和基本逻辑。让我们从逻辑开始...
我们需要每个字母的“状态”。让它成为“无状态”、“已选择”、“正确”、“无效”。
接下来,我们将为每个字母创建一个 VM,其中包含符号本身和状态。运行过程中状态发生变化,这意味着必须调用INPC。我将使用CommunityToolkit,你可以使用任何其他方法。
接下来,我们创建这些虚拟机的集合:
我们以任何方便的方式填充它,我将在构造函数中执行以下操作:
现在让我们创建一个选择下一个字符的方法,如下所示:
我们需要在“测试开始”时调用此方法一次(如果您是这样做的话),或者像我一样,仅在构造函数中调用此方法。
现在点击时调用的方法,我们在其中编写验证逻辑,同时我们移动到下一个符号:
就是这样,我们已经完成了逻辑,现在是 UI,它将尽可能简单,即
ItemsControl
像您一样指定一个ItemsPanel
,但是ItemTemplate
将会Border
突出显示TextBlock
符号本身,还会有触发器来设置必要的颜色:以防万一,整个代码:
嗯,结果是:
正如您所看到的,它相对简单,没有不必要的拐杖,一组简单的字母,您可以轻松缩放,更改字体等,即使您将其放在一列中,一切都会按其应有的方式工作和传输。
我解决了这个问题,除此之外,正如我所写,
EvgeniyZ
我还有几个问题我也会解决,但稍后会解决。正如我所说,我创建了一种方法,当当前行的长度大于最大值时,通过创建人为中断来格式化行。每行的字符数。事实上,我的代码也不是很好,因为FormattedText
我在分配ui
值的地方,也就是说,我必须将它们存储在某个地方并将它们绑定到,textblock
以便不存在差异。正如我已经说过的,问题之一是我将字体大小和文本块大小存储在视图模型中,但我真的不知道如何以其他方式做到这一点但这里是我在将 UpdateColoredUserInput 添加到集合后调用的方法,传递当前的 i,该方法检查字符是否为 \n,如果是,则删除当前行并清除用户输入。
代码很可能并不完美,所以我希望您能提出改进它的想法。