【问题标题】:How to raise PropertyChanged for a different Model如何为不同的模型提高 PropertyChanged
【发布时间】:2017-05-29 14:34:53
【问题描述】:

我有以下问题:我的 WPF 应用程序使用 DataGrid 来显示任务列表。这些任务存储在 SQL 数据库中,我们使用实体框架。为了更好的可读性,任务的缩短模型位于此问题的末尾。任务模型分配了一个类别对象。所有类别都存储在数据库中,并且可以在本题末尾看到类别模型。

类别在 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 在代码中设置,例如:

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

这很好用。但是,当我编辑类别(允许编辑类别名称)时,问题就开始了。 ItemsSource 得到相应更新(因此在下拉菜单中,值以新名称显示)。但是,SelectedItem 的绑定不会更新。这意味着已选择已编辑类别的所有 ComboBox 仍显示旧值。

我发现如果我在更改值时这样做:

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

然后立即更新 ComboBox 的 SelectedItem 中的值。我的猜测是当我更改 Category 中的值时,不会为 Task 调用 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;
        }
    }
}

编辑

我确定了问题的根源。首先,我添加了DisplayMemberPath="Name",就像 Ed 在 cmets 中建议的那样。它没有帮助。在找到源代码之前最小化示例应用程序后,我应用于 ComboBox 的样式似乎是问题的根源。该样式旨在在下拉菜单中向组合框项目添加一个“删除”和一个“编辑”按钮,但在未选择项目时显示一个简单的文本框。看看你自己:

<!--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>

【问题讨论】:

  • 我不确定你实际改变了什么价值,应该有什么影响,以及你的实体与另一个实体之间的关系。你能试着澄清你的问题吗?也许至少显示一个屏幕截图,因此如果您不想显示完整的 XAML,那么很清楚哪些控件绑定到了哪些内容。此外,由于this.Tasks 似乎是一个集合,如果您想正确跟踪更改,您可能会对ObservableCollection 感兴趣。
  • “我的猜测是,当我更改 Category 中的值时,不会为 Task 调用 PropertyChanged。” -- 对!你没有做任何可能导致这种情况发生的事情,所以它不会发生。如果Category 需要在其属性更改时通知 UI,Category 必须在每个属性上引发PropertyChanged,没有任何异常,并且其Tasks 必须是ObservableCollection。跨度>
  • 作为一项规则,OnPropertyChanged 应该受到保护。通常,任何视图模型都不应该为不同的视图模型引发PropertyChanged(这些是视图模型,而不是模型)。如果您尝试这样做,请返回并找出导致您要解决的问题的错误设计决策。
  • @poke 我稍后会编辑这个问题。我认为 XAML 就足够了,我不想把它搞砸。基本上,我想更改 Category 对象的名称并实现这会在所有具有此类别对象的 Task 对象中引发 PropertyChanged 事件。
  • @EdPlunkett 好吧,我现在解决了这个问题。部分感谢你。我开始修改我的所有代码以构建一个工作最小的示例。我非常感谢您的时间和友善。我的问题可能非常愚蠢,我道歉。我还是新手

标签: c# wpf binding datagrid


【解决方案1】:

我发现出了什么问题。请记住:使用您自己的模板时,请始终确保正确更新您的更改。在我的情况下,我的 ComboBoxItem 中的绑定依赖于 DataContext:

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

所以,为了订阅 PropertyChanged 触发器,我明确添加了绑定路径:

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

我觉得自己很愚蠢,但我 4 周前开始使用 WPF,以前从未使用过它,突然间我成为了 WPF 程序员。请原谅我的粗心。

编辑

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

【讨论】:

  • 顺便说一句,你需要的只是{Binding Name}——在这种特殊情况下,所有 UpdateSourceTrigger 等标志都是无操作的。
猜你喜欢
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 2013-05-27
  • 1970-01-01
  • 1970-01-01
  • 2019-08-11
  • 1970-01-01
  • 2019-10-21
相关资源
最近更新 更多