Share via

WPF custom data grid with button

Arun Menon 21 Reputation points
2020-06-12T02:24:43.93+00:00

In WPF xaml, I would like to make a custom data grid with button in cell. I want to create button(Say "Add" button) in the last cell of each row as shown in below pic.

9886-customgrid.png

While pressing "add" button some operation will happen and data will be added to that cell and button should be shown in next column ( So button will be different random cells in data grid.) Basically I need to add and remove button from cell dynamically.

I could add column with button using below code. But I don't know how add this button in the last cell of of each row.

              <DataGridTemplateColumn >  
                <DataGridTemplateColumn.CellTemplate>  
                    <DataTemplate>  
                    <Button Height="30" Width="30"/>  
                    </DataTemplate>  
                </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
Developer technologies | Windows Presentation Foundation

Answer accepted by question author

Peter Fleischer (former MVP) 19,351 Reputation points
2020-06-13T06:26:54.027+00:00

Hi, the following demo shows another solution with TemplateSelector .

XAML:

<Window x:Class="Window027"  
        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:WpfApp1.WpfApp027"  
        mc:Ignorable="d"  
        Title="Demo ArunMenon-4250 200612 TemplateSelector" Height="450" Width="800">  
  <Window.Resources>  
    <local:ViewModel x:Key="vm"/>  
    <local:VisibilityConverter x:Key="conv"/>  
    <Style TargetType="{x:Type TextBlock}">  
      <Setter Property="Background" Value="Red"/>  
    </Style>  
    <DataTemplate x:Key="dtTextBox">  
      <Grid Background="{Binding ColumnValue, Converter={StaticResource conv}}">  
        <TextBlock Text="{Binding ColumnValue}" Foreground="Black"  
                   Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"/>  
      </Grid>  
    </DataTemplate>  
    <DataTemplate x:Key="dtButton">  
      <Button Content="Add"   
              Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"  
              Command="{Binding Cmd, Source={StaticResource vm}}"   
              CommandParameter="{Binding}"/>  
    </DataTemplate>  
    <DataTemplate x:Key="dtEmpty">  
      <TextBlock/>  
    </DataTemplate>  
    <local:DTSelector x:Key="myTemplateSelector"  
                      TemplateTextBox="{StaticResource dtTextBox}"  
                      TemplateButton="{StaticResource dtButton}"  
                      TemplateEmpty="{StaticResource dtEmpty}"/>  
    <DataTemplate x:Key="CustomTemplate">  
      <ContentPresenter Content="{Binding}"  
                        ContentTemplateSelector="{StaticResource myTemplateSelector}">  
      </ContentPresenter>  
    </DataTemplate>  
  </Window.Resources>  
  <Grid DataContext="{StaticResource vm}">  
    <Grid.RowDefinitions>  
      <RowDefinition/>  
      <RowDefinition Height="Auto"/>  
    </Grid.RowDefinitions>  
    <DataGrid ItemsSource="{Binding View}"   
              AutoGenerateColumns="False"   
              IsReadOnly="True">  
      <DataGrid.Columns>  
        <DataGridTextColumn Header="Value" Binding="{Binding Value}">  
          <DataGridTextColumn.ElementStyle>  
            <Style TargetType="{x:Type TextBlock}">  
              <Setter Property="VerticalAlignment" Value="Center"/>  
              <Setter Property="Margin" Value="5"/>  
            </Style>  
          </DataGridTextColumn.ElementStyle>  
        </DataGridTextColumn>  
        <DataGridTextColumn Header="Index" Binding="{Binding Index}">  
          <DataGridTextColumn.ElementStyle>  
            <Style TargetType="{x:Type TextBlock}">  
              <Setter Property="VerticalAlignment" Value="Center"/>  
              <Setter Property="HorizontalAlignment" Value="Right"/>  
              <Setter Property="Margin" Value="5"/>  
            </Style>  
          </DataGridTextColumn.ElementStyle>  
        </DataGridTextColumn>  
        <DataGridTemplateColumn Header="Trail 1">  
          <DataGridTemplateColumn.CellTemplate>  
            <DataTemplate>  
              <ContentPresenter Content="{Binding Trail1}" ContentTemplateSelector="{StaticResource myTemplateSelector}">  
              </ContentPresenter>  
            </DataTemplate>  
          </DataGridTemplateColumn.CellTemplate>  
        </DataGridTemplateColumn>  
        <DataGridTemplateColumn Header="Trail 2">  
          <DataGridTemplateColumn.CellTemplate>  
            <DataTemplate>  
              <ContentPresenter Content="{Binding Trail2}" ContentTemplateSelector="{StaticResource myTemplateSelector}">  
              </ContentPresenter>  
            </DataTemplate>  
          </DataGridTemplateColumn.CellTemplate>  
        </DataGridTemplateColumn>  
        <DataGridTemplateColumn Header="Trail 3">  
          <DataGridTemplateColumn.CellTemplate>  
            <DataTemplate>  
              <ContentPresenter Content="{Binding Trail3}" ContentTemplateSelector="{StaticResource myTemplateSelector}">  
              </ContentPresenter>  
            </DataTemplate>  
          </DataGridTemplateColumn.CellTemplate>  
        </DataGridTemplateColumn>  
      </DataGrid.Columns>  
    </DataGrid>  
    <Label Grid.Row="1" Content="{Binding Info}"/>  
  </Grid>  
</Window>  

------------------------------------------

Imports System.Collections.ObjectModel  
Imports System.ComponentModel  
Imports System.Globalization  
Imports System.Runtime.CompilerServices  
  
Namespace WpfApp027  
  
  Public Class ViewModel  
    Implements INotifyPropertyChanged  
  
    Private cvs As New CollectionViewSource  
    Public ReadOnly Property View As ICollectionView  
      Get  
        If cvs.Source Is Nothing Then GetData()  
        Return cvs.View  
      End Get  
    End Property  
  
    Public Property Info As String  
  
    Public ReadOnly Property Cmd As ICommand  
      Get  
        Return New RelayCommand(AddressOf CmdExec)  
      End Get  
    End Property  
  
    Private Sub CmdExec(obj As Object)  
      Dim d = TryCast(obj, ColumnData)  
      If d Is Nothing Then Exit Sub  
      Info = $"Selected Row ID: {d.ID}"  
      OnPropertyChanged(NameOf(Info))  
    End Sub  
  
    Private Sub GetData()  
      Dim rnd As New Random  
      Dim col As New ObservableCollection(Of Data)  
      For i = 1 To 10  
        Dim d = New Data With {.ID = i, .Value = $"Value {i}", .Index = CDec(0.1 * rnd.Next(1, 100))}  
        d.Trail1.ID = i  
        d.Trail2.ID = i  
        d.Trail3.ID = i  
        If rnd.NextDouble < 0.5 Then  
          d.Trail1.State = ColumnState.Button  
        Else  
          d.Trail1.ColumnValue = rnd.Next(50, 200)  
          d.Trail1.State = ColumnState.Value  
          If rnd.NextDouble < 0.5 Then  
            d.Trail2.State = ColumnState.Button  
          Else  
            d.Trail2.ColumnValue = rnd.Next(50, 200)  
            d.Trail2.State = ColumnState.Value  
            d.Trail3.State = ColumnState.Button  
          End If  
        End If  
        col.Add(d)  
      Next  
      cvs.Source = col  
    End Sub  
  
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged  
    Friend Sub OnPropertyChanged(<CallerMemberName> Optional propName As String = "")  
      RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))  
    End Sub  
  
  End Class  
  
  Public Class DTSelector  
    Inherits DataTemplateSelector  
  
    Public Property TemplateTextBox As DataTemplate  
    Public Property TemplateButton As DataTemplate  
    Public Property TemplateEmpty As DataTemplate  
  
    Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate  
      Dim d = TryCast(item, ColumnData)  
      If d Is Nothing Then Return MyBase.SelectTemplate(item, container)  
      Select Case d.State  
        Case ColumnState.Value  
          Return TemplateTextBox  
        Case ColumnState.Button  
          Return TemplateButton  
      End Select  
      Return TemplateEmpty  
    End Function  
  End Class  
  
  Public Class VisibilityConverter  
    Implements IValueConverter  
  
    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert  
      If targetType Is GetType(Brush) Then  
        Return If(CType(value, Integer) < 100, Brushes.Yellow, Brushes.Transparent)  
      End If  
      Return Nothing  
    End Function  
  
    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack  
      Throw New NotImplementedException()  
    End Function  
  End Class  
  
  Public Class Data  
    Public Property ID As Integer  
    Public Property Value As String  
    Public Property Index As Decimal  
    Public Property Trail1 As New ColumnData  
    Public Property Trail2 As New ColumnData  
    Public Property Trail3 As New ColumnData  
  End Class  
  
  Public Class ColumnData  
    Public Property ID As Integer  
    Public Property ColumnValue As Integer = 0  
    Public Property State As ColumnState = ColumnState.Empty  
  End Class  
  
  Public Enum ColumnState  
    Value = 1  
    Empty = 0  
    Button = 2  
  End Enum  
  
End Namespace  

9952-13-06-2020-08-26-06.gif

Was this answer helpful?


1 additional answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,351 Reputation points
    2020-06-13T05:03:47.84+00:00

    Hi, possible solution is the visibility of different controls like in following demo:

    XAML:

    <Window x:Class="Window026"  
            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:WpfApp1.WpfApp026"  
            mc:Ignorable="d"  
            Title="Demo ArunMenon-4250 200612" Height="450" Width="800">  
      <Window.Resources>  
        <local:ViewModel x:Key="vm"/>  
        <local:VisibilityConverter x:Key="conv"/>  
        <Style TargetType="{x:Type TextBlock}">  
          <Setter Property="Background" Value="Red"/>  
        </Style>  
      </Window.Resources>  
      <Grid DataContext="{StaticResource vm}">  
        <Grid.RowDefinitions>  
          <RowDefinition/>  
          <RowDefinition Height="Auto"/>  
        </Grid.RowDefinitions>  
        <DataGrid ItemsSource="{Binding View}"   
                  AutoGenerateColumns="False"   
                  IsReadOnly="True">  
          <DataGrid.Columns>  
            <DataGridTextColumn Header="Value" Binding="{Binding Value}">  
              <DataGridTextColumn.ElementStyle>  
                <Style TargetType="{x:Type TextBlock}">  
                  <Setter Property="VerticalAlignment" Value="Center"/>  
                  <Setter Property="Margin" Value="5"/>  
                </Style>  
              </DataGridTextColumn.ElementStyle>  
            </DataGridTextColumn>  
            <DataGridTextColumn Header="Index" Binding="{Binding Index}">  
              <DataGridTextColumn.ElementStyle>  
                <Style TargetType="{x:Type TextBlock}">  
                  <Setter Property="VerticalAlignment" Value="Center"/>  
                  <Setter Property="HorizontalAlignment" Value="Right"/>  
                  <Setter Property="Margin" Value="5"/>  
                </Style>  
              </DataGridTextColumn.ElementStyle>  
            </DataGridTextColumn>  
            <DataGridTemplateColumn Header="Trail 1">  
              <DataGridTemplateColumn.CellTemplate>  
                <DataTemplate>  
                  <Grid>  
                    <Grid Background="{Binding Trail1.ColumnValue, Converter={StaticResource conv}}"  
                          Visibility="{Binding Trail1.State, Converter={StaticResource conv}, ConverterParameter=1}">  
                      <TextBlock Text="{Binding Trail1.ColumnValue}"   
                                 Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"/>  
                    </Grid>  
                    <Button Content="Add"   
                            Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"  
                            Command="{Binding Cmd, Source={StaticResource vm}}"   
                            CommandParameter="{Binding}"  
                            Visibility="{Binding Trail1.State, Converter={StaticResource conv}, ConverterParameter=2}"/>  
                  </Grid>  
                </DataTemplate>  
              </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
            <DataGridTemplateColumn Header="Trail 2">  
              <DataGridTemplateColumn.CellTemplate>  
                <DataTemplate>  
                  <Grid>  
                    <Grid Background="{Binding Trail2.ColumnValue, Converter={StaticResource conv}}"  
                          Visibility="{Binding Trail2.State, Converter={StaticResource conv}, ConverterParameter=1}">  
                      <TextBlock Text="{Binding Trail2.ColumnValue}"   
                                 Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"/>  
                      </Grid>  
                    <Button Content="Add"   
                            Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"  
                            Command="{Binding Cmd, Source={StaticResource vm}}"   
                            CommandParameter="{Binding}"  
                            Visibility="{Binding Trail2.State, Converter={StaticResource conv}, ConverterParameter=2}"/>  
                  </Grid>  
                </DataTemplate>  
              </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
            <DataGridTemplateColumn Header="Trail 3">  
              <DataGridTemplateColumn.CellTemplate>  
                <DataTemplate>  
                  <Grid>  
                    <Grid Background="{Binding Trail3.ColumnValue, Converter={StaticResource conv}}"  
                          Visibility="{Binding Trail3.State, Converter={StaticResource conv}, ConverterParameter=1}">  
                      <TextBlock Text="{Binding Trail3.ColumnValue}"   
                                 Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"/>  
                      </Grid>  
                    <Button Content="Add"   
                            Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center"  
                            Command="{Binding Cmd, Source={StaticResource vm}}"   
                            CommandParameter="{Binding}"  
                            Visibility="{Binding Trail3.State, Converter={StaticResource conv}, ConverterParameter=2}"/>  
                  </Grid>  
                </DataTemplate>  
              </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
          </DataGrid.Columns>  
        </DataGrid>  
        <Label Grid.Row="1" Content="{Binding Info}"/>  
      </Grid>  
    </Window>  
    

    -----------------------------------------

    Imports System.Collections.ObjectModel  
    Imports System.ComponentModel  
    Imports System.Globalization  
    Imports System.Runtime.CompilerServices  
      
    Namespace WpfApp026  
      
      Public Class ViewModel  
        Implements INotifyPropertyChanged  
      
        Private cvs As New CollectionViewSource  
        Public ReadOnly Property View As ICollectionView  
          Get  
            If cvs.Source Is Nothing Then GetData()  
            Return cvs.View  
          End Get  
        End Property  
      
        Public Property Info As String  
      
        Public ReadOnly Property Cmd As ICommand  
          Get  
            Return New RelayCommand(AddressOf CmdExec)  
          End Get  
        End Property  
      
        Private Sub CmdExec(obj As Object)  
          Dim d = TryCast(obj, Data)  
          If d Is Nothing Then Exit Sub  
          Info = $"Selected Row: {d.Value}"  
          OnPropertyChanged(NameOf(Info))  
        End Sub  
      
        Private Sub GetData()  
          Dim rnd As New Random  
          Dim col As New ObservableCollection(Of Data)  
          For i = 1 To 10  
            Dim d = New Data With {.Value = $"Value {i}", .Index = CDec(0.1 * rnd.Next(1, 100))}  
            If rnd.NextDouble < 0.5 Then  
              d.Trail1.State = ColumnState.Button  
            Else  
              d.Trail1.ColumnValue = rnd.Next(50, 200)  
              d.Trail1.State = ColumnState.Value  
              If rnd.NextDouble < 0.5 Then  
                d.Trail2.State = ColumnState.Button  
              Else  
                d.Trail2.ColumnValue = rnd.Next(50, 200)  
                d.Trail2.State = ColumnState.Value  
                d.Trail3.State = ColumnState.Button  
              End If  
            End If  
            col.Add(d)  
          Next  
          cvs.Source = col  
        End Sub  
      
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged  
        Friend Sub OnPropertyChanged(<CallerMemberName> Optional propName As String = "")  
          RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))  
        End Sub  
      
      End Class  
      
      Public Class VisibilityConverter  
        Implements IValueConverter  
      
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert  
          If targetType Is GetType(Visibility) Then  
            Return If(CType(value, Integer).ToString = parameter.ToString, Visibility.Visible, Visibility.Collapsed)  
          End If  
          If targetType Is GetType(Brush) Then  
            Return If(CType(value, Integer) < 100, Brushes.Yellow, Brushes.Transparent)  
          End If  
          Return Nothing  
        End Function  
      
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack  
          Throw New NotImplementedException()  
        End Function  
      End Class  
      
      Public Class Data  
        Public Property Value As String  
        Public Property Index As Decimal  
        Public Property Trail1 As New CulumnData  
        Public Property Trail2 As New CulumnData  
        Public Property Trail3 As New CulumnData  
      End Class  
      
      Public Class CulumnData  
        Public Property ColumnValue As Integer = 0  
        Public Property State As ColumnState = ColumnState.Empty  
      End Class  
      
      Public Enum ColumnState  
        Value = 1  
        Empty = 0  
        Button = 2  
      End Enum  
      
    End Namespace  
    

    9900-13-06-2020-07-02-41.gif

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.