none
WPFのコードビハインドで、RichTextBoxの文字列を検索して、その文字列のフォントを赤くして、スクロールして選択する RRS feed

  • 質問

  • 質問の通り、WPFのコードビハインドを使っています。
    ある文字列を検索するにあたり、見つかったら、メッセージボックスで、見つかったことを教えるようにします。
    この場合、通常のTextBoxですと、
    string text = "探す文字列";
                    int foundIndex = sAt.IndexOf(text);
                    BoxText.Select(foundIndex, text.Length);
    として、スクロールして選択して、メッセージボックスを表示することはできるのですが、メッセージボックスを表示したことで、TextBoxの選択して該当文字列を見せるのが、メッセージボックスを閉じたときになります。また、文字列の色は変わりません。
    この選択した文字列を見せたいために、メッセージボックスを表示しているときは、TextBoxと同じ位置とサイズのRichTextBoxを作っておいて、メッセージボックスを開いているときだけ、そのRichBoxを表示して、その文字列を見せる位置にスクロールして、その文字のフォントを赤くし、メッセージボックスを閉じるときに、RichTextBoxのVisibilityをHiddenにして、選択した状態だけを見せようと思っています。

    で、コードは、サイトの見様見真似ですが、
    TextBoxはBoxText
    RichTextBoxは、RichBox

    全体の文字列は、sAll

    探す検索文字列は、sText
    としました。

                    int foundIndex = sAll.IndexOf(sText);
                    BoxArticle.Select(foundIndex, sText.Length);
                    FlowDocument textFlowDoc = new FlowDocument();
                    textFlowDoc.Blocks.Add(new Paragraph(new Run(BoxText.Text)));
                    RichBox.Document = textFlowDoc;
                    RichBox.Visibility = Visibility.Visible;
                    Brush brush = (SolidColorBrush)(new SolidColorBrush(Color.FromRgb(255, 0, 0)));
                    Brush defFont = RichBox.Foreground;
                    var start = RichBox.Document.ContentStart;
                    var startPos = start.GetPositionAtOffset(foundIndex + 2);
                    var endPos = start.GetPositionAtOffset(foundIndex + 2 + sText.Length);
                    var textRange = new TextRange(startPos, endPos);
                    RichBox.Selection.Select(startPos, endPos);
                    textRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
                    textRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
     このあとで、RichTextBoxのVisiblityをHiddenにして、TextBoxでの検索文字列の選択状態が見えるようにします。
    こんな感じなのですが、残念ながら、画面がスクロールしないので、せっかくフォントを赤くしても、検索した文字が見えません。
    これを何とかしたいのですが、どうしたらよいでしょうか?
    どうぞよろしくお願いします。

    • 編集済み FacePanel 2019年9月21日 10:31 変数の意味付加
    2019年9月21日 10:28

回答

  • 面倒なことをせずに、スクロールした後にメッセージボックスを表示させるといいです

    <Window x:Class="WpfApp1.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:WpfApp1"
            mc:Ignorable="d"
            Title="MainWindow" Height="300" Width="300">
    
        <DockPanel>
            <DockPanel.CommandBindings>
                <CommandBinding Command="{x:Static ApplicationCommands.Find}" 
                                Executed="CommandBinding_Executed"/>
            </DockPanel.CommandBindings>
            <DockPanel.InputBindings>
                <KeyBinding Key="F3" Command="{x:Static ApplicationCommands.Find}"/>
            </DockPanel.InputBindings>
            <DockPanel DockPanel.Dock="Top">
                <Button Command="{x:Static ApplicationCommands.Find}" Content="検索" DockPanel.Dock="Right"/>
                <TextBox x:Name="BoxText" />
            </DockPanel>
    
            <RichTextBox x:Name="RichBox" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
    
            </RichTextBox> 
    
    
        </DockPanel>
    </Window>

    namespace WpfApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Documents;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                List<string> chars = Enumerable.Range((int)'A', (int)'Z' - (int)'A' + 1).Select(_ => new string((char)_, 1)).ToList();
                chars.Add("\r");
    
                Random rnd = new Random();
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                for (int i = 0; i < 10000; i++)
                {
                    sb.Append(chars[rnd.Next(0, chars.Count)]);
                }
                FlowDocument doc = new FlowDocument();
                new TextRange(doc.ContentStart, doc.ContentEnd).Text = sb.ToString();
                
                RichBox.Document = doc;
    
                this.BoxText.Text = "AB";
            }
    
            private void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
            {
                RichTextBoxTool.Result r = RichTextBoxTool.Search(this.RichBox, this.BoxText.Text, this.RichBox.Selection.End);
                switch (r.Type)
                {
                    case RichTextBoxTool.ResultType.InvalidSearchText:
                        MessageBox.Show("検索文字を入れてください");
                        break;
                    case RichTextBoxTool.ResultType.NotFound:
                        MessageBox.Show("見つかりませんでした");
                        break;
                    case RichTextBoxTool.ResultType.Found:
                        System.Diagnostics.Debug.WriteLine(new TextRange(r.Start, r.End).Text);
                        new TextRange(r.Start, r.End).ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.Red);
                        new TextRange(r.Start, r.End).ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
                        this.RichBox.Focus();
                        this.RichBox.Selection.Select(r.Start, r.End);
    
                        ///BeginInvokeを使って選択処理が終わった後にメッセージボックスが表示されるように
                        this.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            MessageBox.Show("見つかりました");
                        }), System.Windows.Threading.DispatcherPriority.Input);
                        break;
                }
            }
        }
    
        static class RichTextBoxTool
        {
            public enum ResultType
            {
                InvalidSearchText = -1,
                NotFound = 0,
                Found = 1,
            }
    
            public class Result
            {
                public Result() { }
                public Result(ResultType type) { this.Type = type; }
                public Result(TextPointer start, TextPointer end) { this.Type = ResultType.Found; this.Start = start; this.End = end; }
    
                public ResultType Type = ResultType.NotFound;
                public TextPointer Start;
                public TextPointer End;
            }
    
            public static Result Search(this RichTextBox rich, string text, TextPointer start = null)
            {
                Result result = new Result();
    
                if (text == null || text.Length == 0)
                {
                    return new Result(ResultType.InvalidSearchText);
                }
                if (start == null)
                {
                    start = rich.Document.ContentStart;
                }
                TextPointer end = rich.Document.ContentEnd;
    
    
                TextRange range = new TextRange(start, end);
                string sAll = range.Text;
    
                int index = sAll.IndexOf(text);
                if (index < 0)
                {
                    return new Result(ResultType.NotFound);
                }
                sAll.StartsWith(text);
    
                start = start.GetInsertionPosition(LogicalDirection.Forward);
                while (index-- > 0)
                {
                    while (start.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text)
                    {
                        start = start.GetInsertionPosition(LogicalDirection.Forward);
                    }
                    start = start.GetNextInsertionPosition(LogicalDirection.Forward);
                }
    
                end = start;
                int len = text.Length;
                while (len-- > 0)
                {
                    do
                    {
                        end = end.GetNextInsertionPosition(LogicalDirection.Forward);
                    } while (end.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text);
                }
    
    
                return new Result(start, end);
            }
        }
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2019年9月22日 4:57 xamlのコピペも漏れ
    • 回答としてマーク FacePanel 2019年9月22日 9:27
    2019年9月22日 3:27

すべての返信

  • 面倒なことをせずに、スクロールした後にメッセージボックスを表示させるといいです

    <Window x:Class="WpfApp1.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:WpfApp1"
            mc:Ignorable="d"
            Title="MainWindow" Height="300" Width="300">
    
        <DockPanel>
            <DockPanel.CommandBindings>
                <CommandBinding Command="{x:Static ApplicationCommands.Find}" 
                                Executed="CommandBinding_Executed"/>
            </DockPanel.CommandBindings>
            <DockPanel.InputBindings>
                <KeyBinding Key="F3" Command="{x:Static ApplicationCommands.Find}"/>
            </DockPanel.InputBindings>
            <DockPanel DockPanel.Dock="Top">
                <Button Command="{x:Static ApplicationCommands.Find}" Content="検索" DockPanel.Dock="Right"/>
                <TextBox x:Name="BoxText" />
            </DockPanel>
    
            <RichTextBox x:Name="RichBox" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
    
            </RichTextBox> 
    
    
        </DockPanel>
    </Window>

    namespace WpfApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Documents;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                List<string> chars = Enumerable.Range((int)'A', (int)'Z' - (int)'A' + 1).Select(_ => new string((char)_, 1)).ToList();
                chars.Add("\r");
    
                Random rnd = new Random();
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                for (int i = 0; i < 10000; i++)
                {
                    sb.Append(chars[rnd.Next(0, chars.Count)]);
                }
                FlowDocument doc = new FlowDocument();
                new TextRange(doc.ContentStart, doc.ContentEnd).Text = sb.ToString();
                
                RichBox.Document = doc;
    
                this.BoxText.Text = "AB";
            }
    
            private void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
            {
                RichTextBoxTool.Result r = RichTextBoxTool.Search(this.RichBox, this.BoxText.Text, this.RichBox.Selection.End);
                switch (r.Type)
                {
                    case RichTextBoxTool.ResultType.InvalidSearchText:
                        MessageBox.Show("検索文字を入れてください");
                        break;
                    case RichTextBoxTool.ResultType.NotFound:
                        MessageBox.Show("見つかりませんでした");
                        break;
                    case RichTextBoxTool.ResultType.Found:
                        System.Diagnostics.Debug.WriteLine(new TextRange(r.Start, r.End).Text);
                        new TextRange(r.Start, r.End).ApplyPropertyValue(TextElement.ForegroundProperty, System.Windows.Media.Brushes.Red);
                        new TextRange(r.Start, r.End).ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
                        this.RichBox.Focus();
                        this.RichBox.Selection.Select(r.Start, r.End);
    
                        ///BeginInvokeを使って選択処理が終わった後にメッセージボックスが表示されるように
                        this.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            MessageBox.Show("見つかりました");
                        }), System.Windows.Threading.DispatcherPriority.Input);
                        break;
                }
            }
        }
    
        static class RichTextBoxTool
        {
            public enum ResultType
            {
                InvalidSearchText = -1,
                NotFound = 0,
                Found = 1,
            }
    
            public class Result
            {
                public Result() { }
                public Result(ResultType type) { this.Type = type; }
                public Result(TextPointer start, TextPointer end) { this.Type = ResultType.Found; this.Start = start; this.End = end; }
    
                public ResultType Type = ResultType.NotFound;
                public TextPointer Start;
                public TextPointer End;
            }
    
            public static Result Search(this RichTextBox rich, string text, TextPointer start = null)
            {
                Result result = new Result();
    
                if (text == null || text.Length == 0)
                {
                    return new Result(ResultType.InvalidSearchText);
                }
                if (start == null)
                {
                    start = rich.Document.ContentStart;
                }
                TextPointer end = rich.Document.ContentEnd;
    
    
                TextRange range = new TextRange(start, end);
                string sAll = range.Text;
    
                int index = sAll.IndexOf(text);
                if (index < 0)
                {
                    return new Result(ResultType.NotFound);
                }
                sAll.StartsWith(text);
    
                start = start.GetInsertionPosition(LogicalDirection.Forward);
                while (index-- > 0)
                {
                    while (start.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text)
                    {
                        start = start.GetInsertionPosition(LogicalDirection.Forward);
                    }
                    start = start.GetNextInsertionPosition(LogicalDirection.Forward);
                }
    
                end = start;
                int len = text.Length;
                while (len-- > 0)
                {
                    do
                    {
                        end = end.GetNextInsertionPosition(LogicalDirection.Forward);
                    } while (end.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text);
                }
    
    
                return new Result(start, end);
            }
        }
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2019年9月22日 4:57 xamlのコピペも漏れ
    • 回答としてマーク FacePanel 2019年9月22日 9:27
    2019年9月22日 3:27
  • あんまり完璧で完成されているので、思わずニッコリしてしまいました。
    こんな素晴らしいコードを教えていただき、本当にありがとうございました。
    感激です。


    で、じつは、頂いたコードを確認したところで、私のコードのダメなところを確認していましたら、
            public void DoEvents()
            {
                DispatcherFrame frame = new DispatcherFrame();
                var callback = new DispatcherOperationCallback(ExitFrames);
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, callback, frame);
                Dispatcher.PushFrame(frame);
            }
    や、
                TextPointer p1 = rtBox.Document.ContentStart;
                TextPointer p2 = rtBox.Document.ContentStart;
                rtBox.Selection.Select(p1, p2);
                DoEvents();

    を使うことで、画面がスクロールされて、うまく行くことも分かりました。
    ダメだった主な原因は、RichTextBoxの選択位置を一番左上にしてからでないと、うまく行かないことに気づかなかったところです。左上の位置を選択したところからSelectをすると、目的の位置にスクロールされる用でした。
    それから、必要なところで、上記のDoEvents()をしないと、うまく行かないようでした。
    自分のコードも活用できることが分かったことで、頂いたコードをさらに活用できたことに、気づきました。
    ありがとうございました。


    • 編集済み FacePanel 2019年9月23日 11:29 改めて気付いたこと
    2019年9月22日 9:30