wpfuser-controlsgridmicrosoft-metrowinrt-xaml

Creating a square UserControl


I am designing a user control that needs to be square, and to fill as much room as it is given (to give some context, it is a checkboard).

My user control looks like:

<Grid>
    <!-- My 8 lines / colums, etc. , sized with "1*" to have equal lines -->
</Grid>

Now I would simply like to say "This grid has to be square no matter what room it has to expand".

Tried Solutions in vain:

  1. I can't use a UniformGrid because I actually have the names of the lines & columns in addition, so I have a leading header row and column with different sizes.

  2. If I use a Viewbox with Uniform it messes all up.

  3. I tried using the classic

    <Grid Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}"> ... </Grid>
    

but it only works if I manually set the Width property. Otherwise, this constraint is ignored.

Conclusion

I'm out of idea, and I would really like to avoid setting Width / Height manually as this control may be used in many various places (ListItem templates, games, etc...).


Solution from suggestion:

A solution is available with some code-behind. I did not find a XAML only solution.

Grid is now:

<Grid SizeChanged="Board_FullControlSizeChanged">...</Grid>

And the event handler is:

private void Board_FullControlSizeChanged(object sender, SizeChangedEventArgs args)
{
    double size = Math.min (args.NewSize.Height, args.NewSize.Width);
    ((Grid)sender).Width = size;
    ((Grid)sender).Height = size;
}

Solution

  • I initially tried modifying your binding to ActualWidth and it still did not work when the Grid was the top level element and in some cases it ended up expanding the control further than the available size. Hence tried some other ways of getting the required output.

    Got 2 ways of maybe addressing this:

    Since this is a view related issue (not breaking MVVM, keeping a square formation, if your ok with having a bit of code-behind, you could do something like)

    private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
      double minNewSizeOfParentUserControl = Math.Min(e.NewSize.Height, e.NewSize.Width);
      mainGrid.Width = minNewSizeOfParentUserControl;
      mainGrid.Height = minNewSizeOfParentUserControl;
    }
    

    and in your xaml you would name your main top level grid "mainGrid" and attach the UserControl size changed event handler to the above function not the Grid itself.

    However if you totally hate code-behind for whatever reason, you can be a bit more fancy and create a behavior such as

    public class GridSquareSizeBehavior : Behavior<Grid> {
      private UserControl _parent;
    
      protected override void OnAttached() {
        DependencyObject ucParent = AssociatedObject.Parent;
        while (!(ucParent is UserControl)) {
          ucParent = LogicalTreeHelper.GetParent(ucParent);
        }
        _parent = ucParent as UserControl;
        _parent.SizeChanged += SizeChangedHandler;
        base.OnAttached();
      }
    
      protected override void OnDetaching() {
        _parent.SizeChanged -= SizeChangedHandler;
        base.OnDetaching();
      }
    
      private void SizeChangedHandler(object sender, SizeChangedEventArgs e) {
        double minNewSizeOfParentUserControl = Math.Min(e.NewSize.Height, e.NewSize.Width);
        AssociatedObject.Width = minNewSizeOfParentUserControl;
        AssociatedObject.Height = minNewSizeOfParentUserControl;
      }
    }
    

    For the behavior your xaml would then look like:

      <Grid>
        <i:Interaction.Behaviors>
          <local:GridSquareSizeBehavior />
        </i:Interaction.Behaviors>
      </Grid>
    

    Did test these two methods with Snoop and the square size was maintained while expanding/shrinking. Do note both methods in the crux use the same logic(just a quick mock-up) and you might be able to squeeze some better performance if you update the logic to only update height when width is changed and vice versa instead of both and canceling a resize all together if not desired