You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF MVVM多对多关系问题:如何将ListBox选中车辆关联至租赁单

WPF MVVM 多对多关联:获取ListBox多选车辆并关联到租赁单

我是MVVM初学者,正在学习WPF C#开发,目前卡在租赁单(Rental)与车辆(Car)的多对多关联问题上。创建新租赁单时,需要从ListBox中选择要添加的车辆,但不知道如何获取ListBox中的选中车辆并将其添加到租赁单中。

核心问题分析

ListBox开启SelectionMode="Multiple"后,SelectedItem只能绑定单个选中项,无法满足多选需求;且WPF原生不支持SelectedItems的双向绑定,需要通过附加行为或代码逻辑实现选中项与ViewModel集合的同步。

解决方案步骤

1. 修正ViewModel:添加选中车辆集合,移除UI控件引用

修改RentalEditVM.cs,添加存储选中车辆的集合属性,同时移除对ListBox控件的直接引用(违反MVVM分离原则):

using RentalCar.Model;
using System.Collections.ObjectModel;

namespace RentalCar.ViewModel
{
    internal class RentalEditVM : Entity
    {
        public ObservableCollection<Car> Cars { get; set; }

        private Rental? rental;
        // 存储选中的车辆集合
        public ObservableCollection<Car> SelectedCars { get; set; }

        public RentalEditVM()
        {
            Cars = new ObservableCollection<Car>();
            Rental = new Rental();
            SelectedCars = new ObservableCollection<Car>();
        }

        public RentalEditVM(ObservableCollection<Car> cars, Rental rental = null)
        {
            Cars = cars;
            Rental = rental ?? new Rental();
            SelectedCars = new ObservableCollection<Car>();
            
            // 编辑模式下,初始化已关联的车辆为选中状态
            if (rental?.Cars != null)
            {
                foreach (var car in rental.Cars)
                {
                    SelectedCars.Add(car);
                }
            }
        }

        public Rental? Rental
        {
            get { return rental; }
            set { SetProperty(ref rental, value); }
        }
    }
}

2. 实现ListBox选中项双向绑定:创建附加行为

由于WPF原生不支持SelectedItems双向绑定,我们通过附加行为实现ListBox选中项与ViewModel集合的同步:

第一步:添加行为类ListBoxSelectedItemsBehavior.cs

using System.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;

namespace RentalCar.View
{
    public class ListBoxSelectedItemsBehavior : Behavior<ListBox>
    {
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(ListBoxSelectedItemsBehavior),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));

        public IList SelectedItems
        {
            get => (IList)GetValue(SelectedItemsProperty);
            set => SetValue(SelectedItemsProperty, value);
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject?.SelectionChanged += OnListBoxSelectionChanged;
        }

        protected override void OnDetaching()
        {
            AssociatedObject?.SelectionChanged -= OnListBoxSelectionChanged;
            base.OnDetaching();
        }

        private void OnListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (SelectedItems == null) return;

            foreach (var item in e.AddedItems)
            {
                if (!SelectedItems.Contains(item)) SelectedItems.Add(item);
            }

            foreach (var item in e.RemovedItems)
            {
                if (SelectedItems.Contains(item)) SelectedItems.Remove(item);
            }
        }

        private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = (ListBoxSelectedItemsBehavior)d;
            var listBox = behavior.AssociatedObject;
            if (listBox == null || e.NewValue == null) return;

            var collection = e.NewValue as INotifyCollectionChanged;
            collection?.CollectionChanged -= behavior.OnSelectedItemsCollectionChanged;
            collection?.CollectionChanged += behavior.OnSelectedItemsCollectionChanged;

            // 初始化选中项
            listBox.SelectedItems.Clear();
            foreach (var item in (IList)e.NewValue)
            {
                listBox.SelectedItems.Add(item);
            }
        }

        private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (AssociatedObject == null || SelectedItems == null) return;

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var item in e.NewItems)
                    {
                        if (!AssociatedObject.SelectedItems.Contains(item))
                            AssociatedObject.SelectedItems.Add(item);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var item in e.OldItems)
                    {
                        if (AssociatedObject.SelectedItems.Contains(item))
                            AssociatedObject.SelectedItems.Remove(item);
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    AssociatedObject.SelectedItems.Clear();
                    foreach (var item in SelectedItems)
                    {
                        AssociatedObject.SelectedItems.Add(item);
                    }
                    break;
            }
        }
    }
}

第二步:修改AddEditRental.xaml,绑定行为

在XAML头部添加行为命名空间,然后给ListBox附加行为:

<Window x:Class="RentalCar.View.AddEditRental"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:RentalCar.View"
        Title="Add/Edit Rental" Height="450" Width="800">
    <Grid>
        <!-- 其他控件 -->
        <ListBox x:Name="CarsListBox" 
                 Grid.Row="5" 
                 ItemsSource="{Binding Cars}" 
                 SelectionMode="Multiple">
            <i:Interaction.Behaviors>
                <local:ListBoxSelectedItemsBehavior SelectedItems="{Binding SelectedCars, Mode=TwoWay}" />
            </i:Interaction.Behaviors>
        </ListBox>
    </Grid>
</Window>

3. 保存时关联选中车辆到租赁单

修改MainVM.cs中的CreateRental命令,将ViewModel中选中的车辆添加到租赁单的关联集合:

CreateRental = new RelayCommand(() =>
{
    var w = new AddEditRental();
    var vm = new RentalEditVM(Cars);
    w.DataContext = vm;
    if (w.ShowDialog() == true)
    {
        var rental = vm.Rental;
        // 初始化租赁单的车辆集合(如果未初始化)
        rental.Cars ??= new ObservableCollection<Car>();
        // 添加选中的车辆
        foreach (var car in vm.SelectedCars)
        {
            if (!rental.Cars.Contains(car))
            {
                rental.Cars.Add(car);
            }
        }
        if (logic.Save(rental))
        {
            Rentals.Add(rental);
        }
    }
});

4. 确保Rental模型包含车辆集合

确认你的Rental模型类中定义了与Car的多对多关联集合:

public class Rental : Entity
{
    // 其他属性(如租赁日期、客户信息等)
    public virtual ObservableCollection<Car> Cars { get; set; } = new ObservableCollection<Car>();
}

关键说明

  • MVVM原则:ViewModel永远不要持有UI控件引用,所有UI状态都通过绑定同步到ViewModel的属性。
  • 多选绑定方案:附加行为是WPF实现SelectedItems双向绑定的标准方案,避免了代码后台直接操作UI,符合MVVM模式。
  • 多对多关联:通过将选中车辆添加到租赁单的Cars集合,完成实体间的多对多关联,后续保存到数据库时,EF Core会自动处理中间表的操作(如果已正确配置多对多关系)。

内容的提问来源于stack exchange,提问作者Deák Attila

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 04:28:08