0

I have a WPF form that I want to use to manipulate a database table. To get the data out of the database I use Entity Framework Core. I try to follow the MVVM pattern.

So I have a Model called Employee with a number of string properties like Firstname and so on. The form has a ListView that shows an ObservableCollection of Employee. Further I have a number of TextBoxes to display and manipulate the properties of single Employee objects. The selectedMemberPath of the ListView is bound to a property SelectedEmployee. The setter of SelectedEmployee than copies it's own properties to EditedEmployee. The TextBoxes are bound to EditedEmployee. The idea is, that I don't want to directly edit the ObservableCollection. This should only happen if a save button is clicked.

This is my view model.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using XdcCore.Models;
using XdcCore.ViewModels.Commands;

namespace XdcCore.ViewModels
{
    class EmployeeEditorVM : List<Employee>, INotifyPropertyChanged
    {
        public EmployeeSaveCommand EmployeeSaveCommand { get; set; }

        public EmployeeEditorVM()
        {
            EmployeeSearch();

            EmployeeSaveCommand = new EmployeeSaveCommand(this);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private string employeeSearchText;

        public string EmployeeSearchText
        {
            get { return employeeSearchText; }
            set
            {
                employeeSearchText = value;
                OnPropertyChanged("EmployeeSearch");
                EmployeeSearch();
            }
        }

        private Employee selectedEmployee;

        public Employee SelectedEmployee
        {
            get { return selectedEmployee; }
            set
            {
                selectedEmployee = value;
                OnPropertyChanged("SelectedEmployee");
                EditedEmployee = SelectedEmployee;
            }
        }

        private Employee editedEmployee;

        public Employee EditedEmployee
        {
            get { return editedEmployee; }
            set
            {
                editedEmployee = value;
                OnPropertyChanged("EditedEmployee");
            }
        }

        private ObservableCollection<Employee> employees;

        public ObservableCollection<Employee> Employees
        {
            get { return employees; }
            set
            {
                employees = value;
                OnPropertyChanged("Employees");
            }
        }

        public void EmployeeSearch()
        {
            using (var context = new XdcContext())
            {
                if (employeeSearchText != null)
                {
                    var _employees = context.Employee
                        .Where(x => x.Firstname.Contains(employeeSearchText));
                    Employees = new ObservableCollection<Employee>(_employees);
                }
                else
                {
                    var _employees = context.Employee.Select(x => x);
                    Employees = new ObservableCollection<Employee>(_employees);
                }
            }
        }

        public void EmployeeSave()
        {
            using (var context = new XdcContext())
            {
                var entity = context.Employee.First(x => x.IdEmployee == SelectedEmployee.IdEmployee);
                {
                    entity.Firstname = SelectedEmployee.Firstname;
                    entity.Lastname = SelectedEmployee.Lastname;
                    entity.Initials = SelectedEmployee.Initials;

                    context.SaveChanges();
                }
                EmployeeSearch();
            }
        }
    }
}

this is my View:

<Window
    x:Class="XdcCore.Views.Editors.EmployeeEditor"
    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:local="clr-namespace:XdcCore.Views.Editors"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:XdcCore.ViewModels"
    Title="Employee"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Window.Resources>
        <vm:EmployeeEditorVM x:Key="vm" />
    </Window.Resources>
    <Grid DataContext="{StaticResource vm}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="300" />
                <ColumnDefinition Width="115*" />
                <ColumnDefinition Width="277*" />
            </Grid.ColumnDefinitions>

            <!--  left column  -->
            <Border BorderBrush="DarkGray" BorderThickness="1">
                <Grid Grid.Column="0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="aUTO" />
                    </Grid.RowDefinitions>
                    <Label
                        Grid.Row="0"
                        Margin="3,3,3,3"
                        Content="Filter:" />
                    <TextBox
                        x:Name="TextBoxSearch"
                        Grid.Row="1"
                        Height="25"
                        Margin="3,0,3,3"
                        HorizontalContentAlignment="Left"
                        VerticalContentAlignment="Center"
                        Text="{Binding EmployeeSearchText, Mode=TwoWay}" />
                    <ListView
                        x:Name="ListViewOverview"
                        Grid.Row="2"
                        Margin="3,0,3,0"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        ItemsSource="{Binding Employees, Mode=OneWay}"
                        SelectedItem="{Binding SelectedEmployee, Mode=OneWayToSource}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding Initials}" />
                                    <TextBlock Text=" : " />
                                    <TextBlock Text="{Binding Firstname}" />
                                    <TextBlock Text=" " />
                                    <TextBlock Text="{Binding Lastname}" />
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                    <Grid Grid.Row="3">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>

                        <Button
                            x:Name="ButtonDelete"
                            Grid.Column="0"
                            Height="25"
                            Margin="3,3,3,3"
                            Content="Delete"
                            IsEnabled="False" />
                        <Button
                            x:Name="ButtonNew"
                            Grid.Column="1"
                            Height="25"
                            Margin="3,3,3,3"
                            Content="Add New ..."
                            IsEnabled="True" />
                    </Grid>
                </Grid>
            </Border>

            <!--  right Column  -->
            <Grid Grid.Column="1" Grid.ColumnSpan="2">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>


                <!--  right column, first row  -->
                <Border
                    Grid.Row="0"
                    BorderBrush="DarkGray"
                    BorderThickness="1">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="25" />
                            <RowDefinition Height="25" />
                            <RowDefinition Height="25" />
                            <RowDefinition Height="25" />
                            <RowDefinition Height="25" />
                            <RowDefinition Height="25" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100" />
                            <ColumnDefinition Width="200" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <Label
                            Grid.Row="0"
                            Grid.Column="0"
                            HorizontalAlignment="Right"
                            Content="First Name" />
                        <Label
                            Grid.Row="1"
                            Grid.Column="0"
                            HorizontalAlignment="Right"
                            Content="Last Name" />
                        <Label
                            Grid.Row="2"
                            Grid.Column="0"
                            HorizontalAlignment="Right"
                            Content="Initials" />
                        <Label
                            Grid.Row="3"
                            Grid.Column="0"
                            HorizontalAlignment="Right"
                            VerticalContentAlignment="Center"
                            Content="ID" />
                        <Label
                            Grid.Row="4"
                            Grid.Column="0"
                            HorizontalAlignment="Right"
                            VerticalContentAlignment="Center"
                            Content="RCU" />
                        <Label
                            Grid.Row="5"
                            Grid.Column="0"
                            HorizontalAlignment="Right"
                            VerticalContentAlignment="Center"
                            Content="RCT" />
                        <TextBox
                            x:Name="TextBoxFirstName"
                            Grid.Row="0"
                            Grid.Column="1"
                            Margin="0,3,3,3"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Center"
                            Text="{Binding EditedEmployee.Firstname, Mode=TwoWay}" />
                        <TextBox
                            x:Name="TextBoxLastName"
                            Grid.Row="1"
                            Grid.Column="1"
                            Margin="0,3,3,3"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Center"
                            Text="{Binding EditedEmployee.Lastname, Mode=TwoWay}" />
                        <TextBox
                            x:Name="TextBoxInitials"
                            Grid.Row="2"
                            Grid.Column="1"
                            Margin="0,3,3,3"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Center"
                            Text="{Binding EditedEmployee.Initials, Mode=TwoWay}" />
                        <TextBox
                            x:Name="TextBoxId"
                            Grid.Row="3"
                            Grid.Column="1"
                            Margin="0,3,3,3"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Center"
                            IsReadOnly="True" />
                        <TextBox
                            x:Name="TextBoxRCU"
                            Grid.Row="4"
                            Grid.Column="1"
                            Margin="0,3,3,3"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Center"
                            IsReadOnly="True" />
                        <TextBox
                            x:Name="TextBoxRCT"
                            Grid.Row="5"
                            Grid.Column="1"
                            Margin="0,3,3,3"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Center"
                            IsReadOnly="True" />

                    </Grid>
                </Border>
                <Grid Grid.Row="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>

                    <Button
                        x:Name="ButnSave"
                        Grid.Column="1"
                        Width="76"
                        Height="25"
                        Margin="60,3,60,0"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Top"
                        Command="{Binding EmployeeSaveCommand}"
                        Content="Save"
                        IsEnabled="True" />
                </Grid>
                <!--  Add new  -->

            </Grid>
        </Grid>
    </Grid>
</Window>

In other words: I want to click on an item in the list view, it's details are shown in the text boxes. I can change the text in the text boxes but the changes are not applied to the observable collection (or even database) until I click "save". The problem is, that exactly that happens: when I edit text in the text boxes the changes are also shown in the list view. And I can#t see why.

herrwolken
  • 389
  • 1
  • 4
  • 12
  • 1
    Question: why your view model (EmployeeEditorVM ) is inhering List? – Marlonchosky May 06 '20 at 16:42
  • 1
    Make a copy of the whole observable collection and use that for details: on save copy back the collection – Emanuele May 06 '20 at 16:47
  • 1
    Just [copy](https://stackoverflow.com/questions/6569486/creating-a-copy-of-an-object-in-c-sharp) the collection item to `EditedEmployee`. Simple assign is not copying the object. – aepot May 06 '20 at 16:48
  • 2
    `EditedEmployee = SelectedEmployee` the source of the problem is here. You're not cloning the object here but making properties `EditedEmployee` and `SelectedEmployee` pointing to the same object instance in memory. – aepot May 06 '20 at 17:00

1 Answers1

0

So, I got the concept of "copy vs point" of a variable wrong.

I had two solutions for my problem.

  1. To my Employee model I added the method Copy:
    public partial class Employee
    {
        public int IdEmployee { get; set; }
        public string Initials { get; set; }
        public string Firstname { get; set; }
        public string Lastname { get; set; }
        public string Rcu { get; set; }
        public DateTime Rct { get; set; }

        public Employee Copy()
        {
            return (Employee)this.MemberwiseClone();
        }
    }

Then I Changed my ViewModel so that EditedEmployee is a copy of SelectedEmployee

        private Employee selectedEmployee;

        public Employee SelectedEmployee
        {
            get { return selectedEmployee; }
            set
            {
                selectedEmployee = value;
                OnPropertyChanged("SelectedEmployee");
                if (SelectedEmployee != null)
                {
                    EditedEmployee = SelectedEmployee.Copy();
                }
            }
        }

        private Employee editedEmployee;

        public Employee EditedEmployee
        {
            get { return editedEmployee; }
            set
            {
                editedEmployee = value;
                OnPropertyChanged("EditedEmployee");
            }
        }

The problem with this approach is, that if I mess around with the database and then need to run Scaffold-DbContext (I prefer the code-first approach of EFC) again, I would have to modify all models again. A solution to that might be some a second, modified Model that inherits from the original Model and then adds the Copy method.

My second approach is to bind the ListView via SelectedIndex instead of SelectedItem.

<ListView
...
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedIndex="{Binding SelectedItemIndex, Mode=TwoWay}">

In the ViewModel this is then represented by an int property. If this int is set (and >= 0) Employee gets copied EmployeeWorkingCopy. Furthermore if EmployeeWorkingCopy is set a single Employee dubbed EditedEmployee is set as the Employee of the working copy with the selected index.

private int selectedItemIndex;

public int SelectedItemIndex
{
  get { return selectedItemIndex; }
  set
  {
  selectedItemIndex = value;
  if (SelectedItemIndex >= 0)
  EmployeesWorkingCopy = new ObservableCollection<Employee>(Employees);
  }
}

private Employee editedEmployee;

public Employee EditedEmployee
{
 get { return editedEmployee; }
 set
 {
 editedEmployee = value;
 OnPropertyChanged("EditedEmployee");
 }
}

private ObservableCollection<Employee> employeesWorkingCopy;

public ObservableCollection<Employee> EmployeesWorkingCopy
{
get { return employeesWorkingCopy; }
set
 {
    employeesWorkingCopy = value;
    EditedEmployee = EmployeesWorkingCopy[SelectedItemIndex];
  }
}

Thanks a lot for pointing out my misconception.

herrwolken
  • 389
  • 1
  • 4
  • 12