1

What I'm trying to do:

I have a WPF app, linked to a SQL-server. I am using the MVVM-light package (I do actually have Prism.Core installed, but I'm not sure if I'm using it or not.... new to MVVM).

There's a DataGrid, bound to an ObservableCollection. I have been trying to implement the PropertyChangedEventHandler, but I can't seem to get it to work.

I have a Delete button bound, and I am able to remove rows, but when I re-open the form, the changes does not carry over.

I tried to change the binding-mode for the DataGrid from OneWay to TwoWay. With OneWay, the changes does not carry over when I re-open the form. With TwoWay, I get this error message when opening the child form (which contains the DataGrid):

System.InvalidOperationException: 'A TwoWay or OneWayToSource binding cannot work on the read->only property 'licenseHolders' of type 'Ridel.Hub.ViewModel.LicenseHoldersViewModel'.'

So, If I then add a set; to my public ObservableCollection<LicenseHolders> licenseHolders { get; }, the program runs, but the previous problem persists, like it did when there was a OneWay mode configuration on the DataGrid.

What do I need to do to get this to work without communicating directly with the Sql-server, which would defy the whole point of using this methodology in the first place?

ViewModel:

public class LicenseHoldersViewModel : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<LicenseHolders> licenseHolders { get; }
        = new ObservableCollection<LicenseHolders>();

    public LicenseHoldersViewModel() {

        FillDataGridLicenseHolders();
    }

    private void FillDataGridLicenseHolders() {

        try {

            using (SqlConnection sqlCon = new(ConnectionString.connectionString))
            using (SqlCommand sqlCmd = new("select * from tblLicenseHolder", sqlCon))
            using (SqlDataAdapter sqlDaAd = new(sqlCmd))
            using (DataSet ds = new()) {

                sqlCon.Open();
                sqlDaAd.Fill(ds, "tblLicenseHolder");

                foreach (DataRow dr in ds.Tables[0].Rows) {

                    licenseHolders.Add(new LicenseHolders {

                        ID = Convert.ToInt32(dr[0].ToString()),
                        Foretaksnavn = dr[1].ToString(),
                        Foretaksnummer = dr[2].ToString(),
                        Adresse = dr[3].ToString(),
                        Postnummer = (int)dr[4],
                        Poststed = dr[5].ToString(),
                        BIC = dr[6].ToString(),
                        IBAN = dr[7].ToString(),
                        //Profilbilde ???
                        Kontaktperson = dr[8].ToString(),
                        Epost = dr[9].ToString(),
                        Tlf = dr[10].ToString()
                    });
                }
            }

        } catch (Exception ex) {

            MessageBox.Show(ex.Message, "Message", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }

    private RelayCommand<LicenseHolders> _removeLicenseHoldersCommand;

    public RelayCommand<LicenseHolders> RemoveLicenseHoldersCommand => _removeLicenseHoldersCommand
        ??= new RelayCommand<LicenseHolders>(RemoveLicenseHolderExecute, RemoveLicenseHolderCanExecute);

    private bool RemoveLicenseHolderCanExecute(LicenseHolders myLicenseHolder) {

        // Checking for the removeable licenseholder in the collection
        return licenseHolders.Contains(myLicenseHolder);
    }

    private void RemoveLicenseHolderExecute(LicenseHolders myLicenseHolder) {

        licenseHolders.Remove(myLicenseHolder);
    }

    private void OnPropertyChanged(string myLicenseHolder) {

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

Model

public class LicenseHolders {

    public int ID { get; set; }
    public string Foretaksnavn { get; set; }
    public string Foretaksnummer { get; set; }
    public string Adresse { get; set; }
    public int Postnummer { get; set; }
    public string Poststed { get; set; }
    public string BIC { get; set; }
    public string IBAN { get; set; }
    public string Kontaktperson { get; set; }
    public string Epost { get; set; }
    public string Tlf { get; set; }

}

Code-behind

public partial class Personell : Window {

        LicenseHoldersViewModel licenseHoldersViewModel;

        public Personell() {

            InitializeComponent();
            btnLogOut.Content = UserInfo.UserName;

            licenseHoldersViewModel = new LicenseHoldersViewModel();
            base.DataContext = licenseHoldersViewModel;
        }

XAML

<Window x:Class="Ridel.Hub.Personell"
        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:Ridel.Hub" xmlns:viewmodel="clr-namespace:Ridel.Hub.ViewModel" d:DataContext="{d:DesignInstance Type=viewmodel:LicenseHoldersViewModel}"
        mc:Ignorable="d"
        
    <Canvas Margin="10,10,10,10">
    <!-- I click this button when I want to delete a DataGrid row -->
        <Button Style="{DynamicResource ButtonWithRoundCornersGreen}" FontSize="22" x:Name="btnDelete" Content="Delete license holder" Width="187" Height="47" 
                Background="#48bb88" Foreground="White" Canvas.Left="547" Canvas.Top="668" IsEnabled="False" 
                Command="{Binding RemoveLicenseHoldersCommand}" CommandParameter="{Binding SelectedItem, ElementName=dgLicenseHolder}"/>

        <DataGrid             
            x:Name="dgLicenseHolder"
            CanUserAddRows="False"
            ItemsSource="{Binding licenseHolders, Mode=TwoWay}"
            Height="557" 
            Width="505" 
            ColumnWidth="*"
            Canvas.Top="158" 
            FontSize="20"
            IsReadOnly="True"
            SelectionMode="Single"
            AutoGenerateColumns="False"
            CanUserDeleteRows="False"          
            SelectionChanged="dgLicenseHolder_SelectionChanged" Canvas.Left="31" >

            <DataGrid.Columns>
                <DataGridTextColumn Header="ID"             Binding="{Binding Path='ID'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Foretaksnavn"   Binding="{Binding Path='Foretaksnavn'}" IsReadOnly="True" Visibility="Visible"/>
                <DataGridTextColumn Header="Foretaksnummer" Binding="{Binding Path='Foretaksnummer'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Adresse"        Binding="{Binding Path='Adresse'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Postnummer"     Binding="{Binding Path='Postnummer'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Poststed"       Binding="{Binding Path='Poststed'}" IsReadOnly="True" Visibility="Visible"/>
                <DataGridTextColumn Header="BIC"            Binding="{Binding Path='BIC'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="IBAN"           Binding="{Binding Path='IBAN'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Kontaktperson"  Binding="{Binding Path='Kontaktperson'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Epost"          Binding="{Binding Path='Epost'}" IsReadOnly="True" Visibility="Collapsed"/>
                <DataGridTextColumn Header="Tlf"            Binding="{Binding Path='Tlf'}" IsReadOnly="True" Visibility="Collapsed"/>
            </DataGrid.Columns>
            
        </DataGrid>
    </Canvas>
</Window>
Ole M
  • 317
  • 1
  • 17
  • 1
    Please clarify a little. You have opened a Window. We made changes. Changes **MUST NOT** be committed to the database. You have closed the Window. They rediscovered it. Now you need to show in this form as it was before closing with the changes made, and not as in the database? – EldHasp Jun 15 '21 at 14:04
  • Sorry for expressing myself unclearly in the last post. I need the changes done in the DataGrid, to be "live". I thought the deletion of rows would be reflected on the server, because of how the ObservableCollection was bound. So I presumed I didn't need to talk to the Sql server directly. I guess that is still true, but I understand now I missed a piece of the puzzle. I am a bit confused by the answer below, but I will try to implement it. Do you have any tips? – Ole M Jun 15 '21 at 15:18

2 Answers2

1

You are confusing topics. The VM needs InotifyPropertyChanged events, which you have but are not using, to notify the Xaml in the front-end that a VMs property has changed and to bind to the new data reference.

This is needed for Lists or ObservableCollections. Once that is done, the ObservableCollection will then send notifications on changes to the list as items are added or removed.

Because you miss the first step:

public ObservableCollection<LicenseHolders> licenseHolders { get; }
        = new ObservableCollection<LicenseHolders>();

the front end does not get the reference change. Because it does not have a set { _myCollection = value; OnPropertyChanged( "licenseHolders "); }

the Xaml does not know to change the binding from the initial null to a valid reference to a list and you get a non-working visual control.


Frankly if you are not paging the information (adding or removing to the ObservableCollection after the initial run), the ObservableCollection can be replaced by a simple List...just add the rows to a valid local variables list, and at the very end then set it to be licenseHolders list and the front end xaml will work.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • I'm having a bit of trouble implementing this solution, but thank you for your reply, I appreciate it. I will work on this, and will be back if I get stuck! :-) – Ole M Jun 15 '21 at 15:12
  • Thank you for your suggestion, I went with another solution, because I couldn't really understand this one, but I'm sure it would've worked! – Ole M Jun 15 '21 at 17:09
1

I thought the deletion of rows would be reflected on the server, because of how the ObservableCollection was bound.

This is not true.

ObservableCollection only provides notification of its change.
That is, if you added an item to the ObservableCollection, then it will automatically appear in the DataGrid. If you use the collection without notification (for example, List), then this will not happen. And you will have to come up with some additional techniques in order to trigger the update of the view.

ObservableCollection "does not know" how its elements are created, how they are related to the data source (database, file or other source).
In your previous topic, in the code of my example, I showed the bool RemoveFromBD(LicenseHolders license) method just to write the code that removes the license from the database into it.

I don’t know ADO well and I don’t know the name of the table fields in your database at all, but perhaps this implementation of the method will be correct:

        private bool RemoveFromBD(LicenseHolders license)
        {
            string sql = string.Format("Delete from tblLicenseHolder where ID = '{0}'", license.ID);
            using (SqlConnection sqlCon = new SqlConnection(ConnectionString.connectionString))
            using (SqlCommand cmd = new SqlCommand(sql, sqlCon))
            {
                try
                {
                    sqlCon.Open();
                    cmd.ExecuteNonQuery();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • You absolute legend. You are getting mentioned in my evening prayers tonight! I just had to add a `sqlCon.Open();` above the `sqlCmd.ExecuteNonQuery();`, and it works perfectly. – Ole M Jun 15 '21 at 17:08
  • I made changes - maybe someone else will come in handy. I wish you success! – EldHasp Jun 15 '21 at 17:36