Updated the tool to inspect a whole string instead of a mere character.
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UnicodeCharacterInspector
|
||||
{
|
||||
internal sealed class CharacterInspectorViewModel : BindableObject
|
||||
{
|
||||
private class CharacterCollection : INotifyCollectionChanged, IList<string>
|
||||
{
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||||
{
|
||||
add { owner.CollectionChanged += value; }
|
||||
remove { owner.CollectionChanged -= value; }
|
||||
}
|
||||
|
||||
private CharacterInspectorViewModel owner;
|
||||
|
||||
internal CharacterCollection(CharacterInspectorViewModel owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public string this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= owner.characterCount) throw new IndexOutOfRangeException();
|
||||
|
||||
return owner.characters[index];
|
||||
}
|
||||
}
|
||||
|
||||
string IList<string>.this[int index]
|
||||
{
|
||||
get { return this[index]; }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public int Count { get { return owner.characterCount; } }
|
||||
|
||||
bool ICollection<string>.IsReadOnly { get { return true; } }
|
||||
|
||||
void ICollection<string>.Add(string item) { throw new NotSupportedException(); }
|
||||
void IList<string>.Insert(int index, string item) { throw new NotSupportedException(); }
|
||||
|
||||
bool ICollection<string>.Remove(string item) { throw new NotSupportedException(); }
|
||||
void IList<string>.RemoveAt(int index) { throw new NotSupportedException(); }
|
||||
|
||||
void ICollection<string>.Clear() { throw new NotSupportedException(); }
|
||||
|
||||
public int IndexOf(string item) { return Array.IndexOf(owner.characters, item); }
|
||||
public bool Contains(string item) { return Array.IndexOf(owner.characters, item) >= 0; }
|
||||
|
||||
public void CopyTo(string[] array, int arrayIndex) { owner.characters.CopyTo(array, arrayIndex); }
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
int length = owner.characterCount;
|
||||
var array = owner.characters;
|
||||
|
||||
for (int i = 0; i < length; ++i)
|
||||
yield return array[i];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
|
||||
}
|
||||
|
||||
private event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
private string text;
|
||||
private int selectedCharacterIndex = -1;
|
||||
private string[] characters = new string[1024];
|
||||
private int characterCount;
|
||||
private CharacterInfoViewModel selectedCharacterInfo = new CharacterInfoViewModel();
|
||||
private readonly CharacterCollection characterCollection;
|
||||
|
||||
public CharacterInspectorViewModel()
|
||||
{
|
||||
characterCollection = new CharacterCollection(this);
|
||||
}
|
||||
|
||||
private void NotifyCollectionChanged(NotifyCollectionChangedAction action)
|
||||
{
|
||||
var handler = CollectionChanged;
|
||||
|
||||
if (handler != null)
|
||||
handler(this, new NotifyCollectionChangedEventArgs(action));
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return text; }
|
||||
set
|
||||
{
|
||||
// Analyze the string to detect if there are any similarities between the new and old values.
|
||||
bool keepSelectedIndex = value != null
|
||||
&& text != null
|
||||
&&
|
||||
(
|
||||
value.Length <= text.Length ?
|
||||
text.StartsWith(value, StringComparison.Ordinal) :
|
||||
value.StartsWith(text, StringComparison.Ordinal)
|
||||
);
|
||||
|
||||
// Here, we can deduce the information on string equality and simply return if needed.
|
||||
if (keepSelectedIndex && value.Length == text.Length) return;
|
||||
|
||||
// We don't need the old value anymore.
|
||||
text = value;
|
||||
|
||||
// We always need to reparse the text, as it is not possible to directly map between UTF-16 elements and their respective code point indexes.
|
||||
int index = 0;
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
var enumerator = StringInfo.GetTextElementEnumerator(value);
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (index >= characters.Length) Array.Resize(ref characters, characters.Length * 2);
|
||||
|
||||
// We don't replace pre-existing elements if they already have the correct value.
|
||||
// This should help a bit with GC, by making new string elements as short-lived as possible.
|
||||
string element = enumerator.GetTextElement();
|
||||
if (characters[index] != element) characters[index] = element;
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
characterCount = index;
|
||||
|
||||
// Now that we parsed the text, we really know whether the previous selected index should be left untouched.
|
||||
if (!(keepSelectedIndex = keepSelectedIndex && selectedCharacterIndex < characterCount || selectedCharacterIndex == -1)) selectedCharacterIndex = -1;
|
||||
|
||||
// After all this, raise the change notifications as needed. 😊
|
||||
NotifyPropertyChanged();
|
||||
NotifyCollectionChanged(NotifyCollectionChangedAction.Reset);
|
||||
if (!keepSelectedIndex)
|
||||
{
|
||||
NotifyPropertyChanged("SelectedCharacterIndex");
|
||||
NotifyPropertyChanged("SelectedCharacter");
|
||||
selectedCharacterInfo.Character = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<string> Characters { get { return characterCollection; } }
|
||||
|
||||
public int SelectedCharacterIndex
|
||||
{
|
||||
get { return selectedCharacterIndex; }
|
||||
set
|
||||
{
|
||||
if (selectedCharacterIndex < -1 || selectedCharacterIndex >= characters.Length)
|
||||
throw new ArgumentOutOfRangeException("value");
|
||||
|
||||
if (value != selectedCharacterIndex)
|
||||
{
|
||||
string oldSelectedCharacter = SelectedCharacter;
|
||||
|
||||
selectedCharacterIndex = value;
|
||||
|
||||
string newSelectedCharacter = SelectedCharacter;
|
||||
|
||||
NotifyPropertyChanged();
|
||||
if (newSelectedCharacter != oldSelectedCharacter) NotifyPropertyChanged("SelectedCharacter");
|
||||
selectedCharacterInfo.Character = newSelectedCharacter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedCharacter
|
||||
{
|
||||
get { return selectedCharacterIndex >= 0 ? characters[selectedCharacterIndex] : null; }
|
||||
}
|
||||
|
||||
public CharacterInfoViewModel SelectedCharacterInfo
|
||||
{
|
||||
get { return selectedCharacterInfo; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@
|
||||
xmlns:System="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:local="clr-namespace:UnicodeCharacterInspector"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow"
|
||||
Title="Unicode character inspector"
|
||||
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
|
||||
Height="350" Width="525">
|
||||
<Window.DataContext>
|
||||
<local:CharacterInfoViewModel />
|
||||
<local:CharacterInspectorViewModel />
|
||||
</Window.DataContext>
|
||||
<Grid Margin="10">
|
||||
<Grid.Resources>
|
||||
@@ -23,6 +23,9 @@
|
||||
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource ResourceKey={x:Type TextBlock}}">
|
||||
<Setter Property="Margin" Value="3" />
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ListBox}" BasedOn="{StaticResource ResourceKey={x:Type ListBox}}">
|
||||
<Setter Property="Margin" Value="3" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -34,20 +37,14 @@
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Row="0" Grid.ColumnSpan="2" MaxLength="2" MaxLines="1">
|
||||
<TextBox.Text>
|
||||
<Binding Path="Character" UpdateSourceTrigger="PropertyChanged">
|
||||
<Binding.ValidationRules>
|
||||
<ExceptionValidationRule />
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<TextBlock Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding Character}" FontSize="96" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="Code point" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding CodePoint, StringFormat=U+{0:X4}, TargetNullValue={x:Static System:String.Empty}}" VerticalAlignment="Center" />
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="Category" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Category}" VerticalAlignment="Center" />
|
||||
<TextBox Grid.ColumnSpan="3" MaxLines="1" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<ListBox Grid.Row="1" Grid.RowSpan="4" ItemsSource="{Binding Characters}" SelectedIndex="{Binding SelectedCharacterIndex}" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding SelectedCharacterInfo.Character}" FontSize="96" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<Label Grid.Row="2" Grid.Column="1" Content="Code point" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" Text="{Binding SelectedCharacterInfo.CodePoint, StringFormat=U+{0:X4}, TargetNullValue={x:Static System:String.Empty}}" VerticalAlignment="Center" />
|
||||
<Label Grid.Row="3" Grid.Column="1" Content="Category" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="2" Text="{Binding SelectedCharacterInfo.Category}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
</Compile>
|
||||
<Compile Include="BindableObject.cs" />
|
||||
<Compile Include="CharacterInfoViewModel.cs" />
|
||||
<Compile Include="CharacterInspectorViewModel.cs" />
|
||||
<Compile Include="MainWindow.xaml.cs">
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
||||
Reference in New Issue
Block a user