93 votes

Lier ItemsSource d'une ComboBoxColumn dans WPF DataGrid

J'ai deux classes de modèle simples et un ViewModel...

public class GridItem
{
    public string Name { get; set; }
    public int CompanyID { 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 } };

        CompanyItems = new ObservableCollection<CompanyItem>() {
            new CompanyItem() { ID = 1, Name = "Company 1" },
            new CompanyItem() { ID = 2, Name = "Company 2" } };
    }

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

...et une simple fenêtre :

<Window x:Class="DataGridComboBoxColumnApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name}" />
                <DataGridComboBoxColumn ItemsSource="{Binding CompanyItems}"
                                    DisplayMemberPath="Name"
                                    SelectedValuePath="ID"
                                    SelectedValueBinding="{Binding CompanyID}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Le ViewModel est défini comme le modèle de la MainWindow DataContext dans App.xaml.cs :

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();
    }
}

Comme vous pouvez le voir, j'ai défini le ItemsSource du DataGrid à l'élément GridItems de la collection ViewModel. Cette partie fonctionne, la seule ligne de la grille avec le nom "Jim" est affichée.

Je veux également définir le ItemsSource de la ComboBox dans chaque rangée à l'adresse CompanyItems de la collection ViewModel. Cette partie ne fonctionne pas : La ComboBox reste vide et dans la fenêtre de sortie du débogueur, je vois un message d'erreur :

Erreur System.Windows.Data : 2 : Cannot trouver le FrameworkElement ou le FrameworkContentElement pour l'élément cible. BindingExpression:Path=CompanyItems ; DataItem=null ; l'élément cible est DataGridComboBoxColumn' (HashCode=28633162) (HashCode=28633162) ; la propriété cible est 'ItemsSource' (type 'IEnumerable').

Je crois que WPF s'attend à ce que CompanyItems pour être une propriété de GridItem ce qui n'est pas le cas, et c'est la raison pour laquelle la liaison échoue.

J'ai déjà essayé de travailler avec un RelativeSource et AncestorType comme ça :

<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems, 
    RelativeSource={RelativeSource Mode=FindAncestor,
                                   AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="ID"
                        SelectedValueBinding="{Binding CompanyID}" />

Mais cela me donne une autre erreur dans la sortie du débogueur :

Erreur System.Windows.Data : 4 : Impossible de trouver la source de la liaison avec la référence 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=CompanyItems ; DataItem=null ; l'élément cible est DataGridComboBoxColumn' (HashCode=1150) (HashCode=1150788) ; la propriété cible est ItemsSource' (type 'IEnumerable')

Question : Comment lier le ItemsSource de la colonne DataGridComboBoxColumn à la collection CompanyItems du ViewModel ? Est-ce possible ?

Merci d'avance pour votre aide !

139voto

serge_gubenko Points 11561

Vérifiez si le xaml DataGridComboBoxColumn ci-dessous fonctionne pour vous :

<DataGridComboBoxColumn 
    SelectedValueBinding="{Binding CompanyID}" 
    DisplayMemberPath="Name" 
    SelectedValuePath="ID">

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

Vous pouvez y trouver une autre solution au problème que vous rencontrez : Utilisation de boîtes combo avec le DataGrid WPF

6 votes

Bon sang, ça marche ! !! Si seulement je pouvais comprendre pourquoi ? Et pourquoi pas le code original avec les modifications recommandées par Rachel ? En tout cas, merci beaucoup !

1 votes

Je crois que vous pouvez trouver l'explication ici : wpf.codeplex.com/workitem/8153?ProjectName=wpf (voir les commentaires)

1 votes

Il semble qu'ils aient décidé de transformer ce bogue ("Nous avons enregistré un bogue dans notre base de données interne qui sera corrigé dans une prochaine version") en une fonctionnalité. Jetez un coup d'œil à ma propre réponse dans ce fil : Le problème a été résolu par la documentation, une indication forte que cela ne sera jamais changé.

49voto

Slauma Points 76561

Le site sur MSDN à propos de la ItemsSource de la DataGridComboBoxColumn indique que seules les ressources statiques, le code statique ou les collections en ligne d'éléments de combobox peuvent être liés à l'élément ItemsSource :

Pour remplir la liste déroulante, il faut d'abord définissez la propriété ItemsSource de la ComboBox en utilisant l'une des options suivantes options suivantes :

  • Une ressource statique. Pour plus d'informations, consultez la rubrique StaticResource Markup Extension.
  • Une entité de code x:Static. Pour plus d'informations, voir x:Static Markup. Extension.
  • Une collection en ligne de types de ComboBoxItem.

La liaison à la propriété d'un DataContext n'est pas possible si je comprends bien.

Et en effet : Quand je fais CompanyItems a statique propriété dans ViewModel ...

public static ObservableCollection<CompanyItem> CompanyItems { get; set; }

... ajouter à la fenêtre l'espace de nom où se trouve le ViewModel ...

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"

... et changer la liaison en ...

<DataGridComboBoxColumn
    ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}" 
    DisplayMemberPath="Name"
    SelectedValuePath="ID"
    SelectedValueBinding="{Binding CompanyID}" />

... alors cela fonctionne. Mais le fait que la source des éléments soit une propriété statique peut parfois convenir, mais ce n'est pas toujours ce que je veux.

2 votes

J'espère toujours que microsoft va corriger ce bug

0 votes

Une alternative à l'ajout de xmlns:vm est d'utiliser xmlns:local, puisque c'est déjà un espace de nom.

25voto

RookieRick Points 149

Je me rends compte que cette question date de plus d'un an, mais je viens de tomber dessus en traitant un problème similaire et j'ai pensé que je partagerais une autre solution potentielle au cas où cela pourrait aider un futur voyageur (ou moi-même, lorsque j'oublierai cela plus tard et que je me retrouverai à flotter sur StackOverflow entre les cris et les jets de l'objet le plus proche sur mon bureau).

Dans mon cas, j'ai pu obtenir l'effet que je voulais en utilisant une DataGridTemplateColumn au lieu d'une DataGridComboBoxColumn, comme dans l'extrait suivant. [Attention : j'utilise .NET 4.0, et ce que j'ai lu m'amène à penser que la DataGrid a beaucoup évolué, donc à voir si vous utilisez une version antérieure].

<DataGridTemplateColumn Header="Identifier_TEMPLATED">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox IsEditable="False" 
                Text="{Binding ComponentIdentifier,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                ItemsSource="{Binding Path=ApplicableIdentifiers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ComponentIdentifier}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

0 votes

Après m'être débattu avec les deux premières réponses, j'ai essayé ceci et cela a marché pour moi aussi. Merci.

8voto

Benoit Blanchon Points 2252

RookieRick a raison, utiliser DataGridTemplateColumn au lieu de DataGridComboBoxColumn donne un XAML beaucoup plus simple.

De plus, le fait de mettre le CompanyItem directement accessible à partir de la GridItem vous permet de vous débarrasser de la RelativeSource .

IMHO, cela vous donne une solution très propre.

XAML :

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
    <DataGrid.Resources>
        <DataTemplate x:Key="CompanyDisplayTemplate" DataType="vm:GridItem">
            <TextBlock Text="{Binding Company}" />
        </DataTemplate>
        <DataTemplate x:Key="CompanyEditingTemplate" DataType="vm:GridItem">
            <ComboBox SelectedItem="{Binding Company}" ItemsSource="{Binding CompanyList}" />
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Name}" />
        <DataGridTemplateColumn CellTemplate="{StaticResource CompanyDisplayTemplate}"
                                CellEditingTemplate="{StaticResource CompanyEditingTemplate}" />
    </DataGrid.Columns>
</DataGrid>

Voir le modèle :

public class GridItem
{
    public string Name { get; set; }
    public CompanyItem Company { get; set; }
    public IEnumerable<CompanyItem> CompanyList { get; set; }
}

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

    public override string ToString() { return Name; }
}

public class ViewModel
{
    readonly ObservableCollection<CompanyItem> companies;

    public ViewModel()
    {
        companies = new ObservableCollection<CompanyItem>{
            new CompanyItem { ID = 1, Name = "Company 1" },
            new CompanyItem { ID = 2, Name = "Company 2" }
        };

        GridItems = new ObservableCollection<GridItem> {
            new GridItem { Name = "Jim", Company = companies[0], CompanyList = companies}
        };
    }

    public ObservableCollection<GridItem> GridItems { get; set; }
}

4voto

Rachel Points 49408

Votre ComboBox essaie de se lier à se lier à GridItem[x].CompanyItems qui n'existe pas.

Votre RelativeBinding est proche, mais il doit se lier à DataContext.CompanyItems parce que Window.CompanyItems n'existe pas

0 votes

Merci pour la réponse ! J'ai essayé (remplacé CompanyItems par DataContext.CompanyItems dans le dernier extrait XAML de ma question), mais le débogueur affiche la même erreur.

1 votes

@Slauma Je ne suis pas sûr alors, cela devrait fonctionner. La seule chose inhabituelle que je vois avec le XAML que vous avez est le Mode=FindAncestor et je l'omets habituellement. Avez-vous essayé de donner un nom à votre fenêtre racine et de la référencer par son nom dans votre liaison au lieu d'utiliser RelativeSource ? {Binding ElementName=RootWindow, Path=DataContext.CompanyItems}

0 votes

J'ai essayé les deux solutions (j'ai omis Mode=FindAncestor et j'ai modifié la liaison avec un élément nommé), mais cela ne fonctionne pas. Il est étrange que cette méthode fonctionne pour vous. J'ai créé cette simple application de test pour faire sortir le problème de mon application dans un contexte très simple. Je ne sais pas ce que je pourrais faire de mal, le code que vous voyez dans la question est l'application complète (créée à partir du modèle de projet WPF dans VS2010), il n'y a rien de plus autour de ce code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X