3

This is a question with many different answers, but have not found one working for Windows Phone.
So here goes:

I have an Items Control that displays UserControls.
I bind an ObservableCollection to this ItemsControl like this:

<Canvas Name="CanvasGrid" Grid.Column="1" Background="Transparent" Canvas.ZIndex="5">
    <ItemsControl ItemsSource="{Binding InGame}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Canvas>
                    <View:SInGame/>
                </Canvas>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Canvas>

When an element is added in Itemscontrol the element is given a zindex independent of what I give the element, and the usercontrol which is showed. What I want to do is have to buttons where I can move the selected element up and down in the z direction.

View SInGame

The Canvas inside the itemscontrol is needed for the position to be binded correctly from the following view:

<UserControl x:Class="MVVMTestApp.View.SInGame"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP8"
xmlns:View="clr-namespace:MVVMTestApp.View"
mc:Ignorable="d"
Canvas.Left="{Binding pos.x}" Canvas.Top="{Binding pos.y}">

<Path >
    <.....
    ....../>
</Path>
</UserControl>

It could then be argued that the view should consist of a Canvas that has the position binded. But this would still leave the issue of the zindex.

As stated in the comments. The ItemsControl cannot see the outside canvas and can therefore not use the zindex to reach that. So how is it possible to set the zindex? Or is it not possible using the ItemsControl element?

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
JTIM
  • 2,774
  • 1
  • 34
  • 74
  • Remove the Canvas within the DataTemplate of the ItemsControl. Having another canvas will confuse the rendering for ZIndex. After that I believe you can bind the ZIndex to a property of your entity (items in the InGame collection) – Shawn Kendrot Apr 22 '14 at 19:58
  • @ShawnKendrot without the canvas the element is not rendered, so this is not possible? – JTIM Apr 23 '14 at 06:13
  • Of course it will be rendered. The ItemsControl does not require a Canvas to be in the DataTemplate – Shawn Kendrot Apr 23 '14 at 15:04
  • @ShawnKendrot I am sorry I misremembered why the canvas was there. I updated the question. But the problems is that the itemscontrol cannot see the outlying canvas and the position bindings from the view will therefore not reach the canvas. – JTIM Apr 23 '14 at 15:55

1 Answers1

2

The ItemsControl takes each item, wraps it in a container (ContentPresenter), and adds it to the items panel (StackPanel by default). So, schematically, you will end up with this tree:

<!-- the items panel (StackPanel by default) -->
<StackPanel>
    <!-- the first item wrapper -->
    <ContentPresenter>
        <!-- your item template -->
        <Canvas>
            <View:SInGame/>
        </Canvas>
    </ContentPresenter>

    <!-- more items ... -->
</StackPanel>

You will want to do 2 things:

  • Set ItemsControl.ItemsPanel to give the items a shared Canvas container.
  • Make sure that the ZIndex you want gets applied to the ContentPresenter wrappers.

These two points will give you a schema like this, which should allow you to position the items front-to-back:

<!-- the items panel (change to a Canvas) -->
<Canvas>
    <!-- the first item wrapper (need to set Canvas.ZIndex to position) -->
    <ContentPresenter Canvas.ZIndex="1">
        <!-- your item template -->
        <Canvas>
            <View:SInGame/>
        </Canvas>
    </ContentPresenter>

    <!-- more items ... -->
</Canvas>

To achieve the second point, I believe you will need to subclass ItemsControl, and override PrepareContainerForItemOverride. For example, the following modifies each item container, binding its ZIndex property to the ZIndex of the item itself:

public class CanvasItemsControl : ItemsControl
{
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        var contentPresenter = (ContentPresenter)element;
        var binding = new Binding("ZIndex") { Source = contentPresenter.Content };
        contentPresenter.SetBinding(Canvas.ZIndexProperty, binding);
    }
}

Now you can just bind Canvas.ZIndex on the View:SInGame item to the underlying model property ("pos.x"?).

Putting it all together:

<local:CanvasItemsControl ItemsSource="{Binding InGame}">
    <local:CanvasItemsControl.ItemTemplate>
        <DataTemplate>
            <Canvas Canvas.ZIndex="{Binding pos.x}" >
                <View:SInGame />
            </Canvas>
        </DataTemplate>
    </local:CanvasItemsControl.ItemTemplate>
    <local:CanvasItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    <local:CanvasItemsControl.ItemsPanel>
</local:CanvasItemsControl>

This way, each SInGame instance gets a Z-index, and each should be positioned relative to the others. You can re-position the items front-to-back by modifying the "MyZIndex" property of each view-model item in "InGame".


Edit

There's a simpler alternative to the above, which might work for you. This approach will give you a flattened tree like this:

<!-- the items panel (change to a Canvas) -->
<Canvas>
    <!-- the items -->
    <View:SInGame/>
    <View:SInGame/>
</Canvas>

To do this, override GetContainerForItemOverride() to return the SInGame object:

public class CanvasItemsControl : ItemsControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new SInGame();
    }

    // Any setup of the `SInGame` items should be done here
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        var container = (SInGame)element;
    }
}

This allows you to define the ItemsControl without any ItemTemplate:

<local:CanvasItemsControl ItemsSource="{Binding InGame}">
    <local:CanvasItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    <local:CanvasItemsControl.ItemsPanel>
</local:CanvasItemsControl>
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • Thank you, I will test this friday when I have time and report back. But thank you for your elaborated answer! – JTIM Apr 23 '14 at 18:44
  • I guess Canvas in DataTemplate is not needed. In that case we have Canvas with ContentPresenters with Canvas with SInGame. A bit complicated is'n it? – ad1Dima Apr 24 '14 at 03:54
  • @ad1Dima look at the OP again -- the `SInGame` control has `Canvas.Left` defined. That will only work if its immediate parent is a Canvas... – McGarnagle Apr 24 '14 at 15:07
  • @McGarnagle Okay I have a question for you regarding the optimal use. Since you now say a DataTemplate is not needed becuase of the container in the .cs code. It seems excessive with your first answer. Is there any pros or cons on the two. I unfortunately can first test it on Friday. But was just that I can control the interest :) – JTIM Apr 24 '14 at 16:19
  • 1
    @JTIM The con of the second approach is just that it doesn't integrate the `ItemTemplate` -- that is ok at least for now, as it would appear that you don't need it. I would definitely go with the second approach. – McGarnagle Apr 24 '14 at 16:36
  • @McGarnagle Thanks yet again. I will test that approach firstly and see if the rest of the app will work :) – JTIM Apr 24 '14 at 16:42