Um DataBinding von Document
in einem WPF durchzuführen RichtextBox
, habe ich bisher 2 Lösungen gesehen, die von der abgeleitet werden RichtextBox
und eine hinzufügen sollen DependencyProperty
, sowie die Lösung mit einem "Proxy".
Weder der erste noch der zweite sind zufriedenstellend. Kennt jemand eine andere Lösung oder stattdessen eine kommerzielle RTF-Steuerung, die DataBinding unterstützt ? Das Normale TextBox
ist keine Alternative, da wir eine Textformatierung benötigen.
Irgendeine Idee?
c#
wpf
data-binding
richtextbox
Alex Maker
quelle
quelle
Es gibt einen viel einfacheren Weg!
Sie können einfach eine angehängte
DocumentXaml
(oderDocumentRTF
) Eigenschaft erstellen , mit der Sie dasRichTextBox
Dokument des Dokuments binden können . Es wird wie folgt verwendet, wobeiAutobiography
sich eine Zeichenfolgeeigenschaft in Ihrem Datenmodell befindet:<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 eine neue
FlowDocument
. Wenn dieFlowDocument
Änderungen, den Eigenschaftswert aktualisieren.Dieser Code sollte den Trick machen:
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())); } }; } }); }
Der gleiche Code kann für TextFormats.RTF oder TextFormats.XamlPackage verwendet werden. Für XamlPackage hätten Sie
byte[]
stattdessen eine Eigenschaft vom Typstring
.Das XamlPackage-Format bietet gegenüber einfachem XAML mehrere Vorteile, insbesondere die Möglichkeit, Ressourcen wie Bilder einzuschließen, und es ist flexibler und einfacher zu bearbeiten als RTF.
Es ist kaum zu glauben, dass diese Frage 15 Monate lang gestellt wurde, ohne dass jemand auf den einfachen Weg hingewiesen hat, dies zu tun.
quelle
range.Changed
Veranstaltung wird nie aufgerufen.Ich kann Ihnen eine ok - Lösung geben und man kann mit ihm gehen, aber bevor ich das tue , ich werde zu erklären versuchen , warum Dokument ist nicht ein
DependencyProperty
zu beginnen.Während der Lebensdauer eines
RichTextBox
SteuerelementsDocument
ändert sich die Eigenschaft im Allgemeinen nicht. DasRichTextBox
wird mit a initialisiertFlowDocument
. Dieses Dokument wird angezeigt, kann auf viele Arten bearbeitet und entstellt werden, aber der zugrunde liegende Wert derDocument
Eigenschaft bleibt die eine Instanz vonFlowDocument
. Daher gibt es wirklich keinen Grund, warum es einDependencyProperty
, dh bindbar sein sollte. Wenn Sie mehrere Standorte haben, die darauf verweisenFlowDocument
, benötigen Sie den Verweis 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 werden, obwohl ich nicht sicher bin.Davon abgesehen ist hier eine Lösung. Bevor Sie beginnen, haben wir keine Benachrichtigungen, wenn sich die Document-Eigenschaft der Dokumentation ändert , da
RichTextBox
sie nicht implementiert istINotifyPropertyChanged
und Document keine ist . Daher kann die Bindung nur OneWay sein.DependencyProperty
RichTextBox
Erstellen Sie eine Klasse, die die bereitstellt
FlowDocument
. Die Bindung erfordert die Existenz von aDependencyProperty
, daher erbt diese Klasse vonDependencyObject
.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
Window
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
Window
ein Feld vom Typ anHasDocument
.Der Fensterkonstruktor 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 ableiten lassen,FrameworkElement
damit sie in den logischen Baum eingefügt werden kann.Wenn Sie nun die
Document
Eigenschaft ändern, ändern sich auch dieHasDocument
Rich-Text-FelderDocument
.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;
quelle
Ich habe den vorherigen Code ein wenig optimiert. Zuallererst Range.Changed hat bei mir nicht funktioniert. Nachdem ich range.Changed in richTextBox.TextChanged geändert habe, stellt sich heraus, dass der TextChanged-Ereignishandler SetDocumentXaml rekursiv aufrufen kann, sodass ich Schutz dagegen bereitgestellt habe. 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)); } }; } ) ); }
quelle
<RichTextBox> <FlowDocument PageHeight="180"> <Paragraph> <Run Text="{Binding Text, Mode=TwoWay}"/> </Paragraph> </FlowDocument> </RichTextBox>
Dies scheint bei weitem der einfachste Weg zu sein und wird in keiner dieser Antworten angezeigt.
Im Ansichtsmodell haben Sie nur die
Text
Variable.quelle
Warum nicht einfach einen FlowDocumentScrollViewer verwenden?
quelle
Erstellen Sie ein UserControl mit einer RichTextBox namens RTB. 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; FlowDocument document = e.NewValue as FlowDocument; if (document == null) { control.RTB.Document = new FlowDocument(); //Document is not amused by null :) } else { control.RTB.Document = document; } }
Diese Lösung ist wahrscheinlich die "Proxy" -Lösung, die Sie irgendwo gesehen haben. Allerdings hat RichTextBox einfach kein Dokument als DependencyProperty. Sie müssen dies also auf andere Weise tun ...
HTH
quelle
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
quelle
Diese VB.Net-Version funktioniert für meine Situation. Ich habe das Thread-Sammlungssemaphor entfernt und stattdessen RemoveHandler und AddHandler verwendet. Da ein FlowDocument jeweils nur an eine RichTextBox gebunden werden kann, überprüfe ich, ob IsLoaded = True der RichTextBox ist. Beginnen wir damit, wie ich die Klasse in einer MVVM-App verwendet habe, 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
quelle
Die meisten meiner Bedürfnisse wurden durch diese Antwort https://stackoverflow.com/a/2989277/3001007 von krzysztof befriedigt . Aber ein Problem mit diesem Code (mit dem ich konfrontiert war) war, dass die Bindung nicht mit mehreren Steuerelementen funktioniert. Also habe ich mich
_recursionProtection
mit einerGuid
basierten Implementierung verändert. 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)); } }; } ) ); }
Lassen Sie mich der Vollständigkeit halber 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}" />
quelle
Leute, warum sich mit all dem Faff beschäftigen. Das funktioniert perfekt. Kein Code erforderlich
<RichTextBox> <FlowDocument> <Paragraph> <Run Text="{Binding Mytextbinding}"/> </Paragraph> </FlowDocument> </RichTextBox>
quelle
Text
Die Eigenschaft vonRun
ist keine Abhängigkeitseigenschaft, daher wird diese nicht einmal kompiliert. Nur Abhängigkeitseigenschaften unterstützen eine solche Bindung.