【问题标题】:WPF DataContext is set before properties that affect representation of DataContext are set在设置影响 DataContext 表示的属性之前设置 WPF DataContext
【发布时间】: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


【解决方案1】:

我想知道是否可以在设置 DataContext 之前设置 DependencyProperty Capitalize

尝试更改顺序,即在设置DataContext 属性之前设置Capitalize 属性:

<local:MyControl Capitalize="True" DataContext="Simple string" />

【讨论】:

  • 完全不能保证这会影响订单
  • 这行得通,但我不能保证使用我的UserControl 的每个人都知道它,这也意味着必须明确设置DataContext&lt;Grid DataContext="Amazing string"&gt; &lt;local:MyControl Capitalize="True" /&gt; &lt;/Grid&gt; 会导致同样的问题。
  • @ErnodeWeerd:它将给出 XAML 解析器的当前实现。
  • @ManlkWeet:我刚刚回答了你的问题。如果您的内部实现依赖于顺序,那么您可能应该重新考虑您尝试做的任何事情的整个方法。
  • 它并不像优化那样“依赖”它。生成 FinalText 然后在控件还没完全解析完就立即重新生成是很浪费的。
猜你喜欢
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 2014-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多