1
votes

I have a simple application with a TreeView which I would like to display a Hierarchy of SHAPES. I am using MVVM, so I have a ViewModel, which has a property of ObservableCollection Shapes.

ShapeVm is the base class, and there are several Shape subtypes (SquareVm, CircleVm, ArcVm etc). Pretty basic stuff. The ShapeVm baseclass has an overidable property called ShapeType (an enum: enum { Square, Circle, Arc }, and each ShapeVm subclass provides what type of Shape it is.

Now, you can see where this is going: In the TreeView, all I need is a hierachical display of these shapes, with 'ShapeType' nodes, and each node containing the shapes of that type.

So, I have tried various things, in XAML, like the CollectionViewSource, to provide a means of grouping by ShapeType, which works, but it only shows the items, and groups, that EXIST in the list.

What I need, in the TreeView, is to have those Group nodes displayed, whether they have items or not.

so, for instance (the Windows' DataContext is bound to the ShapesViewModel, which has the 'Shapes' property)

<TreeView>

<!-- Itemtemplate omitted here -->

  <TreeViewItem Header="Rectangles" IsExpanded="true" ItemsSource="{Binding Shapes}">
  </TreeViewItem>

  <TreeViewItem Header="Squares" IsExpanded="true" ItemsSource="{Binding Shapes}">
  </TreeViewItem>

  <TreeViewItem Header="Circles" IsExpanded="true" ItemsSource="{Binding Shapes}">
  </TreeViewItem>

</TreeView>

Now, I have fiddled with my ViewModel, where I expose a property, a collection, of each Type of shape, that gets its data from the main shapes collection. I know that the ViewModels' job is supposed to be there to 'serve' the View with whatever the View needs to do it's job. But I don't want to have to have a whole bunch of public Collection types, each of which need to somehow 'sync' with the main Shapes collection every time that changes etc. It gets complicated.

I just want to know if there is a way to, within the XAML, to say: "Hey, 'Rectangle' node, you must display only 'Rectangles', from the Shapes collection, ok.".

Help appreciated.

1

1 Answers

0
votes

I'm not sure it would be possible for the XAML to limit what type can be in each sub-section but one solution would be to get your data from a outer LINQ join on your Shapes enum.

The class below has Shapes with ShapeTypes and an enum, and the ShapesData property exposes a LINQ outer join that is visualised using the xaml below.

This solution will give you empty groups where no items with that ShapeType exist, as you need, and is easily applied to a TreeView solution.

public class FixedTypes
    {
        public enum ShapeType
        {
            Circle,
            Ellipse,
            Rectangle
        }

        public class Shape
        {
            public ShapeType ShapeType { get; set; }
        }

        public ObservableCollection<Shape> Shapes { get; set; }

        public FixedTypes()
        {
            this.Shapes =
                new ObservableCollection<Shape>(
                    new[]
                        {
                            new Shape() { ShapeType = ShapeType.Circle }, new Shape() { ShapeType = ShapeType.Ellipse },
                            new Shape() { ShapeType = ShapeType.Ellipse }
                        });
        }

        public IEnumerable ShapesData
        {
            get
            {
                var data = from e in (ShapeType[])Enum.GetValues(typeof(ShapeType))
                           join s in Shapes on e equals s.ShapeType into es
                           from s in es.DefaultIfEmpty()
                           select new { ShapeType = e, Shape = s};
                return data;
            }
        }
    }

XAML

<Grid>
        <Grid.Resources>
            <CollectionViewSource x:Key="fixedTypes"
                                  Source="{Binding ShapesData}">
                <CollectionViewSource.GroupDescriptions>
                    <PropertyGroupDescription PropertyName="ShapeType" />
                </CollectionViewSource.GroupDescriptions>
            </CollectionViewSource>
        </Grid.Resources>

        <ItemsControl ItemsSource="{Binding Source={StaticResource fixedTypes}}">
            <ItemsControl.GroupStyle>
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <GroupBox Header="{Binding Name}">
                                            <ItemsPresenter />
                                        </GroupBox>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </ItemsControl.GroupStyle>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Shape.ShapeType}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>