Richtextbox WPF 바인딩
WPF RichtextBox에서 문서의 DataBinding을 수행하기 위해 지금까지 RichtextBox에서 파생 된 두 가지 솔루션과 DependencyProperty를 추가하고 "프록시"가있는 솔루션을 확인했습니다. 첫 번째도 두 번째도 만족 스럽습니다. 누군가가 다른 솔루션을 알고 있습니까, 아니면 대신 데이터 바인딩 이 가능한 상용 RTF을 알고 컨트롤 있습니까? 일반 텍스트 상자는 텍스트 서식이 필요하기 때문에 대안이 아닙니다.
어떤 생각?
이것이 오래된 게시물이라는 것을 알고 있지만 WPF 툴킷을 확장 확인하십시오 . 당신이 원하는 일을 지원하는 RichTextBox가 있습니다.
훨씬 쉬운 방법이 있습니다!
RichTextBox의 문서를 바인딩 할 수 있는 첨부 DocumentXaml
(또는 DocumentRTF
) 속성을 쉽게 만들 수 있습니다 . 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의 경우에는 대신 바이트 [] 유형의 속성이 있습니다.
XamlPackage 형식은 일반 XAML에 비해 몇 가지 장점, 특히 이미지와 같은 리소스를 포함하는 기능이 있고 RTF보다 유연하고 작업하기 표준입니다.
이 질문을 쉽게 할 수있는 방법을 누구도 지적하지 않고 15 개월 동안 믿기 어렵습니다.
나는 당신에게 괜찮은 해결책을 줄 수 있고 당신은 그것과 함께 갈 수 있지만 , 그 전에 문서가 왜 DependencyProperty에 가 아닌지 설명하려고 노력할을 구석으로 입니다.
RichTextBox 컨트롤의 수명 동안 문서 속성은 일반적으로 변경되지 않습니다. RichTextBox는 FlowDocument로 초기화됩니다. 문서가 표시되고 여러 가지 방법으로 편집 및 표시되는 문서 속성의 기본 값은 FlowDocument의 한 인스턴스로 유지됩니다. 따라서 Dependency Property, 즉 Bindable이어야하는 이유가 없습니다. 이 FlowDocument를 참조하는 여러 위치가있는 경우 참조는 한 번만 필요합니다. 모든 곳에서 실행되는 모든 사람이 변경 사항에 액세스 할 수 있습니다.
하지만 FlowDocument가 문서 변경 알림을 지원하지 않는다고 생각합니다.
즉, 여기에 해결책이 있습니다. 시작하기 전에 RichTextBox는 INotifyPropertyChanged를 구현하지 않고 문서는 될 속성이 아니므로 RichTextBox의 문서 속성이 변경 될 때 알림이 바인딩은 OneWay 만 가능합니다.
FlowDocument를 제공 할 클래스를 만듭니다. 바인딩에는 Dependency Property가 사용이 클래스가 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에서 바인딩을 선언 할 수 있도록해야합니다. 문서 클래스를 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;
이전 코드를 약간 조정했습니다. 우선 범위 변경은 나를 위해 작동하지 않습니다. range.Changed를 richTextBox.TextChanged로 변경 한 후에는 TextChanged 이벤트 처리기가 SetDocumentXaml을 재귀 적으로 호출 할 수 있으므로 이에 대한 보호를 제공했습니다. TextRange 대신 XamlReader / XamlWriter를 사용했습니다.
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));
}
};
}
)
);
}
<RichTextBox>
<FlowDocument PageHeight="180">
<Paragraph>
<Run Text="{Binding Text, Mode=TwoWay}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
이것은 지금까지 가장 쉬운 방법으로 보이며 절차는 표시되지 않습니다.
뷰 모델에는 Text
변수 만 있습니다 .
FlowDocumentScrollViewer를 사용하지 않는 이유는 무엇입니까?
RichTextBox가있는 UserControl을 만듭니다. 이제 다음 속성을 추가합니다.
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
다음은 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
수업 종료
이 VB.Net 버전은 내 상황에 적합합니다. 대신 RemoveHandler를 사용하여 AddHandler를 사용하여 컬렉션 컬렉션 세마포를 제거했습니다. 또한 FlowDocument는 한 번에 하나의 RichTextBox에만 바인딩 될 수 있으므로 RichTextBox의 IsLoaded = True인지 확인합니다. Window 대신 ResourceDictionary를 사용하는 MVVM 앱에서 클래스를 사용하는 방법부터 시작하겠습니다.
' 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 에 의해 르지 . 그러나 그 코드의 한 가지 문제는 (내가 직면 한) 바인딩이 여러 컨트롤에서 작동하지 않는다는 것입니다. 그래서 구현으로 변경 _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}" />
얘들 아 왜 모든 faff로 귀찮게하는지. 이것은 완벽하게 작동합니다. 코드가 필요하지 않습니다
<RichTextBox>
<FlowDocument>
<Paragraph>
<Run Text="{Binding Mytextbinding}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
참고 URL : https://stackoverflow.com/questions/343468/richtextbox-wpf-binding
'ProgramingTip' 카테고리의 다른 글
순환 순서이란 무엇입니까? (0) | 2020.11.04 |
---|---|
iOS 장치에서 명령 줄에서 직접 반응 방식을 실행 하시겠습니까? (0) | 2020.11.03 |
Rails에서 has_one과 belongs_to의 차이점은 무엇입니까? (0) | 2020.11.03 |
HTML- 전체 DIV를 하이퍼 링크로 만드는 방법은 무엇입니까? (0) | 2020.11.03 |
MYSQL을 outfile로 "액세스가 거부되었습니다."-내 사용자는 "모든"액세스 권한을 가지고 있고 CHMOD 777입니다. (0) | 2020.11.03 |