Binding a nested list to a WPF canvas panel - c#

I was wondering if its possible to somehow bind a list of lists to a cavas panel. For example having object "layer" which itself is a list of "rectangle" objects. Would if be possible to bind a list of layers to a canvas? So far I can only do it by binding a flattened INumerable of the nested list (using SelectMany function) to an itemcontrol, but this isnt good enough, Id like to keep the "layers" separate and have the Zindex of the rectangles vary according to the layer it is at, allowing to reorder the layers easily.
I have also tried having nested itemcontrols but as expected it only displays the first layer. The aim is to draw a rectangle for every object in every layer on the canvas allowing for layer manipulation, insertion of new objects in each layer, etc...
Thanks in advance! :)

How about making the ItemTemplate of the main ItemsControl another ItemsControl with yet another Canvas as ItemsPanel? (I do not see a reason why it should only display the first layer)
Actually the main ItemsControl does not need to be a Canvas, a Grid would do as well (at least unless your collections have coordinates of their own or a Z-Order, if you use a grid the layer order is he same as their appearance in the collection).
An example (a bit verbose but can't do anything about that):
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding LayerItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<!-- Here would the binding to some properties that take care of placement -->
<Setter Property="Canvas.Top" Value="{Binding Top}" />
<Setter Property="Canvas.Left" Value="{Binding Left}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<!-- Some template for the individual items -->
<DataTemplate>
<Border Padding="5" BorderThickness="1" BorderBrush="Red" CornerRadius="3"
Background="White">
<TextBlock Text="{Binding Name}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Like H.B said, i too can't see a reason why it will not work. Having an ItemsControl in another ItemsContainer works perfectly fine.
<ItemsControl ItemsSource="{Binding Layers}">
<ItemsControl.ItemsContainerStyle>
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsControl ItemsSource="{Binding Rectangles}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ItemsControl.ItemsContainerStyle>
</ItemsControl>
But it think it would be cleaner to put the sub ItemsControl inside an DataTemplate for your Items, without the need to modify the ItemsContainerStyle. I think its bad design if your control needs to know informations about the properties of the model, which is necessary for binding to Rectangles.

Related

Set ZIndex of items in ItemsControl From DataTemplate

I have the following XAML in wpf
<Canvas>
<ItemsControl ItemsSource="{Binding CompositeCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type Type1}">
<Shape1/>
</DataTemplate>
<DataTemplate DataType="{x:Type type2}">
<Shape2/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Canvas>
So essentially I have two different data templates for the two different types my System.Windows.Data.CompositeCollection may contain. Either Shape1 or Shape2 is then drawn based on the type.
I need the zindex for shape1 to be higher than shape2, so I need to set the zindex from the dataTemplate.
How would I do this?
The elements in the ItemTemplate will not become direct child elements of the Canvas in the ItemsPanelTemplate. Hence setting something like
<DataTemplate DataType="{x:Type Type1}">
<Shape1 Panel.ZIndex="1"/>
</DataTemplate>
will have no effect.
You would instead have to declare an ItemContainerStyle:
<ItemsControl ...>
...
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Panel.ZIndex" Value="{Binding ViewModelItemZIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

All Items in Items control are dragging together

I've been building a dragable nodes tree using items control and the final hurdle is that the children of the nodes, the ones that are in the items control are not able to be dragged separately for some reason. The root nodes can be dragged on there own so the dragging canvas is working.
This is a picture of the ui, showing the root nodes and the child nodes that are attached together:
This is the visual tree, it shows that the child nodes are attached to a dragable canvas:
The code itself is
<pnl:DragCanvas>
<ContentControl cal:View.Model="{Binding Root}"/>
<ContentControl cal:View.Model="{Binding Root}"/>
</pnl:DragCanvas>
<pnl:DragCanvas x:Name="CurrCanvas">
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<pnl:DragCanvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=XPos}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=YPos}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</pnl:DragCanvas>
I've got no idea how to fix it and it's really annoying!!

UserControl with Horizontal ListView in vertical ListView

I want to archive a vertical ListView that contains a UserControl in which i have some infos on the left and a second ListView that will scroll horizontal.
The problem is that my second listview will not start to scroll. It seems to pick unlimited space.
First of all some more infos about my setup:
My window is arranged via a grid basically just a row on top with text and the first listview in the second row.
The first listview uses a ItemTamplate like the following:
<ListView.ItemTemplate>
<DataTemplate>
<controls:MyControl />
</DataTemplate>
</ListView.ItemTemplate>
Beside that it has a ItemContainerStyle to display a horizontal line between the items:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem"
BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<StackPanel>
<Rectangle x:Name="Separator"
Height="2"
SnapsToDevicePixels="True"
Fill="{DynamicResource AccentColorBrush}" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
(trigger that disables the unnecessary first line omitted)
MyControl is a UserControl which is also just a grid first column some text and second column is my second listview.
This one is special because it is horizontal. Since their are various approaches for this from arround the internet i will show the one i ended using with.
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"
IsItemsHost="True"
IsVirtualizing="True"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
Item template is again a UserControl (at the moment just a TextBlock) and again the separator lines but this time vertically.
The problem is now that the second ListView is not scrollable. Even if i explicit set them to be visible they are disabled (also after resizing window).
My approach solving this was binding MaxWidth of Stackpanel in ListViewItem Template to Actual with of my MetroWindow.
MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type metroControls:MetroWindow}}}"
But this and some other tries binding different sizes of different items does not work.
Can somebody give a hint to resolve this?

Drawing multiple layers over wpf map control

I have the grid with wpf map control and canvas over it for drawing markers over map.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<UserControl Grid.Row="0" Content="{Binding CurrentMap}" />
<UserControl Grid.Row="0" Content="{Binding DrawningCanvas}" />
</Grid>
Now I needed to use more than 1 layer over the map.
What wpf control I should use for binding to ObservableCollection<Canvas> in DataContext and draw them all over each other?
You seem to be somewhat confused. An ObservableCollection is a collection for data items. A Canvas is a UI control. Therefore, you should never have a data item collection with UI elements in.
Instead, you could have a Marker data class that contains X and Y properties for the marker's positions and maybe Name and ImageSource properties to define what it would look like in the UI. Then you could have a ObservableCollection of type Marker named Markers that you could bind to the ItemsSource property of an ItemsControl. Finally, you could set a Canvas as the ItemsPanel of the ItemsControl:
<ItemsControl ItemsSource="{Binding Markers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type YourXmlNamespacePrefix:Marker}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't really know why you would want to have multiple layers because you could overlay multiple items on the same layer, but if you really need multiple layers, then you can just add more of the ItemsControl elements shown above.

Setting Canvas Children Property without ItemsControl ItemsSource Binding Property

Is there any means to set Canvas Children Property without ItemsControl ItemsSource Binding Property?
In order to keep separated my view from viewmodel, I have to bind the items.
I have used the the canvas as a designer from 'CodeProject'
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
I'm using a canvas for drag-and-drop purposes. It works well when I work manually inside the canvas.
Which means I add and remove the child items using
myCanvas.Children.Add(userControl);
myCanvas.Children.Remove(userControl);
But if I load my usercontrols at run time, they are loaded just as views.
<s:Canvas AllowDrop="True" >
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=userControls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<s:Canvas Background="Transparent"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<s:ControlItem Content="{Binding Path=MyView}"></s:ControlItem >
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</s:Canvas>
No, there aint. (Except manually clearing and adding...)
Ummm yeah just draw items inside the canvas? :)
<Canvas>
<TextBlock Text="I'm Child #1" />
<TextBlock Text="I'm Child #2" Canvas.Top="50" />
</Canvas>
Or you can always do it in code-behind
myCanvas.Children.Add(myTextBlock);
foreach(var someControl in SomeControlList)
myCanvas.Children.Add(someControl);
Edit
I see your update and have no idea what you're asking. If you want to drag/drop items onto a Canvas, you are better off adding/removing items from the ItemsSource than manually adding/removing items from the Canvas. Simply adding/removing them from myCanvas will not update the collection in your ItemsSource
I would recommend taking a look at Bea Stollnitz's article on dragging/dropping databound Items. This means you would keep the ItemsControl you have now, but when you drop an item on top of the Canvas it adds the DataItem behind that object to the ObservableCollection<MyDataItem> that you call userControls (I don't like this name because it suggests that the data items contain UI items, which should not be the case)

Categories