I am migrating an old application and am trying to implement MVVM. The old application has a similar interface to the one described in this post. Although it is slightly simpler. I am not too worried about following MVVM exactly, but I would like to use this as a chance to practice.
I have the following class:
public class LineViewModel
{
public ObservableCollection<LinePoint> Points {get;}
public Geometry LineGeometry {get;}
}
In my application view model I have a collection of lines and I would like to draw these on a Canvas, but I would also like to draw the points that make up each line.
Here is my XAML so far:
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:LineViewModel}">
<Path StrokeThickness="2.0" Stroke="Black" Data="{Binding LineGeometry}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:LinePoint}">
<Ellipse StrokeThickness="1.0" Stroke="Black" Fill="MistyRose" Width="8.0" Height="8.0" />
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I can see the line being drawn and this seems to be working fine. As you can see I have added the template for the points but I do not know how to setup the XAML to draw these. How can I draw the points as well?
Pulling out the Points from the line and exposing these from the application view model seems counter intuitive since it is the LineViewModel that looks after the points in a line - not the application. I would rather not disassociate the points from the lines if I can help it.
Thanks
I'd suggest to create a view model with a single property for the points of a line
class LineViewModel
{
public PointCollection LinePoints { get; set; }
}
class ViewModel
{
public IEnumerable<LineViewModel> Lines { get; set; }
}
Then you could have a nested ItemsControl in the DataTemplate of your "outer" ItemsControl like shown below, which uses a Polyline to draw the line and a collection of Path elements with EllipseGeometries for the circles.
<ItemsControl ItemsSource="{Binding Lines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Polyline Points="{Binding LinePoints}"
StrokeThickness="2.0" Stroke="Black"/>
<ItemsControl ItemsSource="{Binding LinePoints}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path StrokeThickness="1.0" Stroke="Black" Fill="MistyRose">
<Path.Data>
<EllipseGeometry Center="{Binding}"
RadiusX="4" RadiusY="4"/>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In case you have to use your LineViewModel as is, you may write the ItemTemplate like shown below, with a binding converter that converts your LinePoint to Point for the Center of the EllipseGeometry.
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Path StrokeThickness="2.0" Stroke="Black" Data="{Binding LineGeometry}" />
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path StrokeThickness="1.0" Stroke="Black" Fill="MistyRose">
<Path.Data>
<EllipseGeometry
Center="{Binding
Converter={StaticResource LinePointConverter}}"
RadiusX="4" RadiusY="4"/>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
Related
I have an ItemTemplateSelector that uses the typical C# code for logic, but that code never even gets called from my ItemsControl. Right now I have just stubbed in ONE of the Selector Display Templates for "StringTemplate" and I have those in my "Parameters" array, yet it never calls selector.
<Grid>
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.Resources>
<local:ReportRun_LaunchView_ParameterTemplateSelector x:Key="TemplateSelector_FormDetails">
<local:ReportRun_LaunchView_ParameterTemplateSelector.StringTemplate>
<DataTemplate>
<fv:WatermarkTextBox
Grid.Row="1"
Grid.Column="0"
Margin="0,0,7,0"
fv:WpfMvmFocusManager.FocusKey="txtSearchNpi"
Text="{fv:OnChangedBinding Path=StringValue}"
WatermarkContent="{Binding DataContext.Name}" />
</DataTemplate>
</local:ReportRun_LaunchView_ParameterTemplateSelector.StringTemplate>
</local:ReportRun_LaunchView_ParameterTemplateSelector>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
HorizontalAlignment="Stretch"
IsItemsHost="True"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl x:Name="items" ItemTemplateSelector="{StaticResource TemplateSelector_FormDetails}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I am implementing User Controls in my application with MVVM structure. How can I bind different User Controls in a single page and show in a window screen.
Like below image. I want same as it is in the image.
ViewModel code
public RelayCommand OpenWidgetList => new RelayCommand(() =>
{
ControlList = new List<UserControl>();
ControlList.Add(ObjColumn1L);
ControlList.Add(ObjColumn1L);
ControlList.Add(ObjColumn1M);
ControlList.Add(ObjColumn1S);
ControlList.Add(ObjColumn1XL);
ControlList.Add(ObjColumn1XXL);
ControlList.Add(ObjColumn2L);
ControlList.Add(ObjColumn2M);
ControlList.Add(ObjColumn2S);
ControlList.Add(ObjColumn2XL);
ControlList.Add(ObjColumn2XXL);
ControlList.Add(ObjColumn3L);
ControlList.Add(ObjColumn3M);
ControlList.Add(ObjColumn3S);
ControlList.Add(ObjColumn4M);
ControlList.Add(ObjColumn4S);
}, true);
XAML Code
<UniformGrid Name="widgetData" Background="Black" VerticalAlignment="Top" Height="691" Margin="0,50,0,0">
<UniformGrid Columns="3">
<UniformGrid Rows="6">
<UniformGrid>
<ItemsControl ItemsSource="{Binding ControlList}" Name="UserControlColumn" Margin="4,0" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn HorizontalAlignment="Left" Margin="2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
</UniformGrid>
</UniformGrid>
</UniformGrid>
</Grid>
Yes, I found answer.
By coding I tried much but can not do that but in a simple way I had done by XAML side. Nothing but copy the code and separately bind each user Control with only one data from the back-end in DataCollection variable. Without binding each User Control from the back-end codes.
Below are my XAML code of WPF file. where repetedly done for each User Control to bind in the form.
<UniformGrid Rows="1">
<ItemsControl ItemsSource="{Binding DataCollection}" Name="UserControlColumn1" Margin="4,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn1 HorizontalAlignment="Left" Margin="2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
<UniformGrid Rows="1">
<ItemsControl ItemsSource="{Binding DataCollection}" Name="UserControlColumn2" Margin="4,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn2 HorizontalAlignment="Left" Margin="2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
<!--Same for other user Controls as above two...-->
Every user control has its own datacontext so in your main viewmodel you add a property for every usercontrol viewmodel and then you bind it. Something like this:
UserControl1ViewModel
{
....
}
MainViewModel
{
UserControl1ViewModel UserControl1ViewModel {get; private set;}
}
Am using listbox control in WPF its display listbox item in row wise but I want to display in column wise ( Something like bootstrap grid)
XMAL
<ListBox x:Name="lb_items">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10 0 0 0">
<Image Source="{Binding ImageSource}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Am binding Listbox from code behind
lb_items.ItemsSource = modules.ToList();
Try this one.
<ListBox x:Name="lb_items">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10 0 0 0">
<Image Source="{Binding ImageSource}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ListBox has property called ItemsPanel, which determines how items are rendered.
Somewhere in application resource create different ItemsPanelTemplate, so you can easily reuse it:
<ItemsPanelTemplate x:Key="WrapPanelTemplate">
<WrapPanel />
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
then you can easily use it:
<ListBox ItemsPanel="{StaticResource HorizontalStackPanelTemplate}">...
<ItemControl ItemsPanel="{StaticResource WrapPanelTemplate}">...
always put such assets into application resources in order to make the code for your views clean and readable.
Extra Tip:
Here is animated WrapPanel, that animates items, when you resize window or an item is inserted to the list:
<ItemsPanelTemplate x:Key="FluidWrapPanel">
<WrapPanel>
<i:Interaction.Behaviors>
<ei:FluidMoveBehavior AppliesTo="Children" Duration="0:0:0.5">
<ei:FluidMoveBehavior.EaseY>
<SineEase EasingMode="EaseInOut" />
</ei:FluidMoveBehavior.EaseY>
<ei:FluidMoveBehavior.EaseX>
<CubicEase EasingMode="EaseInOut" />
</ei:FluidMoveBehavior.EaseX>
</ei:FluidMoveBehavior>
</i:Interaction.Behaviors>
</WrapPanel>
</ItemsPanelTemplate>
dont forget to include these xml namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
I am working on c# xaml using silverlight and i am bit confused about the hierarchy of this xaml code:
<UserControl.Resources>
<this:MyValueConverter x:Key="TabConverter"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Green">
<ItemsControl ItemsSource="{Binding Path=TabList, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="150" Background="red" Height="100" Canvas.Left="{Binding TabList, Mode=TwoWay, Converter={StaticResource TabConverter}}" Canvas.Top="100" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When i run it gives a big green color window(sorry if technical names are not correct), whereas it should also show red color somewhere in same window as it has Border Width="150" Background="red" .Could some one please explain me what this code is doing ?
ItemsControl really just binds to a list and applies a datatemplate (border with background red) for each item in the list. As for the reason you're only seeing green, well there's probably nothing in the TabList property on your viewmodel. That way, nothing in the items template renders, and all you see is green.
You'll need to make sure that TabList is bound correctly (it exists on your datacontext, whether that's a view model or not) and that it has items in it.
Here's a simpler version of what you may want to accomplish:
<Grid x:Name="LayoutRoot" Background="Green">
<ItemsControl ItemsSource="{Binding Path=TabList, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="150" Background="red" Height="100" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
I have an ItemsControl set up like this:
<Grid>
<ItemsControl ItemsSource="{Binding Asteroids}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black">
<!-- i want to add another polygon to the canvas-->
<!--<Polygon Name ="ShipPolygon" Points="{Binding Ship}" Fill="Blue" />-->
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polygon Fill="Gray" Points="{Binding AsteroidPoints}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
As you can see, the ItemsControl is displaying a collection's elements as Polygons in a Canvas. But i also want to add another Polygon to this Canvas, here named "ShipPolygon". I can't do that this way, because i get an XMLParseException. What is the proper method to do this? Thanks in advance!
You are using an ItemsControl, which is there to display multiple similar items on an itemspanel.
Now obviously you can't add something to your ItemsPanelTemplate there you are only telling the itemscontrol which panel it should use to display its items.
Your ItemsSource and your ItemTemplate suggest that you only want to show Asteroids in your ItemsControl. So the easiest way would be to just overlay your ship onto the itemscontrol with your asteroids
<Grid>
<ItemsControl ItemsSource="{Binding Asteroids}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polygon Fill="Gray" Points="{Binding AsteroidPoints}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Polygon Name ="ShipPolygon" Points="{Binding Ship}" Fill="Blue" />
</Grid>
Otherwise you could add it aswell to the itemscontrol, but then need to use different ItemTemplates. And you need to handle, that your ItemsControl no longer only holds Asteroids.
Using implicit item Templates
<!-- No key, so it is implicit and automatically used-->
<DataTemplate DataType="{x:Type Asteroids}">
<Polygon Fill="Gray" Points="{Binding AsteroidPoints}" />
</DataTemplate>
<DataTemplate DataType="{x:Type Player}">
<Polygon Name ="ShipPolygon" Points="{Binding Ship}" Fill="Blue" />
</DataTemplate>
<!-- above must be in a resource section, like app.xaml -->
<Grid>
<ItemsControl ItemsSource="{Binding Entities}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
Or you could use a DataTemplateSelector.