简体   繁体   中英

Delete selected row in DataGrid bound to an ObservableCollection

I'm writing an app in WPF, trying to use the MVVM-design pattern (which is new to me). I have a DataGrid bound to an ObservableCollection .

What I'm trying to achieve:

Delete the currently selected DataGrid-row using a 'Delete'-button. I've tried a plethora of forum-posts and videos to find a solution. The solution has probably stared me right in the face several times, but at this point I'm more confused than I was when I first started.

Any assistance would be appreciated.

ViewModel (updated with working code, thanks to EldHasp):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Windows;
using GalaSoft.MvvmLight.CommandWpf;
using Ridel.Hub.Model;

namespace Ridel.Hub.ViewModel {

    public class LicenseHoldersViewModel : INotifyPropertyChanged {

        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");

                    //if (licenseHolders == null)
                        //licenseHolders = new ObservableCollection<LicenseHolders>();

                    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
            ?? (_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);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName) {

            if (PropertyChanged != null) {

                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Model:

public class LicenseHolders {

    public string example { get; set; }
    // more of the same...
}

XAML:

<Button Style="{DynamicResource ButtonWithRoundCornersGreen}" 
    FontSize="22" x:Name="btnDelete" Content="Delete" 
    Width="187" Height="47" Background="#48bb88" Foreground="White" 
    Canvas.Left="547" Canvas.Top="668" IsEnabled="False" Click="btnDelete_Click"/>

<DataGrid             
    x:Name="dgLicenseHolder"
    CanUserAddRows="False"
    ItemsSource="{Binding licenseHolders}"
    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="Example" Binding="{Binding Path='Example'}" IsReadOnly="True" Visibility="Collapsed"/>
        // more of the same..
    </DataGrid.Columns>            
</DataGrid>

Code-behind:

public partial class Personell : Window {

    LicenseHoldersViewModel licenseHoldersViewModel;

    public Personell() {

        InitializeComponent();

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

Since you are using MVVM, you should not use button event Click handler to manipulate ViewModel data.
To do this, you must create a command-property in the ViewModel and bind it to the buttons.
The command parameter bind either to SelectedItem (if the Single mode is selected and only one selected row is deleted), or to SelectedItems (if the Extended mode is selected and multiple rows are deleted).

I don't know which implementations of INotifyPropertyChanged and ICommand you are using.
Therefore, I am writing an example based on my own implementation of the BaseInpc and RelayCommand classes .

    public class LicensesViewModel : BaseInpc
    {
        // A collection-property of the ObservableCollection type is best done «ReadOnly».
        public ObservableCollection<LicenseHolders> LicensesHolders { get; }
            = new ObservableCollection<LicenseHolders>();
        public void FillDataGrid()
        {

            //---------------
            //---------------
            //---------------

                //if (licenseHolders == null)
                //    licenseHolders = new ObservableCollection<LicenseHolders>();

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

                    LicensesHolders.Add(new LicenseHolders
                    {

                        example = dr[0].ToString(),
                        // more of the same...

                    });
                }
            }
        }


        private RelayCommand _removeLicenseCommand;
        public RelayCommand RemoveLicenseCommand => _removeLicenseCommand
            ?? (_removeLicenseCommand = new RelayCommand<LicenseHolders>(RemoveLicenseExecute, RemoveLicenseCanExecute));

        private bool RemoveLicenseCanExecute(LicenseHolders license)
        {
            // Checking for the Removable License in the Collection
            return LicensesHolders.Contains(license);
        }

        private void RemoveLicenseExecute(LicenseHolders license)
        {
            // If necessary, the code for removing the license from the database is placed at the beginning of the method.
            // For example, calling some method for this, returning true if the deletion is successful.
            var result = RemoveFromBD(license);

            //It is then removed from the collection.
            if (result)
                LicensesHolders.Remove(license);
        }

        private bool RemoveFromBD(LicenseHolders license)
        {
            throw new NotImplementedException();
        }
    }
<Button Style="{DynamicResource ButtonWithRoundCornersGreen}" 
    FontSize="22" x:Name="btnDelete" Content="Delete" 
    Width="187" Height="47" Background="#48bb88" Foreground="White" 
    Canvas.Left="547" Canvas.Top="668" IsEnabled="False"
    Command="{Binding RemoveLicenseCommand}"
    CommandParameter="{Binding SelectedItem, ElementName=dgLicenseHolder}"/>

An implementation option for the multi-selection mode:

        private RelayCommand _removeLicensesCommand;
        public RelayCommand RemoveLicensesCommand => _removeLicensesCommand
            ?? (_removeLicensesCommand = new RelayCommand<IList>(RemoveLicensesExecute, RemoveLicensesCanExecute));

        private bool RemoveLicensesCanExecute(IList licenses)
        {
            // Checking for the Removable License in the Collection
            return licenses.OfType<LicenseHolders>().Any(license => LicensesHolders.Contains(license));
        }

        private void RemoveLicensesExecute(IList licenses)
        {
            foreach (var license in licenses.OfType<LicenseHolders>().ToArray())
            {
                var result = RemoveFromBD(license);

                if (result)
                    LicensesHolders.Remove(license);
            }
        }
<Button Style="{DynamicResource ButtonWithRoundCornersGreen}" 
    FontSize="22" x:Name="btnDelete" Content="Delete" 
    Width="187" Height="47" Background="#48bb88" Foreground="White" 
    Canvas.Left="547" Canvas.Top="668" IsEnabled="False"
    Command="{Binding RemoveLicensesCommand}"
    CommandParameter="{Binding SelectedItems, ElementName=dgLicenseHolder}"/>

Update! (when trying to implement the solution of 'EldHasp' Error message

The error is due to using MVVMLight.
You initially did not indicate that you are using this package.
In it, the RelayCommand is not the base for the RelayCommand<T> .
Therefore, the type of the property and variable must match the type of the used constructor.

This is how you need it for MVVMLight:

    private RelayCommand<LicenseHolders> _removeLicenseHoldersCommand;

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

Additional advice on assigning DataContext.

Assigning it in Code Behind is not handled by XAML Designer at design time. Therefore, you do not see during development how your UI elements will look when data is transferred to them.
This is extremely inconvenient.
To solve this, you need to set the Development Time DataContext.
But in simple cases, you just need to set the DataContext in XAML.

Clear Code Behind:

public partial class Personell : Window 
{
    public Personell() => InitializeComponent();
}

Add to XAML:

<Window.DataContext>
  <local:LicenseHoldersViewModel/>
</Window.DataContext>

You can use Prism. Intall package Prism.Core then Install-Package Microsoft.Xaml.Behaviors.Wpf -Version 1.1.31 packages in your project, in your xaml declare namespace as - xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

<Button Content="Button">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <i:InvokeCommandAction Command="{Binding MyCommand }" 
                            CommandParameter="{Binding PassRow/IDHere}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>

In Your ViewModel -

public class ViewModel
{
    public ICommand MyCommand { get; set; }

    public ViewModel()
    {
        MyCommand = new DelegateCommand<object>(OnRowDeleted);
    }

    public void OnRowDeleted(object o)
    {
        // remove the value from your observablecollection here using LINQ
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM