Как я могу найти элемент, встроенный в триггер стиля, по имени в WPF?

Во-первых, суть вопроса: если элемент назначается как Content ContentControl через триггер стиля, я не могу найти его по имени.

Теперь для более подробной информации: у меня есть панель, которая сильно различается по своему макету и функциональности в зависимости от контекста данных, что является ошибкой из хранилища ошибок. Когда эта ошибка равна нулю, это форма поиска, когда она не равна нулю, это простая программа для просмотра свойств этой ошибки. Тогда XAML будет выглядеть примерно так:

<ContentControl DataContext="...">
    <ContentControl.Style>
        <Style TargetType="ContentControl">
            <Setter Property="Content">
                <Setter.Value>
                    ...
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <DataTrigger Binding="{Binding}" Value="{x:Null}">
                    <Setter Property="Content">
                        <StackPanel>
                            <TextBox Name="Waldo"/>
                            <Button .../>
                        </StackPanel>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

Когда пользователь нажимает кнопку, которая находится рядом с текстовым полем, я получаю обратный вызов в коде позади. С этого момента я хотел бы иметь доступ к различным свойствам текстового поля. Вопрос в том, где Уолдо? :)

В исходном коде я пробовал несколько следующих вариантов, но без особого успеха:

this.FindName("Waldo"); // Always returns null

Я видел много дискуссий по этой теме в связи с шаблоны, но не в том, что касается настройки содержимого напрямую с помощью триггеров. Может быть, потому, что этим я нарушаю самые лучшие практики :)

Спасибо!


person Dan    schedule 24.02.2014    source источник


Ответы (1)


Если элемент назначен как Content из ContentControl через триггер стиля, я не могу найти его по имени.

Если вам нужно получить доступ к Content до срабатывания триггера, это, скорее всего, невозможно. В этой ситуации главное получить доступ после того, как произойдет DataTrigger.

Я нарушаю самые лучшие практики, делая это

Возможно, это неправильный способ работы с Сontrol в WPF, тем более что вам все равно нужен доступ к динамическому контенту, который в дальнейшем можно будет изменить. Но в любом случае есть два способа работы с Сontrol - как сейчас и в стиле MVVM. Стиль MVVM лучше всего подходит для больших и менее сложных приложений с различной бизнес-логикой. Если в вашем случае для легкого применения, то в данной ситуации я не вижу в этом ничего плохого. Помимо того, что нужно делать проект в стиле MVVM с нуля, сочетать обычный метод и правильный метод - не лучший способ.

Я создал небольшой пример, чтобы продемонстрировать средства управления доступом для данной ситуации. Существует свойство, соответствующее типу Content, по умолчанию Init. Если вы назначаете null для этого свойства, загружается динамическое содержимое.

Вот так я получаю доступ к TextBox:

private void GetAccessToTextBox_Click(object sender, RoutedEventArgs e)
{
    TextBox MyTextBox = null;
    StackPanel panel = MainContentControl.Content as StackPanel;

    foreach (object child in panel.Children)
    {
        if (child is TextBox)
        {
            MyTextBox = child as TextBox;
        }
    }

    if (MyTextBox != null) 
    {
        MyTextBox.Background = Brushes.Gainsboro;
        MyTextBox.Height = 100;
        MyTextBox.Text = "Got access to me!";
    }
}

Ниже приведен полный пример:

XAML

<Window x:Class="AccessToElementInContentControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:AccessToElementInContentControl"
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <this:TestData />
    </Window.DataContext>

    <Window.Resources>
        <Style TargetType="{x:Type ContentControl}">
            <Setter Property="Content">
                <Setter.Value>
                    <Label Content="InitContent"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center" />
                </Setter.Value>
            </Setter>

            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=TypeContent}" Value="{x:Null}">
                    <Setter Property="Content">
                        <Setter.Value>
                            <StackPanel Name="NullStackPanel">
                                <TextBox Name="Waldo" Text="DynamicText" />
                                <Button Width="100" Height="30" Content="DynamicButton" />
                            </StackPanel>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Grid>
        <ContentControl Name="MainContentControl" />

        <Button Name="SetContentType"
                Width="100"
                Height="30" 
                HorizontalAlignment="Left"
                Content="SetContentType"
                Click="SetContentType_Click" />

        <Button Name="GetAccessToButton"
                Width="110"
                Height="30" 
                HorizontalAlignment="Right"
                Content="GetAccessToTextBox"
                Click="GetAccessToTextBox_Click" />
    </Grid>
</Window>

Code-behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void SetContentType_Click(object sender, RoutedEventArgs e)
    {
        TestData test = this.DataContext as TestData;

        test.TypeContent = null;
    }

    private void GetAccessToTextBox_Click(object sender, RoutedEventArgs e)
    {
        TextBox MyTextBox = null;
        StackPanel panel = MainContentControl.Content as StackPanel;

        foreach (object child in panel.Children)
        {
           if (child is TextBox)
           {
                MyTextBox = child as TextBox;
           }
        }

        if (MyTextBox != null) 
        {
            MyTextBox.Background = Brushes.Gainsboro;
            MyTextBox.Height = 100;
            MyTextBox.Text = "Got access to me!";
        }
    }
}

public class TestData : NotificationObject
{
    private string _typeContent = "Init";

    public string TypeContent
    {
        get
        {
            return _typeContent;
        }

        set
        {
            _typeContent = value;
            NotifyPropertyChanged("TypeContent");   
        }
    }
}

public class NotificationObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
person Anatoliy Nikolaev    schedule 24.02.2014
comment
Спасибо за вдумчивое и подробное объяснение! Этот подход имеет свойство, заключающееся в том, что он заставляет знание структуры пользовательского интерфейса в код программной части (а именно, что текстовое поле, которое мы ищем, является дочерним элементом именованной StackPanel), но, похоже, это наименее болезненный подход в целом. . Как вы сказали, вероятно, лучше не слишком увлекаться разделением V / VM для небольшого приложения :) - person Dan; 25.02.2014
comment
Немного поспешно отскочил от пистолета Анатолий, это просто вылетело у меня из головы и я на время отошел от компьютера. Несмотря на антагонистический ответ, я пошел дальше и дал должное :) Это был отличный ответ, еще раз спасибо. - person Dan; 25.02.2014