web-dev-qa-db-ja.com

Richtextbox wpfバインディング

WPF RichtextBoxでドキュメントのDataBindingを行うには、これまで2つのソリューションがありました。RichtextBoxから派生してDependencyPropertyを追加し、「プロキシ」を使用したソリューションも追加しました。 1つ目も2つ目も満足のいくものではありません。誰かが別の解決策を知っていますか、または代わりに、コマーシャルRTFが可能なコントロールDataBinding?)通常のTextboxは代替ではありません。

何か案が?

69
Alex Maker

これは古い投稿であることは知っていますが、 Extended WPF Toolkit を確認してください。あなたがやろうとしていることをサポートするRichTextBoxがあります。

20
Brian Lagunas

もっと簡単な方法があります!

添付のDocumentXaml(またはDocumentRTF)プロパティを簡単に作成して、RichTextBoxのドキュメントをバインドできます。 Autobiographyがデータモデルの文字列プロパティである場合、次のように使用されます。

<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />

出来上がり!完全にバインド可能なRichTextBoxデータ!

このプロパティの実装は非常に簡単です。プロパティが設定されたら、XAML(またはRTF)を新しいFlowDocumentにロードします。 FlowDocumentが変更されたら、プロパティ値を更新します。

このコードはトリックを行う必要があります。

using System.IO;  
using System.Text;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Documents;  
public class RichTextBoxHelper : DependencyObject
{
  public static string GetDocumentXaml(DependencyObject obj) 
  {
    return (string)obj.GetValue(DocumentXamlProperty); 
  }
  public static void SetDocumentXaml(DependencyObject obj, string value) 
  {
    obj.SetValue(DocumentXamlProperty, value); 
  }
  public static readonly DependencyProperty DocumentXamlProperty = 
    DependencyProperty.RegisterAttached(
      "DocumentXaml",
      typeof(string),
      typeof(RichTextBoxHelper),
      new FrameworkPropertyMetadata
      {
        BindsTwoWayByDefault = true,
        PropertyChangedCallback = (obj, e) =>
        {
          var richTextBox = (RichTextBox)obj;

          // Parse the XAML to a document (or use XamlReader.Parse())
          var xaml = GetDocumentXaml(richTextBox);
          var doc = new FlowDocument();
          var range = new TextRange(doc.ContentStart, doc.ContentEnd);

          range.Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml)), 
            DataFormats.Xaml);

          // Set the document
          richTextBox.Document = doc;

          // When the document changes update the source
          range.Changed += (obj2, e2) =>
          {
            if(richTextBox.Document==doc)
            {
              MemoryStream buffer = new MemoryStream();
              range.Save(buffer, DataFormats.Xaml);
              SetDocumentXaml(richTextBox, 
                Encoding.UTF8.GetString(buffer.ToArray()));
            }
          };
       }});
     }

同じコードをTextFormats.RTFまたはTextFormats.XamlPackageに使用できます。 XamlPackageの場合、stringではなくbyte []型のプロパティがあります。

XamlPackage形式には、プレーンXAMLに比べていくつかの利点があります。特に、画像などのリソースを含めることができ、RTFよりも柔軟で簡単に操作できます。

これを行う簡単な方法を誰も指摘することなく、この質問が15ヶ月間続いたとは信じがたい。

97
Ray Burns

私はあなたに大丈夫な解決策を与えることができ、あなたはそれで行くことができますが、私が行う前に、なぜDocumentがnot DependencyPropertyであるのかを説明しようとします。

RichTextBoxコントロールの有効期間中、Documentプロパティは通常変更されません。 RichTextBoxはFlowDocumentで初期化されます。そのドキュメントは表示され、さまざまな方法で編集およびマングルできますが、Documentプロパティの基になる値はFlowDocumentの1つのインスタンスのままです。したがって、実際には、依存関係プロパティ、つまりバインド可能にする必要はありません。このFlowDocumentを参照する場所が複数ある場合、参照は1回だけ必要です。どこでも同じインスタンスであるため、変更は誰でもアクセスできます。

確かではありませんが、FlowDocumentがドキュメント変更通知をサポートしているとは思いません。

そうは言っても、ここに解決策があります。始める前に、RichTextBoxはINotifyPropertyChangedを実装せず、Documentは依存関係プロパティではないため、RichTextBoxのDocumentプロパティが変更されたときに通知がないため、バインディングはOneWayのみになります。

FlowDocumentを提供するクラスを作成します。バインディングにはDependencyプロパティの存在が必要なので、このクラスはDependencyObjectを継承します。

class HasDocument : DependencyObject
    {
        public static readonly DependencyProperty DocumentProperty =
            DependencyProperty.Register("Document", 
                                        typeof(FlowDocument), 
                                        typeof(HasDocument), 
                                        new PropertyMetadata(new PropertyChangedCallback(DocumentChanged)));

        private static void DocumentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("Document has changed");
        }

        public FlowDocument Document
        {
            get { return GetValue(DocumentProperty) as FlowDocument; }
            set { SetValue(DocumentProperty, value); }
        }
    }

XAMLでリッチテキストボックスを持つウィンドウを作成します。

<Window x:Class="samples.Window1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="Flow Document Binding" Height="300" Width="300"
    >
    <Grid>
      <RichTextBox Name="richTextBox" />
    </Grid>
</Window>

ウィンドウにHasDocument型のフィールドを与えます。

HasDocument hasDocument;

ウィンドウコンストラクターはバインディングを作成する必要があります。

hasDocument = new HasDocument();

InitializeComponent();

Binding b = new Binding("Document");
b.Source = richTextBox;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(hasDocument, HasDocument.DocumentProperty, b);

XAMLでバインディングを宣言できるようにするには、HasDocumentクラスをFrameworkElementから派生させて、論理ツリーに挿入できるようにする必要があります。

HasDocumentのDocumentプロパティを変更すると、リッチテキストボックスのDocumentも変更されます。

FlowDocument d = new FlowDocument();
Paragraph g = new Paragraph();
Run a = new Run();
a.Text = "I showed this using a binding";
g.Inlines.Add(a);
d.Blocks.Add(g);

hasDocument.Document = d;
17
Szymon Rozga

以前のコードを少し調整しました。まず、range.Changedが機能しません。 range.ChangedをrichTextBox.TextChangedに変更した後、TextChangedイベントハンドラーがSetDocumentXamlを再帰的に呼び出すことができることが判明したため、それに対する保護を提供しました。 TextRangeの代わりにXamlReader/XamlWriterも使用しました。

public class RichTextBoxHelper : DependencyObject
{
    private static HashSet<Thread> _recursionProtection = new HashSet<Thread>();

    public static string GetDocumentXaml(DependencyObject obj)
    {
        return (string)obj.GetValue(DocumentXamlProperty);
    }

    public static void SetDocumentXaml(DependencyObject obj, string value)
    {
        _recursionProtection.Add(Thread.CurrentThread);
        obj.SetValue(DocumentXamlProperty, value);
        _recursionProtection.Remove(Thread.CurrentThread);
    }

    public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(
        "DocumentXaml", 
        typeof(string), 
        typeof(RichTextBoxHelper), 
        new FrameworkPropertyMetadata(
            "", 
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            (obj, e) => {
                if (_recursionProtection.Contains(Thread.CurrentThread))
                    return;

                var richTextBox = (RichTextBox)obj;

                // Parse the XAML to a document (or use XamlReader.Parse())

                try
                {
                    var stream = new MemoryStream(Encoding.UTF8.GetBytes(GetDocumentXaml(richTextBox)));
                    var doc = (FlowDocument)XamlReader.Load(stream);

                    // Set the document
                    richTextBox.Document = doc;
                }
                catch (Exception)
                {
                    richTextBox.Document = new FlowDocument();
                }

                // When the document changes update the source
                richTextBox.TextChanged += (obj2, e2) =>
                {
                    RichTextBox richTextBox2 = obj2 as RichTextBox;
                    if (richTextBox2 != null)
                    {
                        SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));
                    }
                };
            }
        )
    );
}
15
Krzysztof
 <RichTextBox>
     <FlowDocument PageHeight="180">
         <Paragraph>
             <Run Text="{Binding Text, Mode=TwoWay}"/>
          </Paragraph>
     </FlowDocument>
 </RichTextBox>

これは断然最も簡単な方法のようであり、これらの回答のいずれにも表示されません。

ビューモデルには、Text変数のみがあります。

10
FakeCaleb

FlowDocumentScrollViewerを使用しないのはなぜですか?

9
paparazzo

RichTextBoxを持つUserControlを作成します。次の依存関係プロパティを追加します。

    public FlowDocument Document
    {
        get { return (FlowDocument)GetValue(DocumentProperty); }
        set { SetValue(DocumentProperty, value); }
    }

    public static readonly DependencyProperty DocumentProperty =
        DependencyProperty.Register("Document", typeof(FlowDocument), typeof(RichTextBoxControl), new PropertyMetadata(OnDocumentChanged));

    private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        RichTextBoxControl control = (RichTextBoxControl) d;
        if (e.NewValue == null)
            control.RTB.Document = new FlowDocument(); //Document is not amused by null :)

        control.RTB.Document = document;
    }

このソリューションは、おそらくどこかで見た「プロキシ」ソリューションです。しかし、RichTextBoxには、単にDependencyPropertyとしてDocumentがないため、別の方法でこれを行う必要があります...

HTH

8
Arcturus

Loloの答えのVB.Netバージョンは次のとおりです。

Public Class RichTextBoxHelper
Inherits DependencyObject

Private Shared _recursionProtection As New HashSet(Of System.Threading.Thread)()

Public Shared Function GetDocumentXaml(ByVal depObj As DependencyObject) As String
    Return DirectCast(depObj.GetValue(DocumentXamlProperty), String)
End Function

Public Shared Sub SetDocumentXaml(ByVal depObj As DependencyObject, ByVal value As String)
    _recursionProtection.Add(System.Threading.Thread.CurrentThread)
    depObj.SetValue(DocumentXamlProperty, value)
    _recursionProtection.Remove(System.Threading.Thread.CurrentThread)
End Sub

Public Shared ReadOnly DocumentXamlProperty As DependencyProperty = DependencyProperty.RegisterAttached("DocumentXaml", GetType(String), GetType(RichTextBoxHelper), New FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender Or FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, Sub(depObj, e)
                                                                                                                                                                                                                                                                                                                    RegisterIt(depObj, e)
                                                                                                                                                                                                                                                                                                                End Sub))

Private Shared Sub RegisterIt(ByVal depObj As System.Windows.DependencyObject, ByVal e As System.Windows.DependencyPropertyChangedEventArgs)
    If _recursionProtection.Contains(System.Threading.Thread.CurrentThread) Then
        Return
    End If
    Dim rtb As RichTextBox = DirectCast(depObj, RichTextBox)
    Try
        rtb.Document = Markup.XamlReader.Parse(GetDocumentXaml(rtb))
    Catch
        rtb.Document = New FlowDocument()
    End Try
    ' When the document changes update the source
    AddHandler rtb.TextChanged, AddressOf TextChanged
End Sub

Private Shared Sub TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
    Dim rtb As RichTextBox = TryCast(sender, RichTextBox)
    If rtb IsNot Nothing Then
        SetDocumentXaml(sender, Markup.XamlWriter.Save(rtb.Document))
    End If
End Sub

終了クラス

1
BSalita

このVB.Netバージョンは私の状況に適しています。代わりにRemoveHandlerとAddHandlerを使用して、スレッドコレクションセマフォを削除しました。また、FlowDocumentは一度に1つのRichTextBoxにのみバインドできるため、RichTextBoxのIsLoaded = Trueであることを確認します。まず、WindowではなくResourceDictionaryを使用するMVVMアプリでクラスを使用する方法から始めましょう。

    ' Loaded and Unloaded events seems to be the only way to initialize a control created from a Resource Dictionary
' Loading document here because Loaded is the last available event to create a document
Private Sub Rtb_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    ' only good place to initialize RichTextBox.Document with DependencyProperty
    Dim rtb As RichTextBox = DirectCast(sender, RichTextBox)
    Try
        rtb.Document = RichTextBoxHelper.GetDocumentXaml(rtb)
    Catch ex As Exception
        Debug.WriteLine("Rtb_Loaded: Message:" & ex.Message)
    End Try
End Sub

' Loaded and Unloaded events seems to be the only way to initialize a control created from a Resource Dictionary
' Free document being held by RichTextBox.Document by assigning New FlowDocument to RichTextBox.Document. Otherwise we'll see an of "Document belongs to another RichTextBox"
Private Sub Rtb_Unloaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim rtb As RichTextBox = DirectCast(sender, RichTextBox)
    Dim fd As New FlowDocument
    RichTextBoxHelper.SetDocumentXaml(rtb, fd)
    Try
        rtb.Document = fd
    Catch ex As Exception
        Debug.WriteLine("PoemDocument.PoemDocumentView.PoemRtb_Unloaded: Message:" & ex.Message)
    End Try
End Sub

Public Class RichTextBoxHelper
    Inherits DependencyObject

    Public Shared Function GetDocumentXaml(ByVal depObj As DependencyObject) As FlowDocument
        Return depObj.GetValue(DocumentXamlProperty)
    End Function

    Public Shared Sub SetDocumentXaml(ByVal depObj As DependencyObject, ByVal value As FlowDocument)
        depObj.SetValue(DocumentXamlProperty, value)
    End Sub

    Public Shared ReadOnly DocumentXamlProperty As DependencyProperty = DependencyProperty.RegisterAttached("DocumentXaml", GetType(FlowDocument), GetType(RichTextBoxHelper), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender Or FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, Sub(depObj, e)
                                                                                                                                                                                                                                                                                                                                   RegisterIt(depObj, e)
                                                                                                                                                                                                                                                                                                                               End Sub))


    Private Shared Sub RegisterIt(ByVal depObj As System.Windows.DependencyObject, ByVal e As System.Windows.DependencyPropertyChangedEventArgs)
        Dim rtb As RichTextBox = DirectCast(depObj, RichTextBox)
        If rtb.IsLoaded Then
            RemoveHandler rtb.TextChanged, AddressOf TextChanged
            Try
                rtb.Document = GetDocumentXaml(rtb)
            Catch ex As Exception
                Debug.WriteLine("RichTextBoxHelper.RegisterIt: ex:" & ex.Message)
                rtb.Document = New FlowDocument()
            End Try
            AddHandler rtb.TextChanged, AddressOf TextChanged
        Else
            Debug.WriteLine("RichTextBoxHelper: Unloaded control ignored:" & rtb.Name)
        End If
    End Sub

    ' When a RichTextBox Document changes, update the DependencyProperty so they're in sync.
    Private Shared Sub TextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
        Dim rtb As RichTextBox = TryCast(sender, RichTextBox)
        If rtb IsNot Nothing Then
            SetDocumentXaml(sender, rtb.Document)
        End If
    End Sub

End Class
0
BSalita

私のニーズのほとんどは、この答えで満たされました https://stackoverflow.com/a/2989277/3001007 by krzysztof 。しかし、そのコードの1つの問題(直面していました)、バインディングは複数のコントロールでは機能しません。だから私は_recursionProtectionGuidベースの実装。したがって、同じウィンドウの複数のコントロールでも機能します。

 public class RichTextBoxHelper : DependencyObject
    {
        private static List<Guid> _recursionProtection = new List<Guid>();

        public static string GetDocumentXaml(DependencyObject obj)
        {
            return (string)obj.GetValue(DocumentXamlProperty);
        }

        public static void SetDocumentXaml(DependencyObject obj, string value)
        {
            var fw1 = (FrameworkElement)obj;
            if (fw1.Tag == null || (Guid)fw1.Tag == Guid.Empty)
                fw1.Tag = Guid.NewGuid();
            _recursionProtection.Add((Guid)fw1.Tag);
            obj.SetValue(DocumentXamlProperty, value);
            _recursionProtection.Remove((Guid)fw1.Tag);
        }

        public static readonly DependencyProperty DocumentXamlProperty = DependencyProperty.RegisterAttached(
            "DocumentXaml",
            typeof(string),
            typeof(RichTextBoxHelper),
            new FrameworkPropertyMetadata(
                "",
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                (obj, e) =>
                {
                    var richTextBox = (RichTextBox)obj;
                    if (richTextBox.Tag != null && _recursionProtection.Contains((Guid)richTextBox.Tag))
                        return;


                    // Parse the XAML to a document (or use XamlReader.Parse())

                    try
                    {
                        string docXaml = GetDocumentXaml(richTextBox);
                        var stream = new MemoryStream(Encoding.UTF8.GetBytes(docXaml));
                        FlowDocument doc;
                        if (!string.IsNullOrEmpty(docXaml))
                        {
                            doc = (FlowDocument)XamlReader.Load(stream);
                        }
                        else
                        {
                            doc = new FlowDocument();
                        }

                        // Set the document
                        richTextBox.Document = doc;
                    }
                    catch (Exception)
                    {
                        richTextBox.Document = new FlowDocument();
                    }

                    // When the document changes update the source
                    richTextBox.TextChanged += (obj2, e2) =>
                        {
                            RichTextBox richTextBox2 = obj2 as RichTextBox;
                            if (richTextBox2 != null)
                            {
                                SetDocumentXaml(richTextBox, XamlWriter.Save(richTextBox2.Document));
                            }
                        };
                }
            )
        );
    }

完全を期すために、元の回答からさらに数行追加してみましょう https://stackoverflow.com/a/2641774/3001007 by ray-burns 。これがヘルパーの使用方法です。

<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
0
Ajeeb.K.P