Skip to main content

Microsoft Silverlight

Answered Question ListBox with Canvas containerRSS Feed

(0)

moomph
moomph

Member

Member

185 points

110 Posts

ListBox with Canvas container

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.

  • Set the ItemsPanelTemplate to a Canvas.  Then create and use an ItemsContainerStyle the same as the default but provides Canvas.Top and Canvas.Left properties for the root Grid of the ListBoxItem.  This does not work.  The items do not draw at all.
  • Use an ItemsContainerStyle that is the same as default, except encapsulates everything into a Canvas and sets the Grid's Canvas.Top, Canvas.Left properties.  This almost works, the rectangles draw correctly, but I can no longer capture the items' MouseOver states or get response when I click the items.  This method wouldn't be as clean as others, since it creates a Canvas for each item, instead of one to house them all.
  • The method described here, which is similar to method 1, but instead of changing the ItemsContainerStyle, I create my own ListBox class which overrides the PrepareContainerForItemOverride function .  No go.  Nothing draws.
Any advise?

swildermuth
swildermuth

Star

Star

8320 points

1,546 Posts

Re: ListBox with Canvas container

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.

(If this has answered your question, "Mark as Answer")

Shawn Wildermuth
C# MVP, MCSD, Speaker and Author

Silverlight 3 Workshop
Miami, FL: Oct 12-14th
Portlant, OR: Dec 2-4th
Atlanta, GA: Dec 7-9th
http://silverlight-tour.com

moomph
moomph

Member

Member

185 points

110 Posts

Re: ListBox with Canvas container

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.

swildermuth
swildermuth

Star

Star

8320 points

1,546 Posts

Answered Question

Re: Re: ListBox with Canvas container

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.

(If this has answered your question, "Mark as Answer")

Shawn Wildermuth
C# MVP, MCSD, Speaker and Author

Silverlight 3 Workshop
Miami, FL: Oct 12-14th
Portlant, OR: Dec 2-4th
Atlanta, GA: Dec 7-9th
http://silverlight-tour.com

moomph
moomph

Member

Member

185 points

110 Posts

Re: Re: ListBox with Canvas container

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.  

aldwis
aldwis

Member

Member

6 points

5 Posts

Re: Re: ListBox with Canvas container

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
kwolter

Member

Member

2 points

1 Posts

Re: Re: ListBox with Canvas container

I'm not sure if this works in Silverlight, but in WPF you can use this instead of your code.

<ItemsControl.ItemContainerStyle>

<Style>

<Setter Property="Canvas.Left" Value="{Binding ScreenPosition.X}"/>

<Setter Property="Canvas.Top" Value="{Binding ScreenPosition.Y}"/>

</Style>

</ItemsControl.ItemContainerStyle>

 

  • Unanswered Question
  • Answered Question
  • Announcement
Microsoft Communities