Reputation: 7456
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:
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.
If I use a Viewbox
with Uniform
it messes all up.
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
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
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
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
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
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