Управления richtextbox в WPF привязки
сделать привязку данных документа в WPF RichtextBox, Я видел 2 решения до сих пор, которые должны быть получены из RichtextBox и добавить DependencyProperty, а также решение с "прокси". Ни первый, ни второй не являются удовлетворительными. Кто-нибудь знает другое решение, или вместо этого, коммерческий контроль RTF, который способен привязка данных? Обычное текстовое поле не является альтернативой, так как нам нужно форматирование текста.
любой идея?
11 ответов:
Я знаю, что это старый пост, но проверьте расширенный инструментарий WPF. Он имеет RichTextBox, который поддерживает то, что вы пытаетесь сделать.
есть гораздо более простой способ!
вы можете легко создать присоединенную
DocumentXaml
(илиDocumentRTF
) свойство, которое позволит вам привязать документ RichTextBox. Он используется следующим образом, где Autobiography-это строковое свойство в вашей модели данных:<TextBox Text="{Binding FirstName}" /> <TextBox Text="{Binding LastName}" /> <RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
вуаля! Полностью привязываемые данные RichTextBox!
реализация этого свойства довольно проста: когда свойство установлено, загрузите XAML (или RTF) в новый FlowDocument. Когда FlowDocument изменяет, обновляет значение свойства.
этот код должен делать трюк:
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())); } }; }}); }
тот же код может быть использован для текстовых форматов.RTF или TextFormats.XamlPackage. Для XamlPackage у вас будет свойство типа byte[] вместо string.
формат XamlPackage имеет несколько преимуществ перед обычным XAML, особенно возможность включать такие ресурсы, как изображения, и он более гибкий и простой в работе, чем RTF.
это трудно поверьте, этот вопрос сидел в течение 15 месяцев, и никто не указывал на простой способ сделать это.
Я могу дать вам хорошее решение, и вы можете пойти с ним, но прежде чем я это сделаю, я попытаюсь объяснить, почему документ не DependencyProperty для начала.
во время жизни элемента управления RichTextBox свойство Document обычно не изменяется. RichTextBox инициализируется с помощью FlowDocument. Этот документ отображается, может быть отредактирован и искажен многими способами, но базовое значение свойства документа остается тем, что один экземпляр FlowDocument. Поэтому на самом деле нет причин, по которым это должно быть свойство зависимости, т. е. связываемое. Если у вас есть несколько мест, которые ссылаются на этот FlowDocument, вам нужна только ссылка один раз. Поскольку это один и тот же экземпляр везде, изменения будут доступны для всех.
Я не думаю, что FlowDocument поддерживает уведомления об изменении документа, хотя я не уверен.
Это, как говорится, вот решение. Перед началом работы, так как RichTextBox не реализует INotifyPropertyChanged и Document не является свойством зависимостей, у нас нет уведомлений при изменении свойства документа RichTextBox, поэтому привязка может быть только односторонней.
создайте класс, который будет предоставлять FlowDocument. Привязка требует наличия свойства зависимостей, поэтому этот класс наследуется от 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); } } }
Создать окно с форматированным текстовым полем в 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>
дайте окну поле типа HasDocument.
HasDocument hasDocument;
конструктор окна должен создать привязку.
hasDocument = new HasDocument(); InitializeComponent(); Binding b = new Binding("Document"); b.Source = richTextBox; b.Mode = BindingMode.OneWay; BindingOperations.SetBinding(hasDocument, HasDocument.DocumentProperty, b);
если вы хотите иметь возможность объявлять привязку в XAML, вам нужно будет сделать свой класс HasDocument производным от FrameworkElement, чтобы его можно было вставить в логическое дерево.
теперь, если вы должны были изменить свойство документа на HasDocument, документ форматированного текстового поля также изменится.
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.TextChanged оказывается, что обработчик событий TextChanged может рекурсивно вызывать SetDocumentXaml, поэтому я обеспечил защиту от него. Я также использовал XamlReader / XamlWriter вместо 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)); } }; } ) ); }
создать UserControl, который имеет RichTextBox.. Теперь добавьте следующее свойство зависимостей:
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; }
это решение, вероятно, что "прокси" решение вы видели где-то.. Однако.. RichTextBox просто не имеет документа как DependencyProperty... Поэтому вы должны сделать это по-другому...
HTH
<RichTextBox> <FlowDocument PageHeight="180"> <Paragraph> <Run Text="{Binding Text, Mode=TwoWay}"/> </Paragraph> </FlowDocument> </RichTextBox>
Это кажется самым простым способом на сегодняшний день и не отображается ни в одном из этих ответов.
в модели представления просто
Text
переменной.
вот a VB.Net версия ответа Лоло:
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
Класс
Это VB.Net версия работает для моей ситуации. Я удалил семафор коллекции потоков, вместо этого используя RemoveHandler и AddHandler. Кроме того, поскольку FlowDocument может быть привязан только к одному RichTextBox за раз, я проверяю, что RichTextBox IsLoaded=True. Давайте начнем с того, как я использовал класс В приложении MVVM, которое использует ResourceDictionary вместо 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
большинство моих потребностей были удовлетворены этим ответом https://stackoverflow.com/a/2989277/3001007 by Кшиштоф. Но одна проблема с этим кодом (я стояла), привязка не будет работать с несколькими элементами управления. Так что я изменил
_recursionProtection
СGuid
реализация. Таким образом, он работает для нескольких элементов управления в одном окне.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)); } }; } ) ); }
для полноты картины, Позвольте мне добавить еще несколько строк из оригинального ответа https://stackoverflow.com/a/2641774/3001007 by Рэй-горит. Это как использовать помощника.
<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />