wake-up-neo.net

Richtextbox wpf bindend

Um DataBinding des Dokuments in einer WPF-RichtextBox durchzuführen, habe ich bisher 2 Lösungen gesehen, die von der RichtextBox ableiten und eine DependencyProperty hinzufügen sollen, sowie die Lösung mit einem "Proxy". Weder der erste noch der zweite sind zufriedenstellend. Kennt jemand eine andere Lösung oder stattdessen ein kommerzielles RTF Steuerelement, das in der Lage ist DataBinding? Die normale Textbox ist keine Alternative, da wir eine Textformatierung benötigen.

Irgendeine Idee?

69
Alex Maker

Ich weiß, das ist ein alter Beitrag, aber schau dir das Extended WPF Toolkit an. Es verfügt über eine RichTextBox, die das unterstützt, was Sie versuchen.

19
Brian Lagunas

Es gibt einen viel einfacheren Weg!

Sie können problemlos eine angehängte DocumentXaml (oder DocumentRTF) -Eigenschaft erstellen, mit der Sie das RichTextBox-Dokument binden können. Es wird wie folgt verwendet, wobei Autobiography eine Zeichenfolgeeigenschaft in Ihrem Datenmodell ist:

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

Voila! Vollständig bindbare RichTextBox-Daten!

Die Implementierung dieser Eigenschaft ist recht einfach: Wenn die Eigenschaft festgelegt ist, laden Sie die XAML (oder RTF) in ein neues FlowDocument. Wenn sich das FlowDocument ändert, aktualisieren Sie den Eigenschaftswert.

Dieser Code sollte den Trick tun:

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()));
            }
          };
       }});
     }

Derselbe Code kann für TextFormats.RTF oder TextFormats.XamlPackage verwendet werden. Für XamlPackage hätten Sie eine Eigenschaft vom Typ Byte [] anstelle von String.

Das XamlPackage-Format bietet gegenüber der einfachen XAML-Technologie mehrere Vorteile, insbesondere die Möglichkeit, Ressourcen wie Bilder einzubinden, und ist flexibler und einfacher zu bedienen als RTF.

Es ist kaum zu glauben, dass diese Frage 15 Monate dauerte, ohne dass jemand auf den einfachen Weg hingewiesen hat.

93
Ray Burns

Ich kann Ihnen eine ok-Lösung geben und Sie können damit fortfahren, aber bevor ich dies tue, werde ich versuchen zu erklären, warum Document nicht eine DependencyProperty ist. 

Während der Lebensdauer eines RichTextBox-Steuerelements ändert sich die Document-Eigenschaft im Allgemeinen nicht. Die RichTextBox wird mit einem FlowDocument initialisiert. Dieses Dokument wird angezeigt, kann auf viele Arten bearbeitet und geändert werden, der zugrunde liegende Wert der Document-Eigenschaft bleibt jedoch eine Instanz des FlowDocument. Daher gibt es wirklich keinen Grund, warum es sich um eine Abhängigkeitseigenschaft handeln sollte, dh um bindbar. Wenn Sie über mehrere Standorte verfügen, die auf dieses FlowDocument-Objekt verweisen, benötigen Sie die Referenz nur einmal. Da es sich überall um dieselbe Instanz handelt, sind die Änderungen für alle zugänglich.

Ich glaube nicht, dass FlowDocument Benachrichtigungen über Dokumentänderungen unterstützt, obwohl ich nicht sicher bin.

Davon abgesehen, hier ist eine Lösung. Da RichTextBox INotifyPropertyChanged nicht implementiert und Document keine Abhängigkeitseigenschaft ist, haben wir keine Benachrichtigungen, wenn sich die Document-Eigenschaft der RichTextBox ändert. Daher kann die Bindung nur OneWay sein.

Erstellen Sie eine Klasse, die das FlowDocument bereitstellt. Für das Binden ist eine Abhängigkeitseigenschaft erforderlich, sodass diese Klasse von DependencyObject erbt.

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); }
        }
    }

Erstellen Sie ein Fenster mit einem Rich-Text-Feld in 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>

Geben Sie dem Fenster ein Feld vom Typ HasDocument.

HasDocument hasDocument;

Der Window-Konstruktor sollte die Bindung erstellen.

hasDocument = new HasDocument();

InitializeComponent();

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

Wenn Sie die Bindung in XAML deklarieren möchten, müssen Sie Ihre HasDocument-Klasse von FrameworkElement ableiten, damit sie in den logischen Baum eingefügt werden kann.

Wenn Sie jetzt die Document-Eigenschaft in HasDocument ändern, ändert sich auch das Dokument des Rich-Text-Felds.

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

Ich habe den vorherigen Code ein wenig verbessert. Zunächst hat range.Changed für mich nicht funktioniert. Nachdem ich range.Changed in richTextBox.TextChanged geändert hat, stellt sich heraus, dass der TextChanged-Ereignishandler SetDocumentXaml rekursiv aufrufen kann , also habe ich mich davor geschützt. Ich habe auch XamlReader/XamlWriter anstelle von TextRange verwendet.

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));
                    }
                };
            }
        )
    );
}
13
Krzysztof

Erstellen Sie ein UserControl, das über eine RichTextBox verfügt. Fügen Sie nun die folgende Abhängigkeitseigenschaft hinzu:

    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;
    }

Diese Lösung ist wahrscheinlich die "Proxy" -Lösung, die Sie irgendwo gesehen haben. Allerdings ... RichTextBox hat einfach kein Document als DependencyProperty.

HTH

8
Arcturus

Warum nicht einfach einen FlowDocumentScrollViewer verwenden? 

8
paparazzo
 <RichTextBox>
     <FlowDocument PageHeight="180">
         <Paragraph>
             <Run Text="{Binding Text, Mode=TwoWay}"/>
          </Paragraph>
     </FlowDocument>
 </RichTextBox>

Dies scheint bei weitem der einfachste Weg und wird in keiner dieser Antworten angezeigt.

Im Ansichtsmodell muss nur die Variable Text vorhanden sein. 

8
FakeCaleb

Hier ist eine VB.Net-Version von Lolos Antwort:

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

Klasse beenden

1
BSalita

Diese VB.Net-Version funktioniert für meine Situation. Ich habe den Thread-Auflistungssemaphor entfernt, stattdessen RemoveHandler und AddHandler. Da ein FlowDocument jeweils nur an eine RichTextBox gebunden werden kann, habe ich überprüft, ob IsLoaded = True der RichTextBox ist. Beginnen wir mit der Verwendung der Klasse in einer MVVM-App, die ResourceDictionary anstelle von Window verwendet.

    ' 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

Die meisten meiner Bedürfnisse wurden durch diese Antwort erfüllt https://stackoverflow.com/a/2989277/3001007 von krzysztof . Aber ein Problem mit diesem Code (ich war konfrontiert) war, die Bindung funktioniert nicht mit mehreren Steuerelementen. Also änderte ich _recursionProtection mit einer Guid-basierten Implementierung. Es funktioniert also auch für mehrere Steuerelemente im selben Fenster.

 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));
                            }
                        };
                }
            )
        );
    }

Der Vollständigkeit halber möchte ich noch einige Zeilen aus der ursprünglichen Antwort https://stackoverflow.com/a/2641774/3001007 von ray-burns hinzufügen. So verwenden Sie den Helfer.

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