Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9749

WPFのRichTextBoxにBindingする

$
0
0

概要

WPFで単純な文字列ではなく、複数の書式(文字色, Font, etc.)が入り混じった文字表示をしたい場合はRichTextBoxを使用します。
しかし、このRichTextBoxの中身Documentプロパティは依存関係プロパティではないので、そのままではBindingできず、ViewModelから変更できません。

そこでこれを解決するためのいくつかの方法を紹介します。

解決方法

デモアプリ

解決方法の具体例として、以下のようなデモアプリを考えます。
1つのRichTextBox内に、文字色と内容が固定の文字列("FixText_")と、ViewModelから文字色と内容が変更される文字列があるとします。

RichTextBoxBinding.gif

このデモアプリでは、各行ごとに以降の方法を使って、ViewModelからRichTextBoxの中身を書き換えています。

方法1 Run内のプロパティ単位でBindingする

そもそもRichTextBoxの中身全体をBindingするのではなくて、その一部だけをBindingしてしまう、という方法です。
Runのプロパティは依存関係プロパティなので、問題なくBindingできます。
表題とはずれますが、ViewModelから変更したい内容が、一部分の文字列だけとかであれば、これで十分でしょう。

MainWindow.xaml(部分)
<RichTextBox><FlowDocument><Paragraph><RunText="FixText_"/><RunFontStyle="Italic"Foreground="{Binding TextColor}"Text="{Binding NormalText}"/></Paragraph></FlowDocument></RichTextBox>
MainWindowViewModel.cs(部分)
privatestring_NormalText="NormalText in VM";publicstringNormalText{get=>_NormalText;set{_NormalText=value;RaisePropertyChanged();}}

方法2 添付プロパティでFlowDocumentまるごとBindingする

もっと柔軟に、RichTextBoxの中身のFlowDocumentをまるごと変更したいということであれば、この方法を使用します。
ただしViewModelにゴリゴリのView情報(RichTextBox)が入っているのでMVVM的にはイマイチです。

BindingできないDocumentプロパティにBindingするための添付プロパティを用意します。
この添付プロパティにFlowDocumentがBindingで渡されると、添付対象のRichTextBoxの本来のDocumentプロパティに対して、そのFlowDocumentが設定されます。
その際にFlowDocumentが複数のRichTextBoxに所属するとエラーしてしまうので、必要であればコピーを作成して設定します。

publicclassRichTextBoxHelper:DependencyObject{publicstaticFlowDocumentGetDocument(DependencyObjectobj)=>(FlowDocument)obj.GetValue(DocumentProperty);publicstaticvoidSetDocument(DependencyObjectobj,FlowDocumentvalue)=>obj.SetValue(DocumentProperty,value);publicstaticreadonlyDependencyPropertyDocumentProperty=DependencyProperty.RegisterAttached("Document",typeof(FlowDocument),typeof(RichTextBoxHelper),newFrameworkPropertyMetadata(null,Document_Changed));privatestaticvoidDocument_Changed(DependencyObjectd,DependencyPropertyChangedEventArgse){if(!(disRichTextBoxrichTextBox))return;varattachedDocument=GetDocument(richTextBox);//FlowDocumentは1つのRichTextBoxにしか設定できない。//すでに他のRichTextBoxに所属しているなら、コピーを作成・設定するrichTextBox.Document=attachedDocument.Parent==null?attachedDocument:CopyFlowDocument(attachedDocument);}privatestaticFlowDocumentCopyFlowDocument(FlowDocumentsourceDoc){//もとのFlowDocumentをMemoryStream上に一度SerializeするvarsourceRange=newTextRange(sourceDoc.ContentStart,sourceDoc.ContentEnd);usingvarstream=newMemoryStream();XamlWriter.Save(sourceRange,stream);sourceRange.Save(stream,DataFormats.XamlPackage);//新しくFlowDocumentを作成varcopyDoc=newFlowDocument();varcopyRange=newTextRange(copyDoc.ContentStart,copyDoc.ContentEnd);//MemoryStreamからDesirializeして書き込むcopyRange.Load(stream,DataFormats.XamlPackage);returncopyDoc;}}

この添付プロパティをRichTextBoxに対して使用します。

MainWindow.xaml(部分)
<RichTextBoxlocal:RichTextBoxHelper.Document="{Binding Document}"/>

ViewModelではコードでFlowDocumentを組み立てます。

MainWindowViewModel.cs(部分)
privateFlowDocument_Document=CreateFlowDoc("FlowDocument in VM");publicFlowDocumentDocument{get=>_Document;set{_Document=value;RaisePropertyChanged();}}privatestaticFlowDocumentCreateFlowDoc(stringinnerText){varparagraph=newParagraph();paragraph.Inlines.Add(newRun("FixText_"));paragraph.Inlines.Add(newRun(innerText){Foreground=newSolidColorBrush(Colors.BlueViolet)});returnnewFlowDocument(paragraph);}

方法3 添付プロパティとConverterで柔軟にBindingする

柔軟に変更したいが、ViewModelにViewの情報を入れたくない・適度に抽象化したい、という場合はこの方法です。

まず、RichTextBoxのDocumentに対応するViewModelクラスを作成します。
ここでは色と文字列だけ変更するとします。

publicclassRichTextViewModel{publicstringText{get;set;}publicColorColor{get;set;}}

それをMainWindowViewModelではプロパティとして公開します。

MainWindowViewModel.cs(部分)
privateRichTextViewModel_RichVM=newRichTextViewModel(){Text="Original Text in VM",Color=Colors.Indigo};publicRichTextViewModelRichVM{get=>_RichVM;set{_RichVM=value;RaisePropertyChanged();}}

このプロパティをそのままBindingすることはできませんので、ViewModel→FlowDocumentへの変換のためのConverterを作成します。

publicclassRichTextVmToFlowDocumentConverter:IValueConverter{publicobjectConvert(objectvalue,TypetargetType,objectparameter,System.Globalization.CultureInfoculture){if(!(valueisRichTextViewModelrichVM))returnBinding.DoNothing;varparagraph=newParagraph();paragraph.Inlines.Add(newRun("FixText_"));paragraph.Inlines.Add(newRun(){Text=richVM.Text,Foreground=newSolidColorBrush(richVM.Color),FontStyle=FontStyles.Italic,});returnnewFlowDocument(paragraph);}publicobjectConvertBack(objectvalue,TypetargetType,objectparameter,System.Globalization.CultureInfoculture){thrownewNotImplementedException();}}

このConverterと方法2の添付プロパティを使用して、ViewModelのプロパティとBindingします。

MainWindow.xaml(部分)
<RichTextBoxlocal:RichTextBoxHelper.Document="{Binding RichVM, Converter={StaticResource RichTextVmToFlowDocumentConverter}}"/>

デモアプリコード全体

デモアプリのコード全体はGithubにおいておきます。

https://github.com/soi013/RichTextBoxBinding

注意点

Viewからの変更をViewModelで取得するのは難しいです。基本的にOneWayのみ。
方法2で、内部でFlowDocumentがコピーされていなければViewModel側にも反映されていますが、Documentプロパティ自体は変更されていないので、変更通知などは発生しません。

参考

RichTextBox とデータバインディングする

https://stackoverflow.com/questions/343468/richtextbox-wpf-binding

https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.richtextbox?view=netframework-4.8

環境

VisualStudio2019
.NET Core 3.1
C#8


Viewing all articles
Browse latest Browse all 9749

Trending Articles