0

I'd like to add Elements to a StackPanel (or Listbox / Listview if it would make that easier) and automatically add a Button (like "...") to indicate that there are more children than there is space for. Consider a calendar that has appointment items in each day. If there are more appointments than as much as can be displayed a button is displayed to go to a detailed view for that day.

Can this be done automatically, or how do I calculate the positions of the stackpanel and its items.

It's for windows store development in c#.

mnel
  • 113,303
  • 27
  • 265
  • 254
scientist
  • 1
  • 2

2 Answers2

0

You could use a Behavior (System.Windows.Interactivity Reference needed).

I catched up your idea to calculate the height (or width if the stackpanel has a horisontal orientation) of the stackpanel and its children and show the "more Item" / detail button if nessesary

public class ChildSpaceBehavior : Behavior<StackPanel>
    {
        public static readonly DependencyProperty ShowDetailsCommandProperty = DependencyProperty.Register("ShowDetailsCommand", typeof(RelayCommand), typeof(ChildSpaceBehavior), new PropertyMetadata());

        public RelayCommand ShowDetailsCommand
        {
            get { return (RelayCommand)GetValue(ShowDetailsCommandProperty); }
            set { SetValue(ShowDetailsCommandProperty, value); }
        }

        private bool _isMoreChildrenItemVisible;

        protected override void OnAttached()
        {
            AssociatedObject.LayoutUpdated += OnLayoutUpdated;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.LayoutUpdated -= OnLayoutUpdated;
        }

        private void OnLayoutUpdated(object sender, EventArgs e)
        {
            var height = AssociatedObject.Height;
            var children = AssociatedObject.Children;
            double childHeighSum = 0;

            var childList = new List<FrameworkElement>();

            foreach (FrameworkElement child in children)
            {
                if (childHeighSum > height)
                {
                    ShowMoreChildrenItem(childList);
                    break;
                }

                if (childHeighSum + child.ActualHeight > height)
                {
                    ShowMoreChildrenItem(childList);
                    break;
                }

                childHeighSum += child.ActualHeight;
                childList.Add(child);
            }
        }

        private void ShowMoreChildrenItem(List<FrameworkElement> childList)
        {
            if (_isMoreChildrenItemVisible) return;

            var moreItem = new Button{ Content = "..." };
            moreItem.Command = ShowDetailsCommand;

            var itemsToRemove = new List<FrameworkElement>();

            foreach (FrameworkElement child in AssociatedObject.Children)
            {
                if(childList.Contains(child) == false) itemsToRemove.Add(child);
            }

            foreach (var element in itemsToRemove)
            {
                AssociatedObject.Children.Remove(element);
            }

            AssociatedObject.Children.Add(moreItem);

            _isMoreChildrenItemVisible = true;
        }
    }

For RelayCommand look here

Xaml:

<Grid>
        <StackPanel Height="200" Orientation="Vertical">
            <Border BorderBrush="Gray" BorderThickness="1">
                <Label Height="50" Content="Item 1" />
            </Border>
            <Border BorderBrush="Gray" BorderThickness="1">
                <Label Height="50" Content="Item 2" />
            </Border>
            <Border BorderBrush="Gray" BorderThickness="1">
                <Label Height="50" Content="Item 3" />
            </Border>
            <Border BorderBrush="Gray" BorderThickness="1">
                <Label Height="50" Content="Item 4" />
            </Border>
            <Border BorderBrush="Gray" BorderThickness="1">
                <Label Height="50" Content="Item 5" />
            </Border>
                <i:Interaction.Behaviors>
                <StackPanelBehavior:ChildSpaceBehavior ShowDetailsCommand="{Binding ShowDetailsCommand}" />
            </i:Interaction.Behaviors>
        </StackPanel>
    </Grid>
menty
  • 416
  • 3
  • 6
  • Sounds promising, I will try it out later. Thank you! – scientist Nov 06 '12 at 15:12
  • I think this could work, but the height of my stackPanel is always zero or NaN, cause I create the stackpanel(s) in the code. Calling the behaviour in the Loaded Method of the Page at least computes the ActualHeight of the StackPanel but this doesn't help. At the moment the order of the page instantiation is the following: -> PageConstructor (doesNothing) -> OnNavigatedTo (fills Grid with Stackpanels and add Behaviour) -> PageLoadedEvent (Calls Behaviour by adding elements to StackPanel) Any ideas when the Height gets computed? – scientist Nov 06 '12 at 21:21
0

Since nearly all elements of my page are auto sized the following changes to the above solution helped. Page containing grid of stackpanels:

    public MainPage()
    {
        InitializeComponent();
    }

    private void FillGridWithItems()
    {   
        //Row and ColumnDefinitions
        for (int row = 0; row < 5; row++)
        {
            for (int col = 0; col < 7; col++)
            {
               var item = new MyStackPanel(children)
               Grid.SetColumn(item, col);
               Grid.SetRow(item, row);
               grid.Children.Add(item);
            }
        }
        Loaded+=PageLoaded;            
    }

    /// When page is loaded, ActualHeight of elements are calculated for sure.
    private void PageLoaded(object sender, RoutedEventArgs e)
    {
        foreach (var child in grid.Children)
        {
            var item = child as MyStackPanel;
            if (item != null)
            {
                item.FillInData();
            }
        }
    }

    /// Used instead of constructor, cause a parameter is sent to this page.
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        FillGridWithItems()
    }

MyStackPanel:

private readonly List<string> _items;
public MyStackPanel(List<string> items) 
{
    _items = items;
    var behavior = new MyBehavior();
    behavior.SetBinding(MyBehavior.ShowDetailsCommandProperty, new Binding
    {
        Path = new PropertyPath("ShowDetailsCommand")
    });
    Interaction.GetBehaviors(this).Add(behavior);
}

public void FillInData() 
{
     foreach (string item in _items)
     {         
         var block = new Button();
         block.Click += ItemClick;
         this.Children.Add(block);
     }
}

MyBehaviour (just the changed Part)

...
private void OnLayoutUpdated(object sender, object o)
{
    var container = (Grid) AssociatedObject.Parent;
    if (container == null) return;
    var height = container.RowDefinitions[Grid.GetRow(AssociatedObject)].ActualHeight;

    // ... The same as above
}
....

I didn't implement the ShowDetailsCommand yet, so I can't say if the RelayCommand works as intended, but why should it not ;) There are also some modifications need to be done for windows store, like using the WinRtBehaviors library, that can all be solved by googling.

scientist
  • 1
  • 2