Multiple dataTemplates in ItemsControl and Canvas - c#

Im trying to display some boxes (my own userControl defined in its own xaml file called singleNodeControl) on canvas and connect them with lines (normal xaml Line element binded to LineToParent class)
Both these two items are stored in Binding list in viewModel which is <UserControl> type. Both of these classes (boxes and lines) extending UserControl class so i can store them into single Binding list (canvasNodeSourceList)
SingleNodeControl class containt code in .cs file and also template in .xaml file.
LineToParent class contains only .cs code, where i store X1,X2,Y1,Y2 coordiantes to bind them later.
xaml with itemsControl looks. Which i thought works that if item in canvasNodeSourceList is type LineToParent it will use template bellow othervise it will use template stored in singleNodeControl xaml file (more bellow).
But it looks like that dataTemplate for line isnt used. SingleNodeCOntrols are drawed, but lines arent. What with templates am i missing? And is it even possible to define some template in external file and some in Items Control?
I just wana to display some boxes (which needs on xaml definition because they will have multiple inside elements) which can be connected by lines.
<ItemsControl x:Name="content" ItemsSource="{Binding canvasNodeSourceList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<Canvas x:Name="contentCanvas" Background="White" Width="{Binding Width}" Height="{Binding Height}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type slider:LineToParent}">
<!--<Line X1="100" Y1="100" X2="10000" Y2="10000" StrokeThickness="5" Stroke="RED"></Line>-->
<Line X1="{Binding leftPos1}" Y1="{Binding topPos1}" X2="{Binding leftPos2}" Y2="{Binding topPos2}" StrokeThickness="5" Stroke="Black"></Line>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
SingleNodeControl xaml file
<UserControl x:Class="WHS_qa.View.SingleNodeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WHS_qa.View"
mc:Ignorable="d"
>
<Grid>
<Border BorderBrush="Black" BorderThickness="2">
<StackPanel Name="NodeStackPanel">
</StackPanel>
</Border>
</Grid>
I also tried to modify itemsControl to look like this (changed itemsControl.Resource to itemsControl.itemTemplate) and testline was displayed (with all singleNodeControl elements) But output was full of errors
<ItemsControl x:Name="content" ItemsSource="{Binding canvasNodeSourceList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<Canvas x:Name="contentCanvas" Background="White" Width="{Binding Width}" Height="{Binding Height}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type slider:LineToParent}">
<Line X1="100" Y1="100" X2="10000" Y2="10000" StrokeThickness="5" Stroke="RED"></Line>
<!--<Line X1="{Binding leftPos1}" Y1="{Binding topPos1}" X2="{Binding leftPos2}" Y2="{Binding topPos2}" StrokeThickness="5" Stroke="Black"></Line>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Errors in output
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='LineToParent'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='SingleNodeControl'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='LineToParent'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='SingleNodeControl'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='LineToParent'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='SingleNodeControl'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='LineToParent'

When you're styling based on type, you don't want an itemtemplate. This is because your template is going to be based on datatype.
You do, however, want datatemplates for all the datatypes you will put in the itemssource of your itemscontrol.
This sample does the canvas in an itemscontrol thingm, templating out various objects:
https://1drv.ms/u/s!AmPvL3r385QhgooJ94uO6PopIDs4lQ
<ItemsControl x:Name="ic" ItemsSource="{Binding Items}"
Background="{StaticResource bgroundImage}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:RectangleVM}">
<Rectangle Stroke="Green" Fill="White"
Width="{Binding Width,Mode=TwoWay}"
Height="{Binding Height,Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CircleVM}" >
<StackPanel>
<Ellipse Stroke="Red" Fill="White"
Width="{Binding EllipseWidth}"
Height="{Binding EllipseHeight}"
/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TextBoxVM}">
<TextBox Text="{Binding TbText}"/>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Name="TheCanvas"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
You also probably want the binding for canvas.left and canvas.top on the item container.
All the types presented are fairly different in that sample. If you have similar sort of items you can use a base class for most or some of these and that will be recognised even if you present a subtype.

Related

How to STOP ListView from expanding to content

I'm trying to make my xaml squeeze list items (images) to fit the initial window size, but they cant be fixed size because i want to scale them up as i increase the size of the window. Something like a ViewBox would do.
I load images from 2 folders (software and hardware). Number and size of the images will wary in the runtime so i want to make items be the same size regardless of images size or number of items.
That's why i used uniform grid as a items panel template.
But, this is the result I'm getting...
The ListView loads images in their full size and expands itself to fit them in, cutting of some of the items in the process.
This is my xaml:
<Window x:Class="WPF_UI_Testing.MainWindow"
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:local="clr-namespace:WPF_UI_Testing"
mc:Ignorable="d"
Title="MainWindow" Height="460" Width="640">
<Grid>
<ListView x:Name="listview1">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"
FontWeight="Bold" FontSize="18" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid ></UniformGrid>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding problemImage}"/>
<TextBlock Text="{Binding ImageName}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Is there a way to prevent ListView from expanding beyond window borders when populated with content?
EDIT:
I packed the entire solution with some dummy data and images if anyone wants to have a go at this...
https://drive.google.com/file/d/1IWqxSR3kpsVdCm5Qcgn6QZbZhbYz52n2/view?usp=sharing
The UniformGrid does basically what you want. The idea to use it as item panel is also correct. The only problem that arises, aside from using item groups, is the fact that the ListView wraps its panel into a ScrollViewer, which results in items or the UniformGrid to resize differently as the ScrollViewer gives the panel no size restrictions. UniformGrid needs to be hosted in a fixed size container in order to be able to calculate its children's max sizes.
You should either use the ItemsControl
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:DetailItem}">
<StackPanel>
<Image Source="{Binding problemImage}"/>
<TextBlock Text="{Binding ImageName}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
or override the ControlTemplate of ListView and remove the ScrollViewer (if you need its additional features of ListView):
<ListView>
<ListView.Template>
<ControlTemplate TargetType="ListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<ItemsPresenter />
</Border>
</ControlTemplate>
</ListView.Template>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:DetailItem}">
<StackPanel>
<Image Source="{Binding problemImage}"/>
<TextBlock Text="{Binding ImageName}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The limitations of this approach is when using groups to display the items. UniformGrid should handle GroupItem (nested items) not as expected.
As said in my previous comment, if you want to group items, you need to extend a panel of your choice to manually arrange GroupItem and it's children (nested ItemsPresenter). As you think about how to calculate sizes you may realizes that it is more complicated to calculate group item sizes with dynamic grouped item sizes.
I recommend to let go the grouping and use one of the above solutions or use grouping and embrace the ScrollViewer.

UniformGrid Grid.Row Grid.Column not being adhered to

Following on from this question (DataTemplateSelector for Uniform Grid binding?), I still am not getting my Buttons/Textblocks to adhere to the row and the column that they are bound to in the view model. I will only post the XAML as I know the row and column bindings are correct (Live Visual Tree tells me Row is 1 and Column is 1 for example, but on the grid it shows otherwise...).
If you need anymore code let me know. This is the small bit of XAML.
<Grid DockPanel.Dock="Left" Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="800" Height="400">
<ItemsControl ItemsSource="{Binding ObjCompositeCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid DockPanel.Dock="Top" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Name="objGrid" Grid.Row="1"
Rows="{Binding RowCount}"
Columns="{Binding ColumnCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Row}"/>
<Setter Property="Grid.Column" Value="{Binding Column}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type engine:ObjA}">
<Button Content="{Binding Id}" />
</DataTemplate>
<DataTemplate DataType="{x:Type engine:GridLabeller}">
<TextBlock Text="{Binding HeaderName}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Grid>
A UniformGrid in WPF doesn't care about the Grid.Row and Grid.Column attached properties. You need to create a Grid with RowDefinitions and ColumnsDefinitions for setting these properties to have any effect.
You may create a Grid programmatically in the view based on the values of the RowCount and ColumnCount source properties.

Iterating Through DataBounded Canvas Items Source as if they were the canvas's children

I am currently building a diagram designer using C# and WPF. I have read this article, but found the lack of mvvm driven approach not good enough for my requirements (while this article happened to be too complex for my needs).
I have a basic data class - SimpleDataEntry.cs (holds color, name, left position and top position).
In my ViewModel i have the following property-
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SimpleDataEntry> SimpleDataEntryCollection{ get; set; }
}
In my MainWindow.xaml i have a canvas who's ItemsControl ItemSource is binded to the SimpleDataEntryCollection in the ViewModel :
<Window ...>
<Window.Resources>
<local:ViewModel x:Key="ViewModel"/>
<ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type local:MoveThumb}">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
<ControlTemplate x:Key="DesignerItemControlTemplate" TargetType="ContentControl">
<Grid>
<local:MoveThumb DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
Template="{StaticResource MoveThumbTemplate}"
Cursor="SizeAll"/>
<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Grid>
<ScrollViewer Name="DesignerScrollViewer"
Background="Transparent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<local:DesignerCanvas x:Name="MyDesignerCanvas"
MinHeight="800"
MinWidth="1000"
AllowDrop="True"
Background="Beige">
<ItemsControl ItemsSource="{Binding Source={StaticResource ViewModel}, Path=SimpleDataEntryCollection}">
...A Template for the itmes...
</ItemsControl>
</local:DesignerCanvas>
</ScrollViewer>
</Grid>
The Items that the canvas source is bonded to are templated to show as ellipses that can be dragged around with the mouse (using a MoveThumb class).
Lets call each item of the described here
"Diagram Item"
Here is how the Diagram Item is templated:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<ContentControl Template="{StaticResource DesignerItemControlTemplate}" Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}">
<Grid>
<Ellipse Fill="{Binding Fill}" Width="50" Height="50" IsHitTestVisible="False"/>
<TextBlock Text="{Binding Data}" IsHitTestVisible="False"/>
</Grid>
</ContentControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
Now i want my canvas, on each time i move a Diagram Item out of its borders, to resize to be big enough so it can contain the Diagram Item at its new location. To achieve this im using a class by sukram(from the first article mentioned here) Called "DesignerCanvas" that overrides the method "MeasureOverride". By doing so, the DesignerCanvas cheks if its borders are capable of containing its children locations, and if not - stretching its borders accordingly.
protected override Size MeasureOverride(Size constraint)
{
Size size = new Size();
foreach (UIElement element in Children)
{
double left = Canvas.GetLeft(element);
double top = Canvas.GetTop(element);
left = double.IsNaN(left) ? 0 : left;
top = double.IsNaN(top) ? 0 : top;
element.Measure(constraint);
Size desiredSize = element.DesiredSize;
if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
{
size.Width = Math.Max(size.Width, left + desiredSize.Width);
size.Height = Math.Max(size.Height, top + desiredSize.Height);
}
}
// add some extra margin
size.Width += 10;
size.Height += 10;
return size;
}
This does not work for my approach. My problem here is as follows -
In the example im basing my work upon, the Designer Itmes are Visual Only objects with no logical representation behind them, while in my project the Designer Items are logical entities living in a collection in the viewmodel, a collection my DesignerCanvas takes as items source.
While debugging i have found out that my DesignerCanvas has only one child of the type :
{System.Windows.Controls.ItemsControl Items.Count:3}
//I have 3 Designer Items i create and add to the SimpleDataEntryCollection in the viewModel
I assume that that is because when im adding a new Designer Item im doing so only in the viewmodel, without adding the produced templated Designer Item as a child to the Designer Canvas. If so, where should i insert this addition to children of canvas part?(Is there a way to make this process automated? Any elements in the items source would be automatically added as children of the DesignerCanvas?)
Otherwise, is there a method for me to iterate directly on the DesignerCanvas itemSource as a collection (as if it was the children collection of the DesignerCanvas)?
UPDATE 2
i have tried using an itemspanel as DesignerCanvas. Now my designer canvas has children of the type ContentPresenter. Yet the MeasureOverride() function wont fire when i drag and move the DesignerItems around, only when an item is added to the SimpleDataEntryCollection.
What can be the cause of that?
<ItemsControl ItemsSource="{Binding Source={StaticResource ViewModel}, Path=SimpleDataEntryCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:DesignerCanvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<ContentControl Template="{StaticResource DesignerItemControlTemplate}" Canvas.Left="{Binding Left}" Canvas.Top="{Binding Top}">
<Grid>
<Ellipse Fill="{Binding Fill}" Width="50" Height="50" IsHitTestVisible="False"/>
<TextBlock Text="{Binding Data}" IsHitTestVisible="False"/>
</Grid>
</ContentControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Binding ZIndex from DataTemplate

I have a View that is basically setup like this:
<Grid>
<ViewBox>
<Grid>
<ItemsControl ItemsSource="{Binding MyItems}"
ItemTemplate="{Binding Source={StaticResource MyItemsDataTemplate}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</ViewBox>
</Grid>
The DataTemplate used here can be reduced to this:
<DataTemplate x:Key="AreaItemDisplayDataTemplate">
<Canvas Grid.ZIndex={Binding Z}>
<Grid>
// an shape is displayed here...
</Grid>
</Canvas>
I would now expect the ZIndex to be bound to the Z property of the individual items. When i debug the code, i can also see, that the Z property getter is accessed when i would expect it (whenever i raise the propertychanged event for it, eg) so i assume the binding to work correctly.
However, the ZIndex is not working as expected. The binding to the value has no effect on the actual displayed Z Order. Where am i going wrong with this code?
The content of the DataTemplate gets wrapped in a ContentPresenter so the Canvas in the DataTemplate isn't a direct child of the ItemsPanel Grid. That is the reason the ZIndex property doesn't do anything.
Move the ZIndex Binding to the ItemContainerStyle and it should work.
<ItemsControl ItemsSource="{Binding MyItems}"
ItemTemplate="{Binding Source={StaticResource MyItemsDataTemplate}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.ZIndex" Value="{Binding Z}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

WPF: ItemsControl and DataContext

I have a main window with a single user control in it, called SuperMode. SuperMode consists of a collection of people and each person in this collection has their own collection of tasks. Sounds simple, right?
From file SuperMode.xaml:
<UserControl
x:Class="Prototype.SuperMode"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Prototype"
DataContext="{Binding RelativeSource={RelativeSource Self}}"> <!-- NOTE! -->
<!-- Look at how I'm setting the DataContext, as I think it's
important to solve the problem! -->
<ScrollViewer CanContentScroll="True">
<ItemsControl ItemsSource="{Binding People}" Margin="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</UserControl>
This works fine and I can see four people as I expect! Now all I have to do is get the XAML right for the Person user control so that all of their tasks are displayed as well.
As you can see, I'm using the People property to populate the control with items. The People property has type ObservableCollection<Person>, where Person is another user control as such...
From Person.xaml:
<UserControl
x:Class="Prototype.Person"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Prototype">
<Border Background="Black" CornerRadius="4" Margin="1">
<ItemsControl ItemsSource="{Binding Tasks}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
</UserControl>
Tasks here is a property of Person with type ObservableCollection<Task>. This is where it gets stuck! Apparently WPF cannot find any Tasks property and looking at the output window from VS2008, I find the following:
System.Windows.Data Error: 39 : BindingExpression path error: 'Tasks' property not found on 'object' ''SuperMode' (Name='SuperMode')'. BindingExpression:Path=Tasks; DataItem='SuperMode' (Name='SuperMode'); target element is 'ItemsControl' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
Now I'm lost. It seems that I have to set the DataContext attribute on each Person, otherwise it will still think that the data context is SuperMode, but how do I do that?
Ignoring the rather unpleasant design you have (you should look into MVVM), you should be able to set the DataContext for the child UserControls as follows:
<ItemsControl ItemsSource="{Binding People}" Margin="1">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.DataContext" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

Categories

Resources