Donate

WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And VB.NET

Good day guys!

Here's the VB.NET version of this article WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And C#.NET which is a CRUD (Create,Update and Delete) application using the DataGrid control, ADO.NET Entity Framework 6.x and Model–View–Viewmodel(MVVM) architectural pattern. The project structure and the logic were derived from that article except that this application incorporates Visual Basic.NET/VB.NET as the programming language.

I. Project Setup

1. Create a table in your database called Students using the script from this post WPF CRUD With DataGrid, Entity Framework And C#.NET.
2. Create a WPF Project and add four folders called DataAccess, Model, View and ViewModel. The project structure may resemble with the screenshot provided below.
WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And VB.NET

II. Coding The Model and Repository Class

1. Inside the Model folder, add an ADO.NET Entity Data Model object that connects to the Students table in your database. On my part, I named it StudentModel.
2. For the connectionstring name, I called it as StudentEntities.
3. Next is to add a StudentRecord class that has properties corresponding to the table columns and an ObservableCollection property used as itemsource for the DataGrid. This class inherits the ViewModelBase class added in the ViewModel folder which will mentioned on Coding The ViewModel Classes section so that there's a mechanism in handling property changes and notifications from the controls through data binding.
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized

Public Class StudentRecord
    Inherits ViewModelBase

    Private _id As Integer

    Public Property Id As Integer
        Get
            Return _id
        End Get
        Set(ByVal value As Integer)
            _id = value
            OnPropertyChanged("Id")
        End Set
    End Property

    Private _name As String

    Public Property Name As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
            OnPropertyChanged("Name")
        End Set
    End Property

    Private _age As Integer

    Public Property Age As Integer
        Get
            Return _age
        End Get
        Set(ByVal value As Integer)
            _age = value
            OnPropertyChanged("Age")
        End Set
    End Property

    Private _address As String

    Public Property Address As String
        Get
            Return _address
        End Get
        Set(ByVal value As String)
            _address = value
            OnPropertyChanged("Address")
        End Set
    End Property

    Private _contact As String

    Public Property Contact As String
        Get
            Return _contact
        End Get
        Set(ByVal value As String)
            _contact = value
            OnPropertyChanged("Contact")
        End Set
    End Property

    Private _studentRecords As ObservableCollection(Of StudentRecord)

    Public Property StudentRecords As ObservableCollection(Of StudentRecord)
        Get
            Return _studentRecords
        End Get
        Set(ByVal value As ObservableCollection(Of StudentRecord))
            _studentRecords = value
            OnPropertyChanged("StudentRecords")
        End Set
    End Property

    Private Sub StudentModels_CollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
        OnPropertyChanged("StudentRecords")
    End Sub
End Class
4. Add a repository class within the DataAccess Folder that performs the CRUD operations towards the Students table in the database.
Public Class StudentRepository
    Private studentContext As StudentEntities = Nothing

    Public Sub New()
        studentContext = New StudentEntities()
    End Sub

    Public Function GetStudent(ByVal id As Integer) As Student
        Return studentContext.Students.Find(id)
    End Function

    Public Function GetAll() As List(Of Student)
        Return studentContext.Students.ToList()
    End Function

    Public Sub AddStudent(ByVal student As Student)
        If student IsNot Nothing Then
            studentContext.Students.Add(student)
            studentContext.SaveChanges()
        End If
    End Sub

    Public Sub UpdateStudent(ByVal student As Student)
        Dim studentFind = Me.GetStudent(student.ID)

        If studentFind IsNot Nothing Then
            studentFind.Name = student.Name
            studentFind.Contact = student.Contact
            studentFind.Age = student.Age
            studentFind.Address = student.Address
            studentContext.SaveChanges()
        End If
    End Sub

    Public Sub RemoveStudent(ByVal id As Integer)
        Dim studObj = studentContext.Students.Find(id)

        If studObj IsNot Nothing Then
            studentContext.Students.Remove(studObj)
            studentContext.SaveChanges()
        End If
    End Sub
End Class

III. Coding The ViewModel Classes

1. Add a ViewModelBase class that implements the INofifyPropertyChanged interface. This interface basically informs binding clients that a property value has been updated. This class is inherited by the StudentRecord model of which it's properties are used in data binding and needed some sort of notification when a property's value has been changed.
Imports System.ComponentModel

Public Class ViewModelBase : Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler _
         Implements INotifyPropertyChanged.PropertyChanged

    Protected Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class
2. Next is to add a RelayCommand class that implements the ICommand interface. Commands are used for handling events in WPF with regards to the MVVM Architectural Pattern. The sole purpose of a command is to relay or distribute its functionality to other objects by invoking delegates. The default return value for a CanExecute method is true.
Imports System.Windows.Input

Public Class RelayCommand : Implements ICommand

    Private ReadOnly _execute As Action(Of Object)
    Private ReadOnly _canExecute As Predicate(Of Object)

    Public Sub New(ByVal execute As Action(Of Object))
    End Sub

    Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
        If execute Is Nothing Then Throw New ArgumentNullException("execute")
        _execute = execute
        _canExecute = canExecute
    End Sub

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
        Return (_canExecute Is Nothing) OrElse _canExecute(parameter)
    End Function

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

        AddHandler(ByVal value As EventHandler)
            AddHandler CommandManager.RequerySuggested, value
        End AddHandler

        RemoveHandler(ByVal value As EventHandler)
            AddHandler CommandManager.RequerySuggested, value
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            CommandManager.InvalidateRequerySuggested()
        End RaiseEvent
    End Event

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
        _execute(parameter)
    End Sub

End Class
3. Last is to create a ViewModel class that performs the command binding of the buttons which then calls specific methods that handles the CRUD operations and updating of property values. An example is the DeleteCommand bound to the Delete button inside the DataGrid control. When the delete button is clicked, the delete command then executes the DeleteStudent() method and deletes the information from the database. That method fetches all records from the database and populates the Observable object which is the ItemSource of the DataGrid.
Imports System.Collections.ObjectModel

Public Class StudentViewModel
    Private _saveCommand As ICommand
    Private _resetCommand As ICommand
    Private _editCommand As ICommand
    Private _deleteCommand As ICommand
    Private _repository As StudentRepository
    Private _studentEntity As Student = Nothing
    Public Property StudentRecord As StudentRecord
    Public Property StudentEntities As StudentEntities

    Public ReadOnly Property ResetCommand As ICommand
        Get
            If _resetCommand Is Nothing Then
                _resetCommand = New RelayCommand(AddressOf ResetData, Nothing)
            End If
            Return _resetCommand
        End Get
    End Property

    Public ReadOnly Property SaveCommand As ICommand
        Get
            If _saveCommand Is Nothing Then
                _saveCommand = New RelayCommand(AddressOf SaveData, Nothing)
            End If
            Return _saveCommand
        End Get
    End Property

    Public ReadOnly Property EditCommand As ICommand
        Get
            If _editCommand Is Nothing Then
                _editCommand = New RelayCommand(AddressOf EditData, Nothing)
            End If
            Return _editCommand
        End Get
    End Property

    Public ReadOnly Property DeleteCommand As ICommand
        Get
            If _deleteCommand Is Nothing Then
                _deleteCommand = New RelayCommand(AddressOf DeleteStudent, Nothing)
            End If
            Return _deleteCommand
        End Get
    End Property

    Public Sub New()
        _studentEntity = New Student()
        _repository = New StudentRepository()
        StudentRecord = New StudentRecord()
        GetAll()
    End Sub

    Public Sub ResetData()
        StudentRecord.Name = String.Empty
        StudentRecord.Id = 0
        StudentRecord.Address = String.Empty
        StudentRecord.Contact = String.Empty
        StudentRecord.Age = 0
    End Sub

    Public Sub DeleteStudent(ByVal id As Integer)
        If MessageBox.Show("Confirm delete of this record?", "Student", MessageBoxButton.YesNo) = MessageBoxResult.Yes Then

            Try
                _repository.RemoveStudent(id)
                MessageBox.Show("Record successfully deleted.")
            Catch ex As Exception
                MessageBox.Show("Error occured while saving. " & ex.InnerException.Message)
            Finally
                GetAll()
            End Try
        End If
    End Sub

    Public Sub SaveData()
        If StudentRecord IsNot Nothing Then
            _studentEntity.Name = StudentRecord.Name
            _studentEntity.Age = StudentRecord.Age
            _studentEntity.Address = StudentRecord.Address
            _studentEntity.Contact = StudentRecord.Contact

            Try

                If StudentRecord.Id <= 0 Then
                    _repository.AddStudent(_studentEntity)
                    MessageBox.Show("New record successfully saved.")
                Else
                    _studentEntity.ID = StudentRecord.Id
                    _repository.UpdateStudent(_studentEntity)
                    MessageBox.Show("Record successfully updated.")
                End If

            Catch ex As Exception
                MessageBox.Show("Error occured while saving. " & ex.InnerException.Message)
            Finally
                GetAll()
                ResetData()
            End Try
        End If
    End Sub

    Public Sub EditData(ByVal id As Integer)
        Dim model = _repository.GetStudent(id)
        StudentRecord.Id = model.ID
        StudentRecord.Name = model.Name
        StudentRecord.Age = CInt(model.Age)
        StudentRecord.Address = model.Address
        StudentRecord.Contact = model.Contact
    End Sub

    Public Sub GetAll()
        StudentRecord.StudentRecords = New ObservableCollection(Of StudentRecord)()
        Dim records As New List(Of Student)
        records = _repository.GetAll()

        For Each record As Student In records
            Dim item As New StudentRecord

            item.ID = record.ID
            item.Name = record.Name
            item.Address = record.Address
            item.Age = Convert.ToInt32(record.Age)
            item.Contact = record.Contact

            StudentRecord.StudentRecords.Add(item)
        Next

    End Sub
End Class

IV. Databinding and View

1. Move the MainWindow page into the View folder of the project. In the constructor method , set the class DataContext with the StudentViewModel class. You may opt to set the DataContext through XAML.
Class MainWindow
    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.DataContext = New StudentViewModel()
    End Sub
End Class
2. Next is to add several controls like textboxes for accepting input, buttons to trigger events and the DataGrid control to show the entire updated information from the database. These controls have been glued to the ViewModel class through the Binding property. The input controls are grouped in a GroupBox panel, while the Save and Reset buttons are inside the StackPanel container. The DataGrid is also inside the StackPanel container and each of these containers are arranged horizontally inside a StackPanel parent container.
<Window x:Class="MainWindow"
        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:MVVMDemoVB"
        mc:Ignorable="d"
        Title="Basic Create Update Delete With MVVM In VB.NET"
        Height="500" Width="600">
    <StackPanel Orientation="Vertical">
        <GroupBox Header="Student Form" Margin="10">
            <Grid Height="150">
                <Grid.RowDefinitions>
                    <RowDefinition Height="1*"/>
                    <RowDefinition Height="1*"/>
                    <RowDefinition Height="1*"/>
                    <RowDefinition Height="1*"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Content="Name" HorizontalAlignment="Left" 
                       VerticalContentAlignment="Center" Grid.Column="0" Grid.Row="0"/>
                <TextBox Grid.Row="0" Grid.Column="1" x:Name="TextBoxName" Height="27" 
                       Text="{Binding Path=StudentRecord.Name, Mode=TwoWay}"  Margin="5"  Width="300" HorizontalAlignment="Left"/>
                <Label Content="Age" HorizontalAlignment="Left" VerticalContentAlignment="Center" 
                       Grid.Row="1" Grid.Column="0"/>
                <TextBox Grid.Row="1" Grid.Column="1" x:Name="TextBoxAge" Height="27" 
                       Text="{Binding Path=StudentRecord.Age, Mode=TwoWay}" Margin="5" Width="70" HorizontalAlignment="Left"/>
                <TextBlock Grid.Row="1" Grid.Column="1" x:Name="TextBlockId" 
                       Visibility="Hidden" Text="{Binding Path=StudentRecord.Id, Mode=TwoWay}"/>
                <Label Content="Address" HorizontalAlignment="Left" VerticalContentAlignment="Center" 
                       Grid.Row="2" Grid.Column="0" />
                <TextBox Grid.Row="2" Grid.Column="1" x:Name="TextBoxAddress" Height="27" 
                       Text="{Binding Path=StudentRecord.Address, Mode=TwoWay}" Margin="5" Width="300" HorizontalAlignment="Left"/>
                <Label Content="Contact" HorizontalAlignment="Left" VerticalContentAlignment="Center" 
                       Grid.Row="3" Grid.Column="0" />
                <TextBox Grid.Row="3" Grid.Column="1" x:Name="TextBoxContact" Height="27"
                       Text="{Binding Path=StudentRecord.Contact, Mode=TwoWay}" Margin="5" Width="300" HorizontalAlignment="Left"/>
            </Grid>
        </GroupBox>
        <StackPanel Height="40" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="ButtonSave" Content="Save" Height="30" Width="80"
                    Command="{Binding SaveCommand}"/>
            <Button x:Name="ButtonCancel" Content="Cancel" Height="30" Width="80" 
                    Command="{Binding ResetCommand}" Margin="5,0,10,0"/>
        </StackPanel>
        <StackPanel Height="210">
            <DataGrid x:Name="DataGridStudents" AutoGenerateColumns="False"
                      ItemsSource="{Binding StudentRecord.StudentRecords}" CanUserAddRows="False" Height="200" Margin="10">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding Path=Id}" Visibility="Hidden"/>
                    <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="100"  IsReadOnly="True"/>
                    <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}" Width="50"  IsReadOnly="True"/>
                    <DataGridTextColumn Header="Address" Binding="{Binding Path=Address}" Width="180" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Contact" Binding="{Binding Path=Contact}" Width="125" IsReadOnly="True"/>
                    <DataGridTemplateColumn Width="50">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Select" x:Name="ButtonEdit" CommandParameter="{Binding Path=Id}"
                                        Command="{Binding Path=DataContext.EditCommand,RelativeSource={RelativeSource FindAncestor,
                                                AncestorType=Window}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Width="50">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Delete" x:Name="ButtonDelete" CommandParameter="{Binding Path=Id}"
                                        Command="{Binding Path=DataContext.DeleteCommand, RelativeSource={RelativeSource FindAncestor,
                                                AncestorType=Window}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
    </StackPanel>
</Window>
Since most of the examples out there are written in C#.NET, I hope this article will serve as reference for the VB.NET/Visual Basic.NET developers out there.
Output
WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And VB.NET
WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And VB.NET
Source Code
Finally, I have the spare time to create a code sample for this blog post. You may now download the project written in Visual Studio 2022 at my Github Page.


Cheers!

Comments

Donate

Popular Posts From This Blog

WPF CRUD Application Using DataGrid, MVVM Pattern, Entity Framework, And C#.NET

How To Insert Or Add Emojis In Microsoft Teams Status Message

TypeScript Error Or Bug: The term 'tsc' is not recognized as the name of a cmdlet, function, script file, or operable program.