Bug in tutorial

simsenVejle

Active member
Joined
Feb 14, 2021
Messages
44
Programming Experience
Beginner
Hi,
I am in the process of going through a youtube series with VPF and MVVM and here I have discovered a bug that I can not figure out. to solve

I open a page where there are 3 labels 3 textboxes 4 buttons. Id, Name and Age and then a grid showing an Employee (= my own name) which is hard coded.

One button is Add. When I add the first time, it goes well. Now there are 2 employes in the gridview Anja and then Bob (which I just added).

Then the error comes when I then add one or more employee - then it changes Bob (and the next inserted employee) Lise with Lises name Id and Age. So the first row (can't remember what such a row is called) is displayed correctly (with the name Anja), while the subsequent rows get the last row I have inserted data.

I suspect it is in EmployeeViewModel that there is a problem and here specifically when I run between CurrentEmployee and objEmployee but I am unfortunately not good enough to figure out what to change and where so I hope someone of you can help me.

The zip file is to big, so I cann't upload. Please let me know an email adress to send the zip files. Below I will copy the files.

If you are missing something more than the code shown below, please let me know.

Best regards
Simsen :)

Models.Employees

Models.Employees:
Models.Employees
namespace MvvmDemo.Models
{
    public class Employee : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private int id;
        public int Id
        {
            get => id;

            set { id = value; OnPropertyChanged("Id"); }
        }


        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; OnPropertyChanged("Name"); }
        }


        private int age;
        public int Age
        {
            get { return age; }
            set { age = value; OnPropertyChanged("Age"); }
        }



    }
}
------------------
Models.EmployeeService

Models.EmployeeService:
Models.EmployeeService (her kommer et senere spørgsmål, altså hvis jeg kan få det her til at virke)

namespace MvvmDemo.Models
{
    public class EmployeeService
    {
        private static List<Employee> ObjEmployeesList;

        public EmployeeService()
        {
            ObjEmployeesList = new List<Employee>()
            {
                new Employee { Id=101, Name="Anja", Age=55 }
            };
        }

        public List<Employee> GetAll()
        {
            return ObjEmployeesList;
        }

        public bool Add(Employee objNewEmployee)
        {
            //Age must be between 21 and 58
            if (objNewEmployee.Age < 21 || objNewEmployee.Age > 58)
                throw new ArgumentException("Invalid Age limit for Employee");

            ObjEmployeesList.Add(objNewEmployee);
            return true;
        }

        public bool Update (Employee objEmployeeToUpdate)
        {
            bool IsUpdated = false;

            for (int index = 0; index <ObjEmployeesList.Count; index++)
            {
                if (ObjEmployeesList[index].Id==objEmployeeToUpdate.Id)
                {
                    ObjEmployeesList[index].Name = objEmployeeToUpdate.Name;
                    ObjEmployeesList[index].Age = objEmployeeToUpdate.Age;
                    IsUpdated = true;
                    break;
                }
            }
            return IsUpdated;
        }

        public bool Delete (int id)
        {
            bool IsDeleted = false;

            for (int index = 0; index < ObjEmployeesList.Count; index++)
            {
                if (ObjEmployeesList[index].Id == id)
                {
                    ObjEmployeesList.RemoveAt(index);
                    IsDeleted = true;
                    break;
                }
            }

            return IsDeleted;
        }

        public Employee Search(int id)
        {
            return ObjEmployeesList.FirstOrDefault(e => e.Id == id);
        }
    }
}
----------------------
Commands.RelayCommand

Commands.RelayCommand:
namespace MvvmDemo.Commands
{
    public class RelayCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
        private readonly Action DoWork;
        public RelayCommand(Action work)
        {
            DoWork = work;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            DoWork();
        }
    }
}
-----------------------
Views.EmployeeView

Views.EmployeeView:
Views.EmployeeView
<UserControl x:Class="MvvmDemo.Views.EmployeeView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:local="clr-namespace:MvvmDemo.Views"
            mc:Ignorable="d">
    <Grid Margin="15">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>

        <!--Row 0-->
        <TextBlock Text="Employee Management" Grid.Row="0" Grid.Column="0"
                  Grid.ColumnSpan="2" FontSize="20" FontWeight="Bold"
                  HorizontalAlignment="Center" Margin="5,8" Padding="3"/>

        <!--Row 1-->
        <TextBlock Text="Enter Id" Grid.Row="1" Grid.Column="0" />
        <TextBox x:Name="txtId" Grid.Row="1" Grid.Column="1" Margin="5,8"
                Padding="3" Text="{Binding Path=CurrentEmployee.Id, Mode=TwoWay}" />
        <!--Row 2-->
        <TextBlock Text="Enter Name" Grid.Row="2" Grid.Column="0" />
        <TextBox x:Name="txtName" Grid.Row="2" Grid.Column="1" Margin="5,8" Padding="3"
                  Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay}"/>
        <!--Row 3-->
        <TextBlock Text="Enter Age" Grid.Row="3" Grid.Column="0" />
        <TextBox x:Name="txtAge" Grid.Row="3" Grid.Column="1" Margin="5,8" Padding="3"
                  Text="{Binding Path=CurrentEmployee.Age, Mode=TwoWay}"/>
        <!--Row 4-->
        <StackPanel Orientation="Horizontal" Grid.Row="4" Grid.Column="1">
            <Button x:Name="btnAdd" Content="Add" Margin="5,8" Padding="3"
                    Command="{Binding Path=SaveCommand}"/>
            <Button x:Name="btnSearch" Content="Search" Margin="5,8" Padding="3"
                    Command="{Binding Path=SearchCommand}"/>
            <Button x:Name="btnUpdate" Content="Update" Margin="5,8" Padding="3" />
            <Button x:Name="btnDelete" Content="Delete" Margin="5,8" Padding="3" />
        </StackPanel>
        <!--Row 5-->
        <TextBlock x:Name="txtMessage" Grid.Column="1" Grid.Row="5" Margin="5,8" Padding="3"
                  Text="{Binding Path=Message}"/>
        <!--Row 6-->
        <DataGrid Name="dgEmployees" AutoGenerateColumns="False" Grid.Row="6" Grid.Column="1"
                  Margin="5,8" Padding="3" ItemsSource="{Binding Path=EmployeeList, Mode=TwoWay}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Employee Id" Width="auto" Binding="{Binding Path=Id}" />
                <DataGridTextColumn Header="Employee Name" Width="auto" Binding="{Binding Path=Name}" />
                <DataGridTextColumn Header="Employee Age" Width="auto" Binding="{Binding Path=Age}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>
---------------------
ViewModels.EmployeeViewModel

ViewModels.EmployeeViewModel:
namespace MvvmDemo.ViewModels
{
    public class EmployeeViewModel : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged_Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        readonly EmployeeService ObjEmployeeService;

        public EmployeeViewModel()
        {
            ObjEmployeeService = new EmployeeService();
            LoadData();

            CurrentEmployee = new Employee();
            saveCommand = new RelayCommand(Save);
            searchCommand = new RelayCommand(Search);
        }

        #region DisplayOperation
        private ObservableCollection<Employee> employeeList;
        public ObservableCollection<Employee> EmployeeList
        {
            get { return employeeList; }
            set { employeeList = value; OnPropertyChanged("EmployeeList"); }
        }
        private void LoadData()
        {
            EmployeeList = new ObservableCollection<Employee>(ObjEmployeeService.GetAll());
        }
        #endregion

        private Employee currentEmployee;

        public Employee CurrentEmployee
        {
            get { return currentEmployee; }
            set { currentEmployee = value; OnPropertyChanged("CurrentEmployee"); }
        }

        private readonly RelayCommand saveCommand;

        private string message;

        public string Message
        {
            get { return message; }
            set { message = value; OnPropertyChanged("Message"); }
        }

        #region SaveOperation
        public RelayCommand SaveCommand
        {
            get { return saveCommand; }
        }
        public void Save()
        {
            try
            {
                var isSaved = ObjEmployeeService.Add(CurrentEmployee);
                LoadData();
                if (isSaved)
                    Message = "Employee saved";
                else
                    Message = "Save operation failed";
            }
            catch (Exception ex)
            {
                Message = ex.Message;
            }
        }
        #endregion

        #region Search Operation
        private readonly RelayCommand searchCommand;

        public RelayCommand SearchCommand
        {
            get { return searchCommand; }
        }

        public void Search()
        {
            try
            {
                var objEmployee = ObjEmployeeService.Search(CurrentEmployee.Id);
                if (objEmployee != null)
                {
                    CurrentEmployee.Name = objEmployee.Name;
                    CurrentEmployee.Age = objEmployee.Age;
                }
                else
                {
                    Message = "Employee Not found";
                }
            }
            catch (Exception ex)
            {
                Message = ex.Message;
            }
        }
        #endregion
    }
}
 
Solution
Recall that in C#, most custom types are reference types, not value types.

On line 65 of your view model, you keep adding the same instance of your Current employee object. So your ObjList's second and third elements are are referencing to the same object: CurrentEmployee.

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,993
Location
Chesapeake, VA
Programming Experience
10+
Recall that in C#, most custom types are reference types, not value types.

On line 65 of your view model, you keep adding the same instance of your Current employee object. So your ObjList's second and third elements are are referencing to the same object: CurrentEmployee.
 
Solution

jmcilhinney

C# Forum Moderator
Staff member
Joined
Apr 23, 2011
Messages
4,181
Location
Sydney, Australia
Programming Experience
10+
Recall that in C#, most custom types are reference types, not value types.

On line 65 of your view model, you keep adding the same instance of your Current employee object. So your ObjList's second and third elements are are referencing to the same object: CurrentEmployee.
As a real-life example of this, let's consider adding people's names to a list. Let's say that you are creating a list of people. I present myself to you wearing a read shirt and you add my name. I then present myself to you wearing a blue shirt and you add my name. Finally, I present myself to you wearing a green shirt and you add my name. If you were to then call out the names of the people on the list and I present myself to you each time you call my name, would you be surprised that I was wearing a green shirt each time? The short I was wearing at the time you added me to the list is irrelevant. What you see when you view the people in the list is the last shirt I put on.

The same goes for you single instance of your class. If you keep adding the same item every time, when you look at the list you are just going to see whatever the last property values of that one object were. If you want multiple distinct objects in the list, you need to create multiple distinct objects, which means creating a new object each time you want to add an object to the list.
 
Top Bottom