0

I'm using a datagrid, bound to an observablecollection with TwoWay binding. My goal is, that a user generates a list of data, starting from an empty collection. So I enabled the option CanUserAddRow.

In the code, I generate the obsevrable collection with the following code:

private ObservableCollection<Ticket> idlessTicketList = new ObservableCollection<Ticket>();

The Ticket class, which the ObservableCollection consists of, looks as follows:

public class Ticket
{
    public Ticket() { }

    public bool ticketUsed { get; set; }
    public string ticketNumber { get; set; }
    public string ticketCustomer { get; set; }
    public string ticketText { get; set; }
    public double ticketTime { get; set; }
    public Decimal ticketTypeNr { get; set; }
    public string ticketTypeText { get; set; }

}

In the MainWindow Method I set the itemSource of my Datagrid to my ObservableCollection:

public MainWindow()
{

    InitializeComponent();
    gridIdlessTickets.ItemsSource = idlessTicketList;

}

My problem is now, that the empty row to add a new row is not displayed at startup. If I add a new row by code myGridd.Add(row), then the empty row is displayed correctly and everythings works a expected.

How must the ObservableCollection be initialized and referenced to the itemSource correctly? Where is the best place to initialize an itemSource?

Thanks in advance

hafisch
  • 13
  • 4
  • add `CanUserAddRows="True"` to your `DataGrid`. – Abin Nov 11 '19 at 21:39
  • Thats what I did. And this works. But it works only if a row is already added. If the ObservableCollection is empty, after initialisation, the row is not visible. – hafisch Nov 11 '19 at 21:46
  • The best place to set the items source is in your XAML datagrid. ItemsSource="{Binding idlessTicketList}". For Data Binding you need a DataSource which is probably your Code Behind in this example (normally it should be the ViewModel). – timunix Nov 11 '19 at 22:48
  • Basically if you want an empty FIRST row in your datagrid you need to set your items source to your observable collection, set your datasource to the class where your observable collection is and inside that constructor you need to instantiate your observable collection (as an empty one). This way (because there is an empty list) the datagrid will give you an empty row at the start when the view shows. – timunix Nov 11 '19 at 22:57
  • https://stackoverflow.com/questions/58799113/how-to-query-2-collections-into-2-datagrids-with-mongodb-driver-and-binding/58806993#58806993 – timunix Nov 11 '19 at 23:08

2 Answers2

0

This should work for you. Let me know if it helped:

XAML:

    <Window>
      <Grid>
        <Datagrid ItemsSource="{Binding idlessTicketList }" SelectionMode="Single" SelectionUnit="Cell" IsReadOnly="False" 
                  CanUserAddRows="True" CanUserDeleteRows="False" AutoGenerateColumns="False">

        //ColumnDefinition...etc
                <DataGridTextColumn Header="TicketNumber" Binding="{Binding TicketNumber}"  />
                <DataGridTextColumn Header="TicketCustomer" Binding="{Binding TicketCustomer}"
        </Datagrid>
<Button name="ThisIsYourUpdateButton" Command="{Binding UpdateMyTicket}" Width="200" Content="Update me now"/>
      </Grid>
    </Window>

Code Behind (.xaml.cs):

public MainWindow()
{

    InitializeComponent(); //parses the XAML...
    DataContext = new MainWindowViewModel(); //outsources the view code to the    
    //"controller" to make the view only display and nothing else...

}

ViewModel: (MainWindowViewModel.cs)

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

        namespace YourNameSpace
        {
            public class MainWindowViewModel : INotifyPropertyChanged
            {
                public ICommand UpdateMyTicket => new DelegateCommand<object>(ExecuteUpdateTicket);
                public Ticket TicketInstance {get;set;}
                public event PropertyChangedEventHandler PropertyChanged;
                public virtual void OnPropertyChanged(string propertyName)
                {
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                    }
                }

                private ObservableCollection<Ticket> _idlessTicketList;
                public ObservableCollection<Ticket> idlessTicketList
                {
                    get { return _idlessTicketList; }
                    set
                    {
                        _idlessTicketList = value;
                        OnPropertyChanged("idlessTicketList");
                    }
                }

                //Constructor
                public MainWindowViewModel()
                {
                    idlessTicketList = new ObservableCollection<Ticket>();
                }
                public void ExecuteUpdateTicket(obj param)
                {
                  //if the button is clicked you update your Ticket class properties here!
                }
             }
          }

Add DelegateCommand.cs class like this:

    using System;

namespace YourNamespaceName 
{ 
    public class DelegateCommand<T> : System.Windows.Input.ICommand
    {
        private readonly Predicate<T> _canExecute;
        private readonly Action<T> _execute;

        public DelegateCommand(Action<T> execute)
            : this(execute, null)
        {
        }

        public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
                return true;

            return _canExecute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
        }

        public void Execute(object parameter)
        {
            _execute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
        }

        public event EventHandler CanExecuteChanged;
        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}
timunix
  • 609
  • 6
  • 19
  • Note that INotifyPropertyChanged is very important here because if the user makes changes in the view, the PropertyChanged event will take effect and the changes become live immediately. Your collections values (Ticket properties) will change all the time. Without this the view wouldn't be able to update live. I hope this helps. – timunix Nov 11 '19 at 23:42
  • @hafisch: Now the only thing you need to do is when you click on the button, the properties values are saved into your (mysql) database. This is what you do in the view model in your ``ExecuteUpdateTicket`` method. In your ExecuteMethod you can just create an instance of your mysql connection and INSERT or UPDATE your properties. All of your properties in your TicketModel should be MAPPED (read about Mappers for mysql) to your mySQL database table structure. So your TIcket class is basically your mySQL table and your properties are your mySQL columns and its values are your fields... – timunix Nov 14 '19 at 17:52
  • Don't worry about the DelegateCommand.cs: Just copy and paste it into your project with the right Namespace. The ICommand in you viewmodel and the corresponding method (ExecuteUpdateTicket) do the rest for you. It is just like a helping class that lets you add tons and tons of methods for button click events...SO you don't do the button clicks as before in your xaml.cs button_click but in your view model! This is what ICommand is for. It is that easy and chill. – timunix Nov 14 '19 at 17:55
  • In XAML I added a button with a Command binding to the vm's ICommand property. This ICommand property refers to a method in the view model which gets executed every time you click the button (provided you added the DelegateCommand.cs with the right namespace). In your ExecuteUpdateTicket method in your vmyou add your code to write the changes to your mysql database (for example) or to a text file, whatever you want. You can always add other buttons and ICommands and add them to your view model. So you can add a select or evaluation method later on for example that is bound to another button. – timunix Nov 14 '19 at 18:09
  • If you don't want to update the data with a button click but LIVE (while typing) then you might add the INotifyPropertyChanged interface the same way as in your view model IN YOUR TICKET.cs . Sometimes this is necessary. It depends on your application's needs. Maybe that is already enough for you. Remember that you bound your Datagrid Column to your TicketClasses' properties. So if the columns' values change, the INotifyPropertyChanged event fires and your observableCollection() changes its values, too. – timunix Nov 14 '19 at 18:17
0

With a seperate View-Model Class which initializes the observablecollection, the empty row is displayed correctly. But the data is not updated anymore. Here's what I did:

I added the new class as a new namesspace

xmlns:vm="clr-namespace:nsMainWindowViewModel"

The binding is written like that:

<DataGrid ItemsSource="{Binding Path=idlessTicketList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  AutoGenerateColumns="False" >
       <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=ticketCustomer, UpdateSourceTrigger=PropertyChanged}"/>
            <DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=ticketText, UpdateSourceTrigger=PropertyChanged}"/>
            <DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=ticketTime, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:n2\}}"/>
       </DataGrid.Columns>
</Datagrid>

With this implementation, the PropertyChangedEventHandler is called when the application starts, but not wen a element changes.

How must the binding be written? For the datagrid i tried:

{Binding Path=(idlessTicketList), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
{Binding Path=(vm:idlessTicketList), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
{Binding Path=(vm:MainWindowViewModel.idlessTicketList), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}

and for the columns I tried;

{Binding Mode=TwoWay, Path=ticketCustomer, UpdateSourceTrigger=PropertyChanged}
{Binding Mode=TwoWay, Path=(vm:ticketCustomer), UpdateSourceTrigger=PropertyChanged}
{Binding Mode=TwoWay, Path=(vm:MainWindowViewModel.ticketCustomer), UpdateSourceTrigger=PropertyChanged}

Whit the debugging, I can see that the PropertyChange Method is called initially, but not after editing single elements.

How must the binding be defined to update te observableCollection in another namespace?

Thanks in advance

hafisch
  • 13
  • 4
  • You don't need to add a new namespace for the view model. Just use your viewmodel as a class in the same namespace. Bind your columns in your datagrid to the property names in your Ticket class (your model) like this: If you want to use a new namespace for the view model for whatever reason then you would have to adjust your DataContext in your MainWindow constructor like this: DataContext = new NamespaceName.MainWindowViewModel(); How do you want your data to get updated? By button click or as you are typing the changes (live)? – timunix Nov 13 '19 at 09:38
  • I learn so much these days. Thanks for that. The solution is close, but I need your help one last time. In the beginning, an empty grid with the empty first row is visible, so far so good. If I edit the fields by entering some text, the PropertyChangedEvent is not called. Wit a seperate method wich contains a "ObsevrableCollection.Add" function and a binding "gridIdlessTickets.ItemSource = idlessTicketList", everything works fine. After these two lines, everthing else works as well. Creating new lines directly in the grid, as well as updating cell contents. What could be the reason? – hafisch Nov 13 '19 at 20:53
  • You probably need a ticket class property in your view model like this: public Ticket TicketInstance { get; set; } and then you need an ICommand like this in your viewmodel as well: public ICommand UpdateMyTicket => new DelegateCommand(ExecuteUpdateTicket); I will update my answer and then you can try it out if it works. – timunix Nov 14 '19 at 17:29