Skip to main content
Home Forums Silverlight Programming Silverlight Controls and Silverlight Toolkit ListBox with Canvas container
6 replies. Latest Post by kwolter on July 6, 2009.
(0)
moomph
Member
185 points
110 Posts
04-13-2009 1:59 AM |
I am trying to create a simple ListBox that draws its items into a canvas. The ItemsSource is a collection of a data type that provide position and size information for each item. My binding seems to work fine as the width and height of the elements is being set correctly. However, although I am binding the Canvas.Top, Canvas.Left properties, the Item position remains fixed in the upper-left corner. My testing has involved fixed values for Canvas.Top, Canvas.Left in case there is some issue with the binding. In all cases, I am using a Rectangle as the DataTemplate:
<ListBox.ItemTemplate> <DataTemplate> <Rectangle Stroke="White" StrokeThickness="1" Width="{Binding Width}" Height="{Binding Height}" /> </DataTemplate></ListBox.ItemTemplate>
I have tried a myriad of things to get this working.
swildermuth
Star
8320 points
1,546 Posts
04-13-2009 4:16 AM |
Data Templates don't know about the ListBox's container so using Canvas.Left/Top have no effect. If you want them all to be related to the Canvas' likely creating your own container (a custom control that derives from ItemsControl) is likely to be your best bet.
04-13-2009 4:28 AM |
swildermuth:Data Templates don't know about the ListBox's container so using Canvas.Left/Top have no effect. If you want them all to be related to the Canvas' likely creating your own container (a custom control that derives from ItemsControl) is likely to be your best bet.
I am aware that the DataTemplate is unaware of the ListBox container. That's why I'm not changing the Canvas.Left/Top values in the DataTemplate but the ListBoxItems themselves, which I thought were direct children of whatever ItemsPanelTemplate control I've defined.
04-13-2009 4:44 AM |
After a closer look...
the problem is that the ListBox (or ItemsControl) is going to put your datatemplate inside a ContentPresenter control so its not directly in the Canvas. I also tried to do it with a TranslateTransform but of course that doesn't support binding so it doesn't work either.
<ItemsControl x:Name="theItems"> <ItemsControl.ItemTemplate> <DataTemplate> <Ellipse Fill="Blue" Stroke="Black" StrokeThickness="1" Width="25" Height="25"> <Ellipse.RenderTransform> <TranslateTransform X="{Binding X}" Y="{Binding Y}" /> </Ellipse.RenderTransform> </Ellipse>
</DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>
I think you could do it with a the TranslateTransform, but isntead of using BInding, create a UserControl (as a Content Control to store your object) and then use TranslateTransform to move it on the page. Its hacky, but easier than building your own container.
04-13-2009 5:21 AM |
Hmm. That approach might work in some cases, but I really need the entire ListBoxItem to be moved. If I just move the DataTemplate object, then ListBox'Item container (the thing that also holds the ContentPresenter) will be left behind in its original position, so I will have a bunch of highlighting boxes in the upper left corner, while my Rectangles get drawn in the correct location. If you take a look at the default ListBoxItem style you will see that even moving the ContentPresenter will not solve this issue, you must move the Grid which contains the ContentPresenter and the highlighting Rectangles - what I referred to as the ListBoxItem container.
ListBoxes sure are confusing under the hood, aren't they?
I have, however, come up with a hacky solution thanks to your idea of using the TranslateTransform instead of manipulating the Canvas.Top/Left property. What I do is provide a handler for the Loaded event in the DataTemplate. Within that Handler I use VisualTreeHelper to get the parent of the Rectangle, which would be its ContentPresenter. I then get the parent of the ContentPresenter, which is the ListBoxItem's Grid. I then translate this Grid, by getting the position from the sender object's DataContext, which holds this information.
It looks like this. Note that I have am using my own ItemContainerStyle that defines a TranslateTransform for the grid already, but it would be trivial to just dynamically create one on the fly. Rct is my own structure that provides the position (it uses ints instead of doubles):
private void Rectangle_Loaded(object sender, RoutedEventArgs e){ ContentPresenter cp = VisualTreeHelper.GetParent((DependencyObject)sender) as ContentPresenter; Grid parentGrid = VisualTreeHelper.GetParent((DependencyObject)cp) as Grid; TranslateTransform tt = (TranslateTransform)parentGrid.RenderTransform; Rct rct = (Rct)(sender as Rectangle).DataContext; tt.X = rct.Left; tt.Y = rct.Top;}
And here is the ListBox XAML. Not that it is necessary to define the ItemsPanelTemplate and ControlTemplate as I am doing here, or else the Rectangles will not draw correctly"
<ListBox x:Name="RectListBox" SelectionMode="Extended" SelectionChanged="RectListBox_SelectionChanged" ItemContainerStyle="{StaticResource SpriteSheetListBoxItemStyle}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <Canvas x:Name="RectListBoxCanvas" Background="Transparent"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.Template> <ControlTemplate> <ItemsPresenter /> </ControlTemplate> </ListBox.Template> <ListBox.ItemTemplate> <DataTemplate> <Rectangle Stroke="White" StrokeThickness="1" Width="{Binding Width}" Height="{Binding Height}" Loaded="Rectangle_Loaded" /> </DataTemplate> </ListBox.ItemTemplate></ListBox>Not exactly a graceful solution, but it's the only one I could figure out.
<ListBox x:Name="RectListBox" SelectionMode="Extended" SelectionChanged="RectListBox_SelectionChanged" ItemContainerStyle="{StaticResource SpriteSheetListBoxItemStyle}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <Canvas x:Name="RectListBoxCanvas" Background="Transparent"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.Template> <ControlTemplate> <ItemsPresenter /> </ControlTemplate> </ListBox.Template> <ListBox.ItemTemplate> <DataTemplate> <Rectangle Stroke="White" StrokeThickness="1" Width="{Binding Width}" Height="{Binding Height}" Loaded="Rectangle_Loaded" /> </DataTemplate> </ListBox.ItemTemplate></ListBox>
Not exactly a graceful solution, but it's the only one I could figure out.
aldwis
6 points
5 Posts
06-12-2009 1:11 AM |
Just put Canvas into the DataTemplate:
1 <ListBox.ItemTemplate>2 <DataTemplate>3 <Canvas>4 <Rectangle Stroke="White" StrokeThickness="1" Width="{Binding Width}" Height="{Binding Height}" Canvas.Left="{Binding ...}" Canvas.Top="..." />5 ...
kwolter
2 points
1 Posts
07-06-2009 5:14 PM |
I'm not sure if this works in Silverlight, but in WPF you can use this instead of your code.