简体   繁体   中英

Best way to create a resizable Grid of Button control with constrained proportions (Silverlight. XAML, WinRT)

Writing a WinRT app in XAML/C# where I'd like a simple grid of square shaped buttons. The number of buttons is fixed currently, however in future there will be more added as I create more content.

Having to handle all UI resizes (snapped, filled, portrait, etc) and resolutions I ran into problems with the UIContainer (I was using a Grid then switched the WrapGrid ) simply resizing the buttons automatically because I do not know of any way to constrain the aspect ratio and having square buttons is important to my UI.

Is there a way to constrain the aspect ratio / proportions of the Width and Height of a button control? If so, I'm assuming it would be to create a custom control, but other than creating styles and data templates I'm really just out of my depth.

Any suggestions on the best way to attack this problem?

You can create a simple decorator control that would override ArrangeOverride and always arrange itself into a square, like this:

public class SquareDecorator : ContentControl
{
    public SquareDecorator()
    {
        VerticalAlignment = VerticalAlignment.Stretch;
        HorizontalAlignment = HorizontalAlignment.Stretch;
        VerticalContentAlignment = VerticalAlignment.Stretch;
        HorizontalContentAlignment = HorizontalAlignment.Stretch;
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        var baseSize = base.MeasureOverride(availableSize);

        double sideLength = Math.Max(baseSize.Width, baseSize.Height);

        return new Size(sideLength, sideLength);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double sideLength = Math.Min(finalSize.Width, finalSize.Height);

        var result = base.ArrangeOverride(new Size(sideLength, sideLength));

        return result;
    }
}

Now you can wrap your buttons with this decorator:

<z:SquareDecorator>
    <Button Content="I'm Square"
            VerticalAlignment="Stretch"
            HorizontalAlignment="Stretch" />
</z:SquareDecorator>

I'm assuming you can't just set height and width to a fixed size (on the button itself or in a style for the button).

I tried doing this in Silverlight:

<Button Height={Binding ActualWidth, RelativeSource={RelativeSource Self}}/>

But it doesn't want to work. Don't know why. It might work in WinRT.

Alternatively, you can create a custom Panel to arrange and size your buttons. Should not be difficult given your simple requirements. It involves implementing just two functions and knowledge of basic arithmetic. Here is an example that creates a UniformGrid .

I don't think a UserControl or deriving from Button would be better choices.

Here's my version of Pavlo's answer. It is more efficient and elegant than deriving from ContentControl (which must use a ControlTemplate, adds other elements to the visual tree, and has tons of other unneeded functionality). I also believe it is more correct, because MeasureOverride returns the correct desired size.

public class SquareDecorator : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        if( Children.Count == 0 )   return base.MeasureOverride(availableSize);
        if( Children.Count > 1 )    throw new ArgumentOutOfRangeException("SquareDecorator should have one child");

        Children[0].Measure(availableSize);
        var sideLength = Math.Max(Children[0].DesiredSize.Width, Children[0].DesiredSize.Height);
        return new Size(sideLength, sideLength);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        if( Children.Count == 0 )   return base.ArrangeOverride(finalSize);
        if( Children.Count > 1 )    throw new ArgumentOutOfRangeException("SquareDecorator should have one child");

        double sideLength = Math.Min(finalSize.Width, finalSize.Height);
        Children[0].Arrange(new Rect(0, 0, sideLength, sideLength));
        return new Size(sideLength, sideLength);
    }
}

Use it the same way (Button's Horizontal/VerticalAlignment must be stretch, but this is the default. Also note you can get useful effects if you set SquareDecorator's Horizontal/VerticalAlignment to non-stretch.):

<z:SquareDecorator>
   <Button Content="I'm Square"/>
</z:SquareDecorator>

I would have derived from FrameworkElement, but it looks like neither Silverlight nor WinRT allow you to do that. (FrameworkElement is not sealed, but has no AddVisualChild method which makes it useless to derive from. Sigh, I hate .NET and/or Microsoft)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM