Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM WPF Master Detail Comboboxes

Thanks to some of the advice I have got previously on Stack Overflow I have been making good progress in my understanding of MVVM. However, it is when things start to get more complicated that I am still struggling.

I have the view below which is for the purpose of entering orders. It is bound to a DataContext of OrderScreenViewModel.

<StackPanel>
    <ComboBox Height="25" Width="100" DisplayMemberPath="CustomerCode" SelectedItem="{Binding Path=Order.Customer}" ItemsSource="{Binding Path=Customers}"></ComboBox>
    <ComboBox Height="25" Width="100" DisplayMemberPath="ProductCode" SelectedItem="{Binding Path=CurrentLine.Product}" ItemsSource="{Binding Path=Products}"></ComboBox>
</StackPanel>

The first combobox is used to select a Customer. The second combobox is used to select a ProductCode for a new OrderLine.

There are the items that I cannot work out how to achieve in MVVM:
1) When a Customer is selected update the Products combobox so that its item source only shows Products that have the same CustomerId as the CustomerDto record selected in the combobox
2) When Load is called set the SelectedItem in the Customers combobox so that it displays the Customer with the CustomerId equal to the one on the OrderDto.
3) Apply, the same process as 1) so that only Products belonging to that Customer are shown / loaded and set the SelectedItem on the Products combobox so that it is pointing to the entry with the same ProductId as is contained on the OrderLineDto

I am not sure how to proceed or even if I have got the responsibilities of my viewmodels correct. Maybe it has something to do with NotifyPropertyChanged? Any pointers on how I can achieve the above will be greatly appreciated. I am sure if I get this right it will help me greatly in my app. Many thanks Alex.

 public class OrderScreenViewModel
    {
        public WMSOrderViewModel Order { get; private set; }
        public WMSOrderLineViewModel CurrentLine { get; private set; }

        public OrderScreenViewModel()
        {
            Order = new WMSOrderViewModel();
            CurrentLine = new WMSOrderLineViewModel(new OrderLineDto());
        }

        public void Load(int orderId)
        {
            var orderDto = new OrderDto { CustomerId = 1, Lines = new List<OrderLineDto> { new OrderLineDto{ProductId = 1 }} };
            Order = new WMSOrderViewModel(orderDto);
        }

        public List<CustomerDto> Customers
        {
            get{
                return new List<CustomerDto> { 
                        new CustomerDto{CustomerId=1,CustomerCode="Apple"},
                        new CustomerDto{CustomerId=1,CustomerCode="Microsoft"},
                };
            }
        }

        public List<ProductDto> Products
        {
            get
            {
                return new List<ProductDto> { 
                    new ProductDto{CustomerId=1,ProductId=1,ProductCode="P100",Description="Pepsi"},
                    new ProductDto{CustomerId=1,ProductId=2,ProductCode="P110",Description="Coke"},
                    new ProductDto{CustomerId=2,ProductId=3,ProductCode="P120",Description="Fanta"},
                    new ProductDto{CustomerId=2,ProductId=4,ProductCode="P130",Description="Sprite"}
                };
            }
        }
    public class WMSOrderLineViewModel
    {
        private ProductDto _product;
        private OrderLineDto _orderLineDto;

        public WMSOrderLineViewModel(OrderLineDto orderLineDto)
        {
            _orderLineDto = orderLineDto;
        }

        public ProductDto Product { get { return _product; } 
            set{_product = value; RaisePropertyChanged("Product"); }
    }

    public class WMSOrderViewModel
    {
        private ObservableCollection<WMSOrderLineViewModel> _lines;
        private OrderDto _orderDto;
        public ObservableCollection<WMSOrderLineViewModel> Lines { get { return _lines; } }
        private CustomerDto _customer;

        public CustomerDto Customer { get{return _customer;} set{_customer =value; RaisePropertyChanged("Customer") }

        public WMSOrderViewModel(OrderDto orderDto)
        {
            _orderDto = orderDto;
            _lines = new ObservableCollection<WMSOrderLineViewModel>();
            foreach(var lineDto in orderDto.Lines)
            {
                _lines.Add(new WMSOrderLineViewModel(lineDto));
            }
        }

        public WMSOrderViewModel()
        {
            _lines = new ObservableCollection<WMSOrderLineViewModel>();
        }
    }
like image 203
11 revs Avatar asked Oct 08 '22 18:10

11 revs


1 Answers

You need to make Products and Customers type ObservableCollection.

When you change these observablecollections in your viewmodel they will update the view, because OC's already implement INotifyPropertyChanged.

Order and CurrentLine should just be a type and not really be called a ViewModel.

1) You're going to have to do this when the setter is called on the SelectedItem of the Customer combobox is selected.

2) You'll need to do this probably in the ctr of the OrderScreenViewModel by using your logic to determine what Customer to change the CurrentLine.Customer too. If you do this in the ctr, this will set the value before the binding takes place.

3) Again, as long as you make changes to the ObservableCollection the combobox is bound to, it will update the UI. If you make a change to what the SelectedItem is bound to just make sure you call RaisedPropertyChanged event.

ETA: Change the xaml to this, bind to SelectedProduct and SelectedCustomer for the SelectedItem properties

<StackPanel>
    <ComboBox Height="25" Width="100" DisplayMemberPath="CustomerCode" SelectedItem="{Binding Path=SelectedCustomer}" ItemsSource="{Binding Path=Customers}"></ComboBox>
    <ComboBox Height="25" Width="100" DisplayMemberPath="ProductCode" SelectedItem="{Binding Path=SelectedProduct}" ItemsSource="{Binding Path=Products}"></ComboBox>
</StackPanel>

this should get you started in the right direction, everything, all logic for building customers and products by the customer id needs to happen in your repositories.

   public class OrderScreenViewModel : INotifyPropertyChanged
   {
      private readonly IProductRepository _productRepository;
      private readonly ICustomerRepository _customerRepository;

      public OrderScreenViewModel(IProductRepository productRepository,
         ICustomerRepository customerRepository)
      {
         _productRepository = productRepository;
         _customerRepository = customerRepository;

         BuildCustomersCollection();
      }

      private void BuildCustomersCollection()
      {
         var customers = _customerRepository.GetAll();
         foreach (var customer in customers)
            _customers.Add(customer);
      }

      private ObservableCollection<Customer> _customers = new ObservableCollection<Customer>();
      public ObservableCollection<Customer> Customers
      {
         get { return _customers; }
         private set { _customers = value; }
      }

      private ObservableCollection<Product> _products = new ObservableCollection<Product>();
      public ObservableCollection<Product> Products
      {
         get { return _products; }
         private set { _products = value; }
      }

      private Customer _selectedCustomer;
      public Customer SelectedCustomer
      {
         get { return _selectedCustomer; }
         set
         {
            _selectedCustomer = value;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
            BuildProductsCollectionByCustomer();
         }
      }

      private Product _selectedProduct;
      public Product SelectedProduct
      {
         get { return _selectedProduct; }
         set
         {
            _selectedProduct = value;
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
            DoSomethingWhenSelectedPropertyIsSet();
         }
      }

      private void DoSomethingWhenSelectedPropertyIsSet()
      {
         // elided
      }

      private void BuildProductsCollectionByCustomer()
      {
         var productsForCustomer = _productRepository.GetById(_selectedCustomer.Id);
         foreach (var product in Products)
         {
            _products.Add(product);
         }
      }

      public event PropertyChangedEventHandler PropertyChanged = delegate { };
   }

   public interface ICustomerRepository : IRepository<Customer>
   {
   }

   public class Customer
   {
      public int Id { get; set; }
   }

   public interface IProductRepository : IRepository<Product>
   {
   }

   public class Product
   {
   }

Here's what the standard IRepository looks like, this is called the Repository Pattern:

   public interface IRepository<T>
   {
      IEnumerable<T> GetAll();
      T GetById(int id);
      void Save(T saveThis);
      void Delete(T deleteThis);
   }
like image 121
Mark W Avatar answered Oct 13 '22 11:10

Mark W