简体   繁体   中英

WPF-MVVM, Master - Detail datagrid with calculated column. Rate * Quantity = Total

I have data entry form bound to viewmodel. I follow EF code-first approach and the details tables are represented by observable collections in my models, like below.

public partial class item
{ 
    public item()
    {
        orders = new ObservableCollection<order>();
    }
}

Everything works fine but in few cases I have a master entry with a detail in the form of grid. Here's xaml part which binds SelectedItem.Orders to detail grid.

<UserControl x:Class="ABCD.Views.itemView"
    ....
    DataContext="{Binding itemMaster, Source={StaticResource Locator}}"
    Height="Auto" Width="Auto">
    <Grid> 
        <Grid.ColumnDefinitions.... />
        <Grid.RowDefinitions..../>
        <DataGrid AutoGenerateColumns="False" VerticalAlignment="Top" ItemsSource="{Binding items}" SelectedItem="{Binding SelectedItem}" Name="dgitems" HorizontalAlignment="Stretch" RowDetailsVisibilityMode="VisibleWhenSelected" >
             <DataGrid.Columns>
                        <DataGridTextColumn x:Name="ItemNameColumn" Binding="{Binding Path=ItemName}" Header="Item Name" Width="125" />
                        <DataGridTextColumn x:Name="ItemCodeColumn" Binding="{Binding Path=ItemCode}" Header="Item Code" Width="75" />
                        <DataGridTextColumn x:Name="StockColumn" Binding="{Binding Path=StockLevel}" Header="Stock" Width="60" />
             </DataGrid.Columns>
        </DataGrid>
        <Label Grid.Column="3" Grid.Row="0" Margin="3,3,3,3" Content="Item Code"      VerticalAlignment="Center" />
        <TextBox Grid.Column="4" Grid.Row="0" HorizontalAlignment="Left" Margin="3,3,3,3" Name="ctrlItemCode" Text="{Binding Path=SelectedItem.ItemCode, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" Width="120" Height="Auto" VerticalContentAlignment="Center" />
        <Label Grid.Column="3" Grid.Row="2" Margin="3,3,3,3" Content="Product Name" VerticalAlignment="Center" />
        <TextBox Grid.Column="4" Grid.Row="2" HorizontalAlignment="Left" Margin="3,3,3,3" Name="ctrlProductName" Text="{Binding Path=SelectedItem.ItemName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" Width="120" Height="Auto" VerticalContentAlignment="Center" />
        <DataGrid Grid.Column="2" Grid.Row="16" Grid.ColumnSpan="3"  Height="145" AutoGenerateColumns="False" VerticalAlignment="Top" ItemsSource="{Binding SelectedItem.Orders}"  Name="dgOrders" HorizontalAlignment="Stretch" >
             <DataGrid.Columns>
                  <DataGridTemplateColumn Width="120">
                       <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                 <ComboBox DisplayMemberPath="OrderNumber" SelectedValuePath="OrderId"  ItemsSource="{Binding Path=DataContext.Orders,   RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" SelectedValue="{Binding Path=OrderId}" />
                             </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                  </DataGridTemplateColumn>
                  <DataGridTextColumn x:Name="QtyColumn" Binding="{Binding Path=Quantity}" Header="Qty" Width="75" />
                  <DataGridTextColumn x:Name="PriceColumn" Binding="{Binding Path=Price}" Header="Price" Width="75" />
                  <DataGridTextColumn x:Name="TotalColumn" Binding="{Binding Path=Total}" Header="Total" Width="75" />
              </DataGrid.Columns>
        </DataGrid>
   </Grid>
 </UserControl>

In such cases, I want to calculate the total as soon as the user types price and quantity in the grid. I know it can be handled if we have related property in viewmodel. But in my case, the Orders is a collection in item model.

Thanks in advance.

Ok, After searching and seeking support in the forums, I found the solution which suits my case. Just posting it here so it's useful for somebody.

Briefing the final answer which worked for me. What can be done is to wrap each entity with a viewmodel. So the datagrid that uses that collection there binds it's itermssource to an observablecollection of OrderVM rather than Order (which is of entity type ).

And you can then wrap properties of the entity with properties in my viewmodel and put any extra business logic in there like calculations and raising property changed on a calculated field when one of it's factors changes.

According to this suggestion the below changes in my code helped me to get the issue resolved; An OrderVM collection like below;

 public class OrderVM : baseViewModel
{
    public OrderVM()
    {   }
    private order _order;
    public order Order
    {
        get
        {
            return _order;
        }
        set
        {
            _order = value;
            NotifyPropertyChanged();
        }
    }
public decimal Percentage
    {
        get
        {
            return (decimal)_order.Percentage; //Without passing the selectedOrder.Percentage like this, It doesn't work
        }
        set
        {
            _order.Percentage = value;
            NotifyPropertyChanged();
            calculate();
        }
    }
    public decimal Qty
    {
        get
        {
            return (decimal)_order.Qty;
        }
        set
        {
            _order.Qty = value;
            NotifyPropertyChanged();
            calculate();
        }
    }
    public decimal Total
    {
        get
        {
            return (decimal)_order.Total;
        }
        set
        {
            _order.Total = value;
            NotifyPropertyChanged();
        }
    }
    private void calculate()
    {
        _order.Total = _order.Price * _order.Qty;
        NotifyPropertyChanged("Total");
    }
 }

The itemVM (MainVM) is now having below three important properties, Orders = ObservableCollection of orderVM SelectedItem //Item selected for edit SelectedOrder //Order selected for edit

public class itemsVM : baseViewModel
{
    private item _selectedItem;
    private ObservableCollection<OrderVM> _orders;
    public ObservableCollection<OrderVM> Orders
    {
        get
        {
            if (SelectedItem != null)
            {
                _orders = new ObservableCollection<OrderVM>();
                foreach (Order ord in SelectedItem.Orders)
                {
                    _orders.Add(new OrderVM { Order = ord });
                }
                return _orders;
            }
            else
                return null;
        }
        set { _orders = value; }
    }

//SelectedItem property public item SelectedItem { get { return _selectedItem; } set { _selectedItem = value; NotifyPropertyChanged(); NotifyPropertyChanged("CanModify"); NotifyPropertyChanged("Orders"); } }

private OrderVM _selectedorder;
    public OrderVM SelectedOrder
    {
        get
        {
            return _selectedorder;
        }
        set
        {
            _selectedorder = value;
            NotifyPropertyChanged();
        }
    }
}

And my xaml as below;

<DataGrid Grid.Column="2" Grid.Row="16" Grid.ColumnSpan="3"  Height="145" AutoGenerateColumns="False" VerticalAlignment="Top" 
      ItemsSource="{Binding Orders}" SelectedItem="{Binding SelectedOrder}" Name="dgOrders" HorizontalAlignment="Stretch" >
    <DataGrid.Columns>
        <DataGridTextColumn x:Name="QtyColumn" Binding="{Binding Path=Qty}" Header="Qty" Width="75" />
        <DataGridTextColumn x:Name="PriceColumn" Binding="{Binding Path=Price}" Header="Price" Width="75" />
        <DataGridTextColumn x:Name="TotalColumn" Binding="{Binding Path=Total}" Header="Price" Width="75" />
    </DataGrid.Columns>
</DataGrid>

The total conversation can be found at Conversation at MSDN Forums

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM