Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WPF

WPF: A Strange Layout Issue

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
27 Nov 2009CPOL1 min read 13.8K   3  
WPF: a strange layout issue

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:

XML
<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.

An 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:

XML
<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).

 

C#
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
    {
        /// <summary>
        /// Override that allows us to tell when the
        /// VisualChildren collection has changed
        /// </summary>
        protected override void OnVisualChildrenChanged(
            DependencyObject visualAdded,
            System.Windows.DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded,
                visualRemoved);
            OnGridVisualChildrenChanged(new EventArgs());
        }

        /// <summary>
        /// Raise an event to signal that a VisualChild has been
        /// added/removed
        /// </summary>
        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 an 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.

C#
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:

45186/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.

This article was originally posted at http://sachabarber.net?p=612

License

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


Written By
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 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • 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 --