7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

WPFのRichTextBoxにBindingする

Posted at

概要

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>
         <Run  Text="FixText_" />
         <Run
            FontStyle="Italic"
            Foreground="{Binding TextColor}"
            Text="{Binding NormalText}" />
      </Paragraph>
   </FlowDocument>
</RichTextBox>
MainWindowViewModel.cs(部分)
private string _NormalText = "NormalText in VM";
public string NormalText
{
    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に所属するとエラーしてしまうので、必要であればコピーを作成して設定します。

public class RichTextBoxHelper : DependencyObject
{
    public static FlowDocument GetDocument(DependencyObject obj) => (FlowDocument)obj.GetValue(DocumentProperty);
    public static void SetDocument(DependencyObject obj, FlowDocument value) => obj.SetValue(DocumentProperty, value);

    public static readonly DependencyProperty DocumentProperty = DependencyProperty.RegisterAttached(
        "Document", typeof(FlowDocument), typeof(RichTextBoxHelper),
        new FrameworkPropertyMetadata(null, Document_Changed));

    private static void Document_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is RichTextBox richTextBox))
            return;

        var attachedDocument = GetDocument(richTextBox);

        //FlowDocumentは1つのRichTextBoxにしか設定できない。
        //すでに他のRichTextBoxに所属しているなら、コピーを作成・設定する
        richTextBox.Document = attachedDocument.Parent == null
            ? attachedDocument
            : CopyFlowDocument(attachedDocument);
    }

    private static FlowDocument CopyFlowDocument(FlowDocument sourceDoc)
    {
        //もとのFlowDocumentをMemoryStream上に一度Serializeする
        var sourceRange = new TextRange(sourceDoc.ContentStart, sourceDoc.ContentEnd);
        using var stream = new MemoryStream();
        XamlWriter.Save(sourceRange, stream);
        sourceRange.Save(stream, DataFormats.XamlPackage);

        //新しくFlowDocumentを作成
        var copyDoc = new FlowDocument();
        var copyRange = new TextRange(copyDoc.ContentStart, copyDoc.ContentEnd);
        //MemoryStreamからDesirializeして書き込む
        copyRange.Load(stream, DataFormats.XamlPackage);

        return copyDoc;
    }
}

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

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

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

MainWindowViewModel.cs(部分)
private FlowDocument _Document = CreateFlowDoc("FlowDocument in VM");
public FlowDocument Document
{
    get => _Document;
    set
    {
        _Document = value;
        RaisePropertyChanged();
    }
}

private static FlowDocument CreateFlowDoc(string innerText)
{
    var paragraph = new Paragraph();
    paragraph.Inlines.Add(new Run("FixText_"));
    paragraph.Inlines.Add(new Run(innerText) { Foreground = new SolidColorBrush(Colors.BlueViolet) });
    return new FlowDocument(paragraph);
}

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

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

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

public class RichTextViewModel
{
    public string Text { get; set; }
    public Color Color { get; set; }
}

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

MainWindowViewModel.cs(部分)
private RichTextViewModel _RichVM = new RichTextViewModel() { Text = "Original Text in VM", Color = Colors.Indigo };
public RichTextViewModel RichVM
{
    get => _RichVM;
    set
    {
        _RichVM = value;
        RaisePropertyChanged();
    }
}

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

public class RichTextVmToFlowDocumentConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is RichTextViewModel richVM))
            return Binding.DoNothing;

        var paragraph = new Paragraph();
        paragraph.Inlines.Add(new Run("FixText_"));
        paragraph.Inlines.Add(new Run()
        {
            Text = richVM.Text,
            Foreground = new SolidColorBrush(richVM.Color),
            FontStyle = FontStyles.Italic,
        });
        return new FlowDocument(paragraph);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

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

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

デモアプリコード全体

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

注意点

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

参考

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

環境

VisualStudio2019
.NET Core 3.1
C#8

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?