Преобразованные значения из связанных сущностей не обновляются после редактирования свойства - PullRequest
0 голосов
/ 17 марта 2020

Я использую. net core 3.1 в VS2019. У меня есть пользовательский элемент управления, содержащий ListView, перечисляющий набор элементов прайс-листа, каждый из которых имеет стоимость, наценку (проценты), скидку (процент), предоставляемую связанной сущностью, называемой Поощрение, и 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. Отладка windows Я вижу все изменения, примененные правильно в базовой модели представления до и после их подтверждения в базе данных, однако я вынужден принудительно обновить sh значения, как в следующее изображение.

In this animation the problem is well reflected: 1) Enter edit mode to begin editing the selected item. 2) Change the the Promo related entity (

Это окончательная композиция окна:

  • Окно
    • TabControl
      • TabItem (n)
        • PriceListUserControl (мой пользовательский элемент управления)

Вот эта модель:

EF Model

Вот 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, реализует Generi c INotifyPropertyChange и общие свойства для указания текущего выбранного элемента. в этой коллекции и других полезных состояниях

А вот wpf представления, содержащего pr Пользователь eeve:

<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();

    }
}

1 Ответ

0 голосов
/ 25 марта 2020

Я решил проблему, создав новую модель представления, заключающую в себе прежний 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" />

By реальные свойства в новой модели представления:

<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" />

И теперь это работает.

...