Richtextbox wpf binding

Per eseguire il DataBinding del documento in un RichtextBox WPF , ho visto 2 soluzioni finora, che devono derivare da RichtextBox e aggiungere una DependencyProperty e anche la soluzione con un “proxy”. Né il primo né il secondo sono soddisfacenti. Qualcuno conosce un’altra soluzione o, invece, un controllo RTF commerciale che è capace di DataBinding ? Il normale Textbox non è un’alternativa, dal momento che abbiamo bisogno della formattazione del testo.

Qualche idea?

So che questo è un vecchio post, ma controlla il Toolkit WPF esteso . Ha un RichTextBox che supporta ciò che stai cercando di fare.

C’è un modo molto più semplice!

È ansible creare facilmente una proprietà DocumentXaml (o DocumentRTF ) allegata che consentirà di associare il documento di RichTextBox. Viene utilizzato in questo modo, dove Autobiography è una proprietà stringa nel modello dati:

    

Ecco! Dati RichTextBox completamente associabili!

L’implementazione di questa proprietà è abbastanza semplice: quando la proprietà è impostata, caricare XAML (o RTF) in un nuovo FlowDocument. Quando il FlowDocument viene modificato, aggiornare il valore della proprietà.

Questo codice dovrebbe fare il trucco:

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

Lo stesso codice potrebbe essere utilizzato per TextFormats.RTF o TextFormats.XamlPackage. Per XamlPackage avresti una proprietà di tipo byte [] invece di stringa.

Il formato XamlPackage presenta diversi vantaggi rispetto al semplice XAML, in particolare la possibilità di includere risorse come immagini, ed è più flessibile e più facile da utilizzare rispetto a RTF.

È difficile credere che questa domanda sia rimasta per 15 mesi senza che nessuno indicasse il modo più semplice per farlo.

Posso darti una soluzione ok e puoi seguirla, ma prima di farlo cercherò di spiegare perché Documento non è una proprietà di dipendenza per cominciare.

Durante la vita di un controllo RichTextBox, la proprietà Document in genere non cambia. RichTextBox è inizializzato con un FlowDocument. Questo documento viene visualizzato, può essere modificato e modificato in molti modi, ma il valore sottostante della proprietà Document rimane un’istanza di FlowDocument. Pertanto, non c’è davvero alcuna ragione per cui dovrebbe essere una Proprietà Dipendente, cioè Bindable. Se si dispone di più posizioni che fanno riferimento a questo FlowDocument, è necessario solo il riferimento una volta. Poiché è sempre la stessa istanza, le modifiche saranno accessibili a tutti.

Non penso che FlowDocument supporti le notifiche di modifica del documento, anche se non ne sono sicuro.

Detto questo, ecco una soluzione. Prima di iniziare, poiché RichTextBox non implementa INotifyPropertyChanged e Document non è una proprietà di dipendenza, non abbiamo notifiche quando la proprietà Document di RichTextBox cambia, quindi l’associazione può essere OneWay.

Creare una class che fornirà il FlowDocument. Il binding richiede l’esistenza di una proprietà di dipendenza, quindi questa class eredita da 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); } } } 

Crea una finestra con una casella di testo RTF in XAML.

      

Dare alla finestra un campo di tipo HasDocument.

 HasDocument hasDocument; 

Il costruttore di windows dovrebbe creare l’associazione.

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

Se si desidera essere in grado di dichiarare il binding in XAML, si dovrebbe fare in modo che la class HasDocument derivi da FrameworkElement in modo che possa essere inserita nell’albero logico.

Ora, se si dovesse modificare la proprietà Document su HasDocument, cambierà anche il documento della casella di testo RTF.

 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; 

Ho sintonizzato un po ‘il codice precedente. Prima di tutto, range.Changed non ha funzionato per me. Dopo aver cambiato range.Changed to richTextBox.TextChanged si scopre che il gestore di eventi TextChanged può richiamare SetDocumentXaml in modo ricorsivo, quindi ho fornito protezione contro di esso. Ho anche usato XamlReader / XamlWriter al posto di TextRange.

 public class RichTextBoxHelper : DependencyObject { private static HashSet _recursionProtection = new HashSet(); 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)); } }; } ) ); } 

Creare un UserControl con un RichTextBox. Ora aggiungere la seguente proprietà di dipendenza:

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

Questa soluzione è probabilmente la soluzione “proxy” che hai visto da qualche parte .. Tuttavia .. RichTextBox semplicemente non ha Documento come DependencyProperty … Quindi devi farlo in un altro modo …

HTH

Perché non usare semplicemente un FlowDocumentScrollViewer?

         

Questo sembra il modo più semplice di gran lunga e non è visualizzato in nessuna di queste risposte.

Nel modello di vista basta avere la variabile Text .

Ecco una versione VB.Net della risposta di Lolo:

 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 

End Class

Questa versione VB.Net funziona per la mia situazione. Ho rimosso il semaforo della raccolta thread, utilizzando invece RemoveHandler e AddHandler. Inoltre, poiché un object FlowDocument può essere associato a un solo RichTextBox alla volta, è necessario verificare che IsLoaded di RichTextBox = True. Iniziamo con come ho usato la class in un’app MVVM che utilizza ResourceDictionary invece di Window.

  ' 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 

La maggior parte dei miei bisogni sono stati soddisfatti da questa risposta https://stackoverflow.com/a/2989277/3001007 di krzysztof . Ma un problema con quel codice (ho affrontato era), il legame non funzionerà con più controlli. Così ho cambiato _recursionProtection con un’implementazione basata su Guid . Quindi funziona anche con più controlli nella stessa finestra.

  public class RichTextBoxHelper : DependencyObject { private static List _recursionProtection = new List(); 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)); } }; } ) ); } 

Per completezza, lasciatemi aggiungere altre righe dalla risposta originale https://stackoverflow.com/a/2641774/3001007 di ray-burns . Questo è come usare l’aiutante.

  

Ragazzi, perché preoccuparsi di tutti i falsi. Questo funziona perfettamente. Nessun codice richiesto