wpf data-binding richtextbox

Richtextbox wpf binding



data-binding (11)

Para hacer DataBinding del documento en un WPF RichtextBox , vi 2 soluciones hasta ahora, que se derivan del RichtextBox y agregan una DependencyProperty, y también la solución con un "proxy". Ni el primero ni el segundo son satisfactorios. ¿Alguien conoce otra solución o, en su lugar, un control RTF comercial que sea capaz de enlazar datos ? El cuadro de texto normal no es una alternativa, ya que necesitamos formato de texto.

¿Alguna idea?


¡Hay una manera mucho más fácil!

Puede crear fácilmente una propiedad adjunta DocumentXaml (o DocumentRTF ) que le permitirá vincular el documento de RichTextBox. Se usa así, donde la Autobiografía es una propiedad de cadena en su modelo de datos:

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

Voila! ¡Datos de RichTextBox completamente enlazables!

La implementación de esta propiedad es bastante simple: cuando se establece la propiedad, cargue el XAML (o RTF) en un nuevo FlowDocument. Cuando FlowDocument cambia, actualice el valor de la propiedad.

Este código debería hacer el truco:

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

El mismo código podría usarse para TextFormats.RTF o TextFormats.XamlPackage. Para XamlPackage, tendrías una propiedad de tipo byte [] en lugar de cadena.

El formato XamlPackage tiene varias ventajas sobre XAML simple, especialmente la capacidad de incluir recursos como imágenes, y es más flexible y fácil de trabajar que RTF.

Es difícil creer que esta pregunta se haya realizado durante 15 meses sin que nadie haya señalado la manera fácil de hacerlo.


¿Por qué no simplemente usar un FlowDocumentScrollViewer?


Aquí hay una versión de VB.Net de la respuesta de 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

Clase final


Chicos por qué molestarse con todo el faff. Esto funciona perfectamente No se requiere código

<RichTextBox> <FlowDocument> <Paragraph> <Run Text="{Binding Mytextbinding}"/> </Paragraph> </FlowDocument> </RichTextBox>


Cree un UserControl que tenga un RichTextBox. Ahora agregue la siguiente propiedad de dependencia:

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

Esta solución es probablemente esa solución "proxy" que viste en alguna parte ... Sin embargo, RichTextBox simplemente no tiene Document as DependencyProperty ... Así que tienes que hacer esto de otra manera ...

HTH


Esta versión de VB.Net funciona para mi situación. Eliminé el semáforo de recopilación de subprocesos, en su lugar utilicé RemoveHandler y AddHandler. Además, dado que un FlowDocument solo puede vincularse a un RichTextBox a la vez, compruebo que IsLoaded de RichTextBox es True. Comencemos con cómo utilicé la clase en una aplicación MVVM que utiliza ResourceDictionary en lugar de 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


He afinado el código anterior un poco. En primer lugar, range.Changed no funciona para mí. Después de cambiar el rango. Cambié a richTextBox.TextChanged, resulta que el controlador de eventos TextChanged puede invocar SetDocumentXaml recursivamente, por lo que he proporcionado protección contra él. También utilicé XamlReader / XamlWriter en lugar de TextRange.

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


La mayoría de mis necesidades estaban satisfechas con esta respuesta https://.com/a/2989277/3001007 por . Pero un problema con ese código (me enfrenté fue), el enlace no funcionará con múltiples controles. Así que cambié _recursionProtection con una implementación basada en Guid . Por lo tanto, también funciona para múltiples controles en la misma ventana.

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

Para mayor completitud, permítanme agregar algunas líneas más de la respuesta original https://.com/a/2641774/3001007 de . Esta es la forma de usar el ayudante.

<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />


Puedo darte una solución correcta y puedes ir con ella, pero antes de hacerlo intentaré explicar por qué Document no es una DependencyProperty para empezar.

Durante la vida útil de un control RichTextBox, la propiedad del documento generalmente no cambia. RichTextBox se inicializa con un FlowDocument. Ese documento se muestra, se puede editar y alterar de muchas maneras, pero el valor subyacente de la propiedad del documento permanece esa instancia de FlowDocument. Por lo tanto, realmente no hay ninguna razón por la que deba ser una Propiedad de Dependencia, es decir, Bindable. Si tiene varias ubicaciones que hacen referencia a este FlowDocument, solo necesita la referencia una vez. Dado que es la misma instancia en todas partes, los cambios estarán accesibles para todos.

No creo que FlowDocument admita notificaciones de cambio de documentos, aunque no estoy seguro.

Dicho esto, aquí hay una solución. Antes de comenzar, dado que RichTextBox no implementa INotifyPropertyChanged y el documento no es una propiedad de dependencia, no tenemos notificaciones cuando la propiedad del documento de RichTextBox cambia, por lo que el enlace solo puede ser OneWay.

Crea una clase que proporcione el FlowDocument. La vinculación requiere la existencia de una Propiedad de dependencia, por lo que esta clase hereda de 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 ventana con un cuadro de texto enriquecido en 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>

Dale a la ventana un campo de tipo HasDocument.

HasDocument hasDocument;

El constructor de ventana debe crear el enlace.

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

Si desea poder declarar el enlace en XAML, debería hacer que su clase HasDocument se derive de FrameworkElement para que pueda insertarse en el árbol lógico.

Ahora, si tuviera que cambiar la propiedad del documento en HasDocument, el documento del cuadro de texto enriquecido también cambiará.

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;



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

Esto parece ser la forma más fácil y no se muestra en ninguna de estas respuestas.

En el modelo de vista solo tiene la variable de Text .