DataGrid WPF C# populate by code without binding and editable in runtime

Pablo The Tiger 26 Reputation points
2020-10-23T22:36:47.4+00:00

Hello,
I have a WPF app that manages people data. Each person has some attributes and a list of phone numbers. In the main window I have a DataGrid bound to a ObservableCollection, with the individual properties in a few DataGridTextColumn, and a Column with ComboBox, that each one displays each person's phone number list. By now, everything is perfect. What I want, then, is when clicking a MenuItem (that is OK) for edit a person's data, that a child Window opens, and that I have in some TextBox the current values of the person's individual attributes (which I can modify and save without problems) and a DataGrid that displays (1 or several rows with) 2 columns, with a data and the phone number, and a 3rd column with a Button (that's OK) for deleting the row. Here comes my problem. I want to populate this DataGrid, programmatically, by code, without any binding (it's not necesary), to display the person's current phone number list (that are 2 string by row), and also to be able to edit it in runtime (change cell values, add rows, and delete rows), in order to, then by clicking OK Button, update that person's phone number data list (by changing rows, adding, or deleting them) as well as his individual attributes. By now, I only was able to edit existing rows, with binding, but not to add nor delete. I already know how to deal with the database and ADO.NET and handling the program objects and data, I only need to know how populate the DataGrid without binding and to edit it in runtime.
I hope you can help me. I suspect this is much simpler than it seems to be.
Thank you very much in advance for your help
Regards
Pablo

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,676 questions
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-10-24T06:03:46.68+00:00

    Hi Pablo,
    try following demo:

    XAML MainWindow:

    <Window x:Class="WpfApp1.Window006"  
            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:WpfApp006"  
            mc:Ignorable="d"  
            Title="Master - Detail with Detail List" Height="450" Width="800">  
      <Window.Resources>  
        <local:ViewModel x:Key="vm"/>  
      </Window.Resources>  
      <Grid DataContext="{StaticResource vm}">  
        <Grid.ColumnDefinitions>  
          <ColumnDefinition/>  
          <ColumnDefinition/>  
        </Grid.ColumnDefinitions>  
        <DataGrid ItemsSource="{Binding View}" IsReadOnly="True" AutoGenerateColumns="false">  
          <DataGrid.Columns>  
            <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>  
            <DataGridTemplateColumn Header="Telefones">  
              <DataGridTemplateColumn.CellTemplate>  
                <DataTemplate>  
                  <Grid>  
                    <ComboBox ItemsSource="{Binding TelefonNumbers}"  
                              DisplayMemberPath="Number"/>  
                  </Grid>  
                </DataTemplate>  
              </DataGridTemplateColumn.CellTemplate>  
            </DataGridTemplateColumn>  
          </DataGrid.Columns>  
        </DataGrid>  
        <Grid Grid.Column="1" DataContext="{Binding Detail}">  
          <Grid.ColumnDefinitions>  
            <ColumnDefinition/>  
            <ColumnDefinition/>  
          </Grid.ColumnDefinitions>  
          <Grid.RowDefinitions>  
            <RowDefinition Height="Auto"/>  
            <RowDefinition Height="Auto"/>  
            <RowDefinition Height="Auto"/>  
            <RowDefinition/>  
          </Grid.RowDefinitions>  
          <Label Grid.Row="0" Grid.Column="0" Content="ID: " HorizontalAlignment="Right"/>  
          <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ID}" IsReadOnly="True"/>  
          <Label Grid.Row="1" Grid.Column="0" Content="Info: " HorizontalAlignment="Right"/>  
          <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>  
          <Label Grid.Row="2" Grid.Column="0" Content="Telefon numbers"/>  
          <DataGrid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding TelefonNumbers}" AutoGenerateColumns="False">  
            <DataGrid.Columns>  
              <DataGridTextColumn Header="Date" Binding="{Binding Date}"/>  
              <DataGridTextColumn Header="Telefon" Binding="{Binding Number}"/>  
              <DataGridTemplateColumn Header="Delete">  
                <DataGridTemplateColumn.CellTemplate>  
                  <DataTemplate>  
                    <Button Content="Delete" Command="{Binding Cmd, Source={StaticResource vm}}" CommandParameter="{Binding}"/>  
                  </DataTemplate>  
                </DataGridTemplateColumn.CellTemplate>  
              </DataGridTemplateColumn>  
            </DataGrid.Columns>  
          </DataGrid>  
        </Grid>  
      </Grid>  
    </Window>  
    

    And classes:

    using System;  
    using System.Collections.ObjectModel;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Data;  
    using System.Windows.Input;  
      
    namespace WpfApp006  
    {  
      public class ViewModel : INotifyPropertyChanged  
      {  
      
        private CollectionViewSource cvs = new CollectionViewSource();  
        private ObservableCollection<PersonData> col = new ObservableCollection<PersonData>();  
        public ICollectionView View  
        {  
          get  
          {  
            if (cvs.Source == null)  
            {  
              GetData();  
              cvs.View.CurrentChanged += (sender, e) => Detail = cvs.View.CurrentItem as PersonData;  
            }  
            return cvs.View;  
          }  
        }  
      
        private PersonData _detail = null;  
        public PersonData Detail { get => this._detail; set { this._detail = value; OnPropertyChanged(); } }  
      
        private void GetData()  
        {  
          Random rnd = new Random();  
          for (int i = 1; i < 11; i++)  
          {  
            PersonData p = new PersonData { ID = i, Name = $"Name {i}" };  
            col.Add(p);  
            for (int k = 0; k < rnd.Next(3, 9); k++)  
              p.TelefonNumbers.Add(new TelefonNumber() { Date = DateTime.Now.AddDays(rnd.Next(-1000, 1000)).AddMinutes(rnd.Next(0, 50000)), Number = rnd.Next(10000, 100000).ToString() });  
          }  
          cvs.Source = col;  
        }  
      
        public ICommand Cmd  
        {  
          get => new RelayCommand((state) =>  
          {  
            TelefonNumber t = state as TelefonNumber;  
            if (t != null) Detail.TelefonNumbers.Remove(t);  
          }, null);  
        }  
      
        public event PropertyChangedEventHandler PropertyChanged;  
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "") =>  
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
      }  
      
      public class PersonData  
      {  
        public int ID { get; set; }  
        public string Name { get; set; }  
        public ObservableCollection<TelefonNumber> TelefonNumbers { get; set; } = new ObservableCollection<TelefonNumber>();  
      }  
      
      public class TelefonNumber  
      {  
        public DateTime Date { get; set; }  
        public string Number { get; set; }  
      }  
      
      public class RelayCommand : ICommand  
      {  
        private readonly Predicate<object> _canExecute;  
        private readonly Action<object> _action;  
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }  
        public void Execute(object o) => _action(o);  
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);  
        public event EventHandler CanExecuteChanged  
        {  
          add { CommandManager.RequerySuggested += value; }  
          remove { CommandManager.RequerySuggested -= value; }  
        }  
      }  
    }  
    

    Result:

    34811-x.gif

    1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-10-25T07:21:49.513+00:00

    Hi Pablo,
    if you want change telefon numbers and only then save or not save to database you need copy of List of telefon numbers and button "Save". Try following demo:

    XAML:

    <Window x:Class="WpfApp1.Window008"
            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:WpfApp008"
            mc:Ignorable="d"
            Title="Master - Detail with Detail List" Height="450" Width="800">
      <Window.Resources>
        <local:ViewModel x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <DataGrid ItemsSource="{Binding View}" IsReadOnly="True" AutoGenerateColumns="false">
          <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
            <DataGridTemplateColumn Header="Telefones">
              <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                  <Grid>
                    <ComboBox ItemsSource="{Binding TelefonNumbers}"
                              DisplayMemberPath="Number"/>
                  </Grid>
                </DataTemplate>
              </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
          </DataGrid.Columns>
        </DataGrid>
        <Grid Grid.Column="1" DataContext="{Binding Detail}">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Grid.Row="0" Grid.Column="0" Content="ID: " HorizontalAlignment="Right"/>
          <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ID}" IsReadOnly="True"/>
          <Label Grid.Row="1" Grid.Column="0" Content="Info: " HorizontalAlignment="Right"/>
          <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
          <Label Grid.Row="2" Grid.Column="0" Content="Telefon numbers"/>
          <DataGrid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding TelefonNumbers}" AutoGenerateColumns="False">
            <DataGrid.Columns>
              <DataGridTextColumn Header="Date" Binding="{Binding Date}"/>
              <DataGridTextColumn Header="Telefon" Binding="{Binding Number}"/>
              <DataGridTemplateColumn Header="Delete">
                <DataGridTemplateColumn.CellTemplate>
                  <DataTemplate>
                    <Button Content="Delete" Command="{Binding Cmd, Source={StaticResource vm}}" CommandParameter="{Binding}"/>
                  </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
            </DataGrid.Columns>
          </DataGrid>
          <Button Grid.Row="4" Grid.Column="1" Content="Save" Command="{Binding Cmd, Source={StaticResource vm}}" CommandParameter="Save"/>
        </Grid>
      </Grid>
    </Window>
    

    And classes:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Input;
    
    namespace WpfApp008
    {
      public class ViewModel : INotifyPropertyChanged
      {
    
        private CollectionViewSource cvs = new CollectionViewSource();
        private ObservableCollection<PersonData> col = new ObservableCollection<PersonData>();
        public ICollectionView View
        {
          get
          {
            if (cvs.Source == null)
            {
              GetData();
              cvs.View.CurrentChanged += (sender, e) => Detail = (cvs.View.CurrentItem as PersonData)?.Copy();
            }
            return cvs.View;
          }
        }
    
        private PersonData _detail = null;
        public PersonData Detail { get => this._detail; set { this._detail = value; OnPropertyChanged(); } }
    
        private void GetData()
        {
          Random rnd = new Random();
          for (int i = 1; i < 11; i++)
          {
            PersonData p = new PersonData { ID = i, Name = $"Name {i}" };
            col.Add(p);
            for (int k = 0; k < rnd.Next(3, 9); k++)
              p.TelefonNumbers.Add(new TelefonNumber() { Date = DateTime.Now.AddDays(rnd.Next(-1000, 1000)).AddMinutes(rnd.Next(0, 50000)), Number = rnd.Next(10000, 100000).ToString() });
          }
          cvs.Source = col;
        }
    
        public ICommand Cmd
        {
          get => new RelayCommand((state) =>
          {
            switch (state.ToString())
            {
              case "Save":
                var pd = cvs.View.CurrentItem as PersonData;
                if (pd != null) pd.TelefonNumbers = Detail.TelefonNumbers;
                break;
              default:
                TelefonNumber t = state as TelefonNumber;
                if (t != null) Detail.TelefonNumbers.Remove(t);
                break;
            }
          }, null);
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    
      public class PersonData : INotifyPropertyChanged
      {
        public int ID { get; set; }
        public string Name { get; set; }
        private ObservableCollection<TelefonNumber> _telefonNumbers = new ObservableCollection<TelefonNumber>();
        public ObservableCollection<TelefonNumber> TelefonNumbers { get => this._telefonNumbers; set { this._telefonNumbers = value; OnPropertyChanged(); } }
        public PersonData Copy() =>
         new PersonData()
         {
           ID = this.ID,
           Name = this.Name,
           TelefonNumbers = new ObservableCollection<TelefonNumber>(this.TelefonNumbers)
         };
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
      public class TelefonNumber
      {
        public DateTime Date { get; set; }
        public string Number { get; set; }
      }
    
      public class RelayCommand : ICommand
      {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _action;
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
        public void Execute(object o) => _action(o);
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
        public event EventHandler CanExecuteChanged
        {
          add { CommandManager.RequerySuggested += value; }
          remove { CommandManager.RequerySuggested -= value; }
        }
      }
    }
    
    1 person found this answer helpful.