Search code examples
wpfuser-controlsdatatemplate

DataTemplate delivering correct result for first item in list and then incorect for second item in WPF


I have the following Item in my control:

<Canvas Name="canvasTrack" Height="{Binding CanvasHeight}" Width="{Binding CanvasWidth}"> 
   <ItemsControl  
         ItemsSource="{Binding Path=CurrentSegments}" 
         ItemTemplate="{StaticResource BegSegmentTemplate}">
    </ItemsControl>
</Canvas>

And the following DataTemplate:

<DataTemplate x:Key="BegSegmentTemplate">
    <Ellipse Height="10" Width="10" Margin="{Binding EntryPoint}" Fill="Black" HorizontalAlignment="Center" />
</DataTemplate>

I then have it bound to an ObservableCollection that has two items in it. When I run the program two ellipses appear. The first one is in the correct spot on my canvas but the second one is in a 'random' spot. I have checked the numbers that are being bound and everything appears normal. What could I be missing?


Solution

  • By default, an ItemsControl loops through it's items and puts each of them in a StackPanel, so your end markup basically looks like this:

    <Canvas>
        <StackPanel>
            <ContentPresenter>
                <Ellipse Margin="{Binding EntryPoint}" />
            </ContentPresenter>
            <ContentPresenter>
                <Ellipse Margin="{Binding EntryPoint}" />
            </ContentPresenter>
        </StackPanel>
    </Canvas>
    

    If you want to loop through items and place them on a Canvas based on some bound value, you need to overwrite the ItemsPanelTemplate to use a Canvas instead of a StackPanel, and apply your positioning in the ItemContainerStyle so that you are setting the positioning on the ContentPresenter, and not the Ellipse

    This will make your end result look like:

    <Canvas>
        <ContentPresenter Margin="{Binding EntryPoint}">
            <Ellipse />
        </ContentPresenter>
        <ContentPresenter Margin="{Binding EntryPoint}">
            <Ellipse />
        </ContentPresenter>
    </Canvas>
    

    Some example code to achieve this would be:

    <ItemsControl ItemsSource="{Binding CurrentSegments}">
    
        <!-- ItemsPanelTemplate -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    
        <!-- ItemContainerStyle -->
        <ItemsControl.ItemContainerStyle>
            <Style>
                <Setter Property="Margin" Value="{Binding EntryPoint}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    
        <!-- ItemTemplate -->
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Ellipse Height="10" Width="10" Fill="Black" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    
    </ItemsControl>
    

    See this link for some samples using an ItemsControl