В WPF, как мне найти элемент в шаблоне, который включен с помощью триггера?

#wpf #templates #triggers #findcontrol #contenttemplateselector

#wpf #шаблоны #триггеры #findcontrol #contenttemplateselector

Вопрос:

У меня есть UserControl (не пользовательский элемент управления без внешнего вида), который, в зависимости от некоторых пользовательских свойств состояния, меняет местами различные ContentTemplates, все из которых определены как ресурсы в соответствующем файле XAML. В исходном коде мне нужно найти один из элементов в замененных ContentTemplates.

Теперь в элементе управления без внешнего вида (то есть пользовательском элементе управления) вы просто переопределяете OnApplyTemplate, затем используете FindName , но это переопределение не срабатывает, когда ContentTemplate переключается триггером (… по крайней мере, не для UserControl. Я не тестировал эту функциональность с пользовательским элементом управления.)

Теперь я попытался подключить загруженное событие к элементу управления в заменяемом шаблоне, который срабатывает в коде, затем я просто сохраняю ‘sender’ в переменной уровня класса. Однако, когда я пытаюсь очистить это значение, подписавшись на событие Unloaded, оно также не срабатывает, потому что tempalte заменяется, таким образом отключая это событие до того, как у него появится шанс быть вызванным, и элемент управления автоматически выгружается с экрана, но у меня все еще есть эта ссылка на hung в коде.

Для имитации функциональности OnApplyTemplate я рассматриваю возможность подписки на уведомление ContentTemplateChanged и просто использую VisualTreeHelper для поиска нужного мне элемента управления, но мне интересно, есть ли способ получше, отсюда и этот пост.

Есть идеи?

Для справки, вот очень урезанный пример элемента управления, который у меня есть. В этом примере, если isEditing имеет значение true, я хочу найти текстовое поле с именем ‘FindMe’. Если значение isEditing равно false, что означает, что ContentTemplate не заменен, я хочу получить ‘null’ …

 <UserControl x:Class="Crestron.Tools.ProgramDesigner.Controls.EditableTextBlock"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Crestron.Tools.ProgramDesigner.Controls"
    x:Name="Root">

    <UserControl.Resources>

        <DataTemplate x:Key="EditModeTemplate">

            <TextBox x:Name="FindMe"
                Text="{Binding Text, ElementName=Root}" />

        </DataTemplate>

        <Style TargetType="{x:Type local:EditableTextBlock}">
            <Style.Triggers>

                <Trigger Property="IsEditing" Value="True">
                    <Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
                </Trigger>

            </Style.Triggers>
        </Style>

    </UserControl.Resources>

    <TextBlock x:Name="TextBlock"
        Text="{Binding Text, ElementName=Root}" />

</UserControl>
  

Ааааааа И ВПЕРЕД!

M

Ответ №1:

К сожалению, лучшего способа нет. Вы можете переопределить OnContentTemplateChanged вместо подключения к событию.

Вам нужно будет использовать DataTemplate.Метод FindName для получения фактического элемента. По ссылке приведен пример того, как используется этот метод.

Однако при использовании OnContentTemplateChanged вам пришлось бы отложить вызов FindName, поскольку он не применяется к базовому ContentPresenter немедленно. Что-то вроде:

 protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
    base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);

    this.Dispatcher.BeginInvoke((Action)(() => {
        var cp = FindVisualChild<ContentPresenter>(this);
        var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
        textBox.Text = "Found in OnContentTemplateChanged";
    }), DispatcherPriority.DataBind);
}
  

В качестве альтернативы, вы можете прикрепить обработчик к событию LayoutUpdated пользовательского элемента управления, но это может срабатывать чаще, чем вы хотите. Однако это также будет обрабатывать случаи неявных DataTemplates.

Что-то вроде этого:

 public UserControl1() {
    InitializeComponent();
    this.LayoutUpdated  = new EventHandler(UserControl1_LayoutUpdated);
}

void UserControl1_LayoutUpdated(object sender, EventArgs e) {
    var cp = FindVisualChild<ContentPresenter>(this);
    var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
    textBox.Text = "Found in UserControl1_LayoutUpdated";
}
  

Комментарии:

1. Аааа! Не знал об переопределении OnContentTemplateChanged. Это должно сработать. Во-первых, в примере кода по вашей ссылке для listbox указано ‘IsSynchronizedWithCurrentItem установлено значение True, чтобы это работало’ со строкой кода ‘ListBoxItem myListBoxItem = (ListBoxItem)(myListBox. ItemContainerGenerator. ContainerFromItem(myListBox. Товары. CurrentItem));’ Вы знаете, почему они просто не использовали ‘myListBox.SelectedItem’? Тогда я не думаю, что вам пришлось бы использовать ‘CurrentItem’, верно? Или я что-то упускаю?

2. @MarqueIV — Да, похоже, что SelectedItem будет иметь то же значение, что и Items. CurrentItem, но вам придется это протестировать. Однако, если вы не используете ListBox, вам не нужно этого делать. Похоже, вы используете текстовый блок, это верно? Это должен быть ContentControl, нет?

3. Странная вещь, которую я заметил… когда isEditing имеет значение false (т. Е. Я еще не поменял местами альтернативный шаблон), ContentTemplate возвращает ‘null’. Я думал, что это, по крайней мере, подберет «шаблон» по умолчанию для UserControl (т. Е. основные элементы в UserControl), но, похоже, это не так. Похоже, мне придется проверить, имеет ли ContentTemplate значение null, и если нет, использовать это, но если да, перейдите к обычному элементу управления. Метод FindName. Это всего лишь предположение, но я должен проверить. (Кстати, вот почему я воздерживаюсь от пометки этого как принятого. Технически я еще не совсем там.)

4. @MarqueIV — Нет проблем. Я не думаю, что ContentTemplate установлен по умолчанию. Обычно в этом случае используются неявные DataTemplates (т. Е. DataTemplate, содержащий только TargetType, но без ключа).

5. Все еще возникают проблемы. Я ввел переопределение OnContentTemplateChanged и вызвал ‘var TextBox = newContentTemplate. FindName(«EditTextBox», this);’ и получил сообщение об ошибке, что вы можете вызывать это только для элементов управления, к которым применен этот шаблон. Но в немедленном окне я сделал ‘? newContentTemplate == это. ContentTemplate’ и он вернул true! WTF?!!