views:

28

answers:

1

Hello all. I'm coding a custom panel representing the hand of cards. It's a panel that will stack the cards horizontally. If there isn't enough space, each card will overlap part of the card left of it. Minimum part should be always visible. I accomplished this and this is the code:

using System;
using System.Windows;
using System.Windows.Controls;

namespace Hand
{
   public class Hand : Panel
   {
      //TODO Should be dependancy property
      private const double MIN_PART = 0.5;

      protected override Size MeasureOverride(Size availableSize)
      {
         Size desiredSize = new Size();
         foreach (UIElement element in this.Children)
         {
            element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));

            desiredSize.Width += element.DesiredSize.Width;
            desiredSize.Height = Math.Max(desiredSize.Height, element.DesiredSize.Height);
         }
         return desiredSize;
      }

      protected override Size ArrangeOverride(Size finalSize)
      {
         //percentage of the visible part of the child.
         double part = 1;

         Double desiredWidth = 0;

         //TODO Check how to get desired size because without looping
         //this.DesiredSize is minimum of available size and size returned from MeasureOverride
         foreach (UIElement element in this.Children)
         {
            desiredWidth += element.DesiredSize.Width;
         }

         if (desiredWidth > this.DesiredSize.Width)
         {
            //Every, but the last child should be overlapped
            double lastChildWidth = this.Children[this.Children.Count - 1].DesiredSize.Width;
            part = (this.DesiredSize.Width - lastChildWidth) / (desiredWidth - lastChildWidth);

            part = Math.Max(part, MIN_PART);
         }

         double x = 0;

         foreach (UIElement element in this.Children)
         {
            Rect rect = new Rect(x, 0, element.DesiredSize.Width, element.DesiredSize.Height);
            element.Arrange(rect);
            finalSize.Width = x + element.DesiredSize.Width;
            x += element.DesiredSize.Width * part;
         }

         return finalSize;
      }
   }
}

I would like to add scrollbar when minimum part is reached, so that the user could still be able to view all the cards. I cannot accomplish this. I tried with the ScrollViewer like this:

<Window x:Class="TestScrollPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:h="clr-namespace:Hand;assembly=Hand"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ScrollViewer HorizontalScrollBarVisibility="Auto">
        <h:Hand>
            <Button Width="100">One</Button>
            <Button Width="150">Two</Button>
            <Button Width="200">Three</Button>
        </h:Hand>
        </ScrollViewer>
    </Grid>
</Window>

But this doesn't work because once horizontal scrollbar is visible, MeasureOveride and ArrangeOverride of Hand panel is never called and even if it would be called, Hand would get desired size to arrange all children without overlapping.

Could this be made with ScrollViewer at all and if not, another ideas would be appreciated. Thank you all for ypur help.

Jurica

A: 

Firstly, change your panel's logic to just the opposite: let MeasureOverride pack the cards as tightly as possible, and then let ArrangeOverride spread them evenly over whatever width is given.

Secondly, use the MinWidth property. Bind it to ScrollViewer.ActualWidth.

This way, if the cards can be tightly packed into width less than that of the ScrollViewer, then your Hand will be stretched to all available space. And if they can't, then the Hand's width will be just whatever you calculate it to.

Fyodor Soikin
Thank you very much. This is the answer I was looking for. Very neat. Binding wasn't necessary, I believe, because it works without it.
Jurica Hladek
You're welcome. Glad I was able to help.
Fyodor Soikin