Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

WPF : A Strange Layout Issue

, 27 Nov 2009
Rate this:
Please Sign up or sign in to vote.
The other day I was doing a new View for a WPF app that we are working on, that required a DataTemplate that consisted something like the following: <Expander x:Name="exp" Background="Transparent"

The other day I was doing a new View for a WPF app that we are working on, that required a DataTemplate that consisted something like the following:

<DataTemplate DataType="{x:Type local:EmbeddedViewModel}">
    <Expander x:Name="exp"
    Background="Transparent"
          IsExpanded="True" ExpandDirection="Down"
          Expanded="Expander_Expanded"
              Collapsed="Expander_Collapsed">

        <ListView AlternationCount="0"
             Margin="0"
             Background="Coral"
             ItemContainerStyle="{DynamicResource ListItemStyle}"
             BorderBrush="Transparent"
             VerticalAlignment="Stretch"
             HorizontalAlignment="Stretch"
             ItemsSource="{Binding SubItems}"
             IsSynchronizedWithCurrentItem="True"
             SelectionMode="Single">

            <ListView.View>
                <GridView>

                    <GridViewColumn
                        Width="100" Header="FieldA"
                        DisplayMemberBinding="{Binding FieldA}"/>

                    <GridViewColumn
                        Width="100" Header="FieldB"
                        DisplayMemberBinding="{Binding FieldB}"/>

                </GridView>

            </ListView.View>
        </ListView>
    </Expander>
</DataTemplate>

Which is all cool, but when I let the View out there into the wild for testing the users were like, yeah that’s ok when there are lots of items, but what happens when there is only 1 item. We would like that 1 item to take up all the available space of the screen. And it got me thinking into how to do this. So this is what I came up with.

A ItemsControl that has a specialized Grid (GridWithChildChangedNoitfication) control as its ItemsPanelTemplate. The specialized grid simply raises an event to signal that it has had a new VisualChild added.

Here is the code for the full ItemsControl setup

<ItemsControl x:Name="items"
    ItemsSource="{Binding Path=Items, Mode=OneWay}"
    Background="Transparent"
    ScrollViewer.VerticalScrollBarVisibility="Auto">

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:GridWithChildChangedNoitfication
            VerticalAlignment="Stretch"
            HorizontalAlignment="Stretch"
            Margin="0" Loaded="ItemsGrid_Loaded"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type local:EmbeddedViewModel}">
            <Expander x:Name="exp"
            Background="Transparent"
                  IsExpanded="True" ExpandDirection="Down"
                  Expanded="Expander_Expanded"
                      Collapsed="Expander_Collapsed">

                <ListView AlternationCount="0"
                     Margin="0"
                     Background="Coral"
                     ItemContainerStyle="{DynamicResource ListItemStyle}"
                     BorderBrush="Transparent"
                     VerticalAlignment="Stretch"
                     HorizontalAlignment="Stretch"
                     ItemsSource="{Binding SubItems}"
                     IsSynchronizedWithCurrentItem="True"
                     SelectionMode="Single">

                    <ListView.View>
                        <GridView>

                            <GridViewColumn
                                Width="100" Header="FieldA"
                                DisplayMemberBinding="{Binding FieldA}"/>

                            <GridViewColumn
                                Width="100" Header="FieldB"
                                DisplayMemberBinding="{Binding FieldB}"/>

                        </GridView>

                    </ListView.View>
                </ListView>
            </Expander>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And here is what the C# code looks like for the specialized Grid (GridWithChildChangedNoitfication).

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace ShareOfSize
{
    public class GridWithChildChangedNoitfication : Grid
    {

        /// <span class="code-SummaryComment"><summary>
</span>        /// Override that allows us to tell when the
        /// VisualChildren collection has changed
        /// <span class="code-SummaryComment"></summary>
</span>        protected override void OnVisualChildrenChanged(
            DependencyObject visualAdded,
            System.Windows.DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded,
                visualRemoved);
            OnGridVisualChildrenChanged(new EventArgs());
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Raise an event to signal that a VisualChild has been
        /// added/removed
        /// <span class="code-SummaryComment"></summary>
</span>        public event EventHandler<EventArgs>
            GridVisualChildrenChanged;

        protected virtual void
            OnGridVisualChildrenChanged(EventArgs e)
        {
            EventHandler<EventArgs> handlers =
                GridVisualChildrenChanged;

            if (handlers != null)
            {
                handlers(this, e);
            }

        }
    }
}

The last piece in the puzzle is some code behind that knows how to resize all the children in the ItemsControl when the VisualChild count of the specialized grid changes.

NOTE : This more than likely could be abstracted to a Attached Behaviour via an Attached Property, I’ll leave that as an activity for the user should they wish to do that. For me as it was so UI I did not mind this bit of code behind.

    public partial class Window1 : Window
    {

        private GridWithChildChangedNoitfication itemsGrid;

        public Window1()
        {
            InitializeComponent();
            this.DataContext = new Window1ViewModel();
            this.Unloaded += ViewUnloaded;

        }

        private void ViewUnloaded(object sender, RoutedEventArgs e)
        {
            if(itemsGrid != null)
            {
                itemsGrid.GridVisualChildrenChanged
                    -= GridChildrenChanged;
            }
        }

        private void GridChildrenChanged(object sender, EventArgs e)
        {
            ResizeRows();
        }

        private void ResizeRows()
        {
            if (itemsGrid != null)
            {
                itemsGrid.RowDefinitions.Clear();
                for (int i = 0; i < itemsGrid.Children.Count; i++)
                {
                    RowDefinition row = new RowDefinition();
                    row.Height = new GridLength(
                        itemsGrid.ActualHeight/
                            itemsGrid.Children.Count,GridUnitType.Pixel);
                    itemsGrid.RowDefinitions.Add(row);

                    itemsGrid.Children[i].SetValue(
                        HorizontalAlignmentProperty,
                        HorizontalAlignment.Stretch);
                    itemsGrid.Children[i].SetValue(
                        VerticalAlignmentProperty,
                        VerticalAlignment.Stretch);
                    itemsGrid.Children[i].SetValue(Grid.RowProperty,i);
                }
            }
        }

        private void ItemsGrid_Loaded(object sender, RoutedEventArgs e)
        {
            itemsGrid = sender as GridWithChildChangedNoitfication;
            if(itemsGrid != null)
            {
                itemsGrid.GridVisualChildrenChanged +=
                    GridChildrenChanged;
            }
        }

        private void Expander_Expanded(object sender, RoutedEventArgs e)
        {
            Expander expander = sender as Expander;
            var item = ItemsControl.ContainerFromElement(items,
                (DependencyObject)sender);

            Int32 row = (Int32)(item).GetValue(Grid.RowProperty);

            if (expander.Tag != null)
            {
                itemsGrid.RowDefinitions[row].Height =
                    new GridLength((Double)expander.Tag,GridUnitType.Pixel);
            }
        }

        private void Expander_Collapsed(object sender, RoutedEventArgs e)
        {
            Expander expander = sender as Expander;
            var item = ItemsControl.ContainerFromElement(items,
                (DependencyObject)sender);

            Int32 row = (Int32)(item).GetValue(Grid.RowProperty);

            if (expander.Tag == null)
            {
                expander.Tag = itemsGrid.RowDefinitions[row].Height.Value;
            }
            itemsGrid.RowDefinitions[row].Height =
                new GridLength(40,GridUnitType.Pixel);
        }

    }

Here is what it all looks like

Share_thumb.jpg

Now I am sure there is some bright spark out there that could have done this with Grid.IsSharedSizeScope I just couldn’t see that myself, so this is what I came up with.

As always here is a small demo app:

http://sachabarber.net/wp-content/uploads/2009/11/ShareOfSize.zip

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140905.1 | Last Updated 27 Nov 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid