MVVM WPF主从ComboBoxes

5

感谢之前在Stack Overflow上得到的一些建议,我在理解MVVM方面取得了良好的进展。然而,当事情变得更加复杂时,我仍然感到困惑。

我有一个视图,用于输入订单。它绑定到OrderScreenViewModel的DataContext。

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

第一个组合框用于选择客户。第二个组合框用于为新的订单行选择产品代码。
以下是我无法在MVVM中实现的项目: 1) 当选择客户时,更新产品组合框,以便其项源仅显示具有与组合框中选择的CustomerDto记录相同的CustomerId的产品。 2) 当调用Load时,在客户组合框中设置SelectedItem,使其显示具有等于OrderDto上的CustomerId的客户。 3) 应用与1)相同的过程,以便仅显示/加载属于该客户的产品,并设置Products组合框中的SelectedItem,使其指向与OrderLineDto上包含的ProductId相同的条目。
我不确定如何继续,甚至不确定我的视图模型的职责是否正确。也许这与NotifyPropertyChanged有关?非常感谢您提供了以上信息,如果我做对了,它将极大地帮助我在我的应用程序中。 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>();
        }
    }
1个回答

4
你需要将产品和客户类型更改为ObservableCollection类型。
当您在ViewModel中更改这些ObservableCollection时,它们会更新视图,因为OC已经实现了INotifyPropertyChanged。
订单和CurrentLine应该只是一种类型,不应该被称为ViewModel。
1)当选择Customer组合框的SelectedItem的setter被调用时,你需要这样做。
2)您需要在OrderScreenViewModel的ctr中使用自己的逻辑来确定要将CurrentLine.Customer更改为哪个Customer。如果在ctr中这样做,它将在绑定发生之前设置值。
3)同样,只要对绑定到组合框的ObservableCollection进行更改,它就会更新UI。如果您更改绑定的SelectedItem内容,请确保调用RaisedPropertyChanged事件。
ETA:更改xaml如下,将SelectedItem属性绑定到SelectedProduct和SelectedCustomer。
<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>

我希望这能为你提供一个正确的方向,所有与通过客户ID构建客户和产品相关的逻辑都需要在你的存储库中完成。

   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
   {
   }

以下是标准 IRepository 的样式,这就是所谓的仓储模式(Repository Pattern):

   public interface IRepository<T>
   {
      IEnumerable<T> GetAll();
      T GetById(int id);
      void Save(T saveThis);
      void Delete(T deleteThis);
   }

当你说Order和CurrentLine应该只是一种类型时,你是指像OrderDto这样的类型吗?关于1)我认为这就是我需要做的。我有点卡住了,因为Customer的setter在WMSOrderViewModel上,而我从这里没有对OrderScreenViewModel上的Products的引用 - 我该如何将这两者联系起来? - lostinwpf
谢谢你的代码。我已经在使用NHibernate支持的存储库接口来获取我的数据 - 我不想在这个示例中包含那部分代码。在使用你的代码作为灵感后,我的视图模型工作得更符合我的期望了。现在唯一的问题是它非常大。我认为我需要将OrderScreenViewModel拆分成组件视图模型,并在它们之间进行通信。 - lostinwpf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接