0
votes

I've run into an issue that I think may be a WPF bug. I've got a WPF view that contains two ItemsControl elements that have their ItemsSource properties both bound to the same ObservableCollection property on the ViewModel. The behavior I'm seeing is that only the ItemsControl declared last in the XAML is rendering its content properly.

My View consists of the following:

<Window x:Class="ItemsControlBindingTest.Views.ItemsControlBindingTestView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ItemsControlBindingTestView" Height="300" Width="300">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Grid.ColumnSpan="2" TextWrapping="Wrap">
            Note that of the two ItemsControls in this view, only the last declared in XAML renders its contents properly.
        </TextBlock>
        <ItemsControl Grid.Column="0" Grid.Row="1" ItemsSource="{Binding ButtonList}"/>
        <ItemsControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding ButtonList}"/>
    </Grid>
</Window>

You can see I've simply got a grid with a textblock and two ItemsControls - each bound to the same property on the ViewModel.

The code-behind of the View is pretty straight-forward: using System.Windows; using ItemsControlBindingTest.ViewModels;

namespace ItemsControlBindingTest.Views
{
    /// <summary>
    /// Interaction logic for ItemsControlBindingTestView.xaml
    /// </summary>
    public partial class ItemsControlBindingTestView : Window
    {
        public ItemsControlBindingTestView()
        {
            InitializeComponent();
            DataContext = new ItemsControlBindingTestViewModel();
        }
     }
}

You can see I'm simply setting my View's DataContext property to a new instance of the ItemsControlBindingTestViewModel.

And the code for the ItemsControlBindingTestViewModel:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;

namespace ItemsControlBindingTest.ViewModels
{
    public class ItemsControlBindingTestViewModel : MVVM.ViewModelBase
    {
        private ObservableCollection<Button> buttonList;
        public ObservableCollection<Button> ButtonList
        {
            get { return buttonList; }
            set
            {
                buttonList = value;
                NotifyPropertyChanged(() => ButtonList);
            }
        }

        public ItemsControlBindingTestViewModel()
        {
            List<Button> tempList = new List<Button>();
            tempList.Add(new Button { Content = "Button 1" });
            tempList.Add(new Button { Content = "Button 2" });
            tempList.Add(new Button { Content = "Button 3" });
            ButtonList = new ObservableCollection<Button>(tempList);
        }
    }
}

All we're doing here is adding three buttons to the ButtonList property.

Now, when I display this View, I only see the buttons rendered in the ItemsControl that is declared last in the XAML:

ItemsControl on right is last-declared in XAML

If I switch the assigned column values on my two ItemsControls:

<ItemsControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding ButtonList}"/>
<ItemsControl Grid.Column="0" Grid.Row="1" ItemsSource="{Binding ButtonList}"/>

Then the ItemsControl in column 0 (on the left) will render the content, and the ItemsControl on the right will remain blank:

ItemsControl on left is last-declared in XAML

Looking at the elements in Snoop yields some interesting information:

Both ItemsControls' ItemsSource properties correctly reflect a binding to the ButtonList.

Snoop shows first-declared ItemsControl has valid ItemsSource property

In fact, I can delve into this ItemsSource and see the three buttons in ButtonList: Snoop shows me the three buttons in ButtonList

The last-declared ItemsControl also shows a valid ItemsSource property: Last-declared ItemsControl also has valid ItemsSource

And I can delve into this ItemsSource as well and see the three buttons in ButtonList: Last-declared ItemsControl ItemsSource has three buttons

Snoop also shows the last-declared ItemsControl's StackPanel has children: Last-declared ItemsControl's StackPanel has children

While the first-declared ItemsControl's StackPanel has no children: First-declared ItemsControl's StackPanel has no children

Am I missing something here, or is this a bug in WPF?

1

1 Answers

3
votes

If the Button is System.Windows.Controls.Button, then the behavior is OK. Button (as any other visual), can have only one parent. When last items control renders an item (button), the item detaches from previous parent and attaches to the next one.

In fact, this is a very bad idea - to work with visuals in view models.
Moreover, the concept of view model looses its sense in this case. If you want to generate buttons int the view, set appropriate item template for items control, and keep data items (not visuals!) in view model's ObservableCollection.