As like the subject, I want to add WPF controls of the same template but different data inside as many as I want dynamically.
As you can see in the picture, the control that I want to duplicate is somewhat complicated. The overall control is wrapped inside Canvas
inside ScrollViewer
.
Each StackPanel
wraps TextBlock
and another
Canvas Control and this StackPanel
is what I want to reproduce.
It is coded like:
<ScrollViewer x:Name="ScrollBoard" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Canvas x:Name="CanvasBoard" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Height="250" Background="Gray">
<StackPanel x:Name="CanvasStack" Background="DimGray">
<CheckBox />
<Border x:Name="CanvasBorder" BorderBrush="Black" BorderThickness="1">
<Canvas Width="150" Height="200" ClipToBounds="True">
<Image x:Name="CanvasImage" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" Source="C:\test.jpg"/>
</Canvas>
</Border>
<TextBlock Text="Test.jpg" />
</StackPanel>
</Canvas>
</ScrollViewer>
I want to duplicate that CanvasStack StackPanel
control inside CanvasBoard Canvas
Control.
Of course, not just duplicate but also want to take control of that.
For example, change the position, edit TextBlock
text, replace Image
and get Click event and so on.
Plus, I will not use ListBox
or ListView
for it because each Control should be located in absolute x,y coordination with various size.
There are some examples doing similar things like 'adding a button to certain control'. But what I found were just add control in backend with hard-coded properties which may not fit for this kind of complex control.
Thank you in advance.
Whenever you're thinking of creating the same ui numerous times then you should think in terms of templating.
When you want properties of anything to change then you should think in terms of a datatemplate and bind those changing things to a public property of a viewmodel.
With repeated controls in the same area the first candidate should be some sort of an itemscontrol.
That has a stackpanel in it holds everything - but you can easily change that.
For numerous things on one canvas you can make the itemspanel of an itemscontrol a canvas.
You don't absolutely need a usercontrol to encapsulate your markup, you can just have a datatemplate.
Build a viewmodel for your windows. Build a viewmodel for each of these things you want to reproduce (vm).
Bind an observablecollection of vm to the itemssource of an itemscontrol.
Define a datatemplate associated with that type of viewmodel.
I work in c# so I'm likely to get something wrong if I try and write VB code. Run the code below through an online converter.
Markup:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl x:Name="ic" ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:StackyVM}">
<StackPanel x:Name="CanvasStack" Background="DimGray">
<CheckBox />
<Border x:Name="CanvasBorder" BorderBrush="Black" BorderThickness="1">
<Canvas Width="150" Height="200" ClipToBounds="True">
<Image x:Name="CanvasImage" Canvas.Left="0" Canvas.Top="0" Stretch="Fill"
Source="{Binding ImageSource}"/>
</Canvas>
</Border>
<TextBlock Text="Test.jpg" />
</StackPanel>
</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>
Viewmodel per stack
public class StackyVM : BaseViewModel
{
private Double left;
public Double Left
{
get { return left; }
set
{
left = value;
RaisePropertyChanged();
}
}
private Double top;
public Double Top
{
get { return top; }
set { top = value; RaisePropertyChanged(); }
}
public string ImageUrl { get; set; }
}
Add any other properties which will vary for each stack and bind them.
Instantiate one of those for each stack and add it to the bound observablecollection:
public class MainWindowViewModel : BaseViewModel
{
public ObservableCollection<StackyVM> Items { get; set; }
Each will then be templated into a stack.
BaseViewModel implements inotifypropertychanged:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}