18

Inlines が動的に追加された TextBlock があります (基本的には、斜体または太字の Run オブジェクトの束)。

私のアプリケーションには検索機能があります。

検索されている TextBlock のテキストを強調表示できるようにしたいと考えています。

強調表示とは、TextBlock テキストの色の特定の部分を変更することを意味します (一度に複数の異なる Run オブジェクトを強調表示する場合があることに注意してください)。

私はこの例を試しましたhttp://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered-page.aspx

しかし、それは非常に不安定なようです:(

この問題を解決する簡単な方法はありますか?

4

10 に答える 10

18

この質問は、強調表示されたクエリ用語を使用して WPF 項目コントロールに検索結果を表示する方法に似ています

その質問に答えるために、IValueConverter を使用するアプローチを思いつきました。コンバーターはテキスト スニペットを受け取り、それを有効な XAML マークアップにフォーマットし、XamlReader を使用してマークアップをフレームワーク オブジェクトにインスタンス化します。

完全な説明はかなり長いので、ブログに投稿しました: WPF TextBlock でのクエリ用語の強調表示

于 2011-03-03T16:27:10.440 に答える
12

私はdthrasers の回答を取得し、XML パーサーの必要性を取り除きました。彼は自分のブログでそれぞれの部分を説明する素晴らしい仕事をしていますが、これには追加のライブラリを追加する必要はありませんでした。

ステップ 1、コンバーター クラスを作成します。

class StringToXamlConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string input = value as string;
        if (input != null)
        {
            var textBlock = new TextBlock();
            textBlock.TextWrapping = TextWrapping.Wrap;
            string escapedXml = SecurityElement.Escape(input);

            while (escapedXml.IndexOf("|~S~|") != -1) {
            //up to |~S~| is normal
            textBlock.Inlines.Add(new Run(escapedXml.Substring(0, escapedXml.IndexOf("|~S~|"))));
            //between |~S~| and |~E~| is highlighted
            textBlock.Inlines.Add(new Run(escapedXml.Substring(escapedXml.IndexOf("|~S~|") + 5,
                                      escapedXml.IndexOf("|~E~|") - (escapedXml.IndexOf("|~S~|") + 5))) 
                                      { FontWeight = FontWeights.Bold, Background= Brushes.Yellow });
            //the rest of the string (after the |~E~|)
            escapedXml = escapedXml.Substring(escapedXml.IndexOf("|~E~|") + 5);
            }

            if (escapedXml.Length > 0)
            {
                textBlock.Inlines.Add(new Run(escapedXml));                      
            }
            return textBlock;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException("This converter cannot be used in two-way binding.");
    }

}

ステップ 2: TextBlock の代わりに ContentBlock を使用します。次のように、文字列 (textBlock に使用する) をコンテンツ ブロックに渡します。

<ContentControl Margin="7,0,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Center"
                Content="{Binding Description, Converter={StaticResource CONVERTERS_StringToXaml}, Mode=OneTime}">
</ContentControl>

ステップ 3: 渡すテキストに、強調表示するテキスト部分の|~S~|前後が含まれていることを確認します。|~E~|たとえば、この文字列"my text |~S~|is|~E~| good"では、isが黄色で強調表示されます。

注:
実行中にスタイルを変更して、テキストが強調表示される内容と方法を決定
できます。 Converter クラスを名前空間とリソースに追加してください。これを機能させるには、再構築が必要になる場合もあります。

于 2014-02-25T21:47:45.660 に答える
2

私は同様の問題を抱えていました - 基本的にレポートを表す多数のプレゼンターに対してテキスト検索を実装しようとしました。レポートはもともと文字列に書き込まれており、FlowDocumentViewer の組み込みを利用しctrlFいました。これはあまり良くなく、いくつかの奇妙なオプションがありますが、十分でした。

そのようなものが必要な場合は、次のことができます。

        <FlowDocumentScrollViewer>
            <FlowDocument>
                <Paragraph FontFamily="Lucida Console" FontSize="12">
                    <Run Text="{Binding Content, Mode=OneWay}"/>
                </Paragraph>
            </FlowDocument>
        </FlowDocumentScrollViewer>

レポートはプログラムの残りの部分と同期しており、基本的に編集ごとに変更されるため、レポート全体を毎回再作成する必要があるため、書き直すことにしました。これは非常に遅いことを意味します。必要なビットを更新するモデルに移行することでこれを改善したかったのですが、それを適切な方法で実行できるようにするには、(単なる文字列ではなく) ビュー モデルが必要でした! ただし、レポートを交換する前に検索機能を保持し、「現在の」検索位置を 1 つの色で強調表示し、他の検索ヒットを別の色で強調表示したいと考えました。

これが私のソリューションの簡略版です。そこから派生するクラスはTextBlock、 Type の依存関係プロパティを追加しますHighlightingInformation。名前空間と使用法は機密であるため、含めていません。

public class HighlightingTextBlock : TextBlock
{
    public static readonly DependencyProperty HighlightingProperty =
        DependencyProperty.Register("Highlighting", typeof (HighlightingInformation), typeof (HighlightingTextBlock));

    public HighlightingInformation Highlighting
    {
        get { return (HighlightingInformation)GetValue(HighlightingProperty); }
        set { SetValue(HighlightingProperty, value); }
    }

    public HighlightingTextBlock()
    {
        AddValueChangedCallBackTo(HighlightingProperty, UpdateText);
    }

    private void AddValueChangedCallBackTo(DependencyProperty property, Action updateAction)
    {
        var descriptor = DescriptorFor(property);
        descriptor.AddValueChanged(this, (src, args) => updateAction());
    }

    private DependencyPropertyDescriptor DescriptorFor(DependencyProperty property)
    {
        return DependencyPropertyDescriptor.FromProperty(property, GetType());
    }

    private void UpdateText()
    {
        var highlighting = Highlighting;
        if (highlighting == null)
            return;
        highlighting.SetUpdateMethod(UpdateText);

        var runs = highlighting.Runs;
        Inlines.Clear();
        Inlines.AddRange(runs);
    }
}

このクラスをバインドできる型は、テキストの場合に update メソッドを使用し、ハイライトのリストが変更されて Run のリストが更新されます。ハイライト自体は次のようになります。

public class Highlight
{
    private readonly int _length;
    private readonly Brush _colour;

    public int Start { get; private set; }

    public Highlight(int start, int length,Brush colour)
    {
        Start = start;
        _length = length;
        _colour = colour;
    }

    private string TextFrom(string currentText)
    {
        return currentText.Substring(Start, _length);
    }

    public Run RunFrom(string currentText)
    {
        return new Run(TextFrom(currentText)){Background = _colour};
    }
}

ハイライトの正しいコレクションを生成することは別の問題です。私は基本的に、コンテンツを再帰的に検索するツリーとしてプレゼンターのコレクションを扱うことで解決しました.リーフノードはコンテンツを持ち、他のノードは子を持つノードです. 深さ優先で検索すると、期待どおりの順序で検索できます。次に、基本的に結果のリストの周りにラッパーを記述して、位置を追跡できます。このためのすべてのコードを投稿するつもりはありません-ここでの私の回答は、MVPスタイルでwpfに多色の強調表示をさせる方法を文書化することです.

変更をマルチキャストにする必要がなかったため、ここではINotifyPropertyChangedorを使用していません (たとえば、1 人のプレゼンターが複数のビューを持っている場合など)。CollectionChanged最初に、テキスト用のイベント変更通知とリスト用のイベント変更通知を追加することで、これを実行しようとしました (INotifyCollectionChangedイベントを手動でサブスクライブする必要もあります)。しかし、イベントのサブスクリプションからのメモリ リークが懸念され、テキストとハイライトの更新が同時に行われなかったことが問題でした。

このアプローチの 1 つの欠点は、このコントロールの Text プロパティにバインドしてはならないことです。実際のバージョンでは、人々がこれを行うのを止めるためにいくつかのチェックと例外のスローを追加しましたが、わかりやすくするために例から省略しました!

于 2012-07-13T17:05:02.887 に答える
2

TextBlockこれは、既存のものから構築し、という名前の新しい依存関係プロパティを追加することによって思いついたものですSearchText

public class SearchHightlightTextBlock : TextBlock
{
    public SearchHightlightTextBlock() : base() { }

    public String SearchText { get { return (String)GetValue(SearchTextProperty); }
                               set { SetValue(SearchTextProperty, value); } }      

    private static void OnDataChanged(DependencyObject source,
                                      DependencyPropertyChangedEventArgs e)
    {
        TextBlock tb = (TextBlock)source;

        if (tb.Text.Length == 0)
            return;

        string textUpper = tb.Text.ToUpper();
        String toFind = ((String) e.NewValue).ToUpper();
        int firstIndex = textUpper.IndexOf(toFind);
        String firstStr = tb.Text.Substring(0, firstIndex);
        String foundStr = tb.Text.Substring(firstIndex, toFind.Length);
        String endStr = tb.Text.Substring(firstIndex + toFind.Length, 
                                         tb.Text.Length - (firstIndex + toFind.Length));

        tb.Inlines.Clear();
        var run = new Run();
        run.Text = firstStr;
        tb.Inlines.Add(run);
        run = new Run();
        run.Background = Brushes.Yellow;
        run.Text = foundStr;
        tb.Inlines.Add(run);
        run = new Run();
        run.Text = endStr;

        tb.Inlines.Add(run);
    }

    public static readonly DependencyProperty SearchTextProperty =
        DependencyProperty.Register("SearchText", 
                                    typeof(String), 
                                    typeof(SearchHightlightTextBlock), 
                                    new FrameworkPropertyMetadata(null, OnDataChanged));
}

そして、あなたの見解では、これは:

<view:SearchHightlightTextBlock SearchText="{Binding TextPropertyContainingTextToSearch}" 
                                Text="{Binding YourTextProperty}"/>
于 2016-04-13T15:36:05.363 に答える
0

次のコードを書くことになった

現時点ではバグはほとんどありませんが、問題は解決しています

if (Main.IsFullTextSearch)
{
    for (int i = 0; i < runs.Count; i++)
    {
        if (runs[i] is Run)
        {
            Run originalRun = (Run)runs[i];

            if (Main.SearchCondition != null && originalRun.Text.ToLower()
                .Contains(Main.SearchCondition.ToLower()))
            {
                int pos = originalRun.Text.ToLower()
                          .IndexOf(Main.SearchCondition.ToLower());

                if (pos > 0)
                {
                    Run preRun = CloneRun(originalRun);
                    Run postRun = CloneRun(originalRun);

                    preRun.Text = originalRun.Text.Substring(0, pos);
                    postRun.Text = originalRun.Text
                        .Substring(pos + Main.SearchCondition.Length);

                    runs.Insert(i - 1 < 0 ? 0 : i - 1, preRun);
                    runs.Insert(i + 1, new Run(" "));
                    runs.Insert(i + 2, postRun);

                    originalRun.Text = originalRun.Text
                        .Substring(pos, Main.SearchCondition.Length);

                    SolidColorBrush brush = new SolidColorBrush(Colors.Yellow);
                    originalRun.Background = brush;

                    i += 3;
                }
            }
        }
    }
}
于 2009-04-16T11:07:57.973 に答える
0

If you are handling ContainerContentChanging for your ListViewBase, you can take the following approach: TextBlock highlighting for WinRT/ContainerContentChanging

Please note that this code is for Windows RT. The WPF syntax will be slightly different. Also note that if you are using binding to populate the TextBlock.Text property, the text generated by my approach will be overwritten. I use ContainerContentChanging to populate target fields because of radically-increased performance and improvements in memory usage, vs. normal binding. I use binding only to manage the source data, not the data view.

于 2015-10-14T18:32:04.870 に答える