tags:

views:

71

answers:

1

HI! I want to design view which will contain multiple objects in different locations. For example - it would be great if viewmodel could have field like list of objects (rectangles) and when i change/add members to list, new rectangles appear on view in specified positions. How do i create such view/viewmodel?

+4  A: 

You could have an ICollectionView or ObservableCollection<T> property in your ViewModel and bind the ItemsSource property of an ItemsControl to this property. Then this will display all the items in your collection(view). However, it will typically display them in a StackPanel as this is the default item container for an ItemsControl. As far as I understood your question, you want the items to be placed anywhere on your screen. This could be done by using a Canvas as the ItemsControl's ItemsPanel, and then binding the Canvas.Left and Canvas.Top properties to properties in your ViewModels. Of course, every item would need a Left and Top property then (and maybe also a Width and Height property).

public class ItemViewModel
{
    public double Left { get; set; }
    public double Top { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }

    // whatever you need...
}

public class CollectionViewModel
{
    public ObservableCollection<ItemViewModel> Collection { get; }

    // some code which fills the Collection with items
}

And your XAML:

<ItemsControl ItemsSource="{Binding Collection}">

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type local:ItemViewModel}">
            <Rectangle Width="{Binding Width}" Height="{Binding Height}"
                       Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>

</ItemsControl>

In a final step, you might want the Left and Top properties to be relative to the size of the Canvas, so that the items remain at the relative positions if the size of the Canvas changes. This is some more work:

<DataTemplate DataType="{x:Type local:ItemViewModel}">
    <Rectangle Width="{Binding Width}" Height="{Binding Height}">

        <!-- Make the left position of the item depend on the ActualWidth of the Canvas,
             the relative Left position (between 0 and 1) from the ItemViewModel, and the ActualWidth
             of the item itself. This is needed because the Canvas.Left property defines the
             position of the left side, not the center. Therefore, we calculate the position of
             the center this way:
                  (Canvas.ActualWidth * ItemViewModel.Left) - (Item.ActualWidth / 2)
        -->
        <Canvas.Left>
            <MultiBinding>
                <MultiBinding.Converter>
                    <converters:ExpressionConverter Expression="{}({0} * {1}) - ({2} / 2)"/>
                </MultiBinding.Converter>
                <Binding Path="ActualWidth" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Canvas}}"/>
                <Binding Path="Left"/>
                <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
            </MultiBinding>
        </Canvas.Left>

        <!-- the top position of the items is determined the same way as the left position
             which is described above -->
        <Canvas.Top>
            <MultiBinding>
                <MultiBinding.Converter>
                    <converters:ExpressionConverter Expression="{}({0} * {1}) - ({2} / 2)"/>
                </MultiBinding.Converter>
                <Binding Path="ActualHeight" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Canvas}}"/>
                <Binding Path="Top"/>
                <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
            </MultiBinding>
        </Canvas.Top>

    </Rectangle>
</DataTemplate>

The description of the code is already in the XAML comments. However, I should note that I have used the ExpressionConverter from Kent Boogart's Converter collection. I copied and pasted the above code from one of my applications, so there might be some inconsistencies in there because of quickly adjusting the properties to your scenario. However, the principle should be clear, I think. Good luck!

gehho
Thanks gehho, you also answered my question here:http://stackoverflow.com/questions/2774511/binding-scattered-overlapping-images-to-a-wpf-canvasIf you want to copy/paste your answer there I will Accept it.
bufferz
This is awesome code! Releative coordinates would be way to go! Are you planing on using valueconverter or are there other approaches as well? How does that ItemTemplate works? Why do we use ItemsControl with canvas, not canvas, which has its content set to ItemsControl. This design is not clear for me.. O_o
0xDEAD BEEF
I will add an example with relative coordinates soon. However, this requires some more work. An ItemsControl is used for all scenarios where multiple items should be displayed and removed dynamically (by the way: ListBox and many other controls derive from ItemsControl). The ItemsPanel property defines which panel is used within the ItemsControl to arrange the items. The default is StackPanel, so that items are arrange one below the other. We change it to a Canvas, so that we can arrange the items arbitrarily by specifying Canvas.Left and Canvas.Top.
gehho
BTW: You can find a lot of useful information about ItemsControl at [Dr. WPF's blog](http://drwpf.com/blog/itemscontrol-a-to-z/)!
gehho
I have updated the answer with an example using relative coordinates. I hope that helps.
gehho
Oh, forgot to answer your last question: Why ItemsControl with Canvas and not the other way round? We want the items to be arranged at certain positions, specified by Left and Top. This is only possible within a Canvas! All other panels use other, somehow "semi-automatic", layout techniques. By using a Canvas as ItemsPanel, we can specify the exact positions where the items will appear. If we used an ItemsControl within a Canvas, we could only position the ItemsControl at a specific location, but its items would still by laid out using a StackPanel.
gehho
Nonrelated, but - maybe you got clue on this issue as well! http://stackoverflow.com/questions/2957582/wpf-storyboard-settarget-vs-storyboard-settargetname
0xDEAD BEEF
No, I am sorry, I cannot help in that case. But I think there was a similar issue here on SO some time ago (but related to XAML). Maybe you should search a little.
gehho
gehho - your solution did not work. I had to use this approach - <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Canvas.Left" Value="500" /> </Style> </ItemsControl.ItemContainerStyle>. could you comment on that?
0xDEAD BEEF
Well, as I said: *"there might be some inconsistencies in there"* because I copied it from a project of mine where I do some things differently. For example, I am using a ListBox and not ItemsControl directly (fixed the XAML code above). Furthermore, I do not set the `ItemsControl.ItemTemplate` property, but I simply define `DataTemplate` s in my resources. It might be that the above code does not work perfectly, I did not even try to compile it. It was just thought to give you an idea how it works. It might be necessary to adjust it to your needs. Please provide details if you need more help!
gehho