none
Combobox per Code auf einen bestimmten EIntrag festlegen (aka cascading comboboxes ?) RRS feed

  • Frage

  • Mein Problem dürfte ein Spezialfall des Themas "cascading comboboxes" sein.

    Ich lege mit Combobox1 einen Eintrag fest und lese aus den Daten des selektierten EIntrages den Fremdschlüssel.

    Anhand dieses FK soll jetzt aus Combobox2 der korrespondierende Eintrag gewählt werden.

    Das Problem wird im Netz wohl als cascading ... behandelt.

    Dabei wird ausgehend von einer Selektion in CB1, die Menge an Einträgen in CB2 eingeschränkt, indem die Select-Anweisung neu gestaltet wird.

    z. B wird in CB1 ein Land ausgewählt und anschliessend der Inhalt von CB2 so gestaltet, daß dort die Bundesländer/Staaten aufgelistet werden.

    Wenn ich jetzt meine CB2 maximal einschränke - nämlich auf den DS mit meinem FK, dann kann ich nur noch diesen Eintrag auswählen. Das ginge aber zu weit.

    Ich aber möchte nur, daß in CB2 ein Datensatz passend zum FK selektiert wird ohne die Auswahl einzuschränken. Praktisch eine Vorauswahl, die man dann aber ändern kann.

    Nun enstpricht aber der FK in CB1 sicherlich nur selten dem SelectedIndex von CB2.

    Wie könnnte ich dieses Problem lösen ?

    Sonntag, 14. Dezember 2014 13:53

Alle Antworten

  • Wie genau soll man dein Problem jetzt verstehen?

    Ich gehe davon aus, dass du c# / WPF verwendest, weil du nichts anderes genannt hast.

    Ich habe verstanden, dass man in der ComboBox1 ein Land wählt (Deutschland / Amerika etc.) und dann in der zweiten Box nur passende Vorschläge findet (Deutschland -> NRW, Berlin, ...). Habe ich das so richtig verstanden.

    Wenn es so ist, würde ich Datenmodelle verwenden.

    Du könntest z.B. folgendes Machen:

    //Box1 und Box2 -> beides ComboBoxen
    
    void CreateList() {
        List<CountryModelItem> list = CreateList(...) //Sub-Methode
        this.Box1.ItemsSource = list;
    }
    
    
    void OnBox1SelctionChanged(...) {
      CountryModelItem item = (CountryModelItem)this.Box1.SelectedItem;
      this.Box2.ItemsSource = item.SubCountries; //Ist ja wieder eine Liste
    }
    
    
    //... Modell
    
    public class CountryModelItem {
       private string _name;
       private list<string> _subCountries;
    
       public CountryModellItem(int lngId /* Der Ländercode */) { 
            //Name des Landes UND seiner Unterländer definieren... -> Stichwort Datenquelle (*.xml usw.)
            //_name und _subCountries belegen (dürfen nicht Null sein)
        }
    
        public string Name {
            get { return this._name; }
        }
    
        public string SubCountries {
            get { return this._subCountries; }
        }
        
    }

    Eine andere Möglichkeit wäre die Benutzung von DataBindings in XAML (bei gleicher Verwendung von der Klasse

    CountryModelItem

    ).


    (C) 2014 Thomas Roskop

    Sonntag, 14. Dezember 2014 14:27
  • Wegen den Bindings, so würde es funktionieren:

    XAML:

    <ComboBox Name="CB1" MinWidth="150" SelectionChanged="CB1_SelectionChanged"/> <ComboBox Name="CB2" MinWidth="150" ItemsSource="{Binding ElementName=CB1, Path=SelectedItem.SubCountries}"/>

    Und der passende c# Code:

    private void Window_Loaded(object sender, RoutedEventArgs e) { List<MyCountry> c = new List<MyCountry>(); string[] names = { "Germany", "USA", "England", "Russia" }; foreach (var item in names) { c.Add(new MyCountry(item)); } this.CB1.ItemsSource = c; } private void CB1_SelectionChanged(object sender, SelectionChangedEventArgs e) {

    //Wenn kein Binding verwendet wird, bitte Kommentar entfernen! //this.CB2.ItemsSource = ((MyCountry)this.CB1.SelectedItem).SubCountries; } ///***** public class MyCountry { private string _name; private List<string> _subCountries; public MyCountry(string name) { this._name = name; this._subCountries = new List<string>(); for (int i = 0; i < 7; i++) { this._subCountries.Add(name + " " + i.ToString()); //Unterkategoerein erstellen :) } } public string Name { get { return _name; } } public List<string> SubCountries { get { return _subCountries; } } public override string ToString() { return this._name; } }

    Sollte selbsterklärend sein.


    (C) 2014 Thomas Roskop


    • Bearbeitet Thomas Roskop Sonntag, 14. Dezember 2014 14:46 Notiz
    Sonntag, 14. Dezember 2014 14:45
  • Hallo,
    mir geht es wie Thomas, ich bin mir nicht sicher ob ich verstanden habe, was du vor hast.

    Ich habe dich so verstanden, dass du in CB1 eine Liste von Ländern hast (Bspws.: Deutschland, Österreich, Schweiz) und in CB2 die einzelnen Bundeländer/Kantone (Bspws.: Sachsen, Berlin, Bayern,...,Bern, Luzern, ...)

    Meine Lösung ist auch auf WPF ausgelegt:

    Class MainWindow 
    
        Dim l As New List(Of Item)()
        Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
            l.Add(New Item() With {.Country = "Deutschland", .State = "Sachsen"})
            l.Add(New Item() With {.Country = "Deutschland", .State = "Bayern"})
            l.Add(New Item() With {.Country = "Deutschland", .State = "Berlin"})
            l.Add(New Item() With {.Country = "Schweiz", .State = "Luzern"})
            l.Add(New Item() With {.Country = "Schweiz", .State = "St. Gallen"})
            l.Add(New Item() With {.Country = "Schweiz", .State = "Thurgau"})
            'usw.'
    
            'Länder ausgeben
            cb1.ItemsSource = l.Select(Function(x) x.Country).Distinct()
            'Die Bundesländer ausgeben
            cb2.ItemsSource = l.Select(Function(x) x.State)
        End Sub
    
        Private Sub ComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
            cb2.SelectedItem = l.First(Function(x) x.Country = cb1.SelectedItem.ToString()).State
        End Sub
    
    End Class
    
    Public Class Item
        Public Property Country As String
        Public Property State As String
    End Class
    Wobei hier dann natürlich auch Kombinationen wie Schweiz-Sachsen als Auswahl möglich wären. Ich vermute daher mal, das du eine weitere Einschränkung brauchst, wonach nur die passenden Bundesländer auftauchen:
        Dim l As New List(Of Item)()
        Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
            'l befüllen
            'usw.'
    
            'Länder ausgeben
            cb1.ItemsSource = l.Select(Function(x) x.Country).Distinct()
            cb2.IsEnabled = False
        End Sub
    
        Private Sub ComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
            cb2.IsEnabled = True
            cb2.ItemsSource = l.Where(Function(x) x.Country = cb1.SelectedItem.ToString()).Select(Function(x) x.State)
            cb2.SelectedIndex = 0
        End Sub
    Wobei für letzteres Thomas Lösung vielleicht besser wäre, also die Bundesländer als Unterliste zu den Staaten. In meiner Signatur findest du einen C# -> VB.NET Konverter, falls du den Code nicht verstehst.


    Tom Lambert - C# MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Sonntag, 14. Dezember 2014 15:46
  • Leider war ich zu unpräzise:

    1. ich verwende VB.NET

    2. alle Combo-Boxen werden aus Datasets befüllt , sind aber nicht an DB-Felder gebunden.

    Wähle ich in CB1 einen Eintrag, dann lese ich per Code aus weiteren SPalten des gewählten Eintrages einen Fremdschlüssel.

    In CB2 sind wiederum Datensätze aus einem anderen Dataset.

    Ich möchte nun per Code in CB2 den Datensatz vorwählen, der den betreffenden FK hat. SelectedIndex dürfte nicht hinhauen, da der FK nur selten dem Index entsprechen dürfte.

    Mittwoch, 17. Dezember 2014 18:47
  • Comboboxes aus Dataset füllen, wie geht das denn? Ein Dataset besteht aus zahlreichen Tabellen mit unterpunkten.

    Vielleicht könntest du einen Teil deines Source Codes zeigen (wird ja kein Hochsicherheitsobjekt im Top-Secret-Niveau sein). Dann könnte man es besser nachvollziehen. Ein Code sagt mehr als tausend Worte...


    Mittwoch, 17. Dezember 2014 19:02
  • Hi,
    nachfolgend eine kleine Demo, wie man das machen könnte:

    Public Class Form1
    
      Dim cb1 As New ComboBox With {.Top = 10, .Left = 10, _
                                    .DisplayMember = "Text1", _
                                    .ValueMember = "ID"}
      Dim cb2 As New ComboBox With {.Top = 40, .Left = 10, _
                                    .DisplayMember = "Text2", _
                                    .ValueMember = "ID"}
      Dim ds1 As New DataSet
      Dim WithEvents bs1 As New BindingSource
      Dim ds2 As DataSet
    
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Me.Controls.AddRange(New Control() {cb2, cb1})
        ' DataSet 1 mit CB1 '
        With ds1
          .Tables.Add(New DataTable With {.TableName = "Tab1"})
          With .Tables(0).Columns
            .Add("ID", GetType(Integer))
            .Add("Text1", GetType(String))
          End With
          With .Tables(0)
            For i = 1 To 9
              Dim dr = .NewRow
              dr(0) = i * 2
              dr(1) = String.Format("Zeile {0}", i)
              .Rows.Add(dr)
            Next
          End With
        End With
        bs1.DataSource = ds1.Tables(0)
        cb1.DataSource = bs1
      End Sub
    
      Private Sub CB1_Change() Handles bs1.PositionChanged
        ' DataSet 2 mit CB2 '
        ds2 = New DataSet
        With ds2
          .Tables.Add(New DataTable With {.TableName = "Tab2"})
          With .Tables(0).Columns
            .Add("ID", GetType(Integer))
            .Add("Text2", GetType(String))
          End With
          With .Tables(0)
            For i = 1 To 9
              Dim dr = .NewRow
              dr(0) = i
              dr(1) = String.Format("{0} {1}", CType(bs1.Current, DataRowView).Row(1), i)
              .Rows.Add(dr)
            Next
          End With
        End With
        cb2.DataSource = ds2.Tables(0)
      End Sub
    
    End Class

    Es gibt aber auch noch andere Möglichkeiten, die u.U. besser sind. Dazu braucht man aber etwas mehr Information, was erreicht werden soll.

    --
    Peter



    Mittwoch, 17. Dezember 2014 19:41
  • Wie genau soll man dein Problem jetzt verstehen?

    Ich gehe davon aus, dass du c# / WPF verwendest, weil du nichts anderes genannt hast.

    ...


    (C) 2014 Thomas Roskop

    Hi Thomas,
    wer im VB.NET-Forum postet, meint vielleicht auch VB.NET und nicht C#.NET.

    --
    Peter

    Mittwoch, 17. Dezember 2014 19:45
  • Ja, das mit dem VB.NET habe ich übersehen. Aber beie Sprachen sind doch fast gleich, zumindestens wenn man sie auf .NET verwendet. Und nicht umsonst gibt es genug c# <-> VB.NET Converter im Internet.


    © 2015 Thomas Roskop

    Mittwoch, 17. Dezember 2014 19:54
  • Hallo,
    ich habe nochmal versucht dein Vorhaben in WPF zu realisieren. In nachfolgendem Code sind cb1 und cb2 die ComboBoxen:

        Dim dt1 As New DataTable
        Dim dt2 As New DataTable
        Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
            dt1.Columns.Add("Col1", GetType(String))
            dt1.Columns.Add("Col2", GetType(String))
            For i = 0 To 100
                dt1.Rows.Add("Wert " & i, "Key" & i)
            Next
    
            dt2.Columns.Add("Col1", GetType(String))
            dt2.Columns.Add("Col2", GetType(String))
            For i = 0 To 100
                'Die Werte entsprechen der 1. DataTable, aber der Key ist hier die erste Spalte'
                'Damit die SelectedIndex verschieden sind, hier rückwärts'
                dt2.Rows.Add("Key" & (100 - i), "Wert " & (100 - i))
            Next
    
            cb1.ItemsSource = dt1.AsDataView()
            cb2.ItemsSource = dt2.AsDataView()
        End Sub
    
        Private Sub cb1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles cb1.SelectionChanged
          
            Dim d = cb2.ItemsSource.OfType(Of DataRowView).FirstOrDefault(Function(x) x.Row.Field(Of String)("Col1") = CType(cb1.SelectedItem, DataRowView).Row("Col2").ToString)
            cb2.SelectedItem = d
    
        End Sub

    Das Herzstück ist die Abfrage in der letzten Methode. Dort wird das erste Element aus cb2 heraus gesucht, welches in Col1 den Wert stehen hat, der in Col2 in cb1 ausgewählt wurde.

    Wenn das noch immer nicht das ist, was du brauchst, fände ich ein Beispiel hilfreich. Also mit ein paar Beispieldaten, was in welchem Fall ausgewählt werden soll.
    Nach wie vor steht die Frage ob du WPF oder WinForms verwendest.


    Tom Lambert - C# MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Mittwoch, 17. Dezember 2014 19:58
  • C# und VB.NET verwenden zwar intern häufig die selben Klassen, es gibt aber wichtige Unterschiede. So kann man beispielsweise VBs Nothing nicht mit C#s null gleich setzen usw. Weiterhin machen auch die Konverter Fehler, die nicht immer ganz klar sind. Auch kann es sein das es unter VB.NET einen effizienteren Weg gibt, als den, den C# verwendet. (Besonders in älteren Versionen, wo VB.NET bei weitem nicht den Funktionsumfang von C# hatte)

    Alles in Allem ist es daher besser direkt VB.NET Code zu posten.


    Tom Lambert - C# MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Mittwoch, 17. Dezember 2014 20:03
  • Hi Thomas,
    nach Deiner Argumentation kann ich in Zukunft Quellcode in Lisp.NET posten, da es ja kein Problem ist, den MSIL-Code in die gewünschte Sprache zu rekompilieren. Ich denke, dass das für Anfänger kein optimaler Weg ist und wir doch bei den Themen bleiben sollten, die durch die Foren vorgegeben sind.

    Übrigens solltest Du mal Deinen VB.NET-Code laufen lassen und so korrigieren, dann man damit auch etwas anfangen kann.

    --
    Peter


    Mittwoch, 17. Dezember 2014 20:51
  • OK, ich gebe mich geschlagen.

    Ich nahm an, dass der Code so gut portabel ist, da ich, als ich von Früher VB.Net auf c# (weil ich nebenbei c++ gemacht habe) gewechselt bin, keine Probleme hatte zu migrieren.

    Und das es Lisp auch für .NET gibt, erstaunt mich und schockiert mich doch zugleich.

    Ich wünsche noch einen schönen Abend.


    © 2015 Thomas Roskop

    Mittwoch, 17. Dezember 2014 21:01
  • Hi Thomas,
    es gibt über 20 Programmiersprachen, für die es Compiler für die Erzeugung von  MSIL-Code gibt, z.B. auch COBOL. Wieso schockiert Dich das?

    Die automatischen Konverter von Code aus MSIL in eine Programmiersprache sind nur bedingt für einfache Konstrukte brauchbar. Schau Dir mal an, was der VB.NET-Compiler zusätzlich alles einbaut, was C#.NET-Programmierer meist nicht machen, wie beispielsweise das Aufräumen von Objekten. Auch kenne ich keinen Konverter, der aus dem optimierten MSIL-Code wieder übersichtliche With-Konstrukte baut.

    --
    Peter
    Meine Homepage mit Tipps und Tricks

    Donnerstag, 18. Dezember 2014 04:15