wpf - 编辑属性后不刷新来自相关实体的转换值
问题描述
我在 VS2019 中使用 .net core 3.1。我有一个用户控件,其中包含一个 ListView,其中列出了价目表的一组项目,每个项目都有一个成本、标记(百分比)、一个名为 Promotion 的相关实体给出的折扣(百分比)以及SalePrice、FinalPrice 和 PromoDescription字段由转换器计算。例如,如果商品没有促销, FinalPrice等于 SalePrice,否则为 (SalePrice - SalePrice*item.Promotion.Discount/100.0)。
顺便说一句,所有这些实体都属于带有代理延迟加载的 EF Core 模型,并且所有集合都是 ObervableCollection,它们的加载方式如下:
var dbset = Context.Promos;
dbset.Load();
return dbset.Local.ToObservableCollection();
一切正常,除非我更改标记或促销属性,这会导致重新计算转换器提供的值并且不涉及或涉及 Promo 类型的相关实体。调试窗口我可以看到在确认到数据库之前和之后在底层视图模型中正确应用的所有更改,但是我必须强制它刷新值,如下图所示。
这是窗口的最终组成:
- 窗户
- 选项卡控件
- 选项卡项 (n)
- PriceListUserControl(我的用户控件)
- 选项卡项 (n)
- 选项卡控件
这是模型:
这是包含列表视图的用户控件的 wpf:
<UserControl x:Class="MyApp.View.UserControls.PriceListUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyApp.View.UserControls"
xmlns:localModel="clr-namespace:MyApp.Model"
xmlns:localVm="clr-namespace:MyApp.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="UserControlPriceList">
<UserControl.Resources>
<localVm:PriceListItemConverter x:Key="priceListItemConverter"/>
<localVm:DateConverter x:Key="dateConverter"/>
<localVm:PromoConverter x:Key="promoConverter" />
<Border Style="{StaticResource BorderStyle}" Name="bottomBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="106" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Producto:</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Costo:</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Markup (%):</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Precio de Lista:</TextBlock>
<TextBlock Grid.Row="4" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Promoción:</TextBlock>
<TextBlock Grid.Row="5" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Precio Final:</TextBlock>
<TextBlock Name="ProductDescription" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=Product.Description}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="CostPrice" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=Product.CostPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBox Name="MarkupPriceEntryForm" AutomationProperties.Name="Markup" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding ElementName=UserControlPriceList, Path=DataContext.IsReadOnly}" >
<TextBox.Text>
<Binding Path="Markup" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Name="SalePrice" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=SalePrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="Promotion" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=PromoFriendlyDescription}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5"
Visibility="{Binding ElementName=UserControlPriceList, Path=DataContext.HideIfEditing}"/>
<ComboBox Name="PromosComboBox" AutomationProperties.Name="PromoDiscount" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left"
Text="(Seleccione promo)"
ItemsSource="{Binding ElementName=UserControlPriceList, Path=DataContext.PromoCollection}"
SelectedValue="{Binding ElementName=UserControlPriceList, Path=DataContext.Current.PromoId, Mode=TwoWay}"
SelectedValuePath="Id"
Style="{StaticResource ComboBoxStyle}"
ItemContainerStyle="{StaticResource ComboBoxItemStyle}" Margin="8,5,0,5"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
IsDropDownOpen="False"
StaysOpenOnEdit="True"
IsEditable="True" IsReadOnly="False"
Visibility="{Binding ElementName=UserControlPriceList, Path=DataContext.ShowIfEditing}"
Width="{Binding ElementName=Promotion, Path=Width}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type localModel:Promo}">
<TextBlock Text="{Binding Converter={StaticResource promoConverter}, ConverterParameter=PromoFriendlyDescription}"/>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
<TextBlock Name="FinalPrice" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=FinalPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MediumTitleStyle}" Margin="5"
Text="{Binding Path=List.Name, StringFormat='Lista: {0}'}" />
<TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Style="{StaticResource MediumTitleStyle}" Margin="5" HorizontalAlignment="Right"
Text="{Binding Path=List.PrintDate, StringFormat='Última impresión: {0:dd/MM/yyyy}'}" />
<Border Grid.Row="1" Style="{StaticResource BorderStyle}" Grid.ColumnSpan="4">
<ListView Name="ListViewPriceListItems" Grid.Row="1" Grid.ColumnSpan="4"
ItemsSource="{Binding Path=Items}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="OnPriceListItemSelectionChanged"
MouseDoubleClick="EnterPriceListItemEditMode"
IsEnabled="{Binding Path=IsReadOnly}">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Detalle de productos, precios finales y promociones">
<GridViewColumn DisplayMemberBinding="{Binding Path=Product.Description}" Header="Producto" Width="300"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Product.CostPrice, StringFormat=C}" Header="Costo"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Markup, StringFormat=N2}" Header="Markup (%)" />
<GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=SalePrice, StringFormat=C}" Header="Precio Lista"/>
<GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=PromoFriendlyDescription}" Header="Promoción" Width="200" />
<GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=FinalPrice, StringFormat=C}" Header="Precio Final" />
</GridView>
</ListView.View>
</ListView>
</Border>
<ContentControl Name="PriceListDetail" Grid.Row="2" Grid.ColumnSpan="4"
Content="{Binding Path=Current}"
ContentTemplate="{StaticResource PriceListItemDetailTemplate}"
Margin="9,0,0,0" />
<StackPanel Grid.Row="3" Grid.ColumnSpan="4" Orientation="Horizontal" HorizontalAlignment="Right">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button Name="EditButton" HorizontalAlignment="Right" Content="_Modificar" Style="{StaticResource AcctionButtonStyle}"
ToolTip="Editar precio y promoción del producto seleccionado en esta lista."
Click="EditSelectedPriceListItem" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="SaveButton" HorizontalAlignment="Right" Content="_Aceptar" Style="{StaticResource ActionButtonOKStyle}"
ToolTip="Guardar los cambios realizados."
Click="SavePriceListItem" Visibility="{Binding Path=ShowIfEditing}"/>
<Button Name="CancelButton" HorizontalAlignment="Right" Content="_Cancelar" Style="{StaticResource ActionButtonCancelStyle}"
ToolTip="Deshacer los cambios realizados."
Click="UndoPriceListItem" Visibility="{Binding Path=ShowIfEditing}"/>
</StackPanel>
</Grid>
</Border>
这是它的视图模型:
public partial class PriceListItemsViewModel: BaseViewModel<PriceListItem>
{
private PriceList _list;
public PriceListItemsViewModel(PriceList list, ObservableCollection<Promo> promoCollection)
{
this.List = list ?? throw new ArgumentNullException(nameof(list));
this.PromoCollection = promoCollection;
this.Items = list.Products;
this.IsEditMode = false;
this.IsNew = false;
this.Current = this.Items.FirstOrDefault();
}
public PriceList List { get => this._list; set => this._list = value; }
public ObservableCollection<Promo> PromoCollection { get; }
}
注意:BaseViewModel 有一个 ObservableCollection,实现了 Generic INotifyPropertyChange,以及用于指示该集合中的当前选定项和其他有用状态的通用属性
这里是包含先前用户控件的视图的 wpf:
<Window.Resources>
<uc:PriceListUserControl x:Key="userControlPList"/>
</Window.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="TabControlPriceLists" Grid.Row="1" Grid.ColumnSpan="3" TabStripPlacement="Top"
ItemsSource="{Binding Items}"
SelectedItem="{Binding Current}"
IsEnabled="{Binding IsReadOnly}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type localVm:PriceListItemsViewModel}">
<uc:PriceListUserControl x:Name="UserControlPriceList"/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding List.Name}" />
<Setter Property="MaxWidth" Value="100" />
<Setter Property="ToolTip" Value="{Binding List.Name}" />
<Setter Property="Background" Value="Bisque" />
<Setter Property="FontWeight" Value="DemiBold" />
<EventSetter Event="MouseDoubleClick" Handler="OnTabItemDoubleClick"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Border>
及其视图模型:
public class PriceListsViewModel : BaseViewModel<PriceListItemsViewModel>
{
private ObservableCollection<Promo> promoCollection;
public PriceListsViewModel()
{
var promoDefaultItems = new List<Promo>
{
new Promo { FriendlyName = PromoConverter.PromoFriendlyNameNoPromo, DiscountPct = 0.0F } //Workaround: Adds "(No promo) to allow the user to left an item without promotion
//new Promo { FriendlyName = PromoConverter.PromoFriendlyNameNewPromo, DiscountPct = 0F }
};
this.promoCollection = new ObservableCollection<Promo>(promoDefaultItems);
var promos = PromoRepository.PromoGetAll();
foreach (var promo in promos)
{
this.promoCollection.Add(promo);
}
var lists = PriceListRepository.PriceListGetAll();
this.Items = new ObservableCollection<PriceListItemsViewModel>();
foreach (var l in lists)
{
this.Items.Add(new PriceListItemsViewModel(l, promoCollection));
}
this.IsEditMode = false;
this.IsNew = false;
this.Current = this.Items.FirstOrDefault();
}
}
解决方案
我通过创建一个新的视图模型来解决这个问题,该模型包含以前的 PriceListItem 并提供真实属性并通知它们的更改。这是新的视图模型(仅显示新属性的相关代码)
public class PriceListItemViewModel : SupervisedEntity
{
private PriceListItem _item;
public PriceListItemViewModel(PriceListItem item)
{
this._item = item ?? throw new ArgumentNullException(nameof(item));
//Bind the parent properties to its dependants (actually they are computed properties without setter) so they notify property changes when their parent property changes.
this.AddDependentProperty(nameof(this.PromoId), nameof(this.SalePrice));
this.AddDependentProperty(nameof(this.PromoId), nameof(this.FinalPrice));
this.AddDependentProperty(nameof(this.PromoId), nameof(this.PromoFriendlyDescription));
this.AddDependentProperty(nameof(this.Markup), nameof(this.SalePrice));
this.AddDependentProperty(nameof(this.Markup), nameof(this.FinalPrice));
}
// The properties and code not related to the solution were removed for clarity
public int? PromoId
{
get => this._item.PromoId;
set
{
this._item.PromoId = (value is null || value == 0) ? null : value;
this.NotifyPropertyChanged(); //This notifies not only the change in PromoId but also in all its dependent properties
}
}
public string ProductDescription => this._item.Product.Description;
public float CostPrice => this._item.Product.CostPrice;
[Required]
public float Markup
{
get => this._item.Markup;
set
{
this._item.Markup = value;
this.NotifyPropertyChanged();
}
}
//Using properties instead of extension methods allows me to create notification for them as dependent
public float SalePrice => this._item.SalePrice();
public float FinalPrice => this._item.FinalPrice();
public int Id => this.Item.Id;
public string PromoFriendlyDescription => this._item.PromoId is null ? string.Empty : this._item.Promotion.ToString();
}
在用户控件的 xaml 中,我用新视图模型中的实际属性替换了所有转换器,当它们依赖的属性之一发生更改时,它们没有得到通知。首先,用新的视图模型替换模型的类,如下所示:
<DataTemplate x:Key="PriceListItemDetailTemplate" DataType="{x:Type localVm:PriceListItemViewModel}">
然后替换更改的属性中对转换器的所有引用:
<TextBlock Name="SalePrice" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=SalePrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="FinalPrice" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=FinalPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
通过新视图模型中的真实属性:
<TextBlock Name="SalePrice" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=SalePrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="FinalPrice" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FinalPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
现在它可以工作了。
推荐阅读
- docker - docker:来自守护进程的错误响应:OCI 运行时创建失败:无效挂载 {Destination::/app Type:bind on windows 10
- c# - 使用Epplus删除Excel中的重复行
- python - 无法理解具有两个循环的列表理解
- opencv - 使用移动相机从立面图像创建全景图
- python - Ipywidgets 和 plotly 交互
- r - tidyr::expand_grid() 未按预期运行;我错过了什么?
- node.js - Nodejs Netsuite 不安的错误:(节点:1128)UnhandledPromiseRejectionWarning:未处理的承诺拒绝
- reactjs - next build `pages/404` 不能有 getInitialProps/getServerSideProps 但我没有 getInitialProps 或 getServerSideProps
- r - 如何使用ggplot为多列创建条形图?
- python - 相同的pyechart在不同的模板中渲染不同