Подтвердить что ты не робот

Связывание Richtextbox wpf

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

Любая идея?

4b9b3361

Ответ 1

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

Ответ 2

Есть намного более простой способ!

Вы можете легко создать прикрепленное свойство DocumentXaml (или DocumentRTF), которое позволит вам связать документ RichTextBox. Он используется, как это, где Autobiography является строковым свойством в вашей модели данных:

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

Voila! Полностью привязываемые данные 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 месяцев, и никто не указывал на простой способ сделать это.

Ответ 3

Я могу дать вам хорошее решение, и вы можете пойти с ним, но прежде чем я это сделаю, я попытаюсь объяснить, почему 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, чтобы его можно было вставить в логическое дерево.

Теперь, если вы изменили свойство Document на 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;

Ответ 4

Я немного настроил предыдущий код. Прежде всего range.Changed не работает для меня. После того, как я изменил 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));
                    }
                };
            }
        )
    );
}

Ответ 5

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

НТН

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

Здесь вариант ответа Lolo на 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

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

Ответ 9

Эта версия 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

Ответ 10

Этот ответ удовлетворил большинство моих потребностей fooobar.com/questions/92035/... от 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));
                            }
                        };
                }
            )
        );
    }

Ради полноты позвольте мне добавить еще несколько строк из оригинального ответа fooobar.com/questions/92035/... от ray-burns. Это как использовать помощника.

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

Ответ 11

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

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