I recently came across a requirement to Programmatically Add Buttons to a WPF View in my MVVM based app. Obviously this is not entirely trivial as each Button must contain the correct Bindings to interact with the underlying ViewModel.
The way I tackled this was to use an ItemsControl, with a Canvas Control in the ItemsPanelTemplate. I then added a DataTemplate to the ItemTemplate, with our Button Template in this.
This Button Template contained the relevant binding to hook up the Command Property to my ViewModel.
For ease of use, I then created a Class which housed the various Properties I wanted to expose to each Button, such as it’s Position, CommandParameter and Content, and allowed me to return a Tickness for the Button Position.
From here I created an ObservableCollection of my new Button Class, and bound this to the ItemsControl.
The code can be seen below;
XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas x:Name="MyCanvas">
<ItemsControl ItemsSource="{Binding MyButtons}" Height="237" Width="507">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<Canvas IsItemsHost="true"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="{Binding ControlMargin}" Content="{Binding Content}" Command="{Binding DataContext.ButtonCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding ProductId}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Window>
FluidButton Class:
Public Class FluidButton
Public Property Content As String
Public Property LeftPos As Double
Public Property TopPos As Double
Public Property ProductId As Double
''' <summary>
''' Returns the Control Margin, using the Class Properties
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property ControlMargin As Thickness
Get
Return New Thickness With {.Left = LeftPos, .Top = TopPos}
End Get
End Property
End Class
Properties:
''' <summary>
''' Our Collection of Buttons or Products
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property MyButtons As ObservableCollection(Of FluidButton)
''' <summary>
''' Used to expose the Button Pressed Execute Commands to the UI for Binding
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks>Newed up in the Form Load Event</remarks>
Public Property ButtonCommand As DelegateCommand
MyButtons = New ObservableCollection(Of FluidButton)
MyButtons.Add(New FluidButton With {.Content = "Test1", .LeftPos = 0, .TopPos = 20, .ProductId = 1})
MyButtons.Add(New FluidButton With {.Content = "Test2", .LeftPos = 40, .TopPos = 30, .ProductId = 2})
MyButtons.Add(New FluidButton With {.Content = "Test3", .LeftPos = 80, .TopPos = 40, .ProductId = 3})
MyButtons.Add(New FluidButton With {.Content = "Test4", .LeftPos = 120, .TopPos = 50, .ProductId = 4})
MyButtons.Add(New FluidButton With {.Content = "Test5", .LeftPos = 160, .TopPos = 60, .ProductId = 5})