Richtextbox wpf привязка - PullRequest
       76

Richtextbox wpf привязка

68 голосов
/ 05 декабря 2008

Чтобы выполнить привязку данных документа в WPF RichtextBox , я до сих пор видел 2 решения, которые должны быть производными от RichtextBox и добавить DependencyProperty, а также решение с «прокси». Ни первое, ни второе не являются удовлетворительными. Кто-нибудь знает другое решение или вместо этого коммерческий элемент управления RTF, способный DataBinding ? Обычное Textbox не является альтернативой, так как нам нужно форматирование текста.

Есть идеи?

Ответы [ 11 ]

95 голосов
/ 15 апреля 2010

Есть гораздо более простой способ!

Вы можете легко создать вложенное свойство 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()));
            }
          };
       }});
     }

Тот же код можно использовать для TextFormats.RTF или TextFormats.XamlPackage. Для XamlPackage у вас будет свойство типа byte [] вместо строки.

Формат XamlPackage имеет несколько преимуществ по сравнению с обычным XAML, особенно возможность включать такие ресурсы, как изображения, и он более гибкий и простой в работе, чем RTF.

Трудно поверить, что этот вопрос просидел 15 месяцев, и никто не указал на простой способ сделать это.

19 голосов
/ 16 ноября 2010

Я знаю, что это старый пост, но посмотрите Extended WPF Toolkit . Он имеет RichTextBox, который поддерживает то, что вы пытаетесь сделать.

17 голосов
/ 06 декабря 2008

Я могу дать вам правильное решение, и вы можете пойти с ним, но прежде чем я сделаю это, я попытаюсь объяснить, почему Document для не является DependencyProperty для начала.

Во время жизни элемента управления RichTextBox свойство Document обычно не изменяется. RichTextBox инициализируется с FlowDocument. Этот документ отображается, может быть отредактирован и искажен разными способами, но базовое значение свойства Document остается тем единственным экземпляром FlowDocument. Следовательно, на самом деле нет причин, по которым это должно быть свойство зависимости, то есть Bindable. Если у вас есть несколько мест, которые ссылаются этот FlowDocument, вам нужна ссылка только один раз. Поскольку это везде один и тот же экземпляр, изменения будут доступны каждому.

Я не думаю, что FlowDocument поддерживает уведомления об изменении документа, хотя я не уверен.

Как говорится, вот решение. Прежде чем начать, поскольку RichTextBox не реализует INotifyPropertyChanged и Document не является свойством зависимости, у нас нет уведомлений, когда свойство RichTextBox Document изменяется, поэтому привязка может быть только OneWay.

Создайте класс, который будет предоставлять 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;
13 голосов
/ 07 июня 2010

Я немного настроил предыдущий код. Прежде всего, диапазон. Изменения не работают для меня. После того как я изменил range.Changed на 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));
                    }
                };
            }
        )
    );
}
9 голосов
/ 01 ноября 2017
 <RichTextBox>
     <FlowDocument PageHeight="180">
         <Paragraph>
             <Run Text="{Binding Text, Mode=TwoWay}"/>
          </Paragraph>
     </FlowDocument>
 </RichTextBox>

Это, кажется, самый простой способ, и он не отображается ни в одном из этих ответов.

В модели представления просто есть переменная Text.

9 голосов
/ 09 января 2014

Почему бы просто не использовать FlowDocumentScrollViewer?

8 голосов
/ 05 декабря 2008

Создайте 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 просто не имеет Document as DependencyProperty ... Так что вы должны сделать это по-другому ...

НТН

1 голос
/ 07 сентября 2010

Вот версия ответа Лоло на 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

Конечный класс

0 голосов
/ 21 февраля 2018

Этот ответ удовлетворил большинство моих потребностей https://stackoverflow.com/a/2989277/3001007 krzysztof . Но одна проблема с этим кодом (с которым я столкнулся) заключалась в том, что привязка не будет работать с несколькими элементами управления. Поэтому я изменил _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 от ray-burns . Вот как пользоваться помощником.

<RichTextBox local:RichTextBoxHelper.DocumentXaml="{Binding Autobiography}" />
0 голосов
/ 20 сентября 2014

Ребята, зачем беспокоиться обо всем этом. Это работает отлично. Код не требуется

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