- Если я помещаю 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
находится непосредственно внутри объекта, который также реализует INotifyPropertyChanged для CurrentValue
.Я также пытался использовать RelativeSource для Binding to TemplatedParent, и он не работает в моей ситуации.
Весь код, необходимый для воспроизведения проблемы, приведен ниже, за исключением большей части сборки wpf-timespanpicker (я оставил здесь толькофрагменты, которые имеют отношение к делу).
Шаги для воспроизведения:
- Протестируйте код как есть.
1.1.Запустите программу.
1.2.Нажмите на кнопку Apply TimeSpan.
1.3.TimeSpanPicker отображается в верхней части окна и отображает 0 секунд, хотя в текстовом поле ниже показано 00: 10: 00.
1.4.Измените значение, отображаемое TimeSpanPicker, действуя как конечный пользователь.
1.5.TextBox по-прежнему отображает 00: 10: 00.
Изменить код.
2.1.Поместите это вместо атрибута Style в UserControl1.xaml:
<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), либо значение, установленное конечным пользователем в пользовательском интерфейсе.
Диагностический вывод привязки
Из того, что я вижу в этом выводе, я думаю, что DataContext неверен, он установлен непосредственно для шаблонного родителя, а не для его DataContext.
Если я задаю путь привязки для 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();
}
}
Класс модели
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:
ПРИМЕЧАНИЕ: в этом классе я только устанавливаю и получаю свойство TimeSpany используя стандартную оболочку свойств .NET.Я не устанавливаю никаких Привязок в этом классе.
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"));
}
Хотелось бы, чтобы Привязка, представленная в начале вопроса, работала, но не обновляла ни источник, ни цель.