【发布时间】: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 好吧,我现在解决了这个问题。部分感谢你。我开始修改我的所有代码以构建一个工作最小的示例。我非常感谢您的时间和友善。我的问题可能非常愚蠢,我道歉。我还是新手