MVVM WPF - 如何更新绑定到 ObservableCollection 的 DataGrid

MVVM WPF - How to update DataGrid bound to ObservableCollection

我想做什么:

我有一个 WPF 应用程序,链接到 SQL 服务器。我正在使用 MVVM-light 包(我确实安装了 Prism.Core,但我不确定我是否在使用它...... MVVM 的新手)。

有一个 DataGrid,绑定到一个 ObservableCollection。 我一直在尝试实现 PropertyChangedEventHandler,但我似乎无法让它工作。

我有一个删除按钮绑定,我可以删除行,但是当我重新打开表单时,更改不会继续。

我试图将 DataGrid 的绑定模式从 OneWay 更改为 TwoWay。对于 OneWay,当我重新打开表单时,更改不会继续。使用 TwoWay,我在打开子表单(包含 DataGrid)时收到此错误消息:

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

所以,如果我再添加一个 set; 到我的 public ObservableCollection<LicenseHolders> licenseHolders { get; }, 程序运行,但之前的问题仍然存在,就像 DataGrid.

上有 OneWay 模式配置时一样

在不直接与 Sql-server 通信的情况下,我需要做什么才能使它正常工作,这首先违背了使用这种方法的全部意义?

视图模型:

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

型号

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; }

}

代码隐藏

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>

你在混淆话题。 VM 需要 InotifyPropertyChanged 事件,您拥有但未使用的事件,以通知前端的 Xaml VM 属性 已更改并绑定到 new data reference.

ListObservableCollection 需要这样做。完成后,ObservableCollection 将在添加或删除项目时向列表发送更改通知。

因为你错过了第一步:

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

前端没有得到 reference 的变化。因为它没有 set { _myCollection = value; OnPropertyChanged( "licenseHolders "); }

Xaml 不知道如何将绑定从最初的 null 更改为对列表的有效引用,并且您会得到一个无法正常工作的视觉控件。


坦率地说,如果您不对信息进行分页(在初始 运行 之后添加或删除 ObservableCollection),则可以将 ObservableCollection 替换为简单的列表...只需将行添加到有效的局部变量列表中,然后在最后 然后 将其设置为 licenseHolders 列表,前端 xaml 将起作用。

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

这不是真的。

ObservableCollection 仅提供其更改的通知。
也就是说,如果您将一个项目添加到 ObservableCollection,那么它将自动出现在 DataGrid 中。 如果您在没有通知的情况下使用集合(例如,List),则不会发生这种情况。 并且您将不得不想出一些额外的技术来触发视图的更新。

ObservableCollection“不知道”其元素是如何创建的,它们与数据源(数据库、文件或其他来源)的关系如何。
在你的 中,在我的示例代码中,我展示了 bool RemoveFromBD(LicenseHolders license) 方法只是为了编写从数据库中删除 license 的代码。

我不太了解 ADO,我根本不知道你数据库中 table 字段的名称,但也许这个方法的实现是正确的:

        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;
                }
            }
        }