Search code examples
wpfxamluser-interfaceitemscontrol

How to fill ItemsControl with buttons


I need to fill an panel with clickable button-based cells that looks like this:

with rectangle

this has done with rectangles:

<ItemsControl ItemsSource="{Binding Path=Cells}">
      <ItemsControl.ItemTemplate>
          <DataTemplate>
              <Rectangle>
                  <Rectangle.Fill>
                      <SolidColorBrush Color="Red"></SolidColorBrush>
                  </Rectangle.Fill>
              </Rectangle>
          </DataTemplate>
      </ItemsControl.ItemTemplate>
      <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
               <commonControls:UniformGrid ElementsGap="5" HorizontalCount="{Binding Path=CellsInRow}" />
          </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
  </ItemsControl>

but I need to do it with buttons. I'm trying that:

<ItemsControl ItemsSource="{Binding Path=Cells}">
      <ItemsControl.ItemTemplate>
          <DataTemplate>
              <Button>
                  <Button.Background>
                      <SolidColorBrush Color="Red"></SolidColorBrush>
                  </Button.Background>
              </Button>
          </DataTemplate>
      </ItemsControl.ItemTemplate>
      <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
               <commonControls:UniformGrid ElementsGap="5" HorizontalCount="{Binding Path=CellsInRow}" />
          </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
  </ItemsControl>

But it seems like that:

with button

How i can do cells from first image with buttons?

Code of UniformGrid below:

 public class UniformGrid : Panel
{
    public static readonly DependencyProperty HorizontalCountProperty =
        DependencyProperty.Register("HorizontalCount", typeof (int), typeof (UniformGrid),
                                    new PropertyMetadata(default(int)));

    public int HorizontalCount
    {
        get { return (int) GetValue(HorizontalCountProperty); }
        set { SetValue(HorizontalCountProperty, value); }
    }

    public static readonly DependencyProperty ElementsGapProperty =
        DependencyProperty.Register("ElementsGap", typeof (double), typeof (UniformGrid),
                                    new PropertyMetadata(default(double)));

    public double ElementsGap
    {
        get { return (double) GetValue(ElementsGapProperty); }
        set { SetValue(ElementsGapProperty, value); }
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        return new Size();
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (Children != null && Children.Count != 0)
        {
            var squareSideForElement = (finalSize.Width - (HorizontalCount - 1)*ElementsGap)/HorizontalCount;
            var sizeOfElement = new Size(squareSideForElement, squareSideForElement);
            for (var i = 0; i < Children.Count; i++)
            {
                var rowIndex = i%HorizontalCount;
                var columnIndex = i/HorizontalCount;
                var resultPoint = new Point
                    {
                        X = rowIndex*(squareSideForElement + ElementsGap),
                        Y = columnIndex*(squareSideForElement + ElementsGap)
                    };
                Children[i].Arrange(new Rect(resultPoint, sizeOfElement));

            }

        }
        return finalSize;
    }
}

Solution

  • Your MeasureOverride must call Measure on each child. Please read the Remarks section in MeasureOverride, especially the following note:

    Elements should call Measure on each child during this process, otherwise the child elements will not be correctly sized or arranged

    You should at least do this:

    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement element in InternalChildren)
        {
            element.Measure(availableSize);
        }
    
        return new Size();
    }
    

    If you want to make sure that every child calculates its maximum preferred size, you could pass an infinite width and height to Measure:

    protected override Size MeasureOverride(Size availableSize)
    {
        availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
    
        foreach (UIElement element in InternalChildren)
        {
            element.Measure(availableSize);
        }
    
        return new Size();
    }