ItemsControl не имеет дочерних элементов во время конструктора MainWindow

На основе ответа на вопрос SO "WPF: размещение элементов коллекции в сетке", у меня есть следующее:

    <ItemsControl Name="itemsControl1" ItemsSource="{Binding MyItems}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid Name="theGrid" ShowGridLines="True" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="{x:Type FrameworkElement}">
                <Setter Property="Grid.Row" Value="{Binding RowIndex}" />
                <Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>

Теперь я хочу установить количество строк и столбцов сетки в коде позади:

            theGrid.RowDefinitions.Clear();
            theGrid.ColumnDefinitions.Clear();

            for (uint i = 0; i < theNumberOfRows; i++)
                theGrid.RowDefinitions.Add(new RowDefinition());

            for (uint i = 0; i < theNumberOfCols; i++)
                theGrid.ColumnDefinitions.Add(new ColumnDefinition());

Для этого, конечно, нужно найти сетку. Я использовал FindChild от CrimsonX в вопросе SO WPF способы поиска элементов управления, чтобы сначала найти itemsControl1 а затем, используя его в качестве родителя, найдите theGrid.

    Grid FindTheGrid()
    {

            ItemsControl ic = (ItemsControl)this.FindName("itemsControl1");
            Grid theGrid = FindChild<Grid>(ic, "theGrid");

    }

Это работает при вызове из обработчика события нажатия кнопки. Однако он терпит неудачу при вызове из конструктора MainWindow, потому что ic ChildrenCount равен 0.

int childrenCount = VisualTreeHelper.GetChildrenCount(theParent);

Итак, как я могу установить коллекции строк и столбцов theGrid до того, как окно будет показано пользователю?


person Avi    schedule 04.01.2012    source источник


Ответы (2)


WPF создает «контейнеры» для элементов ItemsControl (обычно DataTemplate) в фоновом режиме, и они не будут доступны сразу.

Единственный способ узнать, когда элементы доступны для использования, — подписаться на событие StatusChanged в свойстве ItemContainerGenerator элемента управления ItemsControl:

itemsControl1.ItemContainerGenerator.StatusChanged += ic_GeneratorStatusChanged;

... а затем отказаться от подписки из обработчика событий, так как вам нужно только один раз:

void ic_GeneratorStatusChanged(object sender, EventArgs e)
{

    if (itemsControl1.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
        return;

    itemsControl1.ItemContainerGenerator.StatusChanged -= ic_GeneratorStatusChanged;

    // your items are now generated
}

Конечно, это обходной путь, но это единственный способ узнать, что элементы ItemsControl существуют в визуальном дереве.

person Matt Hamilton    schedule 04.01.2012
comment
Я пробовал это, но пытаясь очистить RowDefintions после обнаружения сгенерированного контейнера, я получаю исключение Не могу изменить «RowDefinitionCollection» в состоянии только для чтения. - person Avi; 04.01.2012
comment
Ну, это совершенно новый вопрос! :) - person Matt Hamilton; 04.01.2012
comment
Правильный. Выложу еще один. - person Avi; 04.01.2012
comment
здесь stackoverflow.com/questions/8727545/ - person Avi; 04.01.2012

Я устал записывать RowDefinitions и ColumnDefinitions для своих сеток, поэтому создал несколько пользовательских свойств DependencyProperties, которые позволяют указать количество строк/столбцов в определении сетки.

Код для свойств зависимостей можно найти здесь и используются следующим образом:

<Grid local:GridHelpers.RowCount="{Binding RowCount}"
      local:GridHelpers.ColumnCount="{Binding ColumnCount}" />

Если это выдает ошибку о привязке, измените typeof(Grid) в определении DependencyProperty на typeof(GridHelpers). Моя первая версия вспомогательного класса не допускала привязки, и я не могу вспомнить, какую из них я опубликовал.

Изменить

Вот код, который я использую, который работает, включая правильное обновление пользовательского интерфейса при изменении SomeInt. Я тестировал переключение SomeInt между 2 и 3 при нажатии кнопки.

XAML

<Grid ShowGridLines="True"
      local:GridProperties.ColumnCount="{Binding SomeInt}"
      local:GridProperties.RowCount="{Binding SomeInt}">

    <TextBox Text="Test" Grid.Row="0" Grid.Column="0" />
    <TextBox Text="Test" Grid.Row="0" Grid.Column="1" />
    <TextBox Text="Test" Grid.Row="0" Grid.Column="2" />
    <TextBox Text="Test" Grid.Row="1" Grid.Column="0" />
    <TextBox Text="Test" Grid.Row="1" Grid.Column="1" />
    <TextBox Text="Test" Grid.Row="1" Grid.Column="2" />
    <TextBox Text="Test" Grid.Row="2" Grid.Column="0" />
    <TextBox Text="Test" Grid.Row="2" Grid.Column="1" />
    <TextBox Text="Test" Grid.Row="2" Grid.Column="2" />
</Grid>

Свойство зависимости

public class GridProperties
{
    #region RowCount Property

    /// <summary>
    /// Adds the specified number of Rows to RowDefinitions. Default Height is Auto
    /// </summary>
    public static readonly DependencyProperty RowCountProperty =
        DependencyProperty.RegisterAttached("RowCount", typeof(int),
        typeof(GridProperties),
        new PropertyMetadata(-1, RowCountChanged));

    // Get
    public static int GetRowCount(DependencyObject obj)
    {
        return (int)obj.GetValue(RowCountProperty);
    }

    // Set
    public static void SetRowCount(DependencyObject obj, int value)
    {
        obj.SetValue(RowCountProperty, value);
    }

    // Change Event - Adds the Rows
    public static void RowCountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is Grid) || (int)e.NewValue < 0)
            return;

        Grid grid = (Grid)obj;
        grid.RowDefinitions.Clear();

        for (int i = 0; i < (int)e.NewValue; i++)
            grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

        //SetStarRows(grid);
    }

    #endregion

    #region ColumnCount Property

    /// <summary>
    /// Adds the specified number of Columns to ColumnDefinitions. Default Width is Auto
    /// </summary>
    public static readonly DependencyProperty ColumnCountProperty =
        DependencyProperty.RegisterAttached("ColumnCount", typeof(int),
        typeof(GridProperties),
        new PropertyMetadata(-1, ColumnCountChanged));

    // Get
    public static int GetColumnCount(DependencyObject obj)
    {
        return (int)obj.GetValue(ColumnCountProperty);
    }

    // Set
    public static void SetColumnCount(DependencyObject obj, int value)
    {
        obj.SetValue(ColumnCountProperty, value);
    }

    // Change Event - Add the Columns
    public static void ColumnCountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is Grid) || (int)e.NewValue < 0)
            return;

        Grid grid = (Grid)obj;
        grid.ColumnDefinitions.Clear();

        for (int i = 0; i < (int)e.NewValue; i++)
            grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });

        // SetStarColumns(grid);
    }

    #endregion
}

Результат

Example1Пример2

person Rachel    schedule 04.01.2012
comment
Не работает для меня. Когда это был typeof(Grid), я получил исключение. Изменено на typeof(GridHelpers) и производное от dependencyobject — свойства Row/ColumnCountChanged не вызывались, хотя исходные свойства изменились. - person Avi; 04.01.2012
comment
@Avi Ави, кажется, у меня все работает. Я обновлю свой ответ, чтобы включить фактический рабочий код, который у меня есть, который может отличаться от кода, размещенного в моем блоге (он был немного отредактирован с тех пор, как я его опубликовал) - person Rachel; 04.01.2012
comment
@Avi Кроме того, убедитесь, что вы привязываетесь к int, поскольку DependencyProperty настроен на использование int. Другой числовой тип, такой как decimal, не будет работать. - person Rachel; 04.01.2012