Tuesday 26 March 2013

WPF - Programmatically Adding Buttons the MVVM Way

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
Adding Buttons:


 

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})