Раньше я устанавливал шаблон UserControl напрямую, а не через стиль, и все работало хорошо: доступ к корню содержимого можно было получить с помощью this.Template.LoadContent() или через this.Template.FindName("MyControlName", this ), оба вызова выполняются в OnApplyTemplate после вызова base.OnApplyTemplate(). Теперь мне нужен стиль, потому что я использую два триггера данных для отображения типа элемента управления или другого в зависимости от значения привязки.
Путем отладки с помощью приведенного ниже XAML я вижу, что после вызова base.OnApplyTemplate this.Template.LoadContent() возвращает границу с дочерним элементом, установленным в пустой ContentPresenter. Я хочу получить элемент wpf:TimeSpanPicker.
Я прочитал ответ это, и он мне не помогает из-за результата отладки, представленного выше. То же самое с этот ответ.
Раньше мой UserControl имел это прямо внутри (в его файле XAML):
<UserControl.Template>
<ControlTemplate>
<wpf:TimeSpanPicker
HorizontalAlignment = "Stretch"
VerticalAlignment = "Stretch"
HorizontalContentAlignment = "Stretch"
VerticalContentAlignment = "Stretch"
Name = "MyTimeSpanPicker"
Margin = "0,0,7,0"/>
</ControlTemplate>
</UserControl.Template>
Теперь у меня есть это:
<UserControl.Style>
<Style TargetType = "UserControl">
<Style.Triggers>
<DataTrigger Binding = "{Binding Mode=OneWay, Converter = {StaticResource ClockToType}}"
Value = "TimerData">
<Setter Property = "Template">
<Setter.Value>
<ControlTemplate TargetType = "UserControl">
<wpf:TimeSpanPicker
HorizontalAlignment = "Stretch"
VerticalAlignment = "Stretch"
HorizontalContentAlignment = "Stretch"
VerticalContentAlignment = "Stretch"
Name = "MyTimeSpanPicker"
Margin = "0,0,7,0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding = "{Binding Mode=OneWay, Converter = {StaticResource ClockToType}}"
Value = "AlarmData">
<Setter Property = "Template">
<Setter.Value>
<ControlTemplate TargetType = "UserControl">
<Button>Not yet implemented</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Код программной части включает в себя:
internal wpf_timespanpicker.TimeSpanPicker GetMyTimeSpanPicker()
{
return (wpf_timespanpicker.TimeSpanPicker)Template.LoadContent();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GetMyTimeSpanPicker().TimeSpanValueChanged += MyTimeSpanPicker_TimeSpanValueChanged;
}
private void MyTimeSpanPicker_TimeSpanValueChanged(object sender, EventArgs e)
{
CurrentValue = GetMyTimeSpanPicker().TimeSpan;
}
Преобразователь значений ClockToType просто преобразует один из экземпляров моих классов Clock в имя их типа.
Теперь это частично работает из-за ответа, но мне нужно установить свойство зависимости TimeSpan для TimeSpanPicker, когда свойство зависимости CurrentValue для UserControl изменено, а свойство зависимости CurrentValue можно изменить, когда средство выбора временного интервала еще не загружено. Каков наилучший способ отложить эту настройку? Размещение вызова ApplyTemplate перед настройкой, похоже, не работает, потому что переменная класса, в которой я храню ссылку на TimeSpanPicker, имеет значение null:
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = d as ClockValueScreen;
o.ApplyTemplate();
o.MyTimeSpanPicker.TimeSpan = (TimeSpan)e.NewValue; // but here o.MyTimeSpanPicker is null
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate(); // execution reaches this point from the ApplyTemplate call above
}
private void MyTimeSpanPicker_Loaded(object sender, RoutedEventArgs e)
{
MyTimeSpanPicker = (wpf_timespanpicker.TimeSpanPicker)sender;
MyTimeSpanPicker.TimeSpanValueChanged += MyTimeSpanPicker_TimeSpanValueChanged;
}
Разве TimeSpanValueChanged не перенаправленное событие? Вы можете использовать стиль и средство настройки событий или обрабатывать его на уровне управления, если это так.
Вы не можете использовать OnApplyTemplate()
, потому что элемент TimeSpanPicker
недоступен до тех пор, пока привязка не будет разрешена и преобразователь не вернет значение «TimerData».
Вместо этого вы можете подключить обработчик событий в XAML:
<ControlTemplate TargetType = "UserControl">
<wpf:TimeSpanPicker
Loaded = "OnLoaded"
HorizontalAlignment = "Stretch"
VerticalAlignment = "Stretch"
HorizontalContentAlignment = "Stretch"
VerticalContentAlignment = "Stretch"
Name = "MyTimeSpanPicker"
Margin = "0,0,7,0"/>
</ControlTemplate>
... а затем обработайте это в своем коде программной части;
private void OnLoaded(object sender, RoutedEventArgs e)
{
wpf_timespanpicker.TimeSpanPicker timeSpanPicker = ( wpf_timespanpicker.TimeSpanPicker)sender;
}
Редактировать: Если вы хотите что-то сделать с TimeSpanPicker
при изменении свойства CurrentValue
, вы можете использовать класс VisualTreeHelper
, чтобы найти его в визуальном дереве:
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = (ClockValueScreen)d;
var timeSpanPicker = FindVisualChild<wpf_timespanpicker.TimeSpanPicker>(o);
//...
}
private static T FindVisualChild<T>(Visual visual) where T : Visual
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
T descendent = FindVisualChild<T>(child);
if (descendent != null)
{
return descendent;
}
}
}
return null;
Я получаю эту ошибку: The event 'Loaded' cannot be specified on a Target tag in a Style. Use an EventSetter instead.
Что, если вы определите свой шаблон как ресурс и сошлетесь на него в стиле с помощью <Setter Property = "Template" Value = "{StaticResource x}">
?
Я думаю, что это работает, но мне нужна дополнительная помощь. Я обновил свой вопрос. Спасибо.
Если вы хотите что-то сделать при изменении свойства CurrentValue
, вам следует зарегистрировать обратный вызов для этого свойства.
@silviubogan: Смотрите мою правку. Вы сможете найти его в визуальном дереве, если оно там есть.
Его нет в визуальном дереве.
@silviubogan: Так где же он тогда? Очевидно, что вы никогда не сможете получить ссылку на элемент, который еще не существует. Это действительно невозможно.
Вы можете попробовать использовать атрибут
TemplatePart
, чтобы разделить элемент управления на части.