如何为不同的模型引发PropertyChanged

时间:2017-05-29 14:34:54

标签: c# wpf binding datagrid

我有以下问题:我的WPF应用程序使用DataGrid显示任务列表。这些任务存储在SQL数据库中,我们使用实体框架。缩短的任务模型位于此问题的最后,以提高可读性。任务模型已分配Category对象。所有类别都存储在数据库中,类别模型也可以在这个问题的最后看到。

类别在DataGrid中显示为ComboBox,因此您可以选择:

<Window.Resources>    
    <CollectionViewSource x:Key="categoryViewSource" d:DesignSource="{d:DesignInstance {x:Type Models:Category}, CreateList=True}"/>
    <CollectionViewSource x:Key="taskViewSource" d:DesignSource="{d:DesignInstance {x:Type Models:Task}, CreateList=True}"/>
</Window.Resources>
<DataGrid DataContext="{StaticResource taskViewSource}" ItemsSource="{Binding}" EnableRowVirtualization="True">
    <DataGrid.Columns>
        [DataGridTemplateColumn => DataGrid.CellTemplate => DataTemplate and then:]
        <ComboBox x:Name="categoryValues" 
              IsSynchronizedWithCurrentItem="False"
              ItemsSource="{Binding Source={StaticResource categoryViewSource},
                    Mode=OneWay}"
              SelectedItem="{Binding Category,
                    Mode=TwoWay,
                    UpdateSourceTrigger=PropertyChanged}"/>
    </DataGrid.Columns>
</DataGrid>

ViewSources在Code中设置,例如:

await db.Categories.LoadAsync();
categoryViewSource.Source = db.Categories.Local;

这很好用。但是,当我编辑类别(允许编辑类别名称)时,问题就开始了。 ItemsSource相应地更新(因此在下拉菜单中,值显示为新名称)。但是,SelectedItem的Binding不会更新。这意味着所有已经编辑过的类别的组合框仍然显示旧值。

我发现如果我在更改值时执行此操作:

public void ChangeCategoryName(string name, Category c) 
{
    c.Name = name;
    foreach (var task in c.Tasks) 
    {
        task.Category = null;
        task.Category = c;
    }
}

然后立即在ComboBox的SelectedItem中更新该值。我的 guess 是我更改Task中的值时,不会为Category调用PropertyChanged。我试着像这样强迫事件:

public virtual void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    // Force Tasks to update
    this.Tasks?.ForEach(t =>
    {
        t.OnPropertyChanged("Category");
    });
}

那不起作用。有没有人知道如何解决这个问题?

以下是我的模型的代码。

namespace Application.Models 
{
    public class Task : INotifyPropertyChanged
    {
        [Key]
        public int TaskId { get; set; }

        [...]

        // Category is defined in anothre table
        public int? CategoryId { get; set; }
        [ForeignKey("CategoryId")]
        // The Category object will automatically be loaded if access is needed
        public virtual Category Category { get; set; }

        // PropertyChanged Event
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class Category : INotifyPropertyChanged
    {
        [Key]
        public int CategoryId { get; set; }
        public string Name { get; set; }
        // List of Task objects that are associated to this Category object
        public virtual List<Task> Tasks { get; set; }

        // PorpertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void OnPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public override string ToString()
        {
            return Name;
        }
    }
}

修改

我确定了问题的根源。首先,我在评论中添加了Ed建议的DisplayMemberPath="Name"。它没有帮助。在最小化示例性应用程序直到找到源代码之后,样式I应用于ComboBox似乎是问题的根源。该样式旨在在DropDown中向ComboBox项添加“删除”和“编辑”按钮,但在不选择项时显示简单的TextBox。看看自己:

<!--Templates for different Item Styles in ComboBoxes-->
<!--This template is a simple TextBox-->
<ControlTemplate x:Key="SimpleComboBoxItem">
    <StackPanel>
        <TextBlock Text="{Binding UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" />
    </StackPanel>
</ControlTemplate>
<!--This template contains a remove button and is only shown in the DropDown Menu-->
<ControlTemplate x:Key="ExtendedComboBoxItem" >
    <DockPanel HorizontalAlignment="Stretch">
        <TextBlock Text="{Binding UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" />
        <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button Style="{StaticResource ComboBoxRemoveButton}"
                x:Name="EditItemFromComboBoxButton" Click="EditItemFromComboBoxButton_Click"
                Visibility="{Binding Converter={StaticResource RemoveXFromNullValuesConverter}}">
                <iconPacks:PackIconMaterial Kind="Pencil" VerticalAlignment="Center" HorizontalAlignment="Center" Height="8" Width="8"/>
            </Button>
            <Button Style="{StaticResource ComboBoxRemoveButton}"
                x:Name="RemoveItemFromComboBoxButton" Click="RemoveItemFromComboBoxButton_Click"
                Visibility="{Binding Converter={StaticResource RemoveXFromNullValuesConverter}}">
                <iconPacks:PackIconMaterial Kind="Close" VerticalAlignment="Center" HorizontalAlignment="Center" Height="8" Width="8"/>
            </Button>
        </StackPanel>
    </DockPanel>
</ControlTemplate>
<!--Style the ComboBox Items so they can align the Remov "X" button to the right--> 
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}" x:Key="RemovableComboboxItem">
    <Setter Property="HorizontalAlignment" Value="Stretch"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<!--This is the DateTemplate for the ComboBox ItemTemplate-->
<DataTemplate x:Key="RemovableComboBoxItemTemplate">
    <Control x:Name="RemoveableItemsComboBoxControl" Focusable="False" Template="{StaticResource ExtendedComboBoxItem}" />
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
            <Setter TargetName="RemoveableItemsComboBoxControl" Property="Template" Value="{StaticResource SimpleComboBoxItem}" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>
<!--ComboBox Style for ComboBoxes with a X for removing the item-->
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}" x:Key="RemoveItemComboBox">
    <Setter Property="ItemContainerStyle" Value="{StaticResource RemovableComboboxItem}"/>
    <Setter Property="ItemTemplate" Value="{StaticResource RemovableComboBoxItemTemplate}"/>
</Style>

1 个答案:

答案 0 :(得分:1)

我发现了什么问题。请记住:使用自己的模板时,请务必确保正确更新更改。在我的例子中,我的ComboBoxItem中的Binding依赖于DataContext:

<ControlTemplate x:Key="SimpleComboBoxItem">
    <StackPanel>
        <TextBlock Text="{Binding}" />
    </StackPanel>
</ControlTemplate>

因此,为了订阅PropertyChanged Trigger,我明确地添加了绑定路径:

<ControlTemplate x:Key="SimpleComboBoxItem">
    <StackPanel>
        <TextBlock Text="{Binding Name}" />
    </StackPanel>
</ControlTemplate>

我觉得很愚蠢,但是我在4周前开始使用WPF,之前从未使用过它,突然间我成了WPF程序员。请原谅我的无礼。

修改

删除了一些不必要的绑定指令。感谢Ed!