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?
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.
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.
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;
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));
}
};
}
)
);
}
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
Warum nicht einfach einen FlowDocumentScrollViewer verwenden?
<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.
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
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
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}" />