문제

동적으로 추가 된 텍스트 블록이 있습니다 (기본적으로 이탤릭체 또는 대담한 런 객체가 많이 있습니다).

내 응용 프로그램에는 검색 기능이 있습니다.

검색중인 TextBlock의 텍스트를 강조 할 수 있기를 원합니다.

강조한다는 것은 텍스트 블록 텍스트 색상의 특정 부분을 변경한다는 것을 의미합니다 (한 번에 여러 다른 실행 객체를 강조 할 수 있음을 명심해야합니다).

이 예를 시도했습니다 http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-next-on-wpf-rendered-page.aspx

그러나 그것은 매우 불안정하다 :(

이 문제를 해결하는 쉬운 방법이 있습니까?

도움이 되었습니까?

해결책

이 질문은 비슷합니다 강조 표시된 쿼리 용어로 WPF 항목 제어에서 검색 결과를 표시하는 방법

그 질문에 대한 답으로, 나는 Ivalueconverter를 사용하는 접근법을 생각해 냈습니다. 변환기는 텍스트 스 니펫을 가져 와서 유효한 XAML 마크 업으로 형식화하며 XAMLREader를 사용하여 마크 업을 프레임 워크 개체로 인스턴스화합니다.

전체 설명이 다소 길어서 블로그에 게시했습니다. WPF TextBlock에서 쿼리 용어를 강조 표시합니다

다른 팁

나는 가져 갔다 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 단계 : 텍스트 블록 대신 ContentBlock을 사용하십시오. 문자열 (텍스트 블록에 사용되는 경우)을 컨텐츠 블록으로 전달하십시오.

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

3 단계 : 통과하는 테스트가 토큰 화되었는지 확인하십시오. |~S~| 그리고 |~E~|. 그리고 강조 표시를 시작하게하십시오!

메모:
실행의 스타일을 변경하여 텍스트가 무엇을 강조 표시되는지 결정할 수 있습니다.
컨버터 클래스를 네임 스페이스 및 리소스에 추가하십시오. 일을하려면 재건이 필요할 수도 있습니다.

이상한 우연의 일치로, 나는 최근에 같은 문제를 해결하는 기사를 썼습니다. 텍스트 블록과 동일한 속성을 갖는 사용자 정의 컨트롤입니다 (따라서 교체 할 수 있습니다. TextBlock 필요에 따라 어디에 있든) HighLightText, 및 값의 위치 HighLightText 메인에서 발견됩니다 Text 속성 (Case Insensitive), 강조 표시됩니다.

그것은 만들기위한 상당히 간단한 통제였으며 여기에서 기사를 찾을 수 있습니다.

검색 문자열이 일치하는 WPF 텍스트 블록

그리고 여기에서 솔루션으로서의 전체 코드 :

SearchMatchTextBlock (Github)

비슷한 문제가있었습니다. 기본적으로 보고서를 나타내는 많은 발표자에 대한 텍스트 검색을 구현하려고합니다. 이 보고서는 원래 문자열로 작성되었으며 FlowDocumentViewer의 CTRL -F 내장을 활용하고있었습니다. 그다지 좋지는 않지만 몇 가지 옵션이 있지만 충분했습니다.

그런 것을 원한다면 다음을 수행 할 수 있습니다.

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

우리는 보고서가 프로그램의 나머지 부분과 동기화되면서 다시 쓰기로 결정했으며 기본적으로 모든 편집을 변경하여 매번 전체 보고서를 재현해야한다는 것은 매우 느리다는 것을 의미합니다. 우리는 비트-당신이 필요로하는 모델로 옮겨서 이것을 개선하고 싶었지만 제정신 방식으로 그것을 할 수있는 뷰 모델 (문자열이 아닌)이 필요했습니다! 그러나 보고서를 교체하기 전에 검색 기능을 보존하고 더 나은 것으로 가고 한 색상의 '현재'검색 위치를 강조 표시하고 다른 색상에서 다른 검색 히트를 강조 표시하고 싶었습니다.

내 솔루션의 단순화 된 버전은 다음과 같습니다. 텍스트 블록에서 파생 된 클래스는 유형의 종속성 속성을 HighlowingInformation의 종속성 속성을 추가합니다. 나는 네임 스페이스와 기간이 민감하기 때문에 포함하지 않았습니다.

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

이 클래스 유형은 텍스트와 하이라이트 목록이 변경 될 때 업데이트 메소드를 사용할 수 있습니다. 하이라이트 자체는 다음과 같이 보입니다.

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

올바른 하이라이트 모음을 생성하는 것은 별도의 문제입니다. 기본적으로 발표자 모음을 컨텐츠를 재귀 적으로 검색하는 트리로 취급하여 해결 한 것입니다. 잎 노드는 콘텐츠가 있고 다른 노드에는 어린이가있는 것입니다. 깊이 우선을 검색하면 예상 할 주문을받습니다. 그런 다음 기본적으로 결과 목록 주위에 래퍼를 작성하여 위치를 추적 할 수 있습니다. 나는 이것에 대한 모든 코드를 게시하지 않을 것입니다. 여기서 내 응답은 WPF가 MVP 스타일로 멀티 컬러 하이라이트를 만드는 방법을 문서화하는 것입니다.

우리는 멀티 캐스트로 변경할 필요가 없었기 때문에 inotifypropertychanged 또는 collectionchanged를 사용하지 않았습니다 (예 : 한 발표자는 여러 개의 견해를 가지고 있습니다). 처음에는 텍스트에 대한 알림과 목록에 대한 이벤트 변경 사항을 추가하여이를 시도했습니다 (InotifyCollectionChanged 이벤트를 수동으로 구독해야 함). 그러나 이벤트 하위 설명에서 메모리 누출에 대해 우려하고 텍스트와 하이라이트 업데이트가 동시에 발생하지 않았다는 사실이 문제가되었습니다.

이 접근법의 한 가지 단점은 사람들 이이 컨트롤의 텍스트 속성에 묶어서는 안된다는 것입니다. 실제 버전에서는 사람들 이이 작업을 수행하는 것을 막기 위해 체크 + 예외를 추가했지만 Clarity 's Sake를 위해 예제에서 헌신했습니다!

여기에 내가 진입 한 것을 쌓아서 내가 생각해 낸 것입니다. 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}"/>

여기서 나는 텍스트를 강조하기위한 또 다른 접근법을 제시합니다. WPF에서 C# 코드를 장식 해야하는 유스 케이스가 있었지만 TextBlock.inlines.add 유형의 구문을 사용하고 싶지 않은 대신 하이라이트 XAML을 즉석에서 생성 한 다음 동적으로 추가하고 싶었습니다. WPF의 캔버스 또는 다른 컨테이너에.

따라서 다음 코드를 채색하고 그 일부를 강조하고 싶다고 가정 해 봅시다.

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

위의 코드가 test.txt라는 파일에 있다고 가정합니다. 모든 C# 키워드 (공개, 정적, 무효 등) 및 간단한 유형 (int, String)을 파란색 및 Console.writeLine 하이라이트로 채색한다고 가정 해 봅시다.

단계 0. 새 WPF 애플리케이션을 만들고 test.txt라는 파일에 위와 유사한 샘플 코드를 포함시킵니다.

단계 1. 코드 형광펜 클래스 생성 :

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

    }
}

2 단계

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

단계 3. WPF 응용 프로그램에서 다음 코드를 추가하십시오. (test.txt가 올바른 위치에 있는지 확인) :

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

    }
}

다음 코드를 작성했습니다

현재 버그가 거의 없지만 문제를 해결합니다.

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

ListViewBase 용 ContationErContentChanging을 처리하는 경우 다음과 같은 접근 방식을 수행 할 수 있습니다. winrt/contapenterContentChanging의 텍스트 블록 하이라이트

이 코드는 Windows Rt 용입니다. WPF 구문은 약간 다릅니다. 또한 바인딩을 사용하여 TextBlock.text 속성을 채우는 경우 내 접근 방식에 의해 생성 된 텍스트가 덮어 쓸 것입니다. 나는 ContationerContentChanging을 사용하여 대상 필드를 채우기 위해 대상 필드를 채우고 메모리 사용의 성능과 정상 결합의 개선으로 인해 대상 필드를 채 웁니다. 데이터보기가 아니라 소스 데이터를 관리하기 위해 바인딩 만 사용합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top