DependencyProperty CoerceValue & ChangedCallback
DependencyProperty CoerceValue & ChangedCallback
我正在尝试使用依赖属性创建一个简单的自定义数字上下练习。
但我有一些不想要的行为。
我的代码:
XAML:
<Grid Height="22">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<TextBox
x:Name="PART_TextboxEditable"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
Text="{Binding Value, ElementName=parent, Mode=TwoWay}"
IsEnabled="{Binding IsEditable, ElementName=parent}"
PreviewTextInput="TextBox_PreviewTextInput"
FontWeight="Normal">
</TextBox>
</Grid>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<RepeatButton
Click="IncressValueClick"
FontSize="8"
Background="#FFF6F6F6"
BorderThickness="0 1 1 1">
<RepeatButton.Content>
<Path
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="Black"
Data="M4,0 L0,4 L8,4 z"/>
</RepeatButton.Content>
</RepeatButton>
<RepeatButton
Click="DecressValueClick"
Grid.Row="1"
FontSize="8"
BorderThickness="0 0 1 1"
Background="#FFF6F6F6">
<RepeatButton.Content>
<Path
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="Black"
Data="M0,0 L8,0 L4,4 z"/>
</RepeatButton.Content>
</RepeatButton>
</Grid>
</Grid>
代码隐藏:
/// <summary>
/// Interação lógica para IntegerUpDown.xam
/// </summary>
public partial class IntegerUpDown : UserControl
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public int Minimum
{
get { return (int)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public int? Maximum
{
get { return (int?)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public int Increment
{
get { return (int)GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); }
}
public static readonly DependencyProperty IncrementProperty =
DependencyProperty.Register(
"Increment",
typeof(int),
typeof(IntegerUpDown),
new PropertyMetadata(1));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register(
"Maximum",
typeof(int?),
typeof(IntegerUpDown),
new PropertyMetadata(null, new PropertyChangedCallback(MaximumPropertyChangedCallback)));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(
"Minimum",
typeof(int),
typeof(IntegerUpDown),
new PropertyMetadata(0, new PropertyChangedCallback(MinimumPropertyChangedCallback)));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(int),
typeof(IntegerUpDown),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(ValuePropertyChangedCalllback),
new CoerceValueCallback(ValuePropertyCoerceValueCallback)));
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<int>), typeof(IntegerUpDown));
private static void ValuePropertyChangedCalllback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindingExpression be = (d as IntegerUpDown).GetBindingExpression(ValueProperty);
if (be != null)
be.UpdateSource();
(d as IntegerUpDown).RaiseEvent(new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, (int)e.NewValue, ValueChangedEvent));
}
private static void MinimumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs baseValue)
{
var value = (int)baseValue.NewValue;
var obj = d as IntegerUpDown;
obj.SetCurrentValue(ValueProperty, (int)Math.Max(obj.Value, value));
}
private static void MaximumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs baseValue)
{
var value = (int?)baseValue.NewValue;
var obj = d as IntegerUpDown;
obj.SetCurrentValue(ValueProperty, Math.Min(obj.Value, value ?? obj.Value));
}
private static object ValuePropertyCoerceValueCallback(DependencyObject d, object baseValue)
{
var value = (int)baseValue;
var obj = d as IntegerUpDown;
obj.CoerceValue(MaximumProperty);
obj.CoerceValue(MinimumProperty);
int newValue = Math.Max(obj.Minimum, Math.Min(value, obj.Maximum ?? value));
return newValue;
}
public IntegerUpDown()
{
InitializeComponent();
}
private void IncressValueClick(object sender, RoutedEventArgs e)
{
IncressValue();
}
private void DecressValueClick(object sender, RoutedEventArgs e)
{
DecressValue();
}
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = sender as TextBox;
var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);
e.Handled = !int.TryParse(fullText, out _);
}
private void NumericUpDownPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IntegerUpDown control = (IntegerUpDown)sender;
e.Handled = control.Focus() || e.Handled;
}
private void Parent_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
IncressValue();
else
DecressValue();
}
private void IncressValue()
{
Value += Increment;
}
private void DecressValue()
{
Value -= Increment;
}
private void Parent_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
IncressValue();
break;
case Key.Down:
DecressValue();
break;
default:
return;
}
}
这是一个非常简单的代码,但它没有按预期工作。我知道我做错了什么,但我无法识别这里的问题。
问题:
我正在使用 XAML 进行测试:
<local:IntegerUpDown Value="{Binding Value}"
Maximum="15"
Minimum="10"
Increment="2"></local:IntegerUpDown>
<TextBlock
Foreground="White"
Text="{Binding Value}" Grid.Row="1"></TextBlock>
正如您在下面看到的,我设置手动将“15151515”值写入 TextBox
并且我的 CoerceValue
被调用,我的 newValue
返回到 CoerceValue
是 ' 15' 因为 Maximum
值设置为 15。我的 textbox
显示正确的值 (15) 但我的 ViewModel
值不正确。
[
如果我按向上键,我的值为 15:
[
我的 Maximum
总是类似于 Maximum
+ Increment
和 Minimum
- Increment
。我的意思是,当值达到 Minimum
时,我可以再单击一次,然后 Minimum
- Increment
(示例为 8)在 ViewModel
上,但在 [=13 上=] 它显示 MinimumValue
(示例为 10)。
我的代码有什么问题?
CoerceValueCallback
运行 在 源 属性 设置之后。您可以通过将依赖项 属性 的 DefaultUpdateSourceTrigger
属性 设置为 UpdateSourceTrigger.Explicit
并显式设置源 属性:
来解决此问题
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(int),
typeof(IntegerUpDown),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(ValuePropertyChangedCalllback),
new CoerceValueCallback(ValuePropertyCoerceValueCallback))
{ DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit });
...
private static void ValuePropertyChangedCalllback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IntegerUpDown ctrl = (IntegerUpDown)d;
int newValue = (int)e.NewValue;
BindingExpression be = ctrl.GetBindingExpression(ValueProperty);
if (be != null && be.ResolvedSource != null && be.ParentBinding != null && be.ParentBinding.Path != null
&& !string.IsNullOrEmpty(be.ParentBinding.Path.Path))
{
var pi = be.ResolvedSource.GetType().GetProperty(be.ParentBinding.Path.Path);
if (pi != null)
pi.SetValue(be.ResolvedSource, newValue);
}
ctrl.RaiseEvent(new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, newValue, ValueChangedEvent));
}
看到你问的是滑块,也许解释一下它是如何工作的可能会激发另一种选择。
滑块的工作方式是有一种方法可以更改值。每当需要更改时都会调用它并检查最小最大值。
因此,这根本不是 dp 系统的一部分,并且该值由处理检查的中介分离。
仅设置依赖项的值 属性 会忽略此内部方法,因此不会应用 min/max。
因为你有一个文本框,所以你不能 "just" re-template 一个滑块并使用它的内置行为。
不过您也可以使用类似的模式。
添加一个内部 dp 来保存输入值。
我们称其为 invalue。
将其绑定到文本框。
当 invalue 改变时,检查 vs min/max 并设置您的外部值或重置 invalue。
按下重复按钮时,在设置 invalue 和 value 之前检查是否与 min/max 值相对应。
我正在尝试使用依赖属性创建一个简单的自定义数字上下练习。
但我有一些不想要的行为。
我的代码:
XAML:
<Grid Height="22">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<TextBox
x:Name="PART_TextboxEditable"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
Text="{Binding Value, ElementName=parent, Mode=TwoWay}"
IsEnabled="{Binding IsEditable, ElementName=parent}"
PreviewTextInput="TextBox_PreviewTextInput"
FontWeight="Normal">
</TextBox>
</Grid>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<RepeatButton
Click="IncressValueClick"
FontSize="8"
Background="#FFF6F6F6"
BorderThickness="0 1 1 1">
<RepeatButton.Content>
<Path
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="Black"
Data="M4,0 L0,4 L8,4 z"/>
</RepeatButton.Content>
</RepeatButton>
<RepeatButton
Click="DecressValueClick"
Grid.Row="1"
FontSize="8"
BorderThickness="0 0 1 1"
Background="#FFF6F6F6">
<RepeatButton.Content>
<Path
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="Black"
Data="M0,0 L8,0 L4,4 z"/>
</RepeatButton.Content>
</RepeatButton>
</Grid>
</Grid>
代码隐藏:
/// <summary>
/// Interação lógica para IntegerUpDown.xam
/// </summary>
public partial class IntegerUpDown : UserControl
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public int Minimum
{
get { return (int)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public int? Maximum
{
get { return (int?)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public int Increment
{
get { return (int)GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); }
}
public static readonly DependencyProperty IncrementProperty =
DependencyProperty.Register(
"Increment",
typeof(int),
typeof(IntegerUpDown),
new PropertyMetadata(1));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register(
"Maximum",
typeof(int?),
typeof(IntegerUpDown),
new PropertyMetadata(null, new PropertyChangedCallback(MaximumPropertyChangedCallback)));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(
"Minimum",
typeof(int),
typeof(IntegerUpDown),
new PropertyMetadata(0, new PropertyChangedCallback(MinimumPropertyChangedCallback)));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(int),
typeof(IntegerUpDown),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(ValuePropertyChangedCalllback),
new CoerceValueCallback(ValuePropertyCoerceValueCallback)));
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<int>), typeof(IntegerUpDown));
private static void ValuePropertyChangedCalllback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindingExpression be = (d as IntegerUpDown).GetBindingExpression(ValueProperty);
if (be != null)
be.UpdateSource();
(d as IntegerUpDown).RaiseEvent(new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, (int)e.NewValue, ValueChangedEvent));
}
private static void MinimumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs baseValue)
{
var value = (int)baseValue.NewValue;
var obj = d as IntegerUpDown;
obj.SetCurrentValue(ValueProperty, (int)Math.Max(obj.Value, value));
}
private static void MaximumPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs baseValue)
{
var value = (int?)baseValue.NewValue;
var obj = d as IntegerUpDown;
obj.SetCurrentValue(ValueProperty, Math.Min(obj.Value, value ?? obj.Value));
}
private static object ValuePropertyCoerceValueCallback(DependencyObject d, object baseValue)
{
var value = (int)baseValue;
var obj = d as IntegerUpDown;
obj.CoerceValue(MaximumProperty);
obj.CoerceValue(MinimumProperty);
int newValue = Math.Max(obj.Minimum, Math.Min(value, obj.Maximum ?? value));
return newValue;
}
public IntegerUpDown()
{
InitializeComponent();
}
private void IncressValueClick(object sender, RoutedEventArgs e)
{
IncressValue();
}
private void DecressValueClick(object sender, RoutedEventArgs e)
{
DecressValue();
}
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = sender as TextBox;
var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);
e.Handled = !int.TryParse(fullText, out _);
}
private void NumericUpDownPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IntegerUpDown control = (IntegerUpDown)sender;
e.Handled = control.Focus() || e.Handled;
}
private void Parent_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
IncressValue();
else
DecressValue();
}
private void IncressValue()
{
Value += Increment;
}
private void DecressValue()
{
Value -= Increment;
}
private void Parent_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
IncressValue();
break;
case Key.Down:
DecressValue();
break;
default:
return;
}
}
这是一个非常简单的代码,但它没有按预期工作。我知道我做错了什么,但我无法识别这里的问题。
问题:
我正在使用 XAML 进行测试:
<local:IntegerUpDown Value="{Binding Value}"
Maximum="15"
Minimum="10"
Increment="2"></local:IntegerUpDown>
<TextBlock
Foreground="White"
Text="{Binding Value}" Grid.Row="1"></TextBlock>
正如您在下面看到的,我设置手动将“15151515”值写入 TextBox
并且我的 CoerceValue
被调用,我的 newValue
返回到 CoerceValue
是 ' 15' 因为 Maximum
值设置为 15。我的 textbox
显示正确的值 (15) 但我的 ViewModel
值不正确。
[
如果我按向上键,我的值为 15:
[
我的 Maximum
总是类似于 Maximum
+ Increment
和 Minimum
- Increment
。我的意思是,当值达到 Minimum
时,我可以再单击一次,然后 Minimum
- Increment
(示例为 8)在 ViewModel
上,但在 [=13 上=] 它显示 MinimumValue
(示例为 10)。
我的代码有什么问题?
CoerceValueCallback
运行 在 源 属性 设置之后。您可以通过将依赖项 属性 的 DefaultUpdateSourceTrigger
属性 设置为 UpdateSourceTrigger.Explicit
并显式设置源 属性:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(int),
typeof(IntegerUpDown),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(ValuePropertyChangedCalllback),
new CoerceValueCallback(ValuePropertyCoerceValueCallback))
{ DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit });
...
private static void ValuePropertyChangedCalllback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IntegerUpDown ctrl = (IntegerUpDown)d;
int newValue = (int)e.NewValue;
BindingExpression be = ctrl.GetBindingExpression(ValueProperty);
if (be != null && be.ResolvedSource != null && be.ParentBinding != null && be.ParentBinding.Path != null
&& !string.IsNullOrEmpty(be.ParentBinding.Path.Path))
{
var pi = be.ResolvedSource.GetType().GetProperty(be.ParentBinding.Path.Path);
if (pi != null)
pi.SetValue(be.ResolvedSource, newValue);
}
ctrl.RaiseEvent(new RoutedPropertyChangedEventArgs<int>((int)e.OldValue, newValue, ValueChangedEvent));
}
看到你问的是滑块,也许解释一下它是如何工作的可能会激发另一种选择。
滑块的工作方式是有一种方法可以更改值。每当需要更改时都会调用它并检查最小最大值。 因此,这根本不是 dp 系统的一部分,并且该值由处理检查的中介分离。
仅设置依赖项的值 属性 会忽略此内部方法,因此不会应用 min/max。
因为你有一个文本框,所以你不能 "just" re-template 一个滑块并使用它的内置行为。
不过您也可以使用类似的模式。
添加一个内部 dp 来保存输入值。
我们称其为 invalue。
将其绑定到文本框。
当 invalue 改变时,检查 vs min/max 并设置您的外部值或重置 invalue。
按下重复按钮时,在设置 invalue 和 value 之前检查是否与 min/max 值相对应。