こんにちはHabr!
TextBlockに基づいて、テキストを強調表示する機能を持つコントロールを作成しました。 はじめに、その使用例を示し、それがどのように作成されたかを説明します。
コントロールの使用例<local:HighlightTextBlock TextWrapping="Wrap"> <local:HighlightTextBlock.HighlightRules> <local:HighlightRule HightlightedText="{Binding Filter, Source={x:Reference thisWindow}}"> <local:HighlightRule.Highlights> <local:HighlightBackgroung Brush="Yellow"/> <local:HighlightForeground Brush="Black"/> </local:HighlightRule.Highlights> </local:HighlightRule> </local:HighlightTextBlock.HighlightRules> <Run FontWeight="Bold">Property:</Run> <Run Text="{Binding Property}"/> </local:HighlightTextBlock>
開発開始
検索バーに入力された
TextBlockのテキストを強調表示するのに時間がかかりました。 一見、タスクは簡単に思えました。 テキストを3つの
Run要素に分割し、すべてのテキスト、検索文字列、およびその位置(1/2/3)をコンバーターに転送しました。 Middle
Runには
背景があり
ます 。
いくつかの偶然の一致があるかもしれないという考えが私の頭に浮かんだとき、私は実装を始める時間がありませんでした。 したがって、このアプローチは適合しません。
Xamlを 「オンザフライ」で作成し、
XamlReaderを使用して解析し、
TextBlockにスローするというアイデアがまだありました。 しかし、臭いがするので、この考えもすぐに落ちました。
次の(そして最後の)アイデアは、ルールを強調するシステムを作成し、それを
TextBlockに固定することでした。
TextBlockまたは
AttachedPropertyに基づいたブラックジャックと女の子を使用したコントロールの2つのオプションがあります。 いくつかの熟考の後、強調表示機能によって
TextBlock自体の機能に制限が課される可能性があり、それを継承する場合は簡単に解決できるため、個別のコントロールを作成する方が良いと判断しました。
レディコントロールのソース
それでは始めましょう。 私はすぐに、最初のアイデアをテストするのと同じプロジェクトでコントロールを行ったことを警告しますので、名前空間に注意を払ってはいけません。 メインプロジェクトにコントロールを含めるときに(またはgithubにアップロードします)、これらのことを頭に入れておきます。
Xamlコントロールマークアップでは、
Loadedイベントハンドラーを除き、すべてがクリーンです
<TextBlock x:Class="WpfApplication18.HighlightTextBlock" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="TextBlock_Loaded"> </TextBlock>
コードに移動します。
ネタバレ見出し public partial class HighlightTextBlock : TextBlock {
私の意見では、コメントは冗長なので、ここではコードを説明しません。
タスクキューコードは次のとおりです。
ネタバレ見出し public class TaskQueue { Task _worker; Queue<Action> _queue; int _maxTasks; bool _deleteOld; object _lock = new object(); public TaskQueue(int maxTasks, bool deleteOld = true) { if (maxTasks < 1) throw new ArgumentException("TaskQueue: 0"); _maxTasks = maxTasks; _deleteOld = deleteOld; _queue = new Queue<Action>(maxTasks); } public bool Add(Action action) { if (_queue.Count() < _maxTasks) { _queue.Enqueue(action); DoWorkAsync(); return true; } if (_deleteOld) { _queue.Dequeue(); return Add(action); } return false; } void DoWorkAsync() { if(_queue.Count>0) _worker = Task.Factory.StartNew(DoWork); } void DoWork() { lock (_lock) { if (_queue.Count > 0) { var currentTask = Task.Factory.StartNew(_queue.Dequeue()); currentTask.Wait(); DoWorkAsync(); } } } }
ここではすべてが非常に簡単です。 新しい挑戦が到着します。 キューに場所がある場合は、キューに配置されます。 そうでない場合、フィールド
_deleteOld == trueの場合 、次のタスク(最新)を削除して新しいタスクを配置し、そうでない場合はfalse(タスクは追加されません)を返します。
ルールコレクションのコードは次のとおりです。 理論的には、
ObservableCollectionは不要になる
可能性がありますが、このコレクションには将来追加の機能が必要になる場合があります。
ネタバレ見出し public class HighlightRulesCollection : DependencyObject, INotifyCollectionChanged, ICollectionViewFactory, IList, IList<HighlightRule> { ObservableCollection<HighlightRule> _items; public HighlightRulesCollection() { _items = new ObservableCollection<HighlightRule>(); _items.CollectionChanged += _items_CollectionChanged; } public HighlightRule this[int index] { get { return ((IList<HighlightRule>)_items)[index]; } set { ((IList<HighlightRule>)_items)[index] = value; } } object IList.this[int index] { get { return ((IList)_items)[index]; } set { ((IList)_items)[index] = value; } } public int Count { get { return ((IList<HighlightRule>)_items).Count; } } public bool IsFixedSize { get { return ((IList)_items).IsFixedSize; } } public bool IsReadOnly { get { return ((IList<HighlightRule>)_items).IsReadOnly; } } public bool IsSynchronized { get { return ((IList)_items).IsSynchronized; } } public object SyncRoot { get { return ((IList)_items).SyncRoot; } } public event NotifyCollectionChangedEventHandler CollectionChanged; public int Add(object value) { return ((IList)_items).Add(value); } public void Add(HighlightRule item) { ((IList<HighlightRule>)_items).Add(item); } public void Clear() { ((IList<HighlightRule>)_items).Clear(); } public bool Contains(object value) { return ((IList)_items).Contains(value); } public bool Contains(HighlightRule item) { return ((IList<HighlightRule>)_items).Contains(item); } public void CopyTo(Array array, int index) { ((IList)_items).CopyTo(array, index); } public void CopyTo(HighlightRule[] array, int arrayIndex) { ((IList<HighlightRule>)_items).CopyTo(array, arrayIndex); } public ICollectionView CreateView() { return new CollectionView(_items); } public IEnumerator<HighlightRule> GetEnumerator() { return ((IList<HighlightRule>)_items).GetEnumerator(); } public int IndexOf(object value) { return ((IList)_items).IndexOf(value); } public int IndexOf(HighlightRule item) { return ((IList<HighlightRule>)_items).IndexOf(item); } public void Insert(int index, object value) { ((IList)_items).Insert(index, value); } public void Insert(int index, HighlightRule item) { ((IList<HighlightRule>)_items).Insert(index, item); } public void Remove(object value) { ((IList)_items).Remove(value); } public bool Remove(HighlightRule item) { return ((IList<HighlightRule>)_items).Remove(item); } public void RemoveAt(int index) { ((IList<HighlightRule>)_items).RemoveAt(index); } IEnumerator IEnumerable.GetEnumerator() { return ((IList<HighlightRule>)_items).GetEnumerator(); } void _items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke(this, e); } }
ハイライトルールコードは次のとおりです。
ネタバレ見出し public class HighlightRule : DependencyObject { public delegate void HighlightTextChangedEventHandler(object sender, HighlightTextChangedEventArgs e); public event HighlightTextChangedEventHandler HighlightTextChanged; public HighlightRule() { Highlights = new ObservableCollection<Highlight>(); }
ここにはロジックがほとんどないため、コメントはありません。
ハイライト用の抽象クラスは次のとおりです。
public abstract class Highlight : DependencyObject { public abstract void SetHighlight(Span span); public abstract void SetHighlight(TextRange range); }
現在、フラグメントを強調表示する2つの方法を知っています。
Spanおよび
TextRange経由。 これまでのところ、選択したメソッドは強調表示手順のコードで記述されていますが、将来的にはオプションでこれを行う予定です。
背景を強調する相続人です public class HighlightBackgroung : Highlight { public override void SetHighlight(Span span) { Brush brush = null; Application.Current.Dispatcher.BeginInvoke(new ThreadStart(() => { brush = Brush; })).Wait(); span.Background = brush; } public override void SetHighlight(TextRange range) { Brush brush = null; Application.Current.Dispatcher.BeginInvoke(new ThreadStart(() => { brush = Brush; })).Wait(); range.ApplyPropertyValue(TextElement.BackgroundProperty, brush); }
さて、スレッドセーフを除き、コメントすることはありません。 実際には、インスタンスはメインスレッドでスピンする必要があり、メソッドはどこからでも呼び出すことができます。
そして、これはテキスト強調コードです public class HighlightForeground : Highlight { public override void SetHighlight(Span span) { Brush brush = null; Application.Current.Dispatcher.BeginInvoke(new ThreadStart(() => { brush = Brush; })).Wait(); span.Foreground = brush; } public override void SetHighlight(TextRange range) { Brush brush = null; Application.Current.Dispatcher.BeginInvoke(new ThreadStart(() => { brush = Brush; })).Wait(); range.ApplyPropertyValue(TextElement.ForegroundProperty, brush); }
おわりに
まあ、それがおそらくすべてです。 あなたの意見を聞きたいです。
更新:現在のバージョンのコードは、現在githubにあるものとわずかに異なりますが、一般的にコントロールは同じ原理で動作します。 コントロールは
.net Framework 4.0用に作成されました。
GitHubリンク