如何在 UWP 中数据绑定到 RichEditBox 的纯文本值?
How can I databind to the plain-text value of a RichEditBox in UWP?
使用 UWP 中的普通 TextBox
,您可以数据绑定到 Text
属性 并轻松从 ViewModel 获取或设置值。 RichEditBox
doesn't have a data-bindable Text
property though; instead you have to use the ITextDocument
interface 由 Document
属性 公开并使用各种方法来获取和设置文本。
如何将纯文本数据绑定到我的 ViewModel 中的内容?
可以通过使用自定义 attached property data-bind RichEditBox
的 plain-text。这个附件属性处理文档的富文本和纯文本之间的转换。
这是一个示例 XAML 页面,code-behind,ViewModel 显示了附加 属性:
的用法
XAML
将此复制为项目中新页面的内容
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="30">
<RichEditBox local:RichEditBoxExtension.PlainText="{Binding PlainText,
Mode=TwoWay}" x:Name="richedit"/>
<Button Content="Bold selection" Click="MakeBold"/>
<Button Content="Change plain text (view model)" Click="ChangeText"/>
<Button Content="Change rich text (control property)" Click="ChangeRichText"/>
<TextBlock Text="PlainText property is..." />
<TextBlock Text="{Binding PlainText, Mode=OneWay}" />
</StackPanel>
</Grid>
代码隐藏
这假设您使用的是默认值 MainPage.xaml.cs
;适当更改构造函数名称
public MainPage()
{
InitializeComponent();
DataContext = model = new ViewModel();
model.PlainText = "Hello, world";
}
private void ChangeText(object sender, RoutedEventArgs e)
{
model.PlainText = "Here is some plain text";
}
private void ChangeRichText(object sender, RoutedEventArgs e)
{
richedit.Document.SetText(TextSetOptions.None, "Here is some rich text");
var selection = richedit.Document.Selection;
selection.StartPosition = 8;
selection.EndPosition = 12;
selection.CharacterFormat.Underline = UnderlineType.Single;
selection.MoveStart(TextRangeUnit.Word, 1);
selection.Expand(TextRangeUnit.Word);
selection.CharacterFormat.Weight = FontWeights.Bold.Weight;
}
private void MakeBold(object sender, RoutedEventArgs e)
{
richedit.Document.Selection.CharacterFormat.Weight = FontWeights.Bold.Weight;
}
视图模型
没什么特别的;只是一个字符串 属性。您可以将它放在自己的文件中,或将其粘贴到主 code-behind 文件中。
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string plainText;
public string PlainText
{
get { return plainText; }
set
{
plainText = value;
RaisePropertyChanged();
}
}
void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
到目前为止,没有什么特别的。 RichEditBox
使用附加的 属性 RichEditBoxExtension.PlainText
并将其绑定到 ViewModel 属性 PlainText
。页面上还有另一个 TextBlock
用于显示 PlainText
属性 的当前值,以及一些用于操作文本的按钮。
RichEditBoxExtension.PlainText
的实现很漂亮 straight-forward,但由于 dependency-property 基础设施和需要避免无休止的 属性 更新,它需要相当多的代码(更改富文本会触发纯文本,而富文本会触发纯文本,富文本会触发纯文本,依此类推)。
附上属性
这可以在自己的文件中,也可以再次粘贴到 code-behind 文件中。
public class RichEditBoxExtension
{
// Standard attached property. It mimics the "Text" property of normal text boxes
public static readonly DependencyProperty PlainTextProperty =
DependencyProperty.RegisterAttached("PlainText", typeof(string),
typeof(RichEditBoxExtension), new PropertyMetadata(null, OnPlainTextChanged));
// Standard DP infrastructure
public static string GetPlainText(DependencyObject o)
{
return o.GetValue(PlainTextProperty) as string;
}
// Standard DP infrastructure
public static void SetPlainText(DependencyObject o, string s)
{
o.SetValue(PlainTextProperty, s);
}
private static void OnPlainTextChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
var source = o as RichEditBox;
if (o == null || e.NewValue == null)
return;
// This attaches an event handler for the TextChange event in the RichEditBox,
// ensuring that we're made aware of any changes
AttachRichEditBoxChangingHelper(o);
// To avoid endless property updates, we make sure we only change the RichText's
// Document if the PlainText was modified (vs. if PlainText is responding to
// Document being modified)
var state = GetState(o);
switch (state)
{
case RichEditChangeState.Idle:
var text = e.NewValue as string;
SetState(o, RichEditChangeState.PlainTextChanged);
source.Document.SetText(Windows.UI.Text.TextSetOptions.None, text);
break;
case RichEditChangeState.RichTextChanged:
SetState(o, RichEditChangeState.Idle);
break;
default:
Debug.Assert(false, "Unknown state");
SetState(o, RichEditChangeState.Idle);
break;
}
}
#region Glue
// Trivial state machine to determine who last changed the text properties
enum RichEditChangeState
{
Idle,
RichTextChanged,
PlainTextChanged,
Unknown
}
// Helper class that just stores a state inside a textbox, determining
// whether it is already being changed by code or not
class RichEditChangeStateHelper
{
public RichEditChangeState State { get; set; }
}
// Private attached property (never seen in XAML or anywhere else) to attach
// the state variable for us. Because this isn't used in XAML, we don't need
// the normal GetXXX and SetXXX static methods.
static readonly DependencyProperty RichEditChangeStateHelperProperty =
DependencyProperty.RegisterAttached("RichEditChangeStateHelper",
typeof(RichEditChangeStateHelper), typeof(RichEditBoxExtension), null);
// Inject our state into the textbox, and also attach an event-handler
// for the TextChanged event.
static void AttachRichEditBoxChangingHelper(DependencyObject o)
{
if (o.GetValue(RichEditChangeStateHelperProperty) != null)
return;
var richEdit = o as RichEditBox;
var helper = new RichEditChangeStateHelper();
o.SetValue(RichEditChangeStateHelperProperty, helper);
richEdit.TextChanged += (sender, args) =>
{
// To avoid re-entrancy, make sure we're not already changing
var state = GetState(o);
switch (state)
{
case RichEditChangeState.Idle:
string text = null;
richEdit.Document.GetText(Windows.UI.Text.TextGetOptions.None, out text);
if (text != GetPlainText(o))
{
SetState(o, RichEditChangeState.RichTextChanged);
o.SetValue(PlainTextProperty, text);
}
break;
case RichEditChangeState.PlainTextChanged:
SetState(o, RichEditChangeState.Idle);
break;
default:
Debug.Assert(false, "Unknown state");
SetState(o, RichEditChangeState.Idle);
break;
}
};
}
// Helper to set the state managed by the textbox
static void SetState(DependencyObject o, RichEditChangeState state)
{
(o.GetValue(RichEditChangeStateHelperProperty)
as RichEditChangeStateHelper).State = state;
}
// Helper to get the state managed by the textbox
static RichEditChangeState GetState(DependencyObject o)
{
return (o.GetValue(RichEditChangeStateHelperProperty)
as RichEditChangeStateHelper).State;
}
#endregion
}
附件 属性 基本上做了两件事,但是围绕它有很多样板代码和状态机制:
- 当附加的
PlainText
属性 发生变化时,它会使用 source.Document.SetText(TextSetOptions.None, text)
将 RichEditBox
更新为纯文本
- 当
RichEditBox
文本更改(包括富文本更改)时,它会使用 richEdit.Document.GetText(TextGetOptions.None, out text)
更新附加的 属性,然后 o.SetValue(PlainTextProperty, text)
。
请注意,此基本方法可用于 data-bind 您要根据实际 data-bind 可用属性计算的其他 "derived" 属性。
使用 UWP 中的普通 TextBox
,您可以数据绑定到 Text
属性 并轻松从 ViewModel 获取或设置值。 RichEditBox
doesn't have a data-bindable Text
property though; instead you have to use the ITextDocument
interface 由 Document
属性 公开并使用各种方法来获取和设置文本。
如何将纯文本数据绑定到我的 ViewModel 中的内容?
可以通过使用自定义 attached property data-bind RichEditBox
的 plain-text。这个附件属性处理文档的富文本和纯文本之间的转换。
这是一个示例 XAML 页面,code-behind,ViewModel 显示了附加 属性:
的用法XAML
将此复制为项目中新页面的内容
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="30">
<RichEditBox local:RichEditBoxExtension.PlainText="{Binding PlainText,
Mode=TwoWay}" x:Name="richedit"/>
<Button Content="Bold selection" Click="MakeBold"/>
<Button Content="Change plain text (view model)" Click="ChangeText"/>
<Button Content="Change rich text (control property)" Click="ChangeRichText"/>
<TextBlock Text="PlainText property is..." />
<TextBlock Text="{Binding PlainText, Mode=OneWay}" />
</StackPanel>
</Grid>
代码隐藏
这假设您使用的是默认值 MainPage.xaml.cs
;适当更改构造函数名称
public MainPage()
{
InitializeComponent();
DataContext = model = new ViewModel();
model.PlainText = "Hello, world";
}
private void ChangeText(object sender, RoutedEventArgs e)
{
model.PlainText = "Here is some plain text";
}
private void ChangeRichText(object sender, RoutedEventArgs e)
{
richedit.Document.SetText(TextSetOptions.None, "Here is some rich text");
var selection = richedit.Document.Selection;
selection.StartPosition = 8;
selection.EndPosition = 12;
selection.CharacterFormat.Underline = UnderlineType.Single;
selection.MoveStart(TextRangeUnit.Word, 1);
selection.Expand(TextRangeUnit.Word);
selection.CharacterFormat.Weight = FontWeights.Bold.Weight;
}
private void MakeBold(object sender, RoutedEventArgs e)
{
richedit.Document.Selection.CharacterFormat.Weight = FontWeights.Bold.Weight;
}
视图模型
没什么特别的;只是一个字符串 属性。您可以将它放在自己的文件中,或将其粘贴到主 code-behind 文件中。
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string plainText;
public string PlainText
{
get { return plainText; }
set
{
plainText = value;
RaisePropertyChanged();
}
}
void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
到目前为止,没有什么特别的。 RichEditBox
使用附加的 属性 RichEditBoxExtension.PlainText
并将其绑定到 ViewModel 属性 PlainText
。页面上还有另一个 TextBlock
用于显示 PlainText
属性 的当前值,以及一些用于操作文本的按钮。
RichEditBoxExtension.PlainText
的实现很漂亮 straight-forward,但由于 dependency-property 基础设施和需要避免无休止的 属性 更新,它需要相当多的代码(更改富文本会触发纯文本,而富文本会触发纯文本,富文本会触发纯文本,依此类推)。
附上属性
这可以在自己的文件中,也可以再次粘贴到 code-behind 文件中。
public class RichEditBoxExtension
{
// Standard attached property. It mimics the "Text" property of normal text boxes
public static readonly DependencyProperty PlainTextProperty =
DependencyProperty.RegisterAttached("PlainText", typeof(string),
typeof(RichEditBoxExtension), new PropertyMetadata(null, OnPlainTextChanged));
// Standard DP infrastructure
public static string GetPlainText(DependencyObject o)
{
return o.GetValue(PlainTextProperty) as string;
}
// Standard DP infrastructure
public static void SetPlainText(DependencyObject o, string s)
{
o.SetValue(PlainTextProperty, s);
}
private static void OnPlainTextChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
var source = o as RichEditBox;
if (o == null || e.NewValue == null)
return;
// This attaches an event handler for the TextChange event in the RichEditBox,
// ensuring that we're made aware of any changes
AttachRichEditBoxChangingHelper(o);
// To avoid endless property updates, we make sure we only change the RichText's
// Document if the PlainText was modified (vs. if PlainText is responding to
// Document being modified)
var state = GetState(o);
switch (state)
{
case RichEditChangeState.Idle:
var text = e.NewValue as string;
SetState(o, RichEditChangeState.PlainTextChanged);
source.Document.SetText(Windows.UI.Text.TextSetOptions.None, text);
break;
case RichEditChangeState.RichTextChanged:
SetState(o, RichEditChangeState.Idle);
break;
default:
Debug.Assert(false, "Unknown state");
SetState(o, RichEditChangeState.Idle);
break;
}
}
#region Glue
// Trivial state machine to determine who last changed the text properties
enum RichEditChangeState
{
Idle,
RichTextChanged,
PlainTextChanged,
Unknown
}
// Helper class that just stores a state inside a textbox, determining
// whether it is already being changed by code or not
class RichEditChangeStateHelper
{
public RichEditChangeState State { get; set; }
}
// Private attached property (never seen in XAML or anywhere else) to attach
// the state variable for us. Because this isn't used in XAML, we don't need
// the normal GetXXX and SetXXX static methods.
static readonly DependencyProperty RichEditChangeStateHelperProperty =
DependencyProperty.RegisterAttached("RichEditChangeStateHelper",
typeof(RichEditChangeStateHelper), typeof(RichEditBoxExtension), null);
// Inject our state into the textbox, and also attach an event-handler
// for the TextChanged event.
static void AttachRichEditBoxChangingHelper(DependencyObject o)
{
if (o.GetValue(RichEditChangeStateHelperProperty) != null)
return;
var richEdit = o as RichEditBox;
var helper = new RichEditChangeStateHelper();
o.SetValue(RichEditChangeStateHelperProperty, helper);
richEdit.TextChanged += (sender, args) =>
{
// To avoid re-entrancy, make sure we're not already changing
var state = GetState(o);
switch (state)
{
case RichEditChangeState.Idle:
string text = null;
richEdit.Document.GetText(Windows.UI.Text.TextGetOptions.None, out text);
if (text != GetPlainText(o))
{
SetState(o, RichEditChangeState.RichTextChanged);
o.SetValue(PlainTextProperty, text);
}
break;
case RichEditChangeState.PlainTextChanged:
SetState(o, RichEditChangeState.Idle);
break;
default:
Debug.Assert(false, "Unknown state");
SetState(o, RichEditChangeState.Idle);
break;
}
};
}
// Helper to set the state managed by the textbox
static void SetState(DependencyObject o, RichEditChangeState state)
{
(o.GetValue(RichEditChangeStateHelperProperty)
as RichEditChangeStateHelper).State = state;
}
// Helper to get the state managed by the textbox
static RichEditChangeState GetState(DependencyObject o)
{
return (o.GetValue(RichEditChangeStateHelperProperty)
as RichEditChangeStateHelper).State;
}
#endregion
}
附件 属性 基本上做了两件事,但是围绕它有很多样板代码和状态机制:
- 当附加的
PlainText
属性 发生变化时,它会使用source.Document.SetText(TextSetOptions.None, text)
将 - 当
RichEditBox
文本更改(包括富文本更改)时,它会使用richEdit.Document.GetText(TextGetOptions.None, out text)
更新附加的 属性,然后o.SetValue(PlainTextProperty, text)
。
RichEditBox
更新为纯文本
请注意,此基本方法可用于 data-bind 您要根据实际 data-bind 可用属性计算的其他 "derived" 属性。