Как получить доступ из кода программной части к экземпляру элемента управления, определенному в ControlTemplate, в сеттере в стиле в пользовательском элементе управления?

Раньше я устанавливал шаблон 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;
}

Вы можете попробовать использовать атрибут TemplatePart, чтобы разделить элемент управления на части.

Pavel Anikhouski 09.04.2019 15:34

Разве TimeSpanValueChanged не перенаправленное событие? Вы можете использовать стиль и средство настройки событий или обрабатывать его на уровне управления, если это так.

Andy 09.04.2019 16:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
182
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не можете использовать 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.

silviubogan 09.04.2019 15:56

Что, если вы определите свой шаблон как ресурс и сошлетесь на него в стиле с помощью <Setter Property = "Template" Value = "{StaticResource x}">?

mm8 09.04.2019 15:58

Я думаю, что это работает, но мне нужна дополнительная помощь. Я обновил свой вопрос. Спасибо.

silviubogan 09.04.2019 16:56

Если вы хотите что-то сделать при изменении свойства CurrentValue, вам следует зарегистрировать обратный вызов для этого свойства.

mm8 09.04.2019 16:59

@silviubogan: Смотрите мою правку. Вы сможете найти его в визуальном дереве, если оно там есть.

mm8 09.04.2019 17:06

Его нет в визуальном дереве.

silviubogan 09.04.2019 17:42

@silviubogan: Так где же он тогда? Очевидно, что вы никогда не сможете получить ссылку на элемент, который еще не существует. Это действительно невозможно.

mm8 10.04.2019 16:39

Другие вопросы по теме