Qortex
Qortex

Reputation: 7456

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

Upvotes: 2

Views: 3009

Answers (5)

sgib
sgib

Reputation: 1040

I have solved this by setting the margin of the contained control from inside the parent control's size changed event. In my case I have a 'sudoku grid' user control called SudokuBoard inside a standard Grid control called MainGrid (which fills the main window) and it only requires the following code;

    private void MainGrid_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        double verticalMargin = Math.Max((e.NewSize.Height - e.NewSize.Width)*0.5, 0.0);
        double horizontalMargin = Math.Max((e.NewSize.Width - e.NewSize.Height)*0.5, 0.0);
        SudokuBoard.Margin = new Thickness(horizontalMargin, verticalMargin, horizontalMargin, verticalMargin); 
    }

Upvotes: 0

ConfusedHorse
ConfusedHorse

Reputation: 99

Register wherever it suits you (usually in constructor or OnAttached()):

SizeChanged += Square;

and handle size with this:

private void Square(object sender, SizeChangedEventArgs e)
{
    if (e.HeightChanged) Width = e.NewSize.Height;
    else if (e.WidthChanged) Height = e.NewSize.Width;
}

Upvotes: 0

Viv
Viv

Reputation: 17380

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

Upvotes: 3

Paymahn Moghadasian
Paymahn Moghadasian

Reputation: 10329

Try putting your grid in a ViewBox: http://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox.aspx

Here's a code sample I came up with:

The usercontrol:

<UserControl x:Class="StackOverflow.CheckBoard"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Viewbox>
        <Grid Background="Red" Height="200" Width="200">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Button Content="testing" Grid.Row="0"/>
            <Button Content="testing" Grid.Row="1"/>
            <Button Content="testing" Grid.Row="2"/>
        </Grid>
    </Viewbox>
</UserControl>

And the main window:

<Window x:Class="StackOverflow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:StackOverflow"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:AllNoneCheckboxConverter x:Key="converter"/>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <local:CheckBoard MaxWidth="80"/>
        </StackPanel>
    </Grid>
</Window>

What this Viewbox will do is scale the control to the space it's given. Since the grid inside the viewbox is square, the grid will ALWAYS stay square. Try playing around with the MaxWidth property I used in the MainWindow.

Upvotes: 1

Clemens
Clemens

Reputation: 128013

You could bind the Height property to ActualWidth instead of Width:

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

However, a better solution would be to use a Viewbox. The trick to avoid that it "messes all up" is to make its Child square by defining (sensible) equal values for Width and Height:

<Viewbox>
    <Grid Width="500" Height="500">
        ...              
    </Grid>
</Viewbox>

Upvotes: 0

Related Questions