Search code examples
c#wpfmvvmdappercaliburn.micro

WPF, Caliburn Micro and Dapper - Datagrid Checkbox binding


I am using Caliburn Micro and Dapper in a WPF Project where I create a DataGrid which I populate with data from a SQL Server Database Table. Please consider the following code snippets:

ChangesModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PTSRDesktopUI.Models
{
    //public class for all changes attributes
    public class ChangesModel
    {
        public int ID { get; set; }
        public string Facility { get; set; }
        public string Controller { get; set; }
        public string ParameterName { get; set; }
        public string OldValue { get; set; }
        public string NewValue { get; set; }
        public DateTime ChangeDate { get; set; }
        public bool Validated { get; set; }
        public DateTime ValidationDate { get; set; }
    }
}

OverviewView.xaml

<!--Datagrid Table-->
<DataGrid Grid.Row="1" x:Name="Changes" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Stretch">
    <DataGrid.Columns>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="Facility" Binding="{Binding Path=Facility}"/>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="Controller" Binding="{Binding Path=Controller}"/>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="Parameter" Binding="{Binding Path=ParameterName}"/>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="Old Value" Binding="{Binding Path=OldValue}"/>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="New Value" Binding="{Binding Path=NewValue}"/>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="Changed Date" Binding="{Binding Path=ChangeDate, 
                            StringFormat='{}{0:dd.MM HH:mm}'}"/>
        <DataGridTemplateColumn CellStyle="{StaticResource DataGridCellCentered}" 
                                Header="Validated" IsReadOnly="True">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate >
                    <CheckBox IsChecked="{Binding Path=Validated}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn CellStyle="{StaticResource DataGridCellCentered}" IsReadOnly="True" 
                            Header="Validation Date" Binding="{Binding Path=ValidationDate, 
                            StringFormat='{}{0:dd.MM HH:mm}'}"/>
        <DataGridTemplateColumn CellStyle="{StaticResource DataGridCellCentered}" Header="Validate">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Button x:Name="Validate_Btn" cal:Message.Attach="Validate">Validate</Button>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

OverviewViewModel.cs

using Caliburn.Micro;
using PTSRDesktopUI.Helpers;
using PTSRDesktopUI.Models;

namespace PTSRDesktopUI.ViewModels
{
    public class OverviewViewModel : Screen
    {

        //Create new Bindable Collection variable of type ChangesModel
        public BindableCollection<ChangesModel> Changes { get; set; }


        public OverviewViewModel()
        {

            //Create connection to DataAccess class
            DataAccess db = new DataAccess();

            //get the changes from DataAccess function and store them as a bindable collection in Changes
            Changes = new BindableCollection<ChangesModel>(db.GetChanges());

        }

        //Validate_Btn click event
        public void Validate()
        {
           //Some Code        
        }

    }
}

DataAccess.cs

//Function to get all changes from database using stored procedures
public List<ChangesModel> GetChanges()
{
    using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(DBHelper.CnnVal("ptsrDB")))
    {
        var output = connection.Query<ChangesModel>("dbo.getChanges").ToList();
        return output;
    }
}

I use a stored procedure called getChangesto get the data from the SQL Server. The data is displayed and it all works fine. What I want to do now is this: Firstly I want the Validate_Btn to only be visible on rows where the CheckBox is unchecked. Secondly, if the user clicks the Validate_Btn, I want to change the CheckBox to checked, make the button invisible and trigger a new function with a stored procedure from the DataAccess class to update the boolean value for Validated in the database table. Anyone have any ideas how I could do this?


Solution

  • To solve the first part of displaying and hiding the Button based on whether the CheckBox is checked, you should first implement the INotifyPropertyChanged interface in your ChangesModel class and raise the PropertyChanged event when the Validated property is set:

    public class ChangesModel : INotifyPropertyChanged
    {
        public int ID { get; set; }
        public string Facility { get; set; }
        public string Controller { get; set; }
        public string ParameterName { get; set; }
        public string OldValue { get; set; }
        public string NewValue { get; set; }
        public DateTime ChangeDate { get; set; }
    
        private bool _validated;
        public bool Validated
        {
            get { return _validated; }
            set { _validated = value; NotifyPropertyChanged(); }
        }
    
        public DateTime ValidationDate { get; set; }
    
        public event PropertyChangedEventHandler PropertyChanged; 
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
    }
    

    You could then bind the Button's Visibility property to the Validated source property and use a converter to convert between the bool value and a Visibility enumeration value:

    <DataGridTemplateColumn Header="Validate">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <DataTemplate.Resources>
                    <BooleanToVisibilityConverter x:Key="converter" />
                </DataTemplate.Resources>
                <Button x:Name="Validate_Btn" cal:Message.Attach="Validate"
                        Visibility="{Binding Validated, Converter={StaticResource converter}}">Validate</Button>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    

    You may also want to set the UpdateSourcePropertyTrigger on the CheckBox binding for the source property to be set immediately:

    <CheckBox IsChecked="{Binding Path=Validated, UpdateSourceTrigger=PropertyChanged}" />
    

    For your Validate() to get called when you click the button, you could bind the Bind.Model attached property to the view model:

    <Button x:Name="Validate"
            cal:Bind.Model="{Binding DataContext, 
                RelativeSource={RelativeSource AncestorType=DataGrid}}">Validate</Button>
    

    Combining the bindings requires to specify a source of the Visibility binding:

    <Button x:Name="Validate"
        Visibility="{Binding DataContext.Validated, 
           Converter={StaticResource converter}, RelativeSource={RelativeSource AncestorType=DataGridCell}}"
        cal:Bind.Model="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}">Validate</Button>