14 Stimmen

WPF DataGridTemplateColumn mit ComboBox-Bindung (MVVM-Muster)

Ich gehe bonkers mit dem folgenden WPF DataGrid + ComboBox-Szenario.

Ich habe eine Reihe von Klassen, die wie folgt aussehen;

class Owner
{
    int ID { get; }
    string Name { get; }

    public override ToString()
    { 
        return this.Name;
    }
}

class House
{
    int ID { get; }
    Owner HouseOwner { get; set; }
}

class ViewModel
{
    ObservableCollection<Owner> Owners;
    ObservableCollection<House> Houses
}

Nun ist mein gewünschtes Ergebnis ein DataGrid, das eine Liste von Zeilen des Typs Haus und in einer der Spalten befindet sich eine ComboBox, die es dem Benutzer ermöglicht, den Wert von House.HouseOwner .

In diesem Szenario ist der DataContext für das Grid ViewModel.Houses und für die ComboBox möchte ich, dass die ItemsSource an ViewModel.Owners gebunden wird.

Ist das überhaupt möglich? Ich gehe mental mit diesem... das Beste, was ich in der Lage gewesen, zu tun ist, um korrekt die ItemsSource gebunden, aber die ComboBox (innerhalb einer DataGridTemplateColumn) ist nicht die richtigen Werte für House.HouseOwner in jeder Zeile.

HINWEIS: Wenn ich die ComboBox aus dem Bild nehmen und einen TextBlock in der DataTemplate stattdessen setzen, kann ich korrekt die Werte für jede Zeile sehen, aber immer sowohl eine ItemsSource als auch zeigen den richtigen Wert in der Auswahl funktioniert nicht für mich...

In meinem Code habe ich den DataContext im Fenster auf ViewModel und im Raster wird der DataContext auf ViewModel.Houses . Für alles außer dieser Combobox, es funktioniert...

Meine XAML für die beanstandete Spalte sieht wie folgt aus;

<DataGridTemplateColumn Header="HouseOwner">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedItem="{Binding HouseOwner, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
                        SelectedValue="{Binding HouseOwner.ID, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Mode=OneWay}"
                        SelectedValuePath="ID" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Ich würde mich freuen, wenn mir jemand helfen könnte... es scheint, dass ein bisschen Voodoo nötig ist...

16voto

AbdouMoumen Punkte 3774

Comme standard.kramer gesagt, müssen Sie die RelativeSource aus Ihren Bindungen für die SelectedItem et SelectedValue wie folgt (beachten Sie, dass Sie Folgendes hinzufügen sollten Mode=TwoWay zu Ihrer Bindung, damit sich die Änderung in der Combobox in Ihrem Modell widerspiegelt).

<DataGridTemplateColumn Header="House Owner">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                DisplayMemberPath="Name"
                SelectedItem="{Binding HouseOwner, Mode=TwoWay}"
                SelectedValue="{Binding HouseOwner.ID}"
                SelectedValuePath="ID"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Anders als er sagte, müssen Sie die Bindung jedoch nicht entfernen, um die SelectedValue . Wenn Sie es entfernen, wird es nicht funktionieren (sowohl SelectedValue et SelectedValuePath sollte hier gesetzt werden, wie Sie es getan haben), denn das ist es, was dem Bindungsmechanismus erlaubt, die Auswahl aus der Combobox mit der DataGrid's HouseOwner Eigentum.

SelectedValue / SelectedValuePath Kombination ist sehr interessant. SelectedValuePath sagt der Datenbindung, dass die ID Eigenschaft der Owner aktuell ausgewählte Objekt stellt seine Wert , SelectedValue sagt ihm, dass dieser Wert an den HouseOwner.ID das das ausgewählte Objekt im DataGrid ist.

Wenn Sie also diese Bindungen entfernen, weiß der Datenbindungsmechanismus nur noch, "welches Objekt ausgewählt ist", und um die Entsprechung zwischen dem ausgewählten Element in der ComboBox und dem HouseOwner Eigenschaft auf das ausgewählte Element im DataGrid haben, müssen sie "dieselbe Objektreferenz" sein. Das bedeutet, dass zum Beispiel das Folgende nicht funktionieren würde:

Owners = new ObservableCollection<Owner>
                {
                    new Owner {ID = 1, Name = "Abdou"},
                    new Owner {ID = 2, Name = "Moumen"}
                };
Houses = new ObservableCollection<House>
                {
                    new House {ID = 1, HouseOwner = new Owner {ID = 1, Name = "Abdou" }},
                    new House {ID = 2, HouseOwner = new Owner {ID = 2, Name = "Moumen"}}
                };

(Beachten Sie, dass die "HouseOwners" der Houses-Sammlung anders (neu) sind als die der Owners-Sammlung). Allerdings sind die folgenden würde Arbeit:

Owners = new ObservableCollection<Owner>
                {
                    new Owner {ID = 1, Name = "Abdou"},
                    new Owner {ID = 2, Name = "Moumen"}
                };
Houses = new ObservableCollection<House>
                {
                    new House {ID = 1, HouseOwner = Owners[0]},
                    new House {ID = 2, HouseOwner = Owners[1]}
                };

Ich hoffe, das hilft :)

Aktualisierung: Im zweiten Fall können Sie das gleiche Ergebnis erzielen, ohne dass die Verweise gleich sind, indem Sie die Option Entspricht en el Owner Klasse (natürlich, da sie in erster Linie zum Vergleich der Objekte verwendet wird). (Dank an @ RJ Lohan für diesen Hinweis in den Kommentaren unten)

10voto

RJ Lohan Punkte 6407

Vielen Dank für die Hilfe alle - ich habe endlich herausgefunden, warum ich nicht die ComboBox Elemente auswählen konnte - war aufgrund einer Mausvorschau-Ereignishandler, die ich an die Zelle Stil angehängt hatte, wenn ich mit einem DataGridComboBoxColumn .

Dafür habe ich mich selbst geohrfeigt, danke für die andere Hilfe.

Außerdem ist zu beachten, dass dies bei mir nur mit einem zusätzlichen Gerät funktionieren wird;

IsSynchronizedWithCurrentItem="False"

Der ComboBox hinzugefügt, sonst zeigen sie aus irgendeinem Grund alle den gleichen Wert an.

Außerdem benötige ich anscheinend nicht die SelectedValue/SelectedValuePath Eigenschaften in meiner Bindung, ich glaube, weil ich sie überschrieben habe Entspricht in meinem gebundenen Eigentümertyp.

Und schließlich muss ich ausdrücklich festlegen;

Mode=TwoWay, UpdateSourceTrigger=PropertyChanged

In der Bindung, damit die Werte in die gebundenen Elemente zurückgeschrieben werden, wenn sich die ComboBox geändert hat.

Die endgültige (funktionierende) XAML für die Bindung sieht also wie folgt aus;

    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox 
                ItemsSource="{Binding Path=DataContext.Owners,  
                RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                IsSynchronizedWithCurrentItem="False"
                SelectedItem="{Binding HouseOwner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>

Zum Wohl!

rJ

3voto

default.kramer Punkte 5795

Das ist durchaus möglich, und Sie sind auf dem richtigen Weg, wenn Sie eine AncestorType verbindlich für die ItemsSource . Aber ich glaube, ich sehe ein paar Fehler.

Erstens: Ihr ItemsSource sollte verbindlich sein für DataContext.Owners , nicht DataContext.Houses , richtig? Sie möchten, dass die Sammlung der Eigentümer der Ansichtsmodelle in der Dropdown-Liste angezeigt wird. Ändern Sie also zunächst die ItemsSource und nehmen Sie die mit der Auswahl zusammenhängenden Dinge heraus, etwa so:

<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
          DisplayMemberPath="Name" />

Testen Sie es und vergewissern Sie sich, dass die ItemsSource korrekt funktioniert. Versuchen Sie nicht, mit der Auswahl herumzuspielen, bis dieser Teil funktioniert.

Was die Auswahl betrifft, so denke ich, dass Sie verbindlich sein sollten SelectedItem nur - nicht SelectedValue . Für diese Bindung müssen Sie nicht wollen eine RelativeSource Bindung - der DataContext ist ein einzelner House Sie können also direkt seine HouseOwner . Meine Vermutung ist folgende:

<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding HouseOwner}" />

Zum Debuggen von Bindungen schließlich können Sie siehe das Visual Studio-Ausgabefenster oder steigen Sie auf ein Tool wie Snoop ou WPF-Prüfer . Wenn Sie vorhaben, viel mit WPF zu arbeiten, würde ich empfehlen, eher früher als später mit Snoop anzufangen.

0voto

rr789 Punkte 590

Vollständiges Beispiel auf der Grundlage des Vorschlags von AbdouMoumen. Außerdem wurden SelectedValue und SelectedValuePath entfernt.

enter image description here

//---------
//CLASS STRUCTURES.    
//---------
//One grid row per house.    
public class House
{
    public string name { get; set; }
    public Owner ownerObj { get; set; }
}

//Owner is a combobox choice.  Each house is assigned an owner.    
public class Owner
{
    public int id { get; set; }
    public string name { get; set; }
}

//---------
//FOR XAML BINDING.    
//---------
//Records for datagrid.  
public ObservableCollection<House> houses { get; set; }

//List of owners.  Each house record gets an owner object assigned.    
public ObservableCollection<Owner> owners { get; set; }

//---------
//INSIDE “AFTER CONTROL LOADED” METHOD.  
//---------
//Populate list of owners.  For combobox choices.  
owners = new ObservableCollection<Owner>
{
    new Owner {id = 1, name = "owner 1"},
    new Owner {id = 2, name = "owner 2"}
};

//Populate list of houses.  Again, each house is a datagrid record.  
houses = new ObservableCollection<House>
{
    new House {name = "house 1", ownerObj = owners[0]},
    new House {name = "house 2", ownerObj = owners[1]}
};

<DataGrid ItemsSource="{Binding Path=houses, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" >
    <DataGrid.Columns>
        <DataGridTextColumn Header="name" Binding="{Binding name}" />
        <DataGridTextColumn Header="owner (as value)" Binding="{Binding ownerObj.name}"/>

        <DataGridTemplateColumn Header="owner (as combobox)" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox
                            ItemsSource="{Binding Path=owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                            DisplayMemberPath="name"
                            SelectedItem="{Binding ownerObj, Mode=TwoWay}"
                            />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>

</DataGrid>

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X