How do I set WPF's ItemsControl to display each element in a unique location?

2.4k views Asked by At

I'm building a BlackJack program and am currently working on the hand display.

I have a PlayerSeat UserControl which has an ItemsControl inside of it for the display of the cards. As with a normal game of BlackJack the cards (images in my case) are laid ontop of one another. Different game actions (Split, Double Down, etc) must change the layouts of the cards on the screen. The ItemsControl's ItemSource property is a "ObservableCollection<Card> Hand" property in my Player's ViewModel. Card objects contains BitmapSources with Card images.

I have looked through several webpages (see end of post) for ways to accomplish what I want. I'm looking for a way to do one of two options.

  1. (Preferable) Specify a layout for each "hand mode" (Split, Double Down, etc) and specify where each index of the Hand (OC<Card>) should be placed in order. For instance, for the first card in the Hand put a Image control with Source bound to Hand[0].CardImage at (X1, Y1), then put the Hand[1] Image at (X2, Y2), and so on. This would optimally be adjustable by setting some sort of bound template property (to change between the hand modes) on the ItemsControl.

  2. (Fallback) Display all the Image controls with their Source properties bound. Have the Top/Left properties of these images bound to Hand[0].Top/Left and do the placement caculations in the Hand class.

I'm not one to ask without investigating the issue myself. It appears I need to use a ItemsPanelTemplate with a StackPanel but have no idea where to begin. The key is making the images overlap and placed where I want them. Any light you could shed on my issue would be helpful.

Reference: http://drwpf.com/blog/itemscontrol-a-to-z/ (Specifically "ItemsControl: 'P' is for Panel")

2

There are 2 answers

0
Robert Rossney On

Your view model doesn't need to know anything at all about the physical position of your cards, if you design your view properly. Here's a pretty simple example. The default styles I've created for TextBlock and Border control how large they are, and (through the negative margin) how they overlap when they're stacked together. The ItemsControl uses a horizontal StackPanel for layout.

If you adopt this approach, you can position the items controls wherever they need to be positioned in the view (me, I'd use some combination of dock panels and margins to organize that), and either have a different collection in your view model for each items control to bind to.

It's a bit more complicated, maybe, but you could also have a single collection of card objects and bind each ItemsControl's ItemsSource to a CollectionView that filtered on some property of the card. The nice thing about that is that you can "move" a card from one place to another just by changing the value of that property.

In either case, you'll see that once you make the items controls responsible for managing the layout of their content, you can move them around in the view freely without having to touch your view model.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Resources>
  <Style TargetType="TextBlock">
    <Setter Property="Height" Value="150"/>
    <Setter Property="Width" Value="100"/>
  </Style>
   <Style TargetType="Border">
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Background" Value="Lightgray"/>
    <Setter Property="Margin" Value="0, 0, -80, 0"/>
    </Style>
   </Page.Resources>
  <StackPanel>  
    <ItemsControl>
      <ItemsControl.ItemsPanel>
       <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal"/>
       </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <Border>
       <TextBlock Text="Foo"/>
      </Border>
      <Border>
       <TextBlock Text="Bar"/>      
      </Border>
      <Border>
       <TextBlock Text="Baz"/>      
      </Border>
    </ItemsControl>
  </StackPanel>
</Page>
0
devdigital On

Could you use a Canvas as the ItemsPanelTemplate? Something like:

<ItemsControl ItemsSource="{Binding Hand}">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Canvas />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Image Source="{Binding Image}" Width="{Binding ImageWidth}" Height="{Binding ImageHeight}" />
    </DataTemplate>
  </ItemsControl.ItemTemplate>
  <ItemsControl.ItemContainerStyle>
    <Style>
      <Setter Property="Canvas.Left" Value="{Binding ImageX}" />
      <Setter Property="Canvas.Top" Value="{Binding ImageY}" />                    
    </Style>
  </ItemsControl.ItemContainerStyle>
</ItemsControl>

This assumes that you have position properties on your Hand class. If this isn't appropriate, then you could create a wrapper HandViewModel which contains the Hand instance, and these additional X,Y properties that are only used for display purposes. The return values for these properties could change based on your current "hand mode".