简体   繁体   中英

Strange behavior when binding data grid combo box column to CollectionViewSource

When binding to CollectionView source all rows show initially show blank values then when I change the selection on any of the combo boxes then all combo boxes are set to the same value. When I bind directly to the source collection which is an ObservableCollection then it works fine. I want to use a CollectionViewSource so I can make use of it's sorting feature etc. Here is some code which illistrates the problem, with a column binding to a CollectionViewSource and one binding directly to the underlying ObservableCollection. I am using VS 2015.

The View Model:

public class GridItem
{
    public string Name { get; set; }
    public int CompanyID { get; set; }
    public int CompanyID2 { get; set; }
}

public class CompanyItem
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class ViewModel
{
    public ViewModel()
    {
        GridItems = new ObservableCollection<GridItem>() {
            new GridItem() { Name = "Jim", CompanyID = 1, CompanyID2 = 1 },
            new GridItem() { Name = "Ed", CompanyID = 2, CompanyID2 = 2 },
            new GridItem() { Name = "Dave", CompanyID = 3, CompanyID2 = 3 },
            new GridItem() { Name = "Bruce", CompanyID = 4, CompanyID2 = 4 },
            new GridItem() { Name = "Rob", CompanyID = 5, CompanyID2 = 5 }
        };

        CompanyItems = new ObservableCollection<CompanyItem>() {
            new CompanyItem() { ID = 1, Name = "Company 1" },
            new CompanyItem() { ID = 2, Name = "Company 2" },
            new CompanyItem() { ID = 3, Name = "Company 3" },
            new CompanyItem() { ID = 4, Name = "Company 4" },
            new CompanyItem() { ID = 5, Name = "Company 5" },
            new CompanyItem() { ID = 6, Name = "Company 6" },
            new CompanyItem() { ID = 7, Name = "Company 7" },
        };

        CompanyItemsViewSource = new CollectionViewSource();
        CompanyItemsViewSource.Source = CompanyItems;
    }

    public ObservableCollection<GridItem> GridItems { get; set; }
    public ObservableCollection<CompanyItem> CompanyItems { get; set; }
    public CollectionViewSource CompanyItemsViewSource { get; set; }
}

The Window:

<Window x:Class="DataGridTest.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:DataGridTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}" Header="Name"/>

                <DataGridComboBoxColumn 
                    SelectedValueBinding="{Binding CompanyID}"  DisplayMemberPath="Name"  SelectedValuePath="ID" Header="Company (View Source)">
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItemsViewSource.View, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItemsViewSource.View, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>

                <DataGridComboBoxColumn 
                    SelectedValueBinding="{Binding CompanyID2}"  DisplayMemberPath="Name"  SelectedValuePath="ID" Header="Company">
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

The data context is set on app startup:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        ViewModel viewModel = new ViewModel();

        window.DataContext = viewModel;
        window.Show();
    }
}

You are binding the same view of the same CollectionViewSource to all the different rows. When you change one of the rows, you are manipulating the underlying view, which will replicate on the other rows.

You need to have different collection views for the different rows. To do that using only one property, you can create a new view everytime you call the get accessor.

Add a new property of type ICollectionView to your ViewModel like this:

public ICollectionView CompanyItemCollectionView
{
    get
    {            
        return new CollectionViewSource { Source = CompanyItems }.View;
    }
} 

Bind this property to the ItemsSource of the elements of your DataGridComboBoxColumn:

<DataGridComboBoxColumn 
SelectedValueBinding="{Binding CompanyID}"  DisplayMemberPath="Name"  SelectedValuePath="ID" Header="Company (ICollectionView)">
    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="{x:Type ComboBox}">
           <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItemCollectionView, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="{x:Type ComboBox}">
            <Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItemCollectionView, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

You can set filters and sorting order inside the get accessor too:

public ICollectionView CompanyItemCollectionView
{
    get
    {
        ICollectionView view = new CollectionViewSource { Source = CompanyItems }.View;
        view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Descending)); //example                
        return view;
    }
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM