トップ回答者
RichTextBoxにおけるCaretPositionの左のInline要素を取得する

質問
-
環境: .NET Framework 4.6.1
お世話になります。
Run要素とInlineUIContainer要素が混在したRichTextBoxでカーソルの左の要素を取得したく思います。
以下のコードを書きましたが、要素を取得することができませんでした。private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.F2: var rtb = (RichTextBox)sender; var crt = rtb.CaretPosition; var targetInline = from prg in rtb.Document.Blocks.OfType<Paragraph>() select prg.Inlines.Single(inline => inline.ContentEnd.CompareTo(crt) == 0); Inline i = targetInline.First(); MessageBox.Show(i.ToString()); break; } return; }
カーソルの左の要素がRunであるときは正常に動作しますが、InlineUIContainerであるときは一致する要素が存在しない旨の例外が発生します。
(カーソルがRun内に存在するときもエラーが発生しますがここでは考えません)カーソルの左のInlineUIContainerを取得するにはどうしたらよいでしょうか?
回答
-
左の要素というのがInlineであるという意味であれば。
(Blockも含めるのであれば、たとえばParagraph内で先頭のRunの左の要素はParagraphの先頭になるので)InlineUIContainerは例えば以下のようにRunの間にInlineUIContainerを配置していても、実際に実行されるときにはInlineUIConterだけを包むParagraphに配置されてしまうので、Runとは同レベルには存在しないです。
つまり、<Run>A</Run><InlineUIContainer ><TextBox Text="T" /></InlineUIContainer><Run>◆B</Run>
が
<Paragraph><Run>A</Run></Paragraph><Paragraph><InlineUIContainer ><TextBox Text="T" /></InlineUIContainer></Paragraph><Paragraph><Run>◆B</Run></Paragraph>
になってしまいます。
そのため◆にカーソルがあるときの左には、Runの先頭、Paragraphの末尾、Inlineの末尾の順にあることになります。というわけで、Documentの先頭方向へ順番に位置をずらしていき、最初にぶつかるInlineを取得してみる。
private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.F2: var rtb = (RichTextBox)sender; var crt = rtb.CaretPosition; //crt = rtb.Selection.Start;//文字選択中などはCaretと選択開始位置は異なる場合あり var elem = GetLeftInline(rtb, crt.GetInsertionPosition(LogicalDirection.Backward)); if (elem is Run) { MessageBox.Show(((Run)elem).Text); } else if (elem is InlineUIContainer) { MessageBox.Show("InlineUIContainer"); } break; } return; } private Inline GetLeftInline(RichTextBox rtb, TextPointer p) { while (p != null) { switch (p.GetPointerContext(LogicalDirection.Backward)) { case TextPointerContext.ElementStart://Runの先頭 break; case TextPointerContext.ElementEnd: if (p.Parent is Run) {//Runの末尾 return (Run)p.Parent; } else {//Paragraphの末尾など } break; case TextPointerContext.EmbeddedElement://埋め込み要素 return p.Parent as Inline; case TextPointerContext.None: //左には何もなかった(つまりドキュメントの先頭) break; case TextPointerContext.Text://Runの中 return (Run)p.Parent; } p = p.GetPositionAtOffset(-1);//Documentの先頭に向かってずらして再調査 } return null;//Documentの先頭に到達 }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
すべての返信
-
左の要素というのがInlineであるという意味であれば。
(Blockも含めるのであれば、たとえばParagraph内で先頭のRunの左の要素はParagraphの先頭になるので)InlineUIContainerは例えば以下のようにRunの間にInlineUIContainerを配置していても、実際に実行されるときにはInlineUIConterだけを包むParagraphに配置されてしまうので、Runとは同レベルには存在しないです。
つまり、<Run>A</Run><InlineUIContainer ><TextBox Text="T" /></InlineUIContainer><Run>◆B</Run>
が
<Paragraph><Run>A</Run></Paragraph><Paragraph><InlineUIContainer ><TextBox Text="T" /></InlineUIContainer></Paragraph><Paragraph><Run>◆B</Run></Paragraph>
になってしまいます。
そのため◆にカーソルがあるときの左には、Runの先頭、Paragraphの末尾、Inlineの末尾の順にあることになります。というわけで、Documentの先頭方向へ順番に位置をずらしていき、最初にぶつかるInlineを取得してみる。
private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.F2: var rtb = (RichTextBox)sender; var crt = rtb.CaretPosition; //crt = rtb.Selection.Start;//文字選択中などはCaretと選択開始位置は異なる場合あり var elem = GetLeftInline(rtb, crt.GetInsertionPosition(LogicalDirection.Backward)); if (elem is Run) { MessageBox.Show(((Run)elem).Text); } else if (elem is InlineUIContainer) { MessageBox.Show("InlineUIContainer"); } break; } return; } private Inline GetLeftInline(RichTextBox rtb, TextPointer p) { while (p != null) { switch (p.GetPointerContext(LogicalDirection.Backward)) { case TextPointerContext.ElementStart://Runの先頭 break; case TextPointerContext.ElementEnd: if (p.Parent is Run) {//Runの末尾 return (Run)p.Parent; } else {//Paragraphの末尾など } break; case TextPointerContext.EmbeddedElement://埋め込み要素 return p.Parent as Inline; case TextPointerContext.None: //左には何もなかった(つまりドキュメントの先頭) break; case TextPointerContext.Text://Runの中 return (Run)p.Parent; } p = p.GetPositionAtOffset(-1);//Documentの先頭に向かってずらして再調査 } return null;//Documentの先頭に到達 }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)