Прив'язка wpf до Richtextbox


78

Щоб виконати прив'язку даних Documentу файлі WPF RichtextBox, я бачив на сьогодні 2 рішення, які мають походити з RichtextBoxі додати a DependencyProperty, а також рішення з "проксі".

Ні перше, ні друге не задовільні. Хтось знає інше рішення, або, натомість, комерційний контроль RTF, який здатний прив'язувати дані ? Звичайне TextBoxне є альтернативою, оскільки нам потрібно форматування тексту.

Будь-яка ідея?

Відповіді:


25

Я знаю, що це стара публікація, але перегляньте Розширений набір інструментів WPF . Він має RichTextBox, який підтримує те, що ви намагаєтесь зробити.


10
RichTextBox з розширеного набору інструментів WPF дійсно повільний, я б не рекомендував цього.
Капітан Мліко

6
@ViktorLaCroix ти розумієш, що це лише WPF RichTextBox з додатковою властивістю, чи не так?
Сем

2
(перехід до 2017 ...) набори інструментів wpf RichTextBox працюють із розширеним текстом або звичайним текстом прямо з поля. Це також здається набагато швидшим, ніж використання допоміжного методу нижче (який видає виняток, якщо його просто скопіювати / вставити)
DanW

2
Їх безкоштовна ліцензія призначена лише для некомерційного використання. : /
Ерік

104

Є набагато простіший спосіб!

Ви можете легко створити вкладене 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 у вас буде властивість type byte[]замість string.

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

Важко повірити, що це питання тривало 15 місяців, а хтось не вказав на простий спосіб зробити це.


6
@Kelly, використовуйте DataFormats.Rtf, це може вирішити багато проблем із RichTexbox.
CharlieShi

16
Двосторонній не працює для мене (за допомогою Rtf). range.ChangedПодія ніколи не викликався.
Патрік

1
@FabianBigler - Привіт - на випадок, якщо хтось має таку ж проблему - вам потрібно додати локальну декларацію xmlns: до вашого файлу xaml, яка вказуватиме на простір імен, де це доступно
Bartosz,

2
хтось може навести приклад значення автобіографії?
longlostbro

1
@AntonBakulev Дякую!
longlostbro

17

Я можу дати вам нормальне рішення, і ви можете з ним піти, але перш ніж це зробити, я спробую пояснити, чому Document не єDependencyProperty для початку.

Протягом життя елемента RichTextBoxуправління Documentвластивість, як правило, не змінюється. RichTextBoxІніціалізується з FlowDocument. Цей документ відображається, його можна редагувати та спотворювати різними способами, але основна вартість Documentвластивості залишається одним екземпляром FlowDocument. Тому насправді немає жодної причини, щоб він мав бути DependencyProperty, тобто, прив'язуваним. Якщо у вас є кілька місцеположень, які посилаються на це FlowDocument, вам потрібно лише один раз. Оскільки скрізь однаковий екземпляр, зміни будуть доступні кожному.

Я не думаю, що FlowDocumentпідтримує сповіщення про зміну документа, хоча я не впевнений.

З огляду на це, ось рішення. Перш ніж почати, оскільки RichTextBoxне реалізується INotifyPropertyChangedі Document не є DependencyProperty, ми не отримуємо повідомлень про те, щоRichTextBox зміну властивості Document, тому прив'язка може бути лише OneWay.

Створіть клас, який забезпечить FlowDocument. Прив'язка вимагає існування a DependencyProperty, тому цей клас успадковує від 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); }
    }
}

Створіть Windowполе з розширеним текстом у 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>

Дайте 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, зміниться і текстове поле розширеного тексту Document.

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;

3
+1 за хорошу відповідь, але одна примхлива ситуація: Є причина зробити властивість Document властивістю залежності - полегшити використання елемента керування з шаблоном MVVM.
Девід Вінеман

1
Справедливий момент, але я не згоден; те, що MVVM широко використовується в програмах WPF, не означає, що API WPF повинен змінюватися лише для його розміщення. Ми обходимо це як завгодно, як тільки можемо. Це одне рішення. Ми можемо також просто вибрати інкапсуляцію нашого текстового вікна в елементі керування користувача та мати властивість залежності, визначену на UserControl.
Szymon Rozga

16

Я трохи налаштував попередній код. Перш за все діапазон. Змінений не працює для мене. Після того, як я змінив 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));
                    }
                };
            }
        )
    );
}

Дякую Лоло! У мене теж були проблеми з початковим класом. Це мені це виправило. Величезна економія часу!
Марк Бонафе

Я знайшов невелику проблему з цим рішенням. Можна встановити гачок для TextChanged get кілька разів, якщо подання не закрито і відтворено між викликами. Я створюю один перегляд один раз і завантажую за допомогою виділеного списку. Щоб це виправити, я створив більш типовий метод підключення події TextChanged. Потім я просто відчіплюю метод, перш ніж підключити його. Це гарантує, що він підключений лише один раз. Більше немає витоку пам'яті (і більше не працює повільний код).
Марк Бонафе

Це приємне робоче рішення, однак, на моєму досвіді, воно не працює з декількома елементами керування, див. Мою відповідь .
Ajeeb.KP

Це чудово, дякую. Не працює, якщо ви хочете встановити прив'язку програмно, хоча, я припускаю, оскільки потік, що встановлює прив'язку, відрізняється від того, який встановлюється через XAML. Тому мені довелося додати метод SetDocumentXamlFirst, який не використовує захист від рекурсії і буде викликаний лише вручну, коли ви вперше хочете встановити значення.
stuzor

Рядок підтримки у віртуальній машині повинен бути FlowDocument у форматі xml, інакше завантаження не вдасться. Наприклад:<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><Paragraph Foreground="Red"><Bold>Hello</Bold></Paragraph></FlowDocument>
Іштван Гекль

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

Здається, це найпростіший спосіб на сьогодні, і він не відображається в жодній із цих відповідей.

У моделі подання просто є Textзмінна.


1
У моєму випадку це рішення відображає текст властивості Text у моделі подання у вертикальному положенні, тобто по одному в кожному рядку.
kintela

Це все, що мені потрібно. Дякую!
Крістофер Пейнтер,

Чим це рішення відрізняється від звичайного TextBox, прив'язаного до властивості Text? Це перешкоджає призначенню розширеного текстового поля, що підтримує форматування, яке ефективно вимикається за допомогою цього коду.
Даап

Це було ідеально для мене. Чим простіше, тим краще!
gcdev

9

Чому б просто не використовувати FlowDocumentScrollViewer?


1
Це власне відповідь.
Den

Ви не можете редагувати текст у FlowDocumentScrollViewer
Ерік

9

Створіть UserControl, який має RichTextBox з іменем RTB. Тепер додайте наступну властивість залежності:

    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;
        FlowDocument document = e.NewValue as FlowDocument;
        if (document  == null)
        {
            control.RTB.Document = new FlowDocument(); //Document is not amused by null :)
        }
        else
        {
            control.RTB.Document = document;
        }
    }

Це рішення - це, мабуть, те проксі-рішення, яке ви десь бачили .. Однак .. RichTextBox просто не має Document як DependencyProperty ... Тож вам доведеться зробити це іншим способом ...

HTH


В останньому рядку ви використовуєте "документ", який видає помилку в моєму коді. Це повинен бути екземпляр Document через статичний метод. Але приклад чого? Я встановлюю документ, який я отримую через DependencyProperty, "Документ". Видалення "static" порушує останній аргумент DependencyProperty. Так ось я застряг. Клас Helper зверху теж не показує жодного тексту :(
2016

1

Ось версія 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

Ця версія 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

0

Більшість моїх потреб були задоволені цією відповіддю 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 на променевих-опіках . Ось як користуватися помічником.

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

-1

Хлопці, навіщо морочитися з усіма фафами. Це працює чудово. Код не потрібен

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

1
У моєму випадку він не працює без тегу 'FlowDocument'.
Клаоніс

Textвластивість Runне є властивістю залежності, тому це навіть не компілюється. Тільки властивості залежностей підтримують подібне прив'язування.
Безнадійний

Це було саме те, що мені потрібно.
Крістофер Пейнтер,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.