WPF MVVM datagrid在另一个更改时更新属性

时间:2018-09-27 18:02:23

标签: wpf xaml mvvm combobox datagrid

我正在尝试创建一个用于创建工作分配列表(AssignmentPlanItem类)的数据网格,该列表具有Employee,Assignment和Workcenter的组合框(AssignmentPlanItem的所有单独的类和外键。该计划直接填充到datagrid我知道如果通过表单完成添加项目可能会更容易,但是我认为这是一种快捷的方法,我不想更改它。

在此问题上工作了许多天后,我已经完成了所有其他工作,但是我还有一个DefaultAssignmentId作为Employee类的属性,并且我希望当选择了雇员时,DefaultAssignment可自动获取到datagrid的assignment字段中。 。这是我的第一个WPF应用程序,因此可能是因为我的代码仅在某些奇迹中起作用,因此随时提供一般性提示。我觉得我已经尝试了所有可能的绑定组合,所以现在我不得不寻求帮助,因为我在Google找不到任何东西。

XAML:

             

<Grid Margin="20">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="150"/>
    </Grid.ColumnDefinitions>

    <Grid Grid.Column="0">

        <DataGrid x:Name="assignmentPlanItemsDataGrid" Margin="0,3,0,0" ItemsSource="{Binding DataGridRows, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" AutoGenerateColumns="False" CanUserAddRows="False" SelectedItem="{Binding CurrentItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" IsSynchronizedWithCurrentItem="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Employee" Width="*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding Path = DataContext.EmployeeComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"                                         
                                      SelectedItem="{Binding DataContext.SelectedEmployee,Mode=TwoWay, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
                                      SelectedValue="{Binding EmployeeId, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                      SelectedValuePath="Id"
                                      IsEditable="True"
                                      DisplayMemberPath="FullName">
                            </ComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>

                <DataGridTemplateColumn Header="Assignment" Width="*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding DataContext.AssignmentComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
                                      SelectedItem="{Binding DataContext.SelectedAssignment,Mode=TwoWay, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
                                      SelectedValuePath="Id"
                                      DisplayMemberPath="Description"
                                      SelectedValue="{Binding AssignmentId, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                      IsEditable="True"/>
                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>        
</Grid>

ViewModel:

public class AssignmentPlanItemViewModel : ViewModelBase
{
    DataContext context = new DataContext();

    //the datagrid collection
    private ObservableCollection<AssignmentPlanItem> _dataGridRows = new ObservableCollection<AssignmentPlanItem>();

    //datagrid selected item
    private AssignmentPlanItem _currentItem;

    //combobox itemssource collections
    public ObservableCollection<Employee> EmployeeComboRows { get; set; }
    public ObservableCollection<Assignment> AssignmentComboRows { get; set; }
    public ObservableCollection<WorkStation> WorkStationComboRows { get; set; }

    //the source event for the current assignment plan
    public Event CurrentEvent;

    public AssignmentPlanItemViewModel()
    {
        //populate combobox collections
        EmployeeComboRows = new ObservableCollection<Employee>(context.Employees);
        AssignmentComboRows = new ObservableCollection<Assignment>(context.Assignments);
        WorkStationComboRows = new ObservableCollection<WorkStation>(context.WorkStations);

        //getting the current event (yes, non-MVVM, I know)
        CurrentEvent = context.Events.Find(AssignmentPlanWindow.eventId);

        var planItems = CurrentEvent.AssignmentPlans.Last().AssignmentPlanItems;
        DataGridRows = new ObservableCollection<AssignmentPlanItem>(planItems);
    }

    public AssignmentPlanItem CurrentItem
    {
        get { return _currentItem; }
        set
        {
            if (value != _currentItem)
            {
                _currentItem = value;
                OnPropertyChanged("CurrentItem");
                OnPropertyChanged("DataGridRows");
            }
        }
    }

    public ObservableCollection<AssignmentPlanItem> DataGridRows
    {
        get { return _dataGridRows; }
        set
        {
            _dataGridRows = value;
            OnPropertyChanged("DataGridRows");
        }
    }

    private Employee _selectedEmployee;
    public Employee SelectedEmployee
    {
        get
        {
            return _selectedEmployee;
        }
        set
        {
            if (CurrentItem != null)
            {
                _selectedEmployee = value;
                if (_selectedEmployee != null)
                {
                    CurrentItem.EmployeeId = _selectedEmployee.Id;
                    var defaultAssigment = context.Assignments.Find((int)_selectedEmployee.DefaultAssignmentId);
                    CurrentItem.Assignment = defaultAssigment;
                    CurrentItem.AssignmentId = (int)_selectedEmployee.DefaultAssignmentId;
                    OnPropertyChanged("CurrentItem");
                }
            }
        }

    }

    private Assignment _selectedAssignment;
    public Assignment SelectedAssignment
    {
        get
        {
            return _selectedAssignment;
        }
        set
        {
            if (CurrentItem != null)
            {
                _selectedAssignment = value;
                if (_selectedAssignment != null)
                {
                    CurrentItem.AssignmentId = _selectedAssignment.Id;
                    CurrentItem.Assignment = _selectedAssignment;
                    OnPropertyChanged("CurrentItem");
                }
            }
        }

    }
}

因此,我使用SelectedEmployee和SelectedAssignment属性尝试更改数据网格的所选项目(CurrentItem)。该项已更改,但更改未更新到网格。保存网格后,关闭并重新获得,分配也已更改。

在我尝试过的XAML分配组合框中

<SelectedValue="{Binding DataContext.CurrentItem.AssignmentId, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"/> 

实际更新了视图,但即使我到处都有IsSynchronizedWithCurrentItem=False,它仍将数据网格中所有行的所有赋值字段更改为与CurrentItem中相同的值。

我的模型类没有实现INotifyPropertyChanged,并且ViewModelBase是我从网络上窃取的。

那么,谁能告诉我我做错了什么?

2 个答案:

答案 0 :(得分:0)

INotifyPropertyChange有许多层次需要理解。

分配给列表类型结构时,仅当列表的引用更改时才发出通知;又名新列表已创建。 notify事件不会标记列表中的内容或不在列表中的任何更改,也不会标记列表中可能有属性更改的任何单个项目。

一个可观察的集合会在添加或删除其列表中的项目时发送通知,而不是在列表属性中的单个项目发生更改时发送通知。


如果您希望在更改属性后将其属性反映在数据网格中,则该对象实例必须遵守INotifyPropertyChanged,并且该属性必须使用其属性名称调用PropertyChanged进行广播。

您最有可能拥有一个不遵守INotifyPropertyChanged的DTO对象,因此即使在您的Selected...引用中正确引用了当前实例,包含或显示特定属性值的控件无法知道属性已更改;因为它仅监视该属性名称的更改事件。


在这种情况下,您需要做的是在工作类的基础上创建一个Partial类,并将INotifyPropertyChanged添加到局部类中,并通过变更调用为属性提供覆盖(PropertyChanged("FirstName )(或您的方法调用是什么)),将需要显示其更改。


尽管这并不代表您的直接情况,但这是我的博客文章,它确实显示了人们如何有效使用INotifyPropertyChanged。它在VM上,但是可以将相同的方法应用于部分DTO对象。

Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding

答案 1 :(得分:0)

好的,我在ΩmegaMan的帮助下工作了。解决方案是让AssignmentPlanItem从ViewModelBase继承(即实现INotifyPropertyChanged),然后从以下位置更改AssignmentId属性:

public AssignmentId {get; set; }

private int _assignmentId;
public int AssignmentId
{
    get { return _assignmentId; }
    set
    {
        _assignmentId = value;
        OnPropertyChanged("AssignmentId");
    }
}

datagrid组合框必须具有以下设置(不确定是否还有多余的东西):

<DataGrid x:Name="assignmentPlanItemsDataGrid" Margin="0,3,0,0" ItemsSource="{Binding DataGridRows, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" CanUserAddRows="False" SelectedItem="{Binding CurrentItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="False">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Employee" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path = DataContext.EmployeeComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"                                         
                              SelectedItem="{Binding DataContext.SelectedEmployee,Mode=OneWayToSource, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
                              SelectedValue="{Binding EmployeeId}"
                              SelectedValuePath="Id"
                              IsEditable="True"
                              DisplayMemberPath="FullName"
                              IsSynchronizedWithCurrentItem="False">
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Assignment" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding DataContext.AssignmentComboRows, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
                              SelectedItem="{Binding DataContext.SelectedAssignment,Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
                              SelectedValuePath="Id"
                              DisplayMemberPath="Description"
                              SelectedValue="{Binding AssignmentId}"
                              IsEditable="True"
                              IsSynchronizedWithCurrentItem="False"/>
                </DataTemplate>

            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

并且ViewModel中的SelectedEmployee具有以下代码来更改分配:

private Employee _selectedEmployee;
public Employee SelectedEmployee
{
    get
    {
        return _selectedEmployee;
    }
    set
    {
        if (CurrentItem!= null)
        {
            _selectedEmployee = value;
            OnPropertyChanged("SelectedEmployee");
            if (SelectedEmployee != null)
            {
                CurrentItem.EmployeeId = SelectedEmployee.Id;
                var defaultAssigment = Context.Assignments.Find((int)SelectedEmployee.DefaultAssignmentId);
                CurrentItem.AssignmentId = (int)SelectedEmployee.DefaultAssignmentId;
                CurrentItem.Assignment = defaultAssigment;
            }
    }
}

还有一个棘手的部分,即将ComboBox SelectedItem绑定模式设置为OneWayToSource。否则,该列中的所有组合框都将获得CurrentItem的分配。因此,据我所知,这意味着ComboBox绑定模式负责处理ViewModel和Model的更新,并且Model上的属性更改通知将其通过SelectedValue带回到视图。我仍然不确定它是否可以工作或应该像这样工作,但是无论如何,它可以完全按照我想要的方式工作。