用户控件依赖属性更新多个控件值
Posted
技术标签:
【中文标题】用户控件依赖属性更新多个控件值【英文标题】:User Control Dependency Property Updating Multiple control values 【发布时间】:2021-05-14 15:11:57 【问题描述】:我觉得我对这个问题的基本问题是绑定一些内部属性。
在用户控件中,我有一个包含线性渐变的矩形。我创建了一个依赖属性,以便能够给出一个值(0 到 1)来指定渐变线在矩形中的位置。为了动态调整这个值,我连接了一个滑块进行测试。
我还添加了一些反馈文本块,让我知道一些数据是如何流动的以及是否流动。从那我可以告诉我从我的用户控件属性绑定到我的 GradientStops.offset 值不起作用。如何让用户控件仅通过更改 RectangleLevel.RectLevel 值来更新矩形渐变?
用户控制 XAML
<UserControl x:Class="RectDynamicGradient.RectangleLevel"
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"
xmlns:local="clr-namespace:RectDynamicGradient"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="180" d:DesignWidth="80">
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Cyan" Offset="Binding Gradient_top_color, diag:PresentationTraceSources.TraceLevel=High"/>
<GradientStop Color="Black" Offset="Binding Gradient_bottom_color, diag:PresentationTraceSources.TraceLevel=High"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
用户控制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RectDynamicGradient
/// <summary>
/// Interaction logic for RectangleLevel.xaml
/// </summary>
public partial class RectangleLevel : UserControl
public double Gradient_bottom_color get; set;
public double Gradient_top_color get; set;
public double RectLevel
get return (double)GetValue(RectLevelProperty);
set SetValue(RectLevelProperty, value);
// Using a DependencyProperty as the backing store for RectLevel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RectLevelProperty =
DependencyProperty.Register("RectLevel", typeof(double), typeof(RectangleLevel), new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(ChangeLevel),
new CoerceValueCallback(CoerceLevel)),
new ValidateValueCallback(ValidateLevel));
public static void ChangeLevel(DependencyObject d, DependencyPropertyChangedEventArgs e)
RectangleLevel t = d as RectangleLevel;
t.UpdateGradientStops((double)e.NewValue);
public static object CoerceLevel(DependencyObject d, object value)
if (value is string valstring)
if (Double.TryParse(valstring, out double lvl))
return lvl;
if (value is double valdouble)
return valdouble;
throw new Exception();
public static bool ValidateLevel(object value)
double? level = 0;
if (value is string valstring)
if (Double.TryParse(valstring, out double lvl))
level = lvl;
if (value is double valdouble)
level = valdouble;
if (level.HasValue && level >= 0 && level <= 1)
return true;
else
return false;
public RectangleLevel()
InitializeComponent();
this.DataContext = this;
private void UpdateGradientStops(double level)
double scale = 0;
if (level < .5)
scale = level;
else if (level >= .5)
scale = 1 - level;
Gradient_top_color = level;
Gradient_bottom_color = level + (level * .1 * scale);
主窗口 XAML
<Window x:Class="RectDynamicGradient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RectDynamicGradient"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Slider x:Name="TheSlider" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" LargeChange="0.1" Maximum="1" SmallChange="0.01" >
<Slider.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="2"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform Y="2"/>
</TransformGroup>
</Slider.RenderTransform>
</Slider>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<TextBlock Text="SliderValue:"/>
<TextBlock HorizontalAlignment="Center" FontSize="32" Text="Binding ElementName=TheSlider, Path=Value"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<TextBlock Text="UC LEVEL Value:"/>
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="32" Text="Binding ElementName=MyUserControl, Path=RectLevel"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
<local:RectangleLevel x:Name="MyUserControl" Width="80" VerticalAlignment="Stretch" RectLevel="Binding ElementName=TheSlider, Path=Value"/>
</StackPanel>
</Grid>
</Window>
主窗口代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RectDynamicGradient
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
【问题讨论】:
请注意,如果您遵守广泛接受的命名约定,您可以大大提高问题的可读性。对类和属性名称使用 PascalCasing。根本不要在名称中使用下划线。 我正在整理一个简单的示例并粘贴我所拥有的。命名约定现在应该更加标准化。 【参考方案1】:编辑
根据cmets的反馈,进行了改进。
DataContext 删除了这个并添加了相对源到用户控件(命名为“uc”)
将 NotifyProperties 更改为 DependencyProperties
替换数据上下文
<GradientStop Color="Cyan" Offset="Binding ElementName=uc, Path=GradientFilledColor"/>
更新的依赖属性
/// <summary>
/// Property to change the GradientEmptyColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientEmptyColor
get return (double)GetValue(GradientEmptyColorProperty);
set SetValue(GradientEmptyColorProperty, value);
/// <summary>
/// Using a DependencyProperty as the backing store for GradientEmptyColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientEmptyColorProperty =
DependencyProperty.Register("GradientEmptyColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
/// <summary>
/// Property to change the GradientFilledColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientFilledColor
get return (double)GetValue(GradientFilledColorProperty);
set SetValue(GradientFilledColorProperty, value);
/// <summary>
/// Using a DependencyProperty as the backing store for GradientFilledColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientFilledColorProperty =
DependencyProperty.Register("GradientFilledColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
DependencyProperty 提供与 INotifyProperty PropertChanged 事件相同的功能(但使用不同的机制)。 正如@Clemens 所提到的,将 datacontext 设置为 self 是一个糟糕的想法,并且当用户控件在整个项目中使用并且 datacontext 设置为其他内容时会中断链接。
我仍然愿意接受有助于支持良好做法和学习的建议。
更新代码
用户控制 XAML
<UserControl x:Class="RectDynamicGradient.RectangleLevel"
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"
xmlns:local="clr-namespace:RectDynamicGradient"
mc:Ignorable="d"
d:DesignHeight="180" d:DesignWidth="80" x:Name="uc">
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<LinearGradientBrush StartPoint=".5,1" EndPoint=".5,0">
<GradientStop Color="Cyan" Offset="Binding ElementName=uc, Path=GradientFilledColor"/>
<GradientStop Color="Black" Offset="Binding ElementName=uc, Path=GradientEmptyColor"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
用户控制代码
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace RectDynamicGradient
/// <summary>
/// Interaction logic for RectangleLevel.xaml
/// </summary>
public partial class RectangleLevel : UserControl
/// <summary>
/// Property to change the GradientEmptyColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientEmptyColor
get return (double)GetValue(GradientEmptyColorProperty);
set SetValue(GradientEmptyColorProperty, value);
/// <summary>
/// Using a DependencyProperty as the backing store for GradientEmptyColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientEmptyColorProperty =
DependencyProperty.Register("GradientEmptyColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
/// <summary>
/// Property to change the GradientFilledColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientFilledColor
get return (double)GetValue(GradientFilledColorProperty);
set SetValue(GradientFilledColorProperty, value);
/// <summary>
/// Using a DependencyProperty as the backing store for GradientFilledColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientFilledColorProperty =
DependencyProperty.Register("GradientFilledColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
/// <summary>
/// Property to change the level (GradientStop offsets) of the rectangle by interfacing with the dependency property.
/// </summary>
public double RectLevel
get return (double)GetValue(RectLevelProperty);
set SetValue(RectLevelProperty, value);
/// <summary>
/// Using a DependencyProperty as the backing store for RectLevel. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty RectLevelProperty =
DependencyProperty.Register("RectLevel", typeof(double), typeof(RectangleLevel), new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(ChangeLevel),
new CoerceValueCallback(CoerceLevel)),
new ValidateValueCallback(ValidateLevel));
/// <summary>
/// PropertyChangedCallback for DependencyProperty RectLevelProperty.
/// </summary>
/// <param name="d">Dependency object causing the event.</param>
/// <param name="e">Change event arguments.</param>
public static void ChangeLevel(DependencyObject d, DependencyPropertyChangedEventArgs e)
RectangleLevel t = d as RectangleLevel;
t.UpdateGradientStops((double)e.NewValue);
/// <summary>
/// CoerceValueCallback for DependencyProperty RectLevelProperty.
/// </summary>
/// <param name="d">Dependency object causing the event.</param>
/// <param name="value">Value being sent to RectLevelProperty be coerced.</param>
/// <returns></returns>
public static object CoerceLevel(DependencyObject d, object value)
if (value is string valstring)
if (Double.TryParse(valstring, out double lvl))
return lvl;
if (value is double valdouble)
return valdouble;
throw new Exception();
/// <summary>
/// ValidateValueCallback for DependencyProperty RectLevelProperty
/// </summary>
/// <param name="value">Value being sent to RectLevelProperty be validated.</param>
/// <returns>True, if valid value between and including 0 and 1.</returns>
public static bool ValidateLevel(object value)
double? level = 0;
if (value is string valstring)
if (Double.TryParse(valstring, out double lvl))
level = lvl;
if (value is double valdouble)
level = valdouble;
if (level.HasValue && level >= 0 && level <= 1)
return true;
else
return false;
/// <summary>
/// Constructor sets DataContext to itself.
/// </summary>
public RectangleLevel()
InitializeComponent();
this.DataContext = this;
/// <summary>
/// Updates the variables binded to the GradientStops for the rectangle.
/// </summary>
/// <param name="level">Level where the GradientStops should be. Valid value is 0 to 1 representing 0 to 100% filled.</param>
private void UpdateGradientStops(double level)
double scale = 0;
if (level < .5)
scale = level;
else if (level >= .5)
scale = 1 - level;
GradientFilledColor = level;
GradientEmptyColor = level + (level * .1 * scale);
主窗口 XAML
<Window x:Class="RectDynamicGradient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RectDynamicGradient"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Slider x:Name="TheSlider" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" LargeChange="0.1" Maximum="1" SmallChange="0.01" >
<Slider.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="2"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform Y="2"/>
</TransformGroup>
</Slider.RenderTransform>
</Slider>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<TextBlock Text="SliderValue:"/>
<TextBlock HorizontalAlignment="Center" FontSize="32" Text="Binding ElementName=TheSlider, Path=Value"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<TextBlock Text="UC LEVEL Value:"/>
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="32" Text="Binding ElementName=MyUserControl, Path=RectLevel"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
<local:RectangleLevel x:Name="MyUserControl" Width="80" VerticalAlignment="Stretch" RectLevel="Binding ElementName=TheSlider, Path=Value"/>
</StackPanel>
</Grid>
</Window>
【讨论】:
请注意,在 UserControl 中显式设置自己的 DataContext 是一个常见错误。它破坏了控件依赖属性的任何基于 DataContext 的绑定。实现 INPC 也是没有意义的,因为所有属性都可以只是依赖属性,它们有自己的通知机制。 感谢反馈,我不知道 DependencyProperty 提供了 OnPropertyChanged 事件。它必须发生在注册方法和幕后的所有魔法中。感谢您的反馈和指点! 这是与 INotifyPropertyChanged 不同的机制。 再次感谢您的帮助克莱门斯。我花了时间争论是否应该推动这个,但我认为获取信息而不尝试分享解决方案是我自私的行为。我认为我的问题是有效的,我的解决方案解决了这个问题。现在,当我认为它提供了有用的解决方案时,像我这样的其他初学者会认为这个问题是垃圾。如果有人认为有什么不满意的地方,请告诉我以解决差异,我们可以一起帮助其他人。以上是关于用户控件依赖属性更新多个控件值的主要内容,如果未能解决你的问题,请参考以下文章
使用 ControlTemplate 在用户控件上绑定自定义依赖属性