教你如何從 WPF 的 TextBox 中,取得已經輸入的完整字串,包含原本就在裡面的文字以及還沒變成文字的注音符號或拼音
這陣子公司 PM 給的更新搜尋規格,新增了推薦關鍵字的功能,原先我們做的搜尋是整個打完關鍵字按下 Enter 才會開始搜尋,但是這次要在使用者打到一半時,就先將打到一半的關鍵字,加上還沒被組合成字的注音,送到 Server API 來取得建議使用者搜尋的關鍵字,我們原本的實作是將 TextBox.Text Binding 到 ViewModel 的屬性上,但是這個方法並不會將打到一半的注音符號一起 Set 到該屬性上,會變成像下面這張圖一樣,直到這個字串被 Commit 後,才會被 Set 到屬性中,請注意,後面這些 ㄘ ㄘ ㄘ ㄘ ㄘ ㄘ,下面有橫線所以還在編輯狀態
我們 Google 嘗試了很多方法,像是 Win32 的 IMM 系列 API,或是 TextCompositionManager,不過都還是有點麻煩,最後在一個不小心的情況下發現了一件事情.....
直接拿 TextBox.Text 得到的字串會包含著還沒被 IME Commit 的文字
一整個早上花時間在解的問題,居然最後解法是直接拿 Text 屬性,不知道是我餵狗(Google) 技術不夠好還是眼瞎了,找了很久資料居然都沒看到這件事情 Orz,所以決定寫上來跟大家分享...
下面是這個 Demo App 的原始碼
MainWindow.xaml
<Window x:Class="WpfImeText.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfImeText"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Text="Use binding"/>
<TextBox Text="{Binding BindingText, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding BindingText}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="Use TextBox.Text"/>
<TextBox Name="MyTextBox" KeyUp="MyTextBox_KeyUp"/>
<TextBlock Name="MyTextBlock"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace WpfImeText
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string bindingText;
public string BindingText
{
get
{
return bindingText;
}
set
{
bindingText = value;
NotifyPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void MyTextBox_KeyUp(object sender, KeyEventArgs e)
{
MyTextBlock.Text = MyTextBox.Text;
}
}
}