2017-05-30 4 views
1

Ich habe zwei Comboboxen: Kategorien und Typen. Wenn mein Formular anfänglich angezeigt wird, listet ich alle Kategorien und Typen auf, die in der Datenbank vorhanden sind. Für beide Comboboxen füge ich Zeile 0 manuell ein, um den Wert "All" zu haben, so dass sie nicht wählen müssen, wenn sie nicht wollen.XAML-Bindungsprobleme mit verbundenen Comboboxen

Ich habe beide Comboboxen an ReactiveObjects gebunden. Wenn der Benutzer eine Kategorie auswählt, wird die Combobox Typ automatisch mit einer Abfrage neu gefüllt, um nur die für die ausgewählte Kategorie relevanten Typen zusammen mit der hinzugefügten Zeile 0 anzuzeigen.

Wenn der Benutzer eine Kategorie auswählt, führt er die Abfrage ordnungsgemäß aus, gibt die relevanten Typen zurück, fügt die Zeile 0 ordnungsgemäß hinzu und die Combobox wird korrekt ausgefüllt. Bei der XAML-Größe wird jedoch die Zeile 0 nicht ausgewählt, und die rote Umrandung um die Combobox wird hinzugefügt, was auf eine ungültige Auswahl hinweist.

Wenn für die Combobox Type keine Auswahl getroffen wurde und das Formular übergeben wurde, wird der korrekte Wert 0 übergeben. Während also alles richtig funktioniert, kommuniziert die rote Box um die Combobox Typen dem Benutzer, dass sie etwas falsch gemacht haben, und ich kann nicht feststellen, warum der XAML die ausgewählten Werte nicht aufnimmt. Ich habe den Code ausgeführt, ohne dass die Zeile 0 hinzugefügt wurde, und es hat immer noch dasselbe Verhalten, d. H. Die Combobox ist korrekt ausgefüllt, aber es ist keine Zeile ausgewählt und die rote Gliederung wird angezeigt.

XAML für Comboboxen

<ComboBox 
    Grid.Row="3" 
    Grid.Column="1" 
    Width="200" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    Style="{StaticResource SimpleComboBox}" 
    ItemsSource="{Binding Categories}" 
    SelectedValue="{Binding SearchCriteria.CategoryID}" 
    SelectedValuePath="ComboValueID" 
    DisplayMemberPath="ComboDataValue" 
    /> 

<TextBlock 
    Grid.Row="3" 
    Grid.Column="2" 
    Style="{StaticResource NormalTextNarrow}" 
    Text="Type" VerticalAlignment="Top" 
    /> 

<ComboBox 
    Grid.Row="3" 
    Grid.Column="3" 
    Width="200" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    Style="{StaticResource SimpleComboBox}" 
    ItemsSource="{Binding Types}" 
    SelectedValue="{Binding SearchCriteria.TypeId}" 
    SelectedValuePath="ComboValueID" 
    DisplayMemberPath="ComboDataValue" 
    /> 

Relevante VM Code

// Definition of SearchCriteria. ResourceItem is a ReactiveObject and 
// all of the relevant properties watch for changes in values. 
private ResourceItem searchCriteria; 
public ResourceItem SearchCriteria 
{ 
    get { return searchCriteria; } 
    set { this.RaiseAndSetIfChanged(ref searchCriteria, value); } 
} 

// This all happens in my constructor 

// Defining Row 0 
var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All" }; 

// Populating the comboboxes from the database 
Categories = omr.GetKTValues("RES_CATEGORIES"); 
Types = omr.GetKTValuesRU("RES_TYPES"); 

// Adding the row 0  
Categories.Insert(0, b); 
Types.Insert(0, b); 

// The form is displayed correctly at this point with the row 0 selected 

Problem-Code

// When the user picks a category, this is the method that is invoked: 
private void categoryChanged() 
{ 
    if (SearchCriteria.CategoryID != 0) 
    { 
     Types = rr.GetCategoryTypes(SearchCriteria.CategoryID); 
     SearchCriteria.TypeId = 0; 
    } 
} 

// This runs correctly and returns the relevant Types 
public List<GenericCombobox> GetCategoryTypes(int categoryId) 
{ 
    string sql = "res.usp_GetCategoryTypes"; 
    var types = new List<GenericCombobox>(); 

    SqlConnection sqlCn = DatabaseCommunication.OpenConnection(); 

    using (SqlCommand cmd = new SqlCommand(sql, sqlCn)) 
    { 
     // Omitting db stuff for brevity... 
     try 
     { 
      SqlDataReader dr = cmd.ExecuteReader(); 
      while (dr.Read()) 
      { 
       types.Add(new GenericCombobox 
       { 
        ComboValueID = (int)dr["TypeId"], 
        ComboDataValue = (string)dr["Type"], 
        IsSelected = false, 
        Description = (string)dr["Type"], 
        ComboDataCode = (string)dr["Type"] 
       }); 
      } 
      // More db-stuff omitted 
     } 

     // Adding the row 0 
     var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All", IsSelected = false, Description = "All", ComboDataCode = "All" }; 
     types.Insert(0, b); 

     return types; 
    } 

Aktualisierung mit Zusatzcode

// Object containing the TypeId property 
public class ResourceItem : ReactiveObject, ISelectable 
{ 
    public int Id { get; set; } 
    public int? OriginalItemId { get; set; } 

    // ...many other properties... 

    private int typeId; 
    public int TypeId 
    { 
     get { return typeId; } 
     set { this.RaiseAndSetIfChanged(ref typeId, value); } 
    } 

    // ...and many more follow... 
+1

Wenn ich richtig sehe, setzen Sie die 'Types'-Eigenschaft auf eine leere Liste in KategorieChanged, direkt bevor' Typ' noch eine andere Liste zugewiesen (von rr.GetCategoryTypes zurückgegeben). Warum machst du das? Und wie denkst du, reagiert die Combobox, wenn diese leere Liste die Bindung passiert? – elgonzo

+0

@elgonzo: tbh, es ist nur das Ergebnis meines Versuchens (und Versagens) bei verschiedenen Lösungen. Es ist nicht notwendig für irgendetwas anderes in meinem Code und es hat dasselbe Verhalten, wenn ich es herausnehme. – bassrek

+2

Was passiert, wenn Sie die SearchCriteria.TypeId-Eigenschaft auf 0 oder einen anderen Wert zurücksetzen, nachdem Sie die Types-Eigenschaft neu gefüllt haben? – mm8

Antwort

1

Ich habe in der Lage, das Problem zu reproduzieren, und ich habe ein paar dummen Sachen finde ich tun kann, dass es geschieht zu stoppen.

  1. Wenn ich einen Artikel in anderen Typen als „All“ wählen, dann, wenn ich die Auswahl in Kategorie ändern, wählt er „All“ in Typen.

  2. Es funktioniert auch, wenn in categoryChanged() ich diese Zeile ersetzen ...

    SearchCriteria.TypeId = 0; 
    

    mit dieser:

    SearchCriteria = new ResourceItem() { 
        TypeId = 0, 
        CategoryID = SearchCriteria.CategoryID 
    }; 
    
  3. Wenn ich ResourceItem.TypeId.set raise PropertyChanged unabhängig davon, ob der Wert hat sich geändert oder nicht, es funktioniert wieder korrekt.

Meine Hypothese ist, dass die SelectedItem in den Typen Combobox (die Sie nicht einmal verwenden!) Ändert sich nicht, wenn die Sammlung Änderungen, weil Sie es nicht zu sagen SelectedValue zu aktualisieren.

SearchCriteria.TypeId = 0 Einstellung ist ein no-op, wenn SearchCriteria.TypeId auf Null schon gleich ist, weil RaiseAndSetIfChanged() tut, was nur der Name sagt: Es wird überprüft, ob der Wert wirklich verändert, und wenn es nicht hat, ist es nicht zu erhöhen PropertyChanged.

SelectedValue passiert zufällig, um den gleichen Wert wie der neue "All" -Element hat, aber die Combobox kümmert es nicht. Es weiß nur, dass niemand gesagt hat, dass er eine neue SelectedItem finden soll, und die alte, die es hat, ist nicht mehr gut, weil es nicht in ItemsSource ist.

So funktioniert das auch:

private void categoryChanged() 
{ 
    if (SearchCriteria.CategoryID != 0) 
    { 
     Types = rr.GetCategoryTypes(SearchCriteria.CategoryID); 

     SearchCriteria.SelectedType = Types.FirstOrDefault(); 

     //SearchCriteria.TypeId = 0; 
     //SearchCriteria = new ResourceItem() { TypeId = 0, CategoryID = SearchCriteria.CategoryID }; 
    } 
} 

XAML:

<ComboBox 
    Grid.Row="3" 
    Grid.Column="3" 
    Width="200" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    ItemsSource="{Binding Types}" 
    SelectedValue="{Binding SearchCriteria.TypeId}" 

    SelectedItem="{Binding SearchCriteria.SelectedType}" 

    SelectedValuePath="ComboValueID" 
    DisplayMemberPath="ComboDataValue" 
    /> 

Klasse ResourceItem

private GenericCombobox selectedType; 
public GenericCombobox SelectedType 
{ 
    get { return selectedType; } 
    set { this.RaiseAndSetIfChanged(ref selectedType, value); } 
} 

Ich denke, Ihre beste Wette meine Option # 2 oben ist:

private void categoryChanged() 
{ 
    if (SearchCriteria.CategoryID != 0) 
    { 
     Types = rr.GetCategoryTypes(SearchCriteria.CategoryID); 

     SearchCriteria = new ResourceItem() { 
      TypeId = 0, 
      CategoryID = SearchCriteria.CategoryID 
     }; 

     // Do you need to do this? 
     // SearchCriteria.PropertyChanged += SearchCriteria_PropertyChanged; 
    } 
} 

Das potentielle Problem hier ist, dass ich in meinem Testcode categoryChanged() von einem PropertyChanged Handler auf SearchCriteria rief. Wenn ich ein neues SearchCriteria erstelle, muss ich sicherstellen, dass ich dieses Ereignis auf dem neuen Ereignis behandle.

Gegeben, vielleicht verbindlich SelectedItem auf der Combobox Typen ist die beste Lösung nach allem: Es ist die einzige, die ich denke, dass das Viewmodel nicht seltsame Dinge tun müssen, um Fehlverhalten in der Ansicht, dass es zu kompensieren sollte wirklich nicht bewusst sein.

+1

Das Objekt SearchCriteria neu zu erstellen ist problematisch, da ich viele andere Eigenschaften in dieser Ansicht habe, die ich aus Platzgründen weggelassen habe. Aber Ihre Anmerkung: 'SettingCriteria.TypeId = 0 ist ein No-Op, wenn SearchCriteria.TypeId bereits gleich Null ist ...' ist der Schlüssel. Vielen Dank für die Mühe der Reproduktion. – bassrek

+0

@bassrek Ja, je mehr ich darüber nachdachte, 'SearchCriteria' zu erstellen, desto mehr bekam ich das Gefühl, dass es keine gute Idee für dich sein würde. –