如何使我的自定义 UserControl 在 DataTrigger 内的模板 Setter 内处理双向绑定?
How to make my custom UserControl handle a two-way Binding when it is inside a Template Setter inside a DataTrigger?
- 如果我将 TimeSpanPicker 直接放在 UserControl 元素中,它就可以工作。
- 如果我放置一个 DateTimePicker(来自扩展 WPF 工具包)而不是我的 TimeSpanPicker,它可以两种方式工作。
- (这种情况是我希望使用的,它在下面的代码中)如果我将 TimeSpanPicker 放在一个模板 Setter 里面一个 DataTrigger 里面 Style.Triggers 里面 UserControl.Style , 绑定停止工作。
无论如何都不起作用的绑定(虽然它设置为 TwoWay)是这样的:
TimeSpan="{Binding Path=CurrentValue,
Mode=TwoWay,
RelativeSource={RelativeSource Mode=TemplatedParent},
UpdateSourceTrigger=PropertyChanged}"
TimeSpan 属性 是一个依赖项 属性,CurrentValue
属性 也直接位于为 CurrentValue
实现 INotifyPropertyChanged 的对象中。我还尝试使用绑定到 TemplatedParent 的 RelativeSource,但它在我的情况下不起作用。
重现问题所需的所有代码如下,除了大部分 wpf-timespanpicker 程序集(我只留下相关的部分)。
重现步骤:
- 按现在的样子测试代码。
1.1。 运行 程序。
1.2。单击“应用时间跨度”按钮。
1.3。 TimeSpanPicker 出现在 window 的顶部,显示 0 秒,尽管下面的 TextBox 显示 00:10:00.
1.4。通过像最终用户一样操作来更改 TimeSpanPicker 显示的值。
1.5。 TextBox 仍然显示 00:10:00.
- 更改代码。
2.1。把它放在 UserControl1.xaml:
中而不是 Style 属性
<w:TimeSpanPicker
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinHeight="50" MinWidth="70"
TimeSpan="{Binding Path=CurrentValue,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
2.2。重复步骤 1.2.-1.5。并看到 TextBox 中的值已更新以反映 Model.CurrentValue (00:10:00) 的初始值或最终用户在 UI.
中设置的值
绑定诊断输出
从我在这个输出中看到的,我认为 DataContext 是错误的,它被直接设置到模板父级,而不是它的 DataContext。
如果我将 Binding 的路径设置为 DataContext.CurrentValue 它仍然不起作用,可能是因为 DataContext 未明确设置,它是从父控件继承的。
设置此绑定的最正确方法是什么?
System.Windows.Data Warning: 56 : Created BindingExpression (hash=4620049) for Binding (hash=22799085)
System.Windows.Data Warning: 58 : Path: 'CurrentValue'
System.Windows.Data Warning: 62 : BindingExpression (hash=4620049): Attach to wpf_timespanpicker.TimeSpanPicker.TimeSpan (hash=34786562)
System.Windows.Data Warning: 67 : BindingExpression (hash=4620049): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=4620049): Found data context element: <null> (OK)
System.Windows.Data Warning: 72 : RelativeSource.TemplatedParent found UserControl1 (hash=31201899)
System.Windows.Data Warning: 78 : BindingExpression (hash=4620049): Activate with root item UserControl1 (hash=31201899)
'cs-wpf-test-7.exe' (CLR v4.0.30319: cs-wpf-test-7.exe): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework-SystemCore\v4.0_4.0.0.0__b77a5c561934e089\PresentationFramework-SystemCore.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
System.Windows.Data Warning: 108 : BindingExpression (hash=4620049): At level 0 - for UserControl1.CurrentValue found accessor <null>
System.Windows.Data Error: 40 : BindingExpression path error: 'CurrentValue' property not found on 'object' ''UserControl1' (Name='')'. BindingExpression:Path=CurrentValue; DataItem='UserControl1' (Name=''); target element is 'TimeSpanPicker' (Name=''); target property is 'TimeSpan' (type 'TimeSpan')
System.Windows.Data Warning: 80 : BindingExpression (hash=4620049): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=4620049): TransferValue - using fallback/default value TimeSpan (hash=0)
System.Windows.Data Warning: 89 : BindingExpression (hash=4620049): TransferValue - using final value TimeSpan (hash=0)
UserControl1.xaml:
<UserControl xmlns:wpf_timespanpicker="clr-namespace:wpf_timespanpicker;assembly=wpf-timespanpicker" x:Class="cs_wpf_test_7.UserControl1"
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:xwpf="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
xmlns:local="clr-namespace:cs_wpf_test_7"
xmlns:w="clr-namespace:wpf_timespanpicker;assembly=wpf-timespanpicker"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<local:MyValueConverter x:Key="MyConv"/>
<ControlTemplate x:Key="x">
<w:TimeSpanPicker
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinHeight="50" MinWidth="70"
TimeSpan="{Binding Path=CurrentValue,
Mode=TwoWay,
RelativeSource={RelativeSource Mode=TemplatedParent},
UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
<ControlTemplate x:Key="y">
<xwpf:DateTimePicker
Value="{Binding Path=CurrentValue,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="{x:Type local:UserControl1}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentValue, Mode=OneWay, Converter={StaticResource MyConv}}"
Value="TimeSpan">
<Setter Property="Template" Value="{StaticResource x}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentValue, Mode=OneWay, Converter={StaticResource MyConv}}"
Value="DateTime">
<Setter Property="Template" Value="{StaticResource y}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
</UserControl>
MyValueConverter.cs
public class MyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? "null" : value.GetType().Name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
模特class
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal object _CurrentValue = null;
public object CurrentValue
{
get
{
return _CurrentValue;
}
set
{
if (_CurrentValue != value)
{
_CurrentValue = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(
"CurrentValue"));
}
}
}
}
MainWindow.xaml
<Window x:Class="cs_wpf_test_7.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:cs_wpf_test_7"
mc:Ignorable="d"
Title="MainWindow" Height="187" Width="254"
Loaded="Window_Loaded">
<StackPanel>
<local:UserControl1>
</local:UserControl1>
<TextBox Text="{Binding Path=CurrentValue,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Name="MyApplyTimeSpanButton"
Click="MyApplyTimeSpanButton_Click">
Apply TimeSpan
</Button>
<Button Name="MyApplyDateTimeButton"
Click="MyApplyDateTimeButton_Click">
Apply DateTime
</Button>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
Model m = new Model();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataContext = m;
}
private void MyApplyTimeSpanButton_Click(object sender, RoutedEventArgs e)
{
m.CurrentValue = TimeSpan.FromMinutes(10);
}
private void MyApplyDateTimeButton_Click(object sender, RoutedEventArgs e)
{
m.CurrentValue = DateTime.Now;
}
}
TimeSpanPicker.xaml:
<UserControl x:Class="wpf_timespanpicker.TimeSpanPicker"
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:wpf_timespanpicker"
mc:Ignorable="d"
d:DesignHeight="170" d:DesignWidth="365"
KeyboardNavigation.TabNavigation="Continue"
IsTabStop="True"
Focusable="True"
GotKeyboardFocus="UserControl_GotKeyboardFocus"
LostKeyboardFocus="UserControl_LostKeyboardFocus"
KeyDown="UserControl_KeyDown"
PreviewKeyDown="UserControl_PreviewKeyDown"
PreviewMouseDown="UserControl_PreviewMouseDown"
MouseDown="UserControl_MouseDown"
MouseLeave="UserControl_MouseLeave"
PreviewMouseUp="UserControl_PreviewMouseUp"
GotFocus="UserControl_GotFocus"
LostFocus="UserControl_LostFocus"
IsEnabledChanged="UserControl_IsEnabledChanged"
Loaded="UserControl_Loaded"
MouseWheel="UserControl_MouseWheel">
<Canvas SizeChanged="Canvas_SizeChanged">
<local:ArrowButton x:Name="hPlusBtn" State="True"/>
<local:TwoDigitsDisplay x:Name="tdd1" MouseUp="Tdd1_MouseUp"/>
<local:ArrowButton x:Name="hMinusBtn" State="False"/>
<local:ColonDisplay x:Name="tbc1"/>
<local:ArrowButton x:Name="mPlusBtn" State="True"/>
<local:TwoDigitsDisplay x:Name="tdd2" MouseUp="Tdd2_MouseUp"/>
<local:ArrowButton x:Name="mMinusBtn" State="False"/>
<local:ColonDisplay x:Name="tbc2"/>
<local:ArrowButton x:Name="sPlusBtn" State="True"/>
<local:TwoDigitsDisplay x:Name="tdd3" MouseUp="Tdd3_MouseUp"/>
<local:ArrowButton x:Name="sMinusBtn" State="False"/>
</Canvas>
</UserControl>
TimeSpanPicker.xaml.cs 的一部分:
注意: 在此 class 中,我仅使用标准 .NET 属性 包装器设置和获取 TimeSpan 属性。我没有在此 class.
中设置任何绑定
public static readonly DependencyProperty TimeSpanProperty =
DependencyProperty.Register("TimeSpan", typeof(TimeSpan), typeof(TimeSpanPicker),
new PropertyMetadata(TimeSpan.Zero, OnTimeSpanChanged, TimeSpanCoerceCallback));
private static void OnTimeSpanChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as TimeSpanPicker).OnTimeSpanChanged();
}
private static object TimeSpanCoerceCallback(DependencyObject d, object baseValue)
{
return ((TimeSpan)baseValue).Subtract(
TimeSpan.FromMilliseconds(((TimeSpan)baseValue).Milliseconds));
}
public TimeSpan TimeSpan
{
get
{
return (TimeSpan)GetValue(TimeSpanProperty);
}
set
{
SetValue(TimeSpanProperty, value);
}
}
private void OnTimeSpanChanged()
{
ApplyTimeSpanToVisual(TimeSpan);
TimeSpanValueChanged?.Invoke(this, EventArgs.Empty);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TimeSpan"));
}
我希望问题开始时提供的绑定有效,但它既不更新源也不更新目标。
尝试:
<ControlTemplate x:Key="x" TargetType={x:Type local:ClockValueScreen}>
<wpf:TimeSpanPicker
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Margin="0,0,7,0"
Loaded="MyTimeSpanPicker_Loaded"
TimeSpan="{Binding Path=CurrentValue,RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,diag:PresentationTraceSources.TraceLevel=High}"/>
</ControlTemplate>
<ControlTemplate x:Key="y" TargetType={x:Type local:ClockValueScreen}>
<Viewbox>
<xwpf:DateTimePicker
Value="{Binding Path=CurrentValue,RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Loaded="DateTimePicker_Loaded"/>
</Viewbox>
</ControlTemplate>
我没有验证这一点,但我认为 TargetType 必须在 ControlTemplate.And 中设置 BindingSource 需要明确。
以前,TimeSpanPicker 的 c-tor 是这样的(在将 TimeSpanProperty 重命名为 ValueProperty 之后):
public TimeSpanPicker()
{
InitializeComponent();
hPlusBtn.MyButton.Click += HPlusBtn_Click;
hMinusBtn.MyButton.Click += HMinusBtn_Click;
mPlusBtn.MyButton.Click += MPlusBtn_Click;
mMinusBtn.MyButton.Click += MMinusBtn_Click;
sPlusBtn.MyButton.Click += SPlusBtn_Click;
sMinusBtn.MyButton.Click += SMinusBtn_Click;
LongPressTimer.Tick += LongPressTimer_Tick;
Value = TimeSpan.FromSeconds(0);
ApplyValueToVisual(Value);
}
注册 属性 时设置的 OnValueChanged 静态事件处理程序从未被调用。
我注释掉了 Value = TimeSpan.FromSeconds(0);
行,现在一切正常。这是一个无用的行,因为默认值已经在 ValueProperty 依赖项 属性 的注册中设置。我仍然不明白修复它如何使双向绑定完美工作。我认为默认值可能被发送到 UI (在绑定中)并且 属性 总是将该值与直接在 c-tor 内部设置的值进行比较。
- 如果我将 TimeSpanPicker 直接放在 UserControl 元素中,它就可以工作。
- 如果我放置一个 DateTimePicker(来自扩展 WPF 工具包)而不是我的 TimeSpanPicker,它可以两种方式工作。
- (这种情况是我希望使用的,它在下面的代码中)如果我将 TimeSpanPicker 放在一个模板 Setter 里面一个 DataTrigger 里面 Style.Triggers 里面 UserControl.Style , 绑定停止工作。
无论如何都不起作用的绑定(虽然它设置为 TwoWay)是这样的:
TimeSpan="{Binding Path=CurrentValue,
Mode=TwoWay,
RelativeSource={RelativeSource Mode=TemplatedParent},
UpdateSourceTrigger=PropertyChanged}"
TimeSpan 属性 是一个依赖项 属性,CurrentValue
属性 也直接位于为 CurrentValue
实现 INotifyPropertyChanged 的对象中。我还尝试使用绑定到 TemplatedParent 的 RelativeSource,但它在我的情况下不起作用。
重现问题所需的所有代码如下,除了大部分 wpf-timespanpicker 程序集(我只留下相关的部分)。
重现步骤:
- 按现在的样子测试代码。
1.1。 运行 程序。
1.2。单击“应用时间跨度”按钮。
1.3。 TimeSpanPicker 出现在 window 的顶部,显示 0 秒,尽管下面的 TextBox 显示 00:10:00.
1.4。通过像最终用户一样操作来更改 TimeSpanPicker 显示的值。
1.5。 TextBox 仍然显示 00:10:00.
- 更改代码。
2.1。把它放在 UserControl1.xaml:
中而不是 Style 属性<w:TimeSpanPicker
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinHeight="50" MinWidth="70"
TimeSpan="{Binding Path=CurrentValue,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
2.2。重复步骤 1.2.-1.5。并看到 TextBox 中的值已更新以反映 Model.CurrentValue (00:10:00) 的初始值或最终用户在 UI.
中设置的值绑定诊断输出
从我在这个输出中看到的,我认为 DataContext 是错误的,它被直接设置到模板父级,而不是它的 DataContext。
如果我将 Binding 的路径设置为 DataContext.CurrentValue 它仍然不起作用,可能是因为 DataContext 未明确设置,它是从父控件继承的。
设置此绑定的最正确方法是什么?
System.Windows.Data Warning: 56 : Created BindingExpression (hash=4620049) for Binding (hash=22799085)
System.Windows.Data Warning: 58 : Path: 'CurrentValue'
System.Windows.Data Warning: 62 : BindingExpression (hash=4620049): Attach to wpf_timespanpicker.TimeSpanPicker.TimeSpan (hash=34786562)
System.Windows.Data Warning: 67 : BindingExpression (hash=4620049): Resolving source
System.Windows.Data Warning: 70 : BindingExpression (hash=4620049): Found data context element: <null> (OK)
System.Windows.Data Warning: 72 : RelativeSource.TemplatedParent found UserControl1 (hash=31201899)
System.Windows.Data Warning: 78 : BindingExpression (hash=4620049): Activate with root item UserControl1 (hash=31201899)
'cs-wpf-test-7.exe' (CLR v4.0.30319: cs-wpf-test-7.exe): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework-SystemCore\v4.0_4.0.0.0__b77a5c561934e089\PresentationFramework-SystemCore.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
System.Windows.Data Warning: 108 : BindingExpression (hash=4620049): At level 0 - for UserControl1.CurrentValue found accessor <null>
System.Windows.Data Error: 40 : BindingExpression path error: 'CurrentValue' property not found on 'object' ''UserControl1' (Name='')'. BindingExpression:Path=CurrentValue; DataItem='UserControl1' (Name=''); target element is 'TimeSpanPicker' (Name=''); target property is 'TimeSpan' (type 'TimeSpan')
System.Windows.Data Warning: 80 : BindingExpression (hash=4620049): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=4620049): TransferValue - using fallback/default value TimeSpan (hash=0)
System.Windows.Data Warning: 89 : BindingExpression (hash=4620049): TransferValue - using final value TimeSpan (hash=0)
UserControl1.xaml:
<UserControl xmlns:wpf_timespanpicker="clr-namespace:wpf_timespanpicker;assembly=wpf-timespanpicker" x:Class="cs_wpf_test_7.UserControl1"
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:xwpf="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
xmlns:local="clr-namespace:cs_wpf_test_7"
xmlns:w="clr-namespace:wpf_timespanpicker;assembly=wpf-timespanpicker"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<local:MyValueConverter x:Key="MyConv"/>
<ControlTemplate x:Key="x">
<w:TimeSpanPicker
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinHeight="50" MinWidth="70"
TimeSpan="{Binding Path=CurrentValue,
Mode=TwoWay,
RelativeSource={RelativeSource Mode=TemplatedParent},
UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
<ControlTemplate x:Key="y">
<xwpf:DateTimePicker
Value="{Binding Path=CurrentValue,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="{x:Type local:UserControl1}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentValue, Mode=OneWay, Converter={StaticResource MyConv}}"
Value="TimeSpan">
<Setter Property="Template" Value="{StaticResource x}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentValue, Mode=OneWay, Converter={StaticResource MyConv}}"
Value="DateTime">
<Setter Property="Template" Value="{StaticResource y}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
</UserControl>
MyValueConverter.cs
public class MyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? "null" : value.GetType().Name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
模特class
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal object _CurrentValue = null;
public object CurrentValue
{
get
{
return _CurrentValue;
}
set
{
if (_CurrentValue != value)
{
_CurrentValue = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(
"CurrentValue"));
}
}
}
}
MainWindow.xaml
<Window x:Class="cs_wpf_test_7.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:cs_wpf_test_7"
mc:Ignorable="d"
Title="MainWindow" Height="187" Width="254"
Loaded="Window_Loaded">
<StackPanel>
<local:UserControl1>
</local:UserControl1>
<TextBox Text="{Binding Path=CurrentValue,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Name="MyApplyTimeSpanButton"
Click="MyApplyTimeSpanButton_Click">
Apply TimeSpan
</Button>
<Button Name="MyApplyDateTimeButton"
Click="MyApplyDateTimeButton_Click">
Apply DateTime
</Button>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
Model m = new Model();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataContext = m;
}
private void MyApplyTimeSpanButton_Click(object sender, RoutedEventArgs e)
{
m.CurrentValue = TimeSpan.FromMinutes(10);
}
private void MyApplyDateTimeButton_Click(object sender, RoutedEventArgs e)
{
m.CurrentValue = DateTime.Now;
}
}
TimeSpanPicker.xaml:
<UserControl x:Class="wpf_timespanpicker.TimeSpanPicker"
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:wpf_timespanpicker"
mc:Ignorable="d"
d:DesignHeight="170" d:DesignWidth="365"
KeyboardNavigation.TabNavigation="Continue"
IsTabStop="True"
Focusable="True"
GotKeyboardFocus="UserControl_GotKeyboardFocus"
LostKeyboardFocus="UserControl_LostKeyboardFocus"
KeyDown="UserControl_KeyDown"
PreviewKeyDown="UserControl_PreviewKeyDown"
PreviewMouseDown="UserControl_PreviewMouseDown"
MouseDown="UserControl_MouseDown"
MouseLeave="UserControl_MouseLeave"
PreviewMouseUp="UserControl_PreviewMouseUp"
GotFocus="UserControl_GotFocus"
LostFocus="UserControl_LostFocus"
IsEnabledChanged="UserControl_IsEnabledChanged"
Loaded="UserControl_Loaded"
MouseWheel="UserControl_MouseWheel">
<Canvas SizeChanged="Canvas_SizeChanged">
<local:ArrowButton x:Name="hPlusBtn" State="True"/>
<local:TwoDigitsDisplay x:Name="tdd1" MouseUp="Tdd1_MouseUp"/>
<local:ArrowButton x:Name="hMinusBtn" State="False"/>
<local:ColonDisplay x:Name="tbc1"/>
<local:ArrowButton x:Name="mPlusBtn" State="True"/>
<local:TwoDigitsDisplay x:Name="tdd2" MouseUp="Tdd2_MouseUp"/>
<local:ArrowButton x:Name="mMinusBtn" State="False"/>
<local:ColonDisplay x:Name="tbc2"/>
<local:ArrowButton x:Name="sPlusBtn" State="True"/>
<local:TwoDigitsDisplay x:Name="tdd3" MouseUp="Tdd3_MouseUp"/>
<local:ArrowButton x:Name="sMinusBtn" State="False"/>
</Canvas>
</UserControl>
TimeSpanPicker.xaml.cs 的一部分:
注意: 在此 class 中,我仅使用标准 .NET 属性 包装器设置和获取 TimeSpan 属性。我没有在此 class.
中设置任何绑定public static readonly DependencyProperty TimeSpanProperty =
DependencyProperty.Register("TimeSpan", typeof(TimeSpan), typeof(TimeSpanPicker),
new PropertyMetadata(TimeSpan.Zero, OnTimeSpanChanged, TimeSpanCoerceCallback));
private static void OnTimeSpanChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as TimeSpanPicker).OnTimeSpanChanged();
}
private static object TimeSpanCoerceCallback(DependencyObject d, object baseValue)
{
return ((TimeSpan)baseValue).Subtract(
TimeSpan.FromMilliseconds(((TimeSpan)baseValue).Milliseconds));
}
public TimeSpan TimeSpan
{
get
{
return (TimeSpan)GetValue(TimeSpanProperty);
}
set
{
SetValue(TimeSpanProperty, value);
}
}
private void OnTimeSpanChanged()
{
ApplyTimeSpanToVisual(TimeSpan);
TimeSpanValueChanged?.Invoke(this, EventArgs.Empty);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TimeSpan"));
}
我希望问题开始时提供的绑定有效,但它既不更新源也不更新目标。
尝试:
<ControlTemplate x:Key="x" TargetType={x:Type local:ClockValueScreen}>
<wpf:TimeSpanPicker
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Margin="0,0,7,0"
Loaded="MyTimeSpanPicker_Loaded"
TimeSpan="{Binding Path=CurrentValue,RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,diag:PresentationTraceSources.TraceLevel=High}"/>
</ControlTemplate>
<ControlTemplate x:Key="y" TargetType={x:Type local:ClockValueScreen}>
<Viewbox>
<xwpf:DateTimePicker
Value="{Binding Path=CurrentValue,RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Loaded="DateTimePicker_Loaded"/>
</Viewbox>
</ControlTemplate>
我没有验证这一点,但我认为 TargetType 必须在 ControlTemplate.And 中设置 BindingSource 需要明确。
以前,TimeSpanPicker 的 c-tor 是这样的(在将 TimeSpanProperty 重命名为 ValueProperty 之后):
public TimeSpanPicker()
{
InitializeComponent();
hPlusBtn.MyButton.Click += HPlusBtn_Click;
hMinusBtn.MyButton.Click += HMinusBtn_Click;
mPlusBtn.MyButton.Click += MPlusBtn_Click;
mMinusBtn.MyButton.Click += MMinusBtn_Click;
sPlusBtn.MyButton.Click += SPlusBtn_Click;
sMinusBtn.MyButton.Click += SMinusBtn_Click;
LongPressTimer.Tick += LongPressTimer_Tick;
Value = TimeSpan.FromSeconds(0);
ApplyValueToVisual(Value);
}
注册 属性 时设置的 OnValueChanged 静态事件处理程序从未被调用。
我注释掉了 Value = TimeSpan.FromSeconds(0);
行,现在一切正常。这是一个无用的行,因为默认值已经在 ValueProperty 依赖项 属性 的注册中设置。我仍然不明白修复它如何使双向绑定完美工作。我认为默认值可能被发送到 UI (在绑定中)并且 属性 总是将该值与直接在 c-tor 内部设置的值进行比较。