【发布时间】:2018-02-23 11:09:22
【问题描述】:
我有一个UserControl,它代表我对用户的自定义DataContext。此控件还有一个DependencyProperty(带有PropertyChangedCallback),它会影响DataContext 向用户显示的方式。
我的自定义UserControlXAML:
<UserControl x:Class="WpfApplication1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<TextBox Text="{Binding FinalText,ElementName=Me}"/>
</UserControl>
我自定义的UserControl 后面的代码:
using System.Diagnostics;
using System.Windows;
namespace WpfApplication1
{
public partial class MyControl
{
#region Static Fields and Constants
public static readonly DependencyProperty CapitalizeProperty = DependencyProperty.Register(nameof(Capitalize), typeof(bool),
typeof(MyControl), new PropertyMetadata(default(bool), CapitalizePropertyChanged));
public static readonly DependencyProperty FinalTextProperty =
DependencyProperty.Register(nameof(FinalText), typeof(string), typeof(MyControl), new PropertyMetadata(null));
#endregion
#region Properties and Indexers
public bool Capitalize
{
get => (bool)GetValue(CapitalizeProperty);
set => SetValue(CapitalizeProperty, value);
}
public string FinalText
{
get => (string)GetValue(FinalTextProperty);
set
{
Debug.WriteLine($"Setting {nameof(FinalText)} to value {value}");
SetValue(FinalTextProperty, value);
}
}
#endregion
#region Constructors
public MyControl()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
#endregion
#region Private members
private static void CapitalizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MyControl me)
me.CreateFinalText(me.DataContext as string);
}
private void CreateFinalText(string text)
{
if (text != null)
{
FinalText = Capitalize ? text.ToUpperInvariant() : text.ToLowerInvariant();
}
else
{
FinalText = null;
}
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs args)
{
CreateFinalText(args.NewValue as string);
}
#endregion
}
}
当我以下列方式使用我的UserControl 时:
<Grid>
<local:MyControl DataContext="Simple string" Capitalize="True"/>
</Grid>
我的调试输出显示如下:
将 FinalText 设置为简单字符串的值
将 FinalText 设置为 SIMPLE STRING
我想知道是否可以在设置DataContext 之前设置DependencyProperty Capitalize?这样FinalText 属性就不会被设置两次。
为了使我的问题更复杂一点,我的实际 UserControl 需要支持呈现到 Image 而不附加到 Window,这意味着 Loaded 事件并不总是触发。
我可以添加一个正在使用的DependencyProperty 来代替DataContext,但是仍然无法确保在我的所有其他DependencyProperties 都被填充之后,这个新的DependencyProperty 被填充(Capitalize在示例中)
编辑:
正如 cmets 中所指出的,不建议使用 DataContext,我应该使用另一个 Property 来设置需要渲染的内容。这很好而且很容易做到,但是仍然不能保证这个新的Property 在所有其他Properties 被解析之后被解析。
我想这个问题可以重新表述为:如何检测 UserControl 是否已从 XAML 中完全解析?
【问题讨论】:
-
IMO 像这样使用 DataContext 首先是个坏主意。 DataContext 旨在为控件的依赖属性上的绑定提供默认源对象。您的 UserControl 应该只有一个 Text 属性,然后您可以像
Text="{Binding}"一样绑定它。如果您将大写字母放入 Binding Converter,您甚至根本不需要 UserControl。 -
我可以创建一个 Text 属性,但这仍然不能保证所有其他属性都已填充(这会导致文本首先为小写,然后立即设置为大写)。至于大写字母,这只是一个示例,而我的实际用例是 CPU 密集型的。我想这个问题可以重新表述为“我怎么知道 UserControl 是从 XAML 中完全解析出来的?”
-
通常的方法是对每个属性更改做出反应并更新 UI。因此,您将始终获得“最后一次”属性更改。如果您想懒惰地执行此操作,请使用 Loaded 事件。
-
加载 UserControl 时不会触发 Loaded 事件,它会在 UserControl 附加到 VisualTree 时触发,这并不总是发生(例如在 RenderTargetBitmap 中渲染时)跨度>
-
当控件的视觉外观由于其一个或多个依赖属性的变化而发生变化时,您可以考虑覆盖 OnRender 并将依赖属性注册到
FrameworkPropertyMetadataOptions.AffectsRender。
标签: c# wpf dependency-properties