ItemsControl has no children during MainWindow'

2019-07-09 08:52发布

Based on the answer to SO question "WPF: arranging collection items in a grid", I have the following:

    <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>

Now, I want to set the number of rows and columns of theGrid in the code behind:

            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());

For this, of course, I need to find the grid. I've used CrimsonX's FindChild in SO question WPF ways to find controls to first locate itemsControl1 and then, using it as the parent, locate theGrid.

    Grid FindTheGrid()
    {

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

    }

This works when called from a button's click event handler. It fails, however, when called from MainWindow's constructor because ic's childrenCount is 0.

int childrenCount = VisualTreeHelper.GetChildrenCount(theParent);

So, how can I set theGrid's row and column collections before the window is shown to the user?

2条回答
地球回转人心会变
2楼-- · 2019-07-09 09:18

I got tired of writing out RowDefinitions and ColumnDefinitions for my Grids, so created some custom DependencyProperties that let you specify the number of Rows/Columns in the Grid definition.

The code for the Dependency Properties can be found here and are used like this:

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

If it gives you an error about the binding then change the typeof(Grid) in the DependencyProperty definition to typeof(GridHelpers). My first version of the helper class didn't allow bindings and I can't remember which one I have posted.

Edit

Here's the code I am using which works, including correctly updating the UI when SomeInt changes. I was testing with switching SomeInt between 2 and 3 when clicking a button

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>

DependencyProperty

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
}

Result

Example1 Example2

查看更多
Evening l夕情丶
3楼-- · 2019-07-09 09:34

WPF generates the "containers" for an ItemsControl's items (usually a DataTemplate) in the background, and they won't be available immediately.

The only way to know when the items are available to use is by subscribing to the StatusChanged event on the ItemsControl's ItemContainerGenerator property:

itemsControl1.ItemContainerGenerator.StatusChanged += ic_GeneratorStatusChanged;

... and then unsubscribing from within the event handler since you only need it to fire the once:

void ic_GeneratorStatusChanged(object sender, EventArgs e)
{

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

    itemsControl1.ItemContainerGenerator.StatusChanged -= ic_GeneratorStatusChanged;

    // your items are now generated
}

It's a roundabout way of doing things, for sure, but it's the only way to know that the ItemsControl's items exist in the visual tree.

查看更多
登录 后发表回答