WPF ListBox with arbitrary positioning and custom shaped items.

Start a clean WPF application and build along! Note: I am not going over the basics of WPF, sorry.

Question

I thought it would be original to start my blog with a question. So do you need a ListBox that can do this (simplified example)?

CanvasListBox

Problem #1:

I had the requirement to make bodyparts on a human body selectable so certain collections could be filtered by the selected bodyparts. The ListBox came to mind because that control supports the behaviour I need (selecting/deselecting items).  As you know the human body is not made up out of rectangles and ellipses, which are easy controls in XAML. No, unfortunatly the human body consists out of odd shapes which we need to capture in Path controls in XAML.

Solution:

Luckily the ListBox has an ItemTemplate which can contain any control you like. So all I had to do was define a template with a Path control in it. Bind the data attribute to the property in the viewmodel which contains the data about the shape needed (shape can be ‘arm’ or ‘leg’ or ‘airplane’ whatever) and presto! Note(s): ItemsSource is BodyParts. Set CanvasListBoxViewModel as DataContext of Window.

XAML

           <ListBox.ItemTemplate>
                <DataTemplate>
                    <Path Data=”{Binding PathData}” ToolTip=”{Binding Name}” Fill=”Blue” />
                </DataTemplate>
            </ListBox.ItemTemplate>

ViewModels

    public class BodyPartViewModel {

        private int _id;

        public int ID {
            get { return _id; }
            set {
                _id = value;
            }
        }

        private string _name;

        public string Name {
            get { return _name; }
            set {
                _name = value;
             }
        }

        private string _pathData;

        public string PathData {
            get { return _pathData; }
            set {
                _pathData = value;
            }
        }
        
    }

    public class CanvasListBoxViewModel {

        private ObservableCollection<BodyPartViewModel> _bodyParts;

        public ObservableCollection<BodyPartViewModel> BodyParts {
            get { return _bodyParts; }
            set {
                _bodyParts = value;
            }
        }

        public CanvasListBoxViewModel() {
            this.BodyParts = new ObservableCollection<BodyPartViewModel>(){new BodyPartViewModel(){ID = 1,
            Name = “Test1”,
            PathData = “M0.5,0.5 L58.356998,0.5 58.356998,99.500001 0.5,99.500001 0.5,98.82943 1.1896362,98.726861 C26.988049,94.590165 46.570995,74.347993 46.570995,50.000001 46.570995,25.652011 26.988049,5.4098402 1.1896362,1.2731375 L0.5,1.1705698 z”},
            new BodyPartViewModel(){ID = 2,
            Name = “Test2”,
            PathData = “M0.5,0.5 L49.5,0.5 49.5,0.50632697 50,0.5 C77.338097,0.5 99.5,22.661905 99.5,50 99.5,77.338097 77.338097,99.5 50,99.5 L49.5,99.493675 49.5,99.5 0.5,99.5 0.5,50 z”},
            new BodyPartViewModel(){ID = 3,
            Name = “Test3”,
            PathData = “M0.5,0.5 L58.356998,0.5 58.356998,99.500001 0.5,99.500001 0.5,98.82943 1.1896362,98.726861 C26.988049,94.590165 46.570995,74.347993 46.570995,50.000001 46.570995,25.652011 26.988049,5.4098402 1.1896362,1.2731375 L0.5,1.1705698 z”},
            new BodyPartViewModel(){ID = 4,
            Name = “Test4”,
            PathData = “M0.5,0.5 L49.5,0.5 49.5,0.50632697 50,0.5 C77.338097,0.5 99.5,22.661905 99.5,50 99.5,77.338097 77.338097,99.5 50,99.5 L49.5,99.493675 49.5,99.5 0.5,99.5 0.5,50 z”}};

        }
        
    }

Problem #2:

Furthermore bodyparts are not neatly stacked so I can’t use a StackPanel. They are all over the place actually so I would have to be able to give a X and a Y coördinate or something.

Solution:

The best containercontrol I can think of to hold items on random positions is the Canvas control. Per default ListBox uses a StackPanel as it’s template for ItemsPanel. Well why don’t we make a Canvas out of that, like this in XAML:

         <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas IsItemsHost=”True” />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>

Now how do I pass along the Canvas.Left and Canvas.Top per item so I can place them on the Canvas? This is where the ItemContainerStyle is going to help us. Please observe the following XAML:

            <ListBox.ItemContainerStyle>
                <Style TargetType=”{x:Type ListBoxItem}”>
                    <Setter Property=”Canvas.Left”
                            Value=”{Binding PositionX}”/>
                    <Setter Property=”Canvas.Top”
                            Value=”{Binding PositionY}”/>
                </Style>
            </ListBox.ItemContainerStyle>

And we extend the BodyPartViewModel with a PositionX and PositionY property. And don’t forget to populate these properties in the CanvasListBoxViewModel too. Observe ViewModels code below:

    public class BodyPartViewModel {

        private int _id;

        public int ID {
            get { return _id; }
            set {
                _id = value;
            }
        }

        private string _name;

        public string Name {
            get { return _name; }
            set {
                _name = value;
             }
        }

        private string _pathData;

        public string PathData {
            get { return _pathData; }
            set {
                _pathData = value;
            }
        }

        private int _positionX;

        public int PositionX {
            get { return _positionX; }
            set {
                _positionX = value;
            }
        }

        private int _positionY;

        public int PositionY {
            get { return _positionY; }
            set {
                _positionY = value;
            }
        }
        
    }

    public class CanvasListBoxViewModel {

        private ObservableCollection<BodyPartViewModel> _bodyParts;

        public ObservableCollection<BodyPartViewModel> BodyParts {
            get { return _bodyParts; }
            set {
                _bodyParts = value;
            }
        }

        public CanvasListBoxViewModel() {
            this.BodyParts = new ObservableCollection<BodyPartViewModel>(){new BodyPartViewModel(){ID = 1,
            Name = “Test1”,
            PathData = “M0.5,0.5 L58.356998,0.5 58.356998,99.500001 0.5,99.500001 0.5,98.82943 1.1896362,98.726861 C26.988049,94.590165 46.570995,74.347993 46.570995,50.000001 46.570995,25.652011 26.988049,5.4098402 1.1896362,1.2731375 L0.5,1.1705698 z”,
            PositionX = 10,
            PositionY = 10},
            new BodyPartViewModel(){ID = 2,
            Name = “Test2”,
            PathData = “M0.5,0.5 L49.5,0.5 49.5,0.50632697 50,0.5 C77.338097,0.5 99.5,22.661905 99.5,50 99.5,77.338097 77.338097,99.5 50,99.5 L49.5,99.493675 49.5,99.5 0.5,99.5 0.5,50 z”,
            PositionX = 100,
            PositionY = 100},
            new BodyPartViewModel(){ID = 3,
            Name = “Test3”,
            PathData = “M0.5,0.5 L58.356998,0.5 58.356998,99.500001 0.5,99.500001 0.5,98.82943 1.1896362,98.726861 C26.988049,94.590165 46.570995,74.347993 46.570995,50.000001 46.570995,25.652011 26.988049,5.4098402 1.1896362,1.2731375 L0.5,1.1705698 z”,
            PositionX = 200,
            PositionY = 200},
            new BodyPartViewModel(){ID = 4,
            Name = “Test4”,
            PathData = “M0.5,0.5 L49.5,0.5 49.5,0.50632697 50,0.5 C77.338097,0.5 99.5,22.661905 99.5,50 99.5,77.338097 77.338097,99.5 50,99.5 L49.5,99.493675 49.5,99.5 0.5,99.5 0.5,50 z”,
            PositionX = 200,
            PositionY = 10}};

        }
        
    }

Problem #3:

So now we have odd shapes on random places, but when I hover over the shapes with my mouse and select them a rectangle is drawn. Pretty ugly! Well allas my friends this requires overwriting the ControlTemplate of the ListBoxItem. There is no other way as the default ControlTemplate has a Border control in its root. I have prepared for this post a rudimentary (and ugly hehe) ControlTemplate. Please observe the XAML:

        <Style x:Key=”BodyPartItemContainerStyle” TargetType=”{x:Type ListBoxItem}”>
            <Setter Property=”Template”>
                <Setter.Value>
                    <ControlTemplate TargetType=”{x:Type ListBoxItem}”>
                        <Grid>
                            <Path x:Name=”Bla” Data=”{Binding PathData}” Stroke=”Red” StrokeThickness=”2″ />
                            <ContentPresenter  />
                            <Path x:Name=”Bla2″ Data=”{Binding PathData}” Fill=”Transparent” />
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property=”IsSelected” Value=”True”>
                                <Setter TargetName=”Bla” Property=”StrokeThickness” Value=”10″ />
                            </Trigger>
                            <Trigger Property=”IsSelected” Value=”False”>
                                <Setter TargetName=”Bla” Property=”StrokeThickness” Value=”2″ />
                            </Trigger>
                            <Trigger Property=”IsMouseOver” Value=”True”>
                                <Setter TargetName=”Bla2″ Property=”Fill” Value=”Red” />
                            </Trigger>
                            <Trigger Property=”IsMouseOver” Value=”False”>
                                <Setter TargetName=”Bla2″ Property=”Fill” Value=”Transparent” />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property=”Canvas.Left” Value=”{Binding PositionX}” />
            <Setter Property=”Canvas.Top” Value=”{Binding PositionY}” />
        </Style>

Remove the ItemContainerStyle from problem #2 and set ItemContainerStyle in the ListBox as: ItemContainerStyle=”{StaticResource BodyPartItemContainerStyle}”. This will cause the items to turn red when you hover over them. And increase/decrease the borders when selecting/deselecting items. And all this while following the shape of the item! Please adjust according to needs.

Thank you for reading this blog. Hope it was helpfull. And if you have any questions, hit me up!

Advertisements

About Danny

Bachelor in Commercial ICT MCTS Winforms .NET 2.0 MCTS ASP.NET 3.5 PSM I
This entry was posted in .NET programming, WPF and tagged , . Bookmark the permalink.

2 Responses to WPF ListBox with arbitrary positioning and custom shaped items.

  1. Molle says:

    Thank you,
    that is what i am looking for….

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s