Vistaからでしょうか。追加された4バイト文字とか2バイトに入りきらない文字たち。こいつら、stringとcharではうまく扱えません。
例を見てみましょう。
こんなXAMLを用意します。
<Page x:Class="App37.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App37" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <TextBox x:Name="TextBox" TextChanged="TextBox_TextChanged" /> <TextBlock x:Name="TextBlock" /> <TextBlock x:Name="TextBlockLength" /> </StackPanel> </Page>
そして、TextChangedに以下のようなコードを書きます。テキストの長さと、1文字ずつばらした内容を出力するといった感じです。
using Windows.UI.Xaml.Controls; namespace App37 { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { var result = ""; for (int i = 0; i < this.TextBox.Text.Length; i++) { result += $"[{this.TextBox.Text[i]}]"; } this.TextBlock.Text = result; this.TextBlockLength.Text = this.TextBox.Text.Length.ToString(); } } }
実行すると以下のような感じに動くプログラムです。
ここに𩹉とか🐑とか🍖とかを書き込むと残念な結果になります。文字数も出力もだめだめです。
🐑とかがcharでいう2つぶんのサイズになってるっぽいですね。こういうのを正しく扱うためにStringInfoというクラスがあります。 このクラスでstringをくるんでやることで、文字数を正しくカウントできるようになります。LengthInTextElementsプロパティがそれになります。コードを書き換えてみましょう。
using System.Globalization; using Windows.UI.Xaml.Controls; namespace App37 { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { var stringInfo = new StringInfo(this.TextBox.Text); var result = ""; for (int i = 0; i < stringInfo.LengthInTextElements; i++) { result += $"[{this.TextBox.Text[i]}]"; } this.TextBlock.Text = result; this.TextBlockLength.Text = stringInfo.LengthInTextElements.ToString(); } } }
文字数は改善しましたが、1文字単位に分割するところがまだうまくいってません。これを改善するには、StringInfoのGetNextTextElementメソッドを使えばよさそうに見えます。これを使うと文字列とインデックスを指定するといい感じに、文字を返してくれます。
以下のように修正してみましょう。
using System.Globalization; using Windows.UI.Xaml.Controls; namespace App37 { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { var stringInfo = new StringInfo(this.TextBox.Text); var result = ""; for (int i = 0; i < stringInfo.LengthInTextElements; i++) { result += $"[{StringInfo.GetNextTextElement(this.TextBox.Text ,i)}]"; } this.TextBlock.Text = result; this.TextBlockLength.Text = stringInfo.LengthInTextElements.ToString(); } } }
実行すると惜しい感じになります。
このとき指定するindexは、何文字目かではなく、素のstringのindexなので4バイト文字とかがあるとずれてしまいます。正しいindexを取得するには、StringInfoクラスのParseCombiningCharactersメソッドを使って取得します。 このメソッドの戻り値に、何番目の文字列がstringのindexの何個目かという情報が入っています。こんな感じで使います。
using System.Globalization; using Windows.UI.Xaml.Controls; namespace App37 { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { var stringInfo = new StringInfo(this.TextBox.Text); // 1文字単位に分割するのに必要なindexを取得 var index = StringInfo.ParseCombiningCharacters(this.TextBox.Text); var result = ""; for (int i = 0; i < stringInfo.LengthInTextElements; i++) { // ParseCombiningCharactersの戻り値からindexを取得する result += $"[{StringInfo.GetNextTextElement(this.TextBox.Text ,index[i])}]"; } this.TextBlock.Text = result; this.TextBlockLength.Text = stringInfo.LengthInTextElements.ToString(); } } }
これで実行するとめでたく正しい表示が出来るようになります。
まとめ
4バイト文字とかが入ってても正しい文字数を取得するにはStringInfoでくるんでLengthInTextElementsプロパティを使う。1文字単位で文字を取得するにはStringInfo.ParseCombiningCharactersとStringInfo.GetNextTextElementを組み合わせて使う。ということでした。