Laden...

DataGridComboBoxColumn und MVVM

Erstellt von ChrDressler vor 14 Jahren Letzter Beitrag vor 11 Jahren 5.902 Views
C
ChrDressler Themenstarter:in
182 Beiträge seit 2006
vor 14 Jahren
DataGridComboBoxColumn und MVVM

Hallo,

hat schonmal jemand folgende Konstellation zum laufen bekommen?
( am Beispiel Northwind, Products )

  • WPF DataGrid (auf dem Toolkit oder Framework40), gebunden an Products
  • Dort eine Spalte auf Basis DataGridComboBoxColumn, gebunden an CategoryID
  • Normaler MVVM-Kontext, also _Products _als ObservableCollection<Product> und _Catagories _als ObservableCollection<Category>
  • Die Combobox soll Categories.CategoryName anzeigen
  • die Combobox soll aber gebunden sein über Categories.CategoryID an Products.CategoryID.

Dass Probem ist, dass die Combobox nicht an den DataContext gebunden werden kann ( wohl weil sie nicht Teil des Visual Tree ist? ).
Also gib es eine Lösung mit einem Dataprovider, das klappt auch:

<UserControl.Resources>
    <ObjectDataProvider x:Key="CategoriesProvider" ObjectType="{x:Type local:ProductsVM}" MethodName="GetCategories"/>
</UserControl.Resources>
...

<dg:DataGridComboBoxColumn SelectedValueBinding="{Binding CategoryID}" Header="Category" Width="100"
    ItemsSource="{Binding Source={StaticResource CategoriesProvider}}"
    SelectedValuePath="CategoryID"
    DisplayMemberPath="CategoryName"/>

Problem ist dabei, dass der Form-Designer den ObjectDataProvider zur Entwurfszeit nicht auflösen kann, also nicht benutzbar ist. 🙁
Ich könnte die Methode GetCategories in einer statischen Klasse ansiedeln, dann hätte ich aber wieder keinen Zugriff auf mein Viewmodel. X(

Es gibt auch noch eine Lösung mit der _DataGridTemplateColumn _und DataTemplates. Diese bindet aber die Comboboxitems nicht über eine ID, sondern direkt. Das nützt mir auch nichts, ich wüsste auch nicht, wie ich das umbauen müste, damit das über _CategoryID _läuft.
Die Quelle http://sweux.com/blogs/smoura/index.php/tag/datagrid/ ist trotzdem ein sehr interessantes Tut zum WPF DataGrid.

Insgesamt scheint mir die DataGridComboBoxColumn wenig praxisnah gestaltet. 😭
Daran hat sich auch nichts an dem vor ein paar Tagen erschienen RC vom VS2010+FW40 geändert.

Vielleicht hat jemand eine Idee dazu?

-christoph

328 Beiträge seit 2006
vor 14 Jahren

Es hilft dir in deinem Problem zwar nicht wirklich weiter, doch das Problem wurde vor einigen Tagen auch hier diskutiert: List<string> an DataGridComboBoxColumn binden.
Und da mich dass Ganze jetzt auch interessiert, versuche ich mich auch mal an dem problem.

€dit: also ich hab das Problem lösen können (dank: http://joemorrison.org )

Man muss von der Klasse DataGridComboBoxColumn ableiten:


    public class DataGridComboBoxColumnDataContextIsRoot : DataGridComboBoxColumn
    {
        protected override System.Windows.FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
        {
            FrameworkElement element = base.GenerateEditingElement(cell, dataItem);
            CopyItemsSource(element);
            return element;
        }

        protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
        {
            FrameworkElement element = base.GenerateElement(cell, dataItem);
            CopyItemsSource(element);
            return element;
        }

        private void CopyItemsSource(FrameworkElement element) {
            BindingOperations.SetBinding(element, ComboBox.ItemsSourceProperty, BindingOperations.GetBinding(this, ComboBox.ItemsSourceProperty));
        }
    }

Und dann kann man die Kategorien folgendermaßen binden:


        <DataGrid ItemsSource="{Binding Products}"
                  AutoGenerateColumns="False"
                  >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding ProductID}" Header="ID" />
                <DataGridTextColumn Binding="{Binding Name}" Header="Name" />
                <self:DataGridComboBoxColumnDataContextIsRoot ItemsSource="{Binding DataContext.ProductSubcategories, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}" 
                                                              DisplayMemberPath="Name"
                                                              SelectedValuePath="ProductSubcategoryID"
                                                              SelectedValueBinding="{Binding ProductSubcategoryID}"
                                                              Header="Sub Category"
                                                              />
            </DataGrid.Columns>

        </DataGrid>

MfG TripleX

Träume nicht dein Leben sondern lebe deinen Traum.
Viele Grüße, David Teck

C
ChrDressler Themenstarter:in
182 Beiträge seit 2006
vor 14 Jahren

Ja, Super! 👍 Danke!
Genauso dachte ich mir das.
Hätte ich nur das Web nicht immer nach _DataGridComboBoxColumn _abgesucht...

Noch eine optische Frage: wenn die Combox aufklappt, ist die immer in einem 3-D-Rahmen drin. Das sieht unschön aus und vergößert die Zeilenhöhe des Grinds.

Frage: wie kann man an der Optik des Combobox drehen? Die neue Column hat ja naturgemäß dafür keine Properties.

-christoph

A
15 Beiträge seit 2012
vor 11 Jahren

Ich verwende auch MVVM und habe die Lösung von TrippleX umgesetzt, jedoch gibt es das gleiche Problem wie bei ChrDressler und dass ich beim Validieren der Zeile nicht auf den ausgewählten Wert zugreifen kann. Es gibt folgende optische auswirkungen:

  • wird eine neue (leere) Zeile selektiert erscheint ein roter Rahmen um das DataGrid
  • klickt man anschließend auf die DataGridComboBoxColumn (nicht die aus dem WPF Standard, sondern die aus dem Beispiel von TrippleX) erscheint vor der Zeile ein rotes Ausrufezeichen.

Ich habe mir ein Error Template erstellt damit ich die Fehlermeldung lesen kann, dort steht: "Der Wert "" konnte nicht konvertiert werden". Einen Konverter gibt es aber nicht. WPF wird wohl im Hintergrund irgendetwas automatisch machen.

Diese Erkenntnisse habe ich schon gewonnen:

  • Unter dem Link von TrippleX findet man auch die Solution vom Originalbeispiel. Diese ist mit .Net 3.5 erstellt worden. Wenn ich dort im DataGrid eine neue Zeile hinzufüge funktioniert alles. Wenn ich die Solution jedoch auf .Net 4.0 umstelle und alle Kompilierungsfehler behebe, habe ich die gleichen Auswirkungen wie oben.

  • Zum Validieren habe ich mir eine ValidationRule erstellt. Diese habe ich einer BindingGroup zugeordnet, welche wiederrum über die ItemBindingGroup Eigenschaft dem DataGrid zugewiesen wird. Das Funktioniert soweit auch.
    Problem ist aber folgendes, wenn ich aus der ComboBox einen anderen Eintrag auswähle (egal, ob in einer bestehender Zeilen oder einer neuen Zeile), dann kann ich über bindingGroup.items auch auf alle aktuellen Werte in der Zeile zugreifen. Einzige ausnahme ist hier der Text, der aus der ComboBox ausgewählt wurde, dieser enthält noch den Wert vor der aktualisierung (bei einer neuen Zeile also ""), aber auf diesen Wert muss ich zur Validierung auch zugreifen können.
    Also nächster Versuch: Zugriff über die getValue Methode der BindingGroup. Als Belohnung kommt eine ValueUnavailableException. Alle anderen Eigenschaften des Objekts kann ich aber über beide Weisen auslesen. Nochmal zur Verdeutlichung ein Code Beispiel (Name wird in der ComboBox angezeigt):


...
// in ValidationRule-Klasse
myObject row = bindingGroup.items[0] as myObject
//Versuch 1: gibt Wert vor Aktualisierung zurück
Console.WriteLine(row.Name);
//Versuch 2: ValueUnavailableException, Bindungsgruppe enthält keine Bindung, die das Element "myObject und die Name-Eigenschaft verwendet.
Console.WriteLine(bindingGroup.GetValue(row,"Name");
...

Ich habe auch schon den ValidationStep auf UpdatedValue getstellt und das SelectedValueBinding der ComboBox auf updateSourceTrigger=LostFocus

Ich versuche das schon stundenlang zum laufen zu bekommen, aber egal was ich auch versuche es will einfach nicht funktionieren. Es muss doch irgendeine einfache Lösung geben, einer DataGridComboBoxColumn eine ItemSource aus dem DataContext zuzuweisen, die vorallem auch in allen Situationen funktioniert.

A
15 Beiträge seit 2012
vor 11 Jahren

Hat denn niemand eine Idee?

Was ich noch vergessen hatte zu sagen ist, dass ich bei dem SelectedValueBinding die UpdateSourceTrigger Eigenschaft auf LostFocus setzen muss, da sonst der ausgewählte Wert nie angezeigt wird.

Es gibt aber eine neue Erkenntnis.
Ich habe nun einen ObjectDataProvider eingesetzt und diesen als ItemsSource in der Standard DataGridComboBoxColumn von WPF verwendet. Wenn ich in eine neue Zeile klicke kommt nun nicht mehr der Fehler "Der Wert "" konnte nicht konvertiert werden". In der ValidationRule kann ich immer noch auf das ausgewählte Objekt zugreifen, jedoch hat die Eigenschaft, die angezeigt wird noch den alten Wert.

Das Problem mit dem roten Rahmen konnte ich beheben.
Problem war, dass ich im DataGrid das SelectedItem an ein Objekt gebunden hatte.
Lösung: das SelectedItem muss an eine Property vom Typ Object, also kein erbendes Object gebunden werden. Finde ich zwar nicht schön, aber es geht.

MfG Ace