#999 – Using a Canvas as the Items Panel for a ListBox

You can replace the default StackPanel used as the items panel for a ListBox with any other panel element.  If you have items that you want to display at arbitrary locations, you can use a Canvas for your items panel.

The example below presents a list of cities, where each city is placed at its proper latitude and longitude.

Assuming that we have a City class that accepts a name and latitude/longitude values passed to its constructor, we can create a list of cities:

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            CityList = new ObservableCollection<City>
            {
                new City("Duluth", 46.83, 92.18),
                new City("Redmond", 44.27, 121.15),
                new City("Tucson", 32.12, 110.93),
                new City("Denver", 39.75, 104.87),
                new City("Boston", 42.37, 71.03),
                new City("Tampa", 27.97, 82.53)
            };
        }

        private ObservableCollection<City> cityList;
        public ObservableCollection<City> CityList
        {
            get { return cityList; }
            set
            {
                cityList = value;
                RaisePropertyChanged("CityList");
            }
        }

        // INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void RaisePropertyChanged(string propName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

We then bind a ListBox to this list of cities.  We also:

  • Use its ItemContainerStyle to map latitude and longitude values to the attached Top and Left properties of the Canvas element
  • Use value converters to convert latitude and longitude values to canvas positions
  • Specify the Canvas as the ItemsPanel
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow"
        Width="470" Height="310">

    <Window.Resources>
        <ResourceDictionary>
            <local:LatValueConverter x:Key="latValueConverter" />
            <local:LongValueConverter x:Key="longValueConverter" />
            <sys:Double x:Key="mapWidth">440</sys:Double>
            <sys:Double x:Key="mapHeight">240</sys:Double>
        </ResourceDictionary>
    </Window.Resources>

    <StackPanel Orientation="Horizontal" Margin="5" >
        <ListBox ItemsSource="{Binding CityList}"
                 DisplayMemberPath="Name">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Canvas.Left"
                            Value="{Binding Longitude, Converter={StaticResource longValueConverter},
                                            ConverterParameter={StaticResource mapWidth}}"/>
                    <Setter Property="Canvas.Top"
                            Value="{Binding Latitude, Converter={StaticResource latValueConverter},
                                            ConverterParameter={StaticResource mapHeight}}"/>
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas IsItemsHost="True"
                            Width="{StaticResource mapWidth}"
                            Height="{StaticResource mapHeight}"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </StackPanel>
</Window>

Here is the implementation of the value converters:

    public static class Constants
    {
        public const double LatTop = 50.0;
        public const double LatBottom = 24.0;

        public const double LongLeft = 125.0;
        public const double LongRight = 66.0;
    }

    public class LatValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double latitude = (double)value;
            double height = (double)parameter;

            int top = (int)((Constants.LatTop - latitude) / (Constants.LatTop - Constants.LatBottom) * height);
            return top;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class LongValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double longitude = (double)value;
            double width = (double)parameter;

            int left = (int)((Constants.LongLeft - longitude) / (Constants.LongLeft - Constants.LongRight) * width);
            return left;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

The end result is that the city names are displayed at their proper locations on the canvas.  Note that because the cities are displayed in a ListBox, we can still select one of them.

999-001

About Sean
Software developer in the Twin Cities area, passionate about .NET technologies. Equally passionate about my own personal projects related to family history and preservation of family stories and photos.

One Response to #999 – Using a Canvas as the Items Panel for a ListBox

  1. Pingback: Using a Canvas as the Items Panel for a ListBox | Around computing

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

%d bloggers like this: