How to Update ListView over ObervableCollection and PropertyChanged

Sarah 186 Reputation points
2022-02-02T17:06:01.947+00:00

Hi, I am trying to create a WPF app "My Contacts" in .Net 6.0. The app should simply allow adding, editing and deleting contacts in a list using ObservableCollection and DataContext. Unfortunately, those 3 things didn't quite work. Adding a contact does not show up in ListView. Editing one contact affects all other contacts. When deleting a contact I get an error. Maybe anyone can tell me what I am doing wrong here?

Thanks in advance
Sarah

MainWindow:

<Window x:Class="MyContacts.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:MyContacts"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="1100">

    <Window.Resources>
        <CollectionViewSource  x:Key="ListingDataView"  Source="{Binding Source={x:Static Application.Current}, Path=ContactDetails}"/>
    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1000"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>

        <ListView Grid.Column="0" x:Name="ContactListView" ItemsSource="{Binding Source={StaticResource ListingDataView}}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Firstname" x:Name="Firstname" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Firstname}" TextWrapping="Wrap"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Lastname" x:Name="Lastname" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Lastname}" TextWrapping="Wrap"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Address" x:Name="Address" Width="250">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Address}" TextWrapping="Wrap"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Phone" x:Name="Phone" Width="200">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Phone}" TextWrapping="Wrap"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Email" x:Name="Email" Width="250">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Email}" TextWrapping="Wrap"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>

        <StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Top" Margin="0,0,0,0">
            <Button Height="30" Width="60" Content="Add" Margin="10" Click="Add_Click"/>
            <Button Height="30" Width="60" Content="Edit" Margin="10" Click="Edit_Click"/>
            <Button Height="30" Width="60" Content="Remove" Margin="10" Click="Remove_Click"/>
        </StackPanel>
    </Grid>
</Window>

Code:

using System.Windows;
using System.Windows.Data;


namespace MyContacts
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly CollectionViewSource _listingDataView;

        public MainWindow()
        {
            InitializeComponent();
            _listingDataView = (CollectionViewSource)(Resources["ListingDataView"]);
        }

        private void Add_Click(object sender, RoutedEventArgs e)
        {
            var addContactWindow = new AddContactWindow();
            addContactWindow.ShowDialog();
        }

        private void Edit_Click(object sender, RoutedEventArgs e)
        {
            if (ContactListView.SelectedItem != null)
            {
                ContactDetail contactData = ContactListView.SelectedItem as ContactDetail;
                if (contactData == null)
                {
                    return;
                }
                else
                {
                    var addContactWindow = new AddContactWindow();
                    addContactWindow.DataContext = contactData;
                    addContactWindow.Owner = Application.Current.MainWindow;
                    addContactWindow.ShowDialog();
                }
            }
        }

        private void Remove_Click(object sender, RoutedEventArgs e)
        {
            if (ContactListView.SelectedItem != null)
            {
                ((App)DataContext).ContactDetails.Remove(ContactListView.SelectedItem as ContactDetail);
            }
        }
    }
}

AddContactWindow

<Window x:Class="MyContacts.AddContactWindow"
        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:MyContacts"
        mc:Ignorable="d"
        Title="Contact" Height="300" Width="400">
    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,10,0,10">
                <Label Content="Firstname:" Width="120" />
                <TextBox Height="25" Width="230" Margin="0,0,5,0" Text="{Binding Path=Firstname, UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,0,0,10">
                <Label Content="Lastname:" Width="120" />
                <TextBox Height="25" Width="230" Margin="0,0,5,0" Text="{Binding Path=Lastname, UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,0,0,10">
                <Label Content="Address:" Width="120" />
                <TextBox Height="25" Width="230" Margin="0,0,5,0" Text="{Binding Path=Address, UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,0,0,10">
                <Label Content="Phone:" Width="120" />
                <TextBox Height="25" Width="230" Margin="0,0,5,0" Text="{Binding Path=Phone, UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,0,0,10">
                <Label Content="Email:" Width="120" />
                <TextBox Height="25" Width="230" Margin="0,0,5,0" Text="{Binding Path=Email, UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
        </StackPanel>

        <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="10,0,10,10">
            <Button Height="30" Width="70" Content="Close" Margin="10,0,0,0" Click="Close_Click" />
            <Button Height="30" Width="70" Content="Save" Margin="10,0,0,0" Click="Save_Click" />
        </StackPanel>
    </Grid>
</Window>

Code:

using System.Windows;

namespace MyContacts
{
    /// <summary>
    /// Interaktionslogik für Contact.xaml
    /// </summary>
    public partial class AddContactWindow : Window
    {
        public AddContactWindow()
        {
            InitializeComponent();
        }

        private void Close_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void Save_Click(object sender, RoutedEventArgs e)
        {
            var item = (ContactDetail)(DataContext);
            ((App)Application.Current).ContactDetails.Add(item);
            Close();
        }
    }
}

Class ContactDetail

using System.ComponentModel;

namespace MyContacts
{
    public class ContactDetail : INotifyPropertyChanged
    {
        private string _Firstname;
        private string _Lastname;
        private string _Address;
        private string _Phone;
        private string _Email;

        public ContactDetail(string Firstname, string Lastname, string Address, string Phone, string Email)
        {
            _Firstname = Firstname;
            _Lastname = Lastname;
            _Address = Address;
            _Phone = Phone;
            _Email = Email;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {

            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        #region Properties Getters and Setters

        public string Firstname
        {
            get { return _Firstname; }
            set
            {
                _Firstname = value;
                OnPropertyChanged("Firstname");
            }
        }

        public string Lastname
        {
            get { return _Lastname; }
            set
            {
                _Firstname = value;
                OnPropertyChanged("Lastname");
            }
        }

        public string Address
        {
            get { return _Address; }
            set
            {
                _Firstname = value;
                OnPropertyChanged("Address");
            }
        }

        public string Phone
        {
            get { return _Phone; }
            set
            {
                _Firstname = value;
                OnPropertyChanged("Phone");
            }
        }

        public string Email
        {
            get { return _Email; }
            set
            {
                _Firstname = value;
                OnPropertyChanged("Email");
            }
        }

        #endregion Properties Getters and Setters
    }
}

Class App

using System.Collections.ObjectModel;
using System.Windows;

namespace MyContacts
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public ObservableCollection<ContactDetail> ContactDetails { get; set; } = new ObservableCollection<ContactDetail>();
    }
}
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,853 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,336 Reputation points
    2022-02-03T16:36:34.393+00:00

    Hi,
    try following. It works without problems:

    CodeBehind and MainWind0w and classes in my namespace WpfApp003

    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    using WpfApp003;
    
    namespace WpfApp1
    {
      /// <summary>
      /// Interaction logic for Window003.xaml
      /// </summary>
      public partial class Window003 : Window
      {
        public Window003()
        {
          InitializeComponent();
          this.DataContext = ViewModel.Instance;
        }
    
        private void Add_Click(object sender, RoutedEventArgs e)
        {
          var addContactWindow = new Window003A();
          ContactDetail contactData = new ContactDetail("", "", "", "", "");
          addContactWindow.DataContext = contactData;
          addContactWindow.ShowDialog();
          if (addContactWindow.DataContext != null)
          {
            ViewModel.Instance.ContactDetails.Add(contactData);
            ViewModel.Instance.OnPropertyChanged("View");
          }
        }
    
        private void Edit_Click(object sender, RoutedEventArgs e)
        {
          if (ContactListView.SelectedItem != null)
          {
            ContactDetail contactData = ContactListView.SelectedItem as ContactDetail;
            if (contactData == null)
            {
              return;
            }
            else
            {
              var addContactWindow = new Window003A();
              contactData.Cache();
              addContactWindow.DataContext = contactData;
              addContactWindow.Owner = Application.Current.MainWindow;
              addContactWindow.ShowDialog();
              if (addContactWindow.DataContext == null) contactData.Restore();
            }
          }
        }
    
        private void Remove_Click(object sender, RoutedEventArgs e)
        {
          if (ContactListView.SelectedItem != null)
            WpfApp003.ViewModel.Instance.ContactDetails.Remove(ContactListView.SelectedItem as ContactDetail);
        }
      }
    }
    
    
    namespace WpfApp003
    {
      public class ContactDetail : INotifyPropertyChanged
      {
        private string _Firstname;
        private string _Lastname;
        private string _Address;
        private string _Phone;
        private string _Email;
    
        public ContactDetail() { }
        public ContactDetail(string Firstname, string Lastname, string Address, string Phone, string Email)
        {
          _Firstname = Firstname;
          _Lastname = Lastname;
          _Address = Address;
          _Phone = Phone;
          _Email = Email;
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged([CallerMemberName] string name = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    
        #region Properties Getters and Setters
    
        public string Firstname
        {
          get { return _Firstname; }
          set { _Firstname = value; OnPropertyChanged(); }
        }
    
        public string Lastname
        {
          get { return _Lastname; }
          set { _Lastname = value; OnPropertyChanged(); }
        }
    
        public string Address
        {
          get { return _Address; }
          set { _Address = value; OnPropertyChanged(); }
        }
    
        public string Phone
        {
          get { return _Phone; }
          set { _Phone = value; OnPropertyChanged(); }
        }
    
        public string Email
        {
          get { return _Email; }
          set { _Email = value; OnPropertyChanged(); }
        }
    
        #endregion Properties Getters and Setters
    
        private string _Firstname_Cache;
        private string _Lastname_Cache;
        private string _Address_Cache;
        private string _Phone_Cache;
        private string _Email_Cache;
        public void Cache()
        {
          _Firstname_Cache = _Firstname;
          _Lastname_Cache = _Lastname;
          _Address_Cache = _Address;
          _Phone_Cache = _Phone;
          _Email_Cache = _Email;
        }
        public void Restore()
        {
          Firstname = _Firstname_Cache;
          Lastname = _Lastname_Cache;
          Address = _Address_Cache;
          Phone = _Phone_Cache;
          Email = _Email_Cache;
        }
      }
    
      public class ViewModel : INotifyPropertyChanged
      {
        private static ViewModel _instance;
        static ViewModel()
        {
          _instance = new ViewModel();
          _instance.ContactDetails = new ObservableCollection<ContactDetail>();
          _instance.cvs.Source = _instance.ContactDetails;
        }
        public static ViewModel Instance { get => _instance; }
        public ICollectionView View { get => cvs.View; }
        private CollectionViewSource cvs = new CollectionViewSource();
        public ObservableCollection<ContactDetail>? ContactDetails { get; set; }
        public event PropertyChangedEventHandler? PropertyChanged;
        internal void OnPropertyChanged([CallerMemberName] string name = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
      }
    }
    

    Window003A in my demo is your AddContactWindow:

    using System.Windows;
    
    namespace WpfApp1
    {
      /// <summary>
      /// Interaction logic for Window003A.xaml
      /// </summary>
      public partial class Window003A : Window
      {
        public Window003A()
        {
          InitializeComponent();
        }
    
        private void Close_Click(object sender, RoutedEventArgs e)
        {
          this.DataContext = null;
          Close();
        }
    
        private void Save_Click(object sender, RoutedEventArgs e)
        {
          Close();
        }
      }
    }
    
    1 person found this answer helpful.

3 additional answers

Sort by: Most helpful
  1. Sarah 186 Reputation points
    2022-02-05T21:00:35.937+00:00

    Hi, thanks for your willingness to answer my questions. Please try to answer each question with lots of details. And also please understand that I just started learning C# and WPF a few weeks ago.

    For my question, see the comments in your code below:

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = ViewModel.Instance; //Why do I need this? What exactly is DataContext doing at this point?
    }
    
    
    private void Add_Click(object sender, RoutedEventArgs e)
    {
        var addContactWindow = new AddContactWindow();
        ContactDetail contactData = new ContactDetail("", "", "", "", ""); // Why do I need this?
        addContactWindow.DataContext = contactData; // Why do I need this?
        addContactWindow.ShowDialog();
        if (addContactWindow.DataContext != null) // Why do I need the if statement? Should be moved to the function "Save_Click"!
        {
            ViewModel.Instance.ContactDetails.Add(contactData);
            ViewModel.Instance.OnPropertyChanged("View"); //Do I need this call here? Is the ICollectionView not updated automatically?
        }
    }
    
    // => The button "Add" must only open the window "AddContactWiondow", nothing more!
    
    
    private void Edit_Click(object sender, RoutedEventArgs e)
    {
        if (ContactListView.SelectedItem != null)
        {
            ContactDetail contactData = ContactListView.SelectedItem as ContactDetail;
            if (contactData == null)
            {
                return;
            }
            else
            {
                var addContactWindow = new AddContactWindow();
                contactData.Cache(); // Why do I need the function "Cache"? 
                addContactWindow.DataContext = contactData;
                addContactWindow.Owner = Application.Current.MainWindow;
                addContactWindow.ShowDialog();
                if (addContactWindow.DataContext == null) // Why do I need to compare DataContext against null?
                {
                    contactData.Restore(); // Why do I need the function "Restore"? 
                } 
            }
        }
    }
    // => Clicking the Edit button should open the selected item in the AddContactWindow window and clicking the Save button saves the changes made to the item.
    
    
    private void Close_Click(object sender, RoutedEventArgs e)
    {
        this.DataContext = null; //Why do you set DataContext to null?
        Close(); 
    }
    
    private void Save_Click(object sender, RoutedEventArgs e)
    {
        Close(); // Here the new data or the editing of an item should be saved. Closing the window is not logical, even if the case is caught in the Add_Click function
    }
    
    
    namespace MyContacts
    {
        public class ContactDetail : INotifyPropertyChanged
        {
            public ContactDetail() { } //What is the purpose of the empty constucture?
        }
    
        public class ViewModel : INotifyPropertyChanged
        {
            private static ViewModel _instance; //Why do you make an instance of the class in the class itself?
            static ViewModel()
            {
                _instance = new ViewModel();
                _instance.ContactDetails = new ObservableCollection<ContactDetail>();
                _instance.cvs.Source = _instance.ContactDetails;
            }
            public static ViewModel Instance { get => _instance; }
    
    
            public ICollectionView View { get => cvs.View; } //The View function returns itself?
            private CollectionViewSource cvs = new CollectionViewSource();
    
    
            public ObservableCollection<ContactDetail>? ContactDetails { get; set; } //What is the meaning of "?"
    
        }
    }
    
    // Is the ViewModel class necessary? Can't it be saved?
    // Do both ContactDetail and ViewModel classes need to inherit from INotifyPropertyChanged?
    
    1 person found this answer helpful.
    0 comments No comments

  2. Peter Fleischer (former MVP) 19,336 Reputation points
    2022-02-03T03:56:40.317+00:00

    Hi Sara,
    in your code AddContactWindow have not DataContext. Change Add_Click method:

         private void Add_Click(object sender, RoutedEventArgs e)
         {
             var addContactWindow = new AddContactWindow();
             addContactWindow.DataContext = new ContactDetail("", "", "", "", "");
             addContactWindow.ShowDialog();
         }
    

    Additional you must change code of ContactDetail:

    public string Lastname
    {
      get { return _Lastname; }
      set
      {
        // _Firstname = value;  <------------------------
        _Lastname = value; 
        OnPropertyChanged("Lastname");
      }
    }
    

    and so on.

    I recommend you to use partial MVVM pattern and singleton. Try following demo:

    My Window003 is your MainWindow.

    <Window x:Class="WpfApp1.Window003"
            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:WpfApp003"
            mc:Ignorable="d"
            Title="Sarah-3412_220202" Height="450" Width="1200">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="1000"/>
          <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
    
        <ListView Grid.Column="0" x:Name="ContactListView" ItemsSource="{Binding View}">
          <ListView.View>
    

    ... your old XAML

    CodeBehind and classes in my namespace WpfApp003

    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    using WpfApp003;
    
    namespace WpfApp1
    {
      /// <summary>
      /// Interaction logic for Window003.xaml
      /// </summary>
      public partial class Window003 : Window
      {
        public Window003()
        {
          InitializeComponent();
          this.DataContext = ViewModel.Instance;
        }
    
        private void Add_Click(object sender, RoutedEventArgs e)
        {
          var addContactWindow = new Window003A();
          addContactWindow.DataContext = new ContactDetail("", "", "", "", "");
          addContactWindow.ShowDialog();
        }
    
        private void Edit_Click(object sender, RoutedEventArgs e)
        {
          if (ContactListView.SelectedItem != null)
          {
            ContactDetail contactData = ContactListView.SelectedItem as ContactDetail;
            if (contactData == null)
            {
              return;
            }
            else
            {
              var addContactWindow = new Window003A();
              addContactWindow.DataContext = contactData;
              addContactWindow.Owner = Application.Current.MainWindow;
              addContactWindow.ShowDialog();
            }
          }
        }
    
        private void Remove_Click(object sender, RoutedEventArgs e)
        {
          if (ContactListView.SelectedItem != null)
            WpfApp003.ViewModel.Instance.ContactDetails.Remove(ContactListView.SelectedItem as ContactDetail);
        }
      }
    }
    
    
    namespace WpfApp003
    {
      public class ContactDetail : INotifyPropertyChanged
      {
        private string _Firstname;
        private string _Lastname;
        private string _Address;
        private string _Phone;
        private string _Email;
    
        public ContactDetail() { }
        public ContactDetail(string Firstname, string Lastname, string Address, string Phone, string Email)
        {
          _Firstname = Firstname;
          _Lastname = Lastname;
          _Address = Address;
          _Phone = Phone;
          _Email = Email;
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged([CallerMemberName] string name = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    
        #region Properties Getters and Setters
    
        public string Firstname
        {
          get { return _Firstname; }
          set { _Firstname = value; OnPropertyChanged(); }
        }
    
        public string Lastname
        {
          get { return _Lastname; }
          set { _Lastname = value; OnPropertyChanged(); }
        }
    
        public string Address
        {
          get { return _Address; }
          set { _Address = value; OnPropertyChanged(); }
        }
    
        public string Phone
        {
          get { return _Phone; }
          set { _Phone = value; OnPropertyChanged(); }
        }
    
        public string Email
        {
          get { return _Email; }
          set { _Email = value; OnPropertyChanged(); }
        }
    
        #endregion Properties Getters and Setters
      }
    
      public class ViewModel : INotifyPropertyChanged
      {
        private static ViewModel _instance;
        static ViewModel()
        {
          _instance = new ViewModel();
          _instance.ContactDetails = new ObservableCollection<ContactDetail>();
          _instance.cvs.Source = _instance.ContactDetails;
        }
        public static ViewModel Instance { get => _instance; }
        public ICollectionView View { get => cvs.View; }
        private CollectionViewSource cvs = new CollectionViewSource();
        public ObservableCollection<ContactDetail>? ContactDetails { get; set; }
        public event PropertyChangedEventHandler? PropertyChanged;
        internal void OnPropertyChanged([CallerMemberName] string name = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
      }
    }
    

    Window003A in my demo is your AddContactWindow, following CodeBehind:

    using System.Windows;
    using WpfApp003;
    
    namespace WpfApp1
    {
      /// <summary>
      /// Interaction logic for Window003A.xaml
      /// </summary>
      public partial class Window003A : Window
      {
        public Window003A()
        {
          InitializeComponent();
        }
    
        private void Close_Click(object sender, RoutedEventArgs e)
        {
          Close();
        }
    
        private void Save_Click(object sender, RoutedEventArgs e)
        {
          var item = (ContactDetail)(DataContext);
          ViewModel.Instance.ContactDetails.Add(item);
          ViewModel.Instance.OnPropertyChanged("View");
          Close();
        }
      }
    }
    

  3. Peter Fleischer (former MVP) 19,336 Reputation points
    2022-02-06T06:43:42.867+00:00

    Hi Sarah,
    I try to answer:

    DataContext instance for MainWindow

    this.DataContext = ViewModel.Instance; //Why do I need this? What exactly is DataContext doing at this point?

    Instantiate new empty ContactDetail for fuhrter adding, or use ctor without parameters: ContactDetail contactData = new ContactDetail();

    ContactDetail contactData = new ContactDetail("", "", "", "", ""); // Why do I need this?

    DataContext in AddContactWindow

    addContactWindow.DataContext = contactData; // Why do I need this?

    Distinguish between save and close in AddContactWindow, in Save_click you haven't access to ContactDetails in DataContext instance in MainWinwindow

    if (addContactWindow.DataContext != null) // Why do I need the if statement? Should be moved to the function "Save_Click"!

    Yes, in this case is not necessary

    ViewModel.Instance.OnPropertyChanged("View"); //Do I need this call here? Is the ICollectionView not updated automatically?

    after closing AddContactWindow you must distinguish between save and close

    // => The button "Add" must only open the window "AddContactWindow", nothing more!

    Distinguish between save and close in AddContactWindow

    if (addContactWindow.DataContext == null) // Why do I need to compare DataContext against null?

    if user click Close_Click in edit mode you must restore old values to prevent changes

    contactData.Restore(); // Why do I need the function "Restore"?

    using actual selected item from ContactDetails as DataContext in AddContactWindow, you change values of this item without additional activity

    // => Clicking the Edit button should open the selected item in the AddContactWindow window and clicking the Save button saves the changes made to the item.

    Distinguish between save and close in AddContactWindow to submit to caller (MainWindow or ViewModel)

    this.DataContext = null; //Why do you set DataContext to null?

    all changes of values in AddContactWindow are automatically persisted in DataContext (from Add Contact Window), no further activities are required

    Close(); // Here the new data or the editing of an item should be saved. Closing the window is not logical, even if the case is caught in the Add_Click function

    creating a new empty ContactDetail

    public ContactDetail() { } //What is the purpose of the empty constucture?

    singleton pattern, for using in separate objects

    private static ViewModel _instance; //Why do you make an instance of the class in the class itself?

    no itself, returns View property of CollectionViewSource!

    public ICollectionView View { get => cvs.View; } //The View function returns itself?

    see C#-language, without ? you get information in syntaxchecker

    public ObservableCollection<ContactDetail>? ContactDetails { get; set; } //What is the meaning of "?"

    instead of ViewModel class you can use another class to contain View property and other properties for displaying in windows

    // Is the ViewModel class necessary? Can't it be saved?

    any class that wants to report property changes to a UI control must implement INotifyPropertyChanged

    // Do both ContactDetail and ViewModel classes need to inherit from INotifyPropertyChanged?


Your answer

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