Domanda

Ho TextBlock che ha Inlines dynamicly aggiunto ad esso (in pratica mucchio di oggetti Run che sono o corsivo o grassetto).

Nella mia domanda ho funzione di ricerca.

Voglio essere in grado di evidenziare il testo del TextBlock che è in fase di ricercato.

Evidenziando intendo cambiare alcune parti di colore di TextBlock di testo (tenendo presente che si può mettere in evidenza diversi oggetti Run diversi alla volta).

Ho provato questo esempio http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf -rendered-Page.aspx

Ma cuciture molto instabile: (

C'è modo semplice per risolvere questo problema?

È stato utile?

Soluzione

Questa domanda è simile a Come visualizzare i risultati della ricerca in prodotti WPF controllare con termini di ricerca evidenziati

In risposta a questa domanda, mi si avvicinò con un approccio che utilizza un IValueConverter. Il convertitore prende un frammento di testo, formati in valido markup XAML, e utilizza un XamlReader per istanziare il markup in oggetti-quadro.

La spiegazione completa è piuttosto lungo, quindi ho postato sul mio blog: evidenziando termini della query in un WPF TextBlock

Altri suggerimenti

rispondere e tirai fuori la necessità di un parser XML. Si fa un grande lavoro per spiegare ognuno dei pezzi in il suo blog , Tuttavia questo non ha bisogno di me per aggiungere eventuali librerie extra, ecco come ho fatto.

Fase uno, fare una classe convertitore:

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.");
        }

    }

Fase due: Invece di un TextBlock utilizzare un ContentBlock. Passare nella stringa (si sarebbe di utilizzare per il vostro textBlock) al blocco di contenuti, in questo modo:

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

Fase tre: Assicurarsi che il test si passa è tokenized con |~S~| e |~E~|. E lasciare che l'evidenziazione abbia inizio!

Note:
È possibile modificare lo stile nella corsa per determinare cosa e come il testo è evidenziato
Assicurati di aggiungere la classe Converter per lo spazio dei nomi e delle risorse. Questo potrebbe anche richiedere una ricostruzione da far funzionare.

Per strana coincidenza, ho recentemente scritto un articolo che risolve il problema stesso. Si tratta di un controllo personalizzato che ha le stesse proprietà come un TextBlock (in modo da poter scambiare è fuori per un TextBlock ovunque ne abbiate bisogno), ed ha una proprietà in più che è possibile associare a detta HighLightText, e dove il valore di HighLightText è trovato nella struttura principale Text (case insensitive), viene evidenziato.

E 'stato un controllo abbastanza straight-forward per creare, e si può trovare l'articolo qui:

WPF TextBlock Con Search stringa corrispondente

E il codice completo come soluzione qui:

SearchMatchTextblock (GitHub)

Ho avuto un problema simile - cercando di attuare una ricerca di testo su un carico di presentatori che in fondo rappresentano un report. Il rapporto è stato originariamente scritto in una stringa e siamo stati sfruttando di FlowDocumentViewer costruito nel ctrl-F -. Non è molto buona e ha alcune opzioni di suoni bizzarri, ma era sufficiente

Se si desidera solo una cosa del genere è possibile effettuare le seguenti operazioni:

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

Abbiamo deciso di andare per una riscrittura del report è tenuto in sincronia con il resto del programma e praticamente ogni modifica cambia, dovendo ricreare l'intero ogni rapporto significa che questo è piuttosto lento. Volevamo migliorare questo spostando ad un update-le-bits-you-need-per modellare, ma aveva bisogno di vista del modello (e non solo una stringa) per essere in grado di farlo in un modo sano di mente! Abbiamo voluto preservare la funzionalità di ricerca prima di invertire il rapporto comunque e fare ancora meglio e hanno evidenziando la 'corrente' cercare la posizione in un colore e la ricerca di altri colpi in un altro.

Ecco una versione semplificata della mia soluzione; una classe che deriva da TextBlock che aggiunge una proprietà di dipendenza di tipo HighlightingInformation. Io non ho incluso lo spazio dei nomi e usings in quanto sono sensibili.

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);
    }
}

Il tipo di questa classe può essere associato a utilizza il metodo di aggiornamento, quando si tratta di testo e l'elenco dei punti salienti sono cambiati per aggiornare l'elenco di piste. I punti salienti farsi guardare qualcosa di simile:

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};
    }
}

Per produrre l'esatta riscossione punti salienti è un problema separato, che ho praticamente risolto trattando la raccolta dei presentatori come un albero che in modo ricorsivo la ricerca di contenuti - nodi foglia sono quelli che hanno un contenuto e altri nodi basta avere figli. Se si ricerca in profondità si ottiene l'ordine che ci si aspetta. È possibile quindi fondamentalmente scrivere un wrapper per la lista dei risultati per tenere traccia della posizione. Im non andando a inserire tutto il codice per questo -. La mia risposta qui è di documentare come si può fare WPF fare evidenziazione policroma in stile MVP

Non ho usato INotifyPropertyChanged o CollectionChanged qui come non abbiamo bisogno delle modifiche per essere multi-cast (ad esempio un presentatore ha più viste). Inizialmente ho provato a farlo con l'aggiunta di un evento ha cambiato la notifica per il testo e uno per una lista (che si hanno anche per iscriversi manualmente all'evento INotifyCollectionChanged su). Ho avuto preoccupazioni per le perdite di memoria dalle subcriptions evento comunque e il fatto che gli aggiornamenti per il testo e le luci non è venuto allo stesso tempo reso problematico.

L'unico svantaggio di questo approccio è che la gente non dovrebbe legarsi alla proprietà Text di questo controllo. Nella versione reale ho aggiunto qualche controllo + eccezione di lancio per impedire alla gente di fare questo, ma omesso dal esempio per amor di chiarezza!

Ecco cosa mi è venuta costruendo al largo della TextBlock exisiting e l'aggiunta di una nuova proprietà di dipendenza SearchText denominata:

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));
}

E secondo lei, in questo modo:

<view:SearchHightlightTextBlock SearchText="{Binding TextPropertyContainingTextToSearch}" 
                                Text="{Binding YourTextProperty}"/>

Qui presento un altro approccio per evidenziare il testo. Ho avuto un caso d'uso in cui avevo bisogno di decorare un po 'di codice C # in WPF, ma non volevo usare textBlock.Inlines.Add tipo di sintassi, invece ho voluto generare il XAML evidenziando al volo e poi dinamicamente aggiungerlo a una tela di canapa o di qualche altro contenitore in WPF.

Quindi, si supponga di voler colorare il seguente pezzo di codice e anche evidenziare una parte di esso:

public static void TestLoop(int count)
{ 
   for(int i=0;i<count;i++)
     Console.WriteLine(i);
}

Supponiamo che il codice di cui sopra si trova in un file denominato Test.txt. Si supponga di voler colorare tutti i C # parole chiave (pubblica, statico, invalidare, ecc ..) e tipi semplici (int, String) in blu, e Console.WriteLine evidenziazione in giallo.

Passo 0. Creare una nuova applicazione WPF e includono alcuni esempi di codice simile al precedente in un file chiamato Test.txt

Passaggio 1. Creare una classe Codice Highlighter:

using System.IO;
using System.Text;

public enum HighLightType
{
    Type = 0,
    Keyword = 1,
    CustomTerm = 2
}

public class CodeHighlighter
{
    public static string[] KeyWords = { "public", "static", "void", "return", "while", "for", "if" };
    public static string[] Types = { "string", "int", "double", "long" };

    private string FormatCodeInXaml(string code, bool withLineBreak)
    {
        string[] mapAr = { "<","&lt;" , //Replace less than sign
                            ">","&gt;" }; //Replace greater than sign
        StringBuilder sb = new StringBuilder();

        using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
        {
            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();

                line = line.Replace("\t", "&#160;&#160;&#160;&#160;"); //Replace tabs
                line = line.Replace(" ", "&#160;"); //Replace spaces

                for (int i = 0; i < mapAr.Length; i += 2)
                    line = line.Replace(mapAr[i], mapAr[i + 1]);

                if (withLineBreak)
                    sb.AppendLine(line + "<LineBreak/>"); //Replace line breaks
                else
                    sb.AppendLine(line);
            }

        }
        return sb.ToString();
    }


    private string BuildForegroundTag(string highlightText, string color)
    {
        return "<Span Foreground=\"" + color + "\">" + highlightText + "</Span>";
    }

    private string BuildBackgroundTag(string highlightText, string color)
    {
        return "<Span Background=\"" + color + "\">" + highlightText + "</Span>";
    }

    private string HighlightTerm(HighLightType type, string term, string line)
    {
        if (term == string.Empty)
            return line;

        string keywordColor = "Blue";
        string typeColor = "Blue";
        string statementColor = "Yellow";

        if (type == HighLightType.Type)
            return line.Replace(term, BuildForegroundTag(term, typeColor));
        if (type == HighLightType.Keyword)
            return line.Replace(term, BuildForegroundTag(term, keywordColor));
        if (type == HighLightType.CustomTerm)
            return line.Replace(term, BuildBackgroundTag(term, statementColor));

        return line;
    }

    public string ApplyHighlights(string code, string customTerm)
    {
        code = FormatCodeInXaml(code, true);
        customTerm = FormatCodeInXaml(customTerm, false).Trim();

        StringBuilder sb = new StringBuilder();
        using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
        {
            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();

                line = HighlightTerm(HighLightType.CustomTerm, customTerm, line);

                foreach (string keyWord in KeyWords)
                    line = HighlightTerm(HighLightType.Keyword, keyWord, line);

                foreach (string type in Types)
                    line = HighlightTerm(HighLightType.Type, type, line);

                sb.AppendLine(line);
            }
        }

        return sb.ToString();

    }
}

Passaggio 2. Aggiungere un tag Canvas XAML al tuo MainWindow.xaml

<Window x:Class="TestCodeVisualizer.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:TestCodeVisualizer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Canvas Name="canvas" />
</Window>

Passaggio 3. In Your WPF Application aggiungere il seguente codice: (assicurarsi che test.txt si trova nella posizione corretta):

using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Markup;

namespace TestCodeVisualizer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            string testText = File.ReadAllText("Test.txt");
            FrameworkElement fe = GenerateHighlightedTextBlock(testText, "Console.WriteLine");
            this.canvas.Children.Add(fe);
        }


        private FrameworkElement GenerateHighlightedTextBlock(string code, string term)
        {
            CodeHighlighter ch = new CodeHighlighter();
            string uc = "<UserControl xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>[CONTENT]</UserControl>";

            string content = "<TextBlock>" + ch.ApplyHighlights(code, term) + "</TextBlock>";
            uc = uc.Replace("[CONTENT]", content);

            FrameworkElement fe = XamlReader.Load(new System.IO.MemoryStream(Encoding.UTF8.GetBytes(uc))) as FrameworkElement;
            return fe;
        }

    }
}

finito per scrivere seguente codice

Al momento ha qualche bug, ma risolve il problema

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;
                }
            }
        }
    }
}

Se si sta gestendo ContainerContentChanging per il vostro ListViewBase, si può prendere il seguente approccio: TextBlock evidenziando per WinRT / ContainerContentChanging

Si prega di notare che questo codice è per Windows RT. La sintassi WPF sarà leggermente diversa. Si noti inoltre che se si utilizza vincolante per popolare la proprietà TextBlock.Text, il testo generato dal mio approccio sarà sovrascritto. Io uso ContainerContentChanging per riempire i campi di destinazione a causa della radicale-aumento delle prestazioni e miglioramenti in utilizzo della memoria, contro normale vincolante. Io uso vincolante solo per gestire i dati di origine, non la visualizzazione dei dati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top