WPF 列表控件數據源綁定多個數據集合方法


在 WPF 用的多的列表控件如 ListBox 或 ListView 等,本文告訴大家在這些列表控件上進行綁定多個數據集合來源的多個實現方法。如有一個顯示動物列表的控件,需要綁定的數據來源是阿貓和阿狗兩個 ObservableCollection 列表,不在后台代碼編寫合並集合的代碼情況下,可以通過 XAML 的編寫,綁定多個數據集合

准備

在開始之前,咱先搭建一點測試使用的代碼,假定咱有一個 列表控件 准備綁定到的數據源是兩個 ObservableCollection 對象,下面來定義這兩個 ObservableCollection 對象和對應的 阿貓和阿狗 的代碼

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            for (int i = 0; i < 10; i++)
            {
                Dogs.Add(new Dog()
                {
                    Name = "Dog" + i
                });

                Cats.Add(new Cat()
                {
                    Name = "Cat" + i
                });
            }

            DataContext = this;
        }

        public ObservableCollection<Dog> Dogs { get; } = new ObservableCollection<Dog>();
        public ObservableCollection<Cat> Cats { get; } = new ObservableCollection<Cat>();
    }

    public class Dog : Animal
    {
    }

    public class Cat : Animal
    {
    }

    public class Animal
    {
        public string Name { get; set; }
    }

可以看到以上代碼里面存在兩個 ObservableCollection 對象,同時 MainWindow 的 DataContext 就是 MainWindow 對象。咱需要將兩個 ObservableCollection 對象作為數據源,放在相同的一個 ListBox 里面

下面是多個不同的實現方式,解決如何在 WPF 中在 ListBox 或 ListView 綁定多個數據集合 ObservableCollection 對象

通過 CollectionViewSource 方式

在 ListView 或 ListBox 資源里面,添加 CollectionViewSource 綁定到集合里面,然后在 ItemsSource 使用 CompositeCollection 進行綁定,代碼如下

    <ListBox>
      <ListBox.Resources>
        <CollectionViewSource x:Key="DogCollection" Source="{Binding Dogs}"/>
        <CollectionViewSource x:Key="CatCollection" Source="{Binding Cats}"/>
      </ListBox.Resources>
      <ListBox.ItemsSource>
        <CompositeCollection>
          <CollectionContainer Collection="{Binding Source={StaticResource DogCollection}}"/>
          <CollectionContainer Collection="{Binding Source={StaticResource CatCollection}}"/>
        </CompositeCollection>
      </ListBox.ItemsSource>

      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Name}"></TextBlock>
        </DataTemplate>
      </ListBox.ItemTemplate>

    </ListBox>

這個方法的優勢在於可以完全使用 XAML 編寫內容,但是缺點在於有重復的代碼,如有多個綁定的集合對象,就需要在資源和 CompositeCollection 里面定義多個 CollectionViewSource 和 CollectionContainer 對象

如果綁定的集合數量不多,那么此寫法還成,但如果集合數量比較多,而且需要不斷變更順序,那以上寫法就有坑

此方法請參考 WPF 很少人知道的科技 - walterlv

通過 CompositeCollection 動態綁定

在 ListView 或 ListBox 的資源里面定義了 CompositeCollection 通過控件的 DataContext 綁定多個集合,代碼如下

        <CompositeCollection x:Key="MyColl">
          <CollectionContainer Collection="{Binding DataContext.Dogs, Source={x:Reference MyList}}"/>
          <CollectionContainer Collection="{Binding DataContext.Cats, Source={x:Reference MyList}}"/>
        </CompositeCollection>

以上代碼的 MyList 就是集合控件,此方法需要用到 x:Reference 獲取對象的引用,同時需要通過 DataContext 的某個屬性獲取到對應的屬性,全部代碼如下

    <ListBox x:Name="MyList" ItemsSource="{DynamicResource MyColl}">
      <ListBox.Resources>
        <CompositeCollection x:Key="MyColl">
          <CollectionContainer Collection="{Binding DataContext.Dogs, Source={x:Reference MyList}}"/>
          <CollectionContainer Collection="{Binding DataContext.Cats, Source={x:Reference MyList}}"/>
        </CompositeCollection>
      </ListBox.Resources>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Name}"></TextBlock>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

對比上面的方法,此方法可以讓綁定集合的代碼只寫一次,看起來代碼更少一點。但不足的地方在於綁定 ItemsSource 需要用到 DynamicResource 的方式,相對性能不如上面方法。為什么需要 DynamicResource 資源?原因是資源本身定義在 Resources 里面。為什么資源需要定義在控件里面的 Resource 里面?原因是為了獲取到控件的 x:Reference 對象。也就是說需要在控件創建出來之后,才能通過 x:Reference 獲取控件,而控件的數據內容需要依賴資源的定義,因此也只有以上方式的寫法

如果能從控件的上層容器拿到數據對象,那可以將資源定義在容器里面,通過 StaticResource 綁定到靜態資源。如放在 Window 的 Resources 里

<Window x:Class="CibairyafocairluYerkinemde.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CibairyafocairluYerkinemde"
        mc:Ignorable="d"
        x:Name="Root"
        Title="MainWindow" Height="450" Width="800">
  <Window.Resources>
    <CompositeCollection x:Key="MyColl">
      <CollectionContainer Collection="{Binding DataContext.Dogs, Source={x:Reference Root}}"/>
      <CollectionContainer Collection="{Binding DataContext.Cats, Source={x:Reference Root}}"/>
    </CompositeCollection>
  </Window.Resources>

  <Grid>
   <ListBox x:Name="MyList" ItemsSource="{StaticResource MyColl}" >
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Name}"></TextBlock>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </Grid>
</Window>

以上寫法沒有啥缺點,也不存在動態資源的性能問題。但實際上在有動態資源下,性能問題也是很小的問題,對比渲染控件本身,動態綁定性能可以忽略

通過多綁定方法

此方法需要添加一點后台代碼,定義 CompositeCollectionConverter 轉換器,實現邏輯是通過多綁定的方法,將多個數據集合當成多個參數進行綁定

    <ListBox>
      <ListBox.ItemsSource>
        <MultiBinding Converter="{x:Static local:CompositeCollectionConverter.Default}">
          <Binding Path="Dogs" />
          <Binding Path="Cats" />
        </MultiBinding>
      </ListBox.ItemsSource>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Name}"></TextBlock>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

可以看到此方法的 XAML 代碼量最小,只是需要一個輔助的 CompositeCollectionConverter 類,代碼如下

    public class CompositeCollectionConverter : IMultiValueConverter
    {
        public static readonly CompositeCollectionConverter Default = new CompositeCollectionConverter();

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var compositeCollection = new CompositeCollection();
            foreach (var value in values)
            {
                if (value is IEnumerable enumerable)
                {
                    compositeCollection.Add(new CollectionContainer { Collection = enumerable });
                }
                else
                {
                    compositeCollection.Add(value);
                }
            }

            return compositeCollection;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("CompositeCollectionConverter ony supports oneway bindings");
        }
    }

可以將 CompositeCollectionConverter 放在庫里面,這樣就可以讓 XAML 代碼看起來簡單

本文所有代碼放在 githubgitee 歡迎小伙伴訪問

參考

本文以上方法參考了如下博客

c# - CompositeCollection + CollectionContainer: Bind CollectionContainer.Collection to property of ViewModel that is used as DataTemplates DataType - Stack Overflow

wpf - How do you bind a CollectionContainer to a collection in a view model? - Stack Overflow

WPF 很少人知道的科技 - walterlv


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM