Laden...

Inhalt der Combobox wird trotz Data-Binding nicht aktualisiert

Erstellt von sacoma vor einem Jahr Letzter Beitrag vor einem Jahr 699 Views
S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr
Inhalt der Combobox wird trotz Data-Binding nicht aktualisiert

Hallo Leute,

ich arbeite seit ca. 4 Wochen bei einer Firma, die C# verwendet und muss ein Problem lösen.

Ich kann euch nicht den firmeninternen Code zeigen, aber das Problem in einem neutralen Beispiel beschreiben.

In dem neutralen Beispiel stellen wir uns vor, es gehe um eine Spedition bzw. Planung der zu fahrenden (Teil-)Strecken eines LKWs.
Auf einer Oberfläche gibt es 2 Tabs. In einem Tab werden die Stationen, die der Speditions-LKW anfahren soll, eingegeben (z.B.: Ort A, Ort B, Ort C etc.)
Im zweiten Tab werden die Teil-Strecken des LKWs eingegeben; das muss man sich so vorstellen:

  1. Datensatz: LWK fährt von Punkt A nach Punkt B
  2. Datensatz: LKW fährt von Punkt B nach Punkt D
  3. Datensatz: LKW fährt von Punkt D nach Punkt C

Die „von“- und „nach“-Angaben werden in Combo-Boxen ausgewählt.

Das Problem:
Wenn im 1. Tab ein neuer Ort hinzugefügt wird, dann erscheint dieser nicht in den Comboboxen des 2. Tab.
Erst beim Neustart des Programms erscheinen alle Orte in den Comboboxen.

Das Data-Binding erfolgt in der XAML (nicht im Code-Behind (xaml.cs-Datei)).

Hier ist ein beispielhafter Code-Ausschnitt der XAML-Datei (es wird ein „UserControl“ genutzt):


<ResourceDictionary>
<vm IrgendeineViewModelKlasse x:Key="Schluessel"/>
</ResourceDictionary>
[…]
<ComboBox ItemsSource="{Binding Source={StaticResource Schluessel}, Path=Sammlung}" 
SelectedValuePath="Id"
 DisplayMemberPath="Ortsname"
 SelectedValue="{Binding fromPos, Converter={StaticResource int32toInt16Converter}}" 
 Grid.Column="1" Grid.Row="2" Margin="0,0,10,0" />
[…]

Hier ist ein beispielhafter Code-Ausschnitt der betreffenden ViewModel-Datei ():


class IrgendeineViewModelKlasse {
private ObservableCollection<irgendeineKlasseAusDB> _sammlung;
 public ObservableCollection<irgendeineKlasseAusDB> Sammlung
 {
 get { return _sammlung;; }
 set
 {
 _sammlung = value;
 RaisePropertyChanged(nameof(Sammlung));
 }
 }
[…]
} // Ende der Klasse

Durch das Debuging habe ich herausgefunden, dass ein neuer Ort in der Datenbank und im Objekt von „IrgendeineViewModelKlasse“ bzw. in der Eigenschaft „Sammlung“ richtig abgespeichert werden. Aber der neue Ort wird trotzdem nicht in der betreffenden Combo-Box angezeigt - trotz des Data-Bindings!
Kann mir jemand vielleicht erklären, warum die Combo-Box nicht aktualisiert wird?

Danke für jede Hilfe im Voraus. 😉

LG,

sacoma🙂

16.835 Beiträge seit 2008
vor einem Jahr

Du darfst die ObservableCollection nicht neu zuweisen, sonst verliert das Binding die Referenz. Es ist einfach ein neues Objekt.
Willst Du den Inhalt einer ObservableCollection ändern, dann änder den Inhalt und nicht die ObservableCollection selbst.

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Abt,

danke für deine Antwort... aber... das "Observable Sammlung" (wie ich es hier im Bsp. genannt habe) wird nur einmal im Konstruktor der Klasse "IrgendeineViewModelKlasse" erstellt.
Wenn im ersten Tab ein neuer Ort eingetragen (sagen wir durch Eingabe in eine Textbox und Klicken eines Save-Buttons), wird eine Methode aufgerufen, die mit "Sammlung.Add(neuer Ort(...));" den neuen Ort der Eigenschaft hinzufügt (und in die Datenbank den neuen Datensatz abspeichert).

Das kann also nicht der Fehler sein. 😠


Mir fällt gerade auf, dass ich vergessen habe zu erwähnen, dass die "IrgendeineViewModelKlasse" das Interface "INotifyPropertyChanged" hat, das doch eigentlich dafür sorgen sollte, dass die View über Änderungen der Eigenschaft informiert wird.

Hier der korrigierte Code:



class IrgendeineViewModelKlasse  : INotifyPropertyChanged 
{
   private ObservableCollection<irgendeineKlasseAusDB> _sammlung;
   public ObservableCollection<irgendeineKlasseAusDB> Sammlung
  {
       get { return _sammlung;; }
       set
      {
          _sammlung = value;
          RaisePropertyChanged(nameof(Sammlung));
      }
  }

  public event PropertyChangedEventHandler PropertyChanged;
  
  protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
  {
            if (PropertyChanged != null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
  }
[…]
} // Ende der Klasse

190 Beiträge seit 2012
vor einem Jahr

Binde die IrgendeineViewModelKlasse an den DataContext der View und die ItemsSource wird gebunden an Sammlung.

Siehe: [Artikel] MVVM und DataBinding

  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF

187 Beiträge seit 2009
vor einem Jahr

Also ich weiß jetzt nicht genau, ob ich das Problem verstanden habe.
Aber wenn im UserControl folgendes steht


<vm IrgendeineViewModelKlasse x:Key="Schluessel"/>

,
dann wird doch für jedes Usercontrol eine eigene Instanz erstellt. Das ist dann nicht das gleiche.

Eine ObservableCollection braucht im Übrigen auch kein PropertyChanged, weil dies bereits in der ObservableCollection implementiert ist.

Folgendes sollte funktionieren.


    public class IrgendeineViewModelKlasse : INotifyPropertyChanged
    {
        public ObservableCollection<IrgendeineKlasseAusDB> Sammlung { get; set; }

        public IrgendeineViewModelKlasse()
        {
            Sammlung = new ObservableCollection<IrgendeineKlasseAusDB>();
        }

        private string neueStation;

        public string NeueStation
        {
            get { return neueStation; }
            set
            {
                if (neueStation != value)
                {
                    neueStation = value;
                    OnPropertyChanged(nameof(NeueStation));
                    if (neueStation.EndsWith('#'))
                    {
                        AddNeueStation();
                    }
                }
            }
        }

        private void AddNeueStation()
        {
            Sammlung.Add(new IrgendeineKlasseAusDB() { Ortsname = NeueStation.TrimEnd('#') });
            NeueStation = string.Empty;
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class IrgendeineKlasseAusDB
    {
        public string Ortsname { get; set; }
    }


<UserControl x:Class="MyCSharp125121.StationView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d">
    
    <Grid>
        <ComboBox ItemsSource="{Binding Sammlung}"
                  DisplayMemberPath="Ortsname" />
    </Grid>
</UserControl>


<Window x:Class="MyCSharp125121.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyCSharp125121"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Window.DataContext>
        <local:IrgendeineViewModelKlasse />
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Grid Grid.Column="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <local:StationView x:Name="Stationen" 
                               Grid.Column="0"
                               Grid.ColumnSpan="2"
                               Grid.Row="0" />
            <TextBox Grid.Row="1"
                     Grid.Column="0"
                     Grid.ColumnSpan="2"
                     Text="{Binding NeueStation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </Grid>
        <local:StationView x:Name="Strecken" Grid.Column="1" />
    </Grid>
</Window>

Ich weiß, dass ich damit keinen Schönheitspreis gewinnen kann!

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Leute,

ich danke euch für eure hilfreichen Posts.

Ich werde diesen Montag versuchen eure Ideen und Anregungen umzusetzen.😉

Ich denke aber, dass ihr mir den richtigen Weg gezeigt habt... danke nochmal. 😉

LG,

sacoma🙂

S
sacoma Themenstarter:in
23 Beiträge seit 2022
vor einem Jahr

Hallo Leute,

danke nochmal für eure Hilfe am Wochenende.

Ich konnte das Problem endlich verstehen und einigermaßen gut lösen. 🙂

++Zum Verständnis: ++
Es gab tatsächlich zwei Klassen-Objekte (mit unterschiedlichen Datentypen; also von verschiedenen Klassen) je für Tab 1 und für Tab 2, die die betreffenden Daten bei sich abspeicherten. Zwar wurden die Sachen vom 1. Tab auch gleich in die DB abgespeichert, aber weil das Klasse-Objekt von Tab 2, die Daten nur beim Starten des Programms aus der DB einmalig abruft und speichert, gab es keine Aktualisierung.

Ich habe also im CodeBehind (in der xaml.cs-Datei) dafür gesorgt, dass auch während der Laufzeit, die beiden Klassen-Objekt ihre Daten abgleichen und ggf. austauschen.

Das klappt prima und ohne euch hätte ich das nicht so schnell geschafft.

Danke noch mal. 😉

LG,

sacoma

16.835 Beiträge seit 2008
vor einem Jahr

Top!

Aber... 😉

Ich habe also im CodeBehind (in der xaml.cs-Datei) dafür gesorgt, dass auch während der Laufzeit, die beiden Klassen-Objekt ihre Daten abgleichen und ggf. austauschen.

Das ist nicht die Idee von WPF.
Bei WPF sollte man mit [Artikel] MVVM und DataBinding arbeiten.
Der Code Behind ist für sowas eher nicht gedacht; führt meist zu Problemen (weil eben WPF mit MVVM konzipiert ist).

P
257 Beiträge seit 2010
vor einem Jahr

Hallo sacoma!

Wie Abt es schon gesagt hat, geht deine Lösung vollkommen am MVVM-Konzept vorbei.

Dabei macht das MVVM-Konzept dein Projekt einfacher, übersichtlicher, flexibler und vor allem funktioniert es dann auch zuverlässig! 🙂)

Laß uns doch mal etwas MVVM zusammenrühren ...

Man nehme:

für das ViewModel:

  • 2 Listen (ObservableCollection).

    • Eine Liste (StationenListe) für die Stationen (es reicht eine String-Liste [es sei denn du möchtest noch mehere Eigenschaften zum Ort angeben]).
    • Eine Liste (RoutenListe) für die Routen. Für die Routen-Einträge benutzt du eine kleine Datenklasse (StreckenEintrag) die eine Start und eine Ziel-Eigenschaft implementiert.
  • 3 Eigenschaften

    • Eine string Eigenschaft (StationsBezeichnung) für die Bezeichnung der neu zu erstellenden Station.
    • Eine string Eigenschaft (RouteStart) für die Start-Station der neu zu erstellenden Route. (Auswahl aus der Stationen-Liste.)
    • Eine string Eigenschaft (RouteZiel) für die Ziel-Station der neu zu erstellenden Route. (Auswahl aus der Stationen-Liste.)
  • 2 Commands (ICommand)

    • Ein Command (StationAddCommand) zum Hinzufügen einer neuen Station zu der Stationen-Liste.
    • Ein Command (RouteAddCommand) zum Hinzufügen einer neuen Route zu der Routen-Liste.

für die View:

  • 3 ComboBoxen

    • Eine ComboBox für die Darstellung der Stationen-Liste. (Die ItemsSource wird an die Stationen-Liste gebunden)
    • Eine ComboBox für die Auswahl der Start-Station (aus der Stationen-Liste) für eine neu zu erstellende Route.
      (Die ItemsSource Eigenschaft wird an die Stationen-Liste und die SelectedValue-Eigenschaft an die Start-Station-Eigenschaft des ViewModels, gebunden.)
    • Eine ComboBox für die Auswahl der Ziel-Station (aus der Stationen-Liste) für eine neu zu erstellende Route.
      (Die ItemsSource Eigenschaft wird an die Stationen-Liste und die SelectedValue-Eigenschaft an die Ziel-Station-Eigenschaft des ViewModels, gebunden.)
  • 1 TextBox

    • Für die Eingabe der Bezeichnung der neu zu erstellenden Station. (Die Text-Eigenschaft wird an die Stations-Bezeichnungs-Eigenschaft des ViewModels gebunden.)
  • 1 ListView

    • Zur Anzeige der Routen (Die ItemsSource wird an die Routen-Liste gebunden).
  • 2 Button

    • Ein Button für das Hinzufügen einer neuen Station. (Die Command-Eigenschaft wird an das Station-Hinzufügen Command des ViewModels gebunden.)
    • Ein Button für das Hinzufügen einer neuen Route. (Die Command-Eigenschaft wird an das Route-Hinzufügen Command des ViewModels gebunden.)
  • Diverse Container zum Positionieren der oben genannten Objekte. (nach Designer-Gefühl 😉)

Für die Erstellung des ViewModels, der Datenklasse und der View lassen wir uns 20 Minuten Zeit.

Nun füllen wir noch die Command-Routinen mit etwas Leben ...

**Station-Hinzufügen Command: **
Der Stationen-Liste wird ein neuer Eintrag mit dem Wert der ViewModel Stationen-Bezeichnungs-Eigenschaft hinzugefügt.

**Route-Hinzufügen Command: **
Der Routen-Liste wird ein neuer Strecken-Eintrag mit den Werten aus den ViewModel Eigenschaften Start-Station und Ziel-Station, hinzugefügt.

Hinweis: Statt der Meldung: ... wurde hinzugefügt, muss an diesen Stellen die zugrundeliegende Tabelle (konform dem zugrundeliegendem Datenbanksystem [INSERT ...]) aktualisiert werden.

Und ... Fertsch!

War das aufwendiger als deine Lösung???

Die Listen im ViewModel sind zentral, (jederzeit) aktuell und können von beliebigen Objekten der View gebunden werden.
Bei Änderung der Funktionalität können die Datenklassen problemlos erweitert werden und stehen im Binding sofort (allen Objekten der View) zur Verfügung.

Hier das komplette Projekt zum Download.

Mal sehen, ob noch genügend Zeichen zur Verfügung stehen ???

View:


<Window x:Class="CBAktualisierung.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CBAktualisierung"
        mc:Ignorable="d" WindowStartupLocation="CenterScreen"
        Title="ComboBox Aktualisierung" Height="300" Width="400">
    <Window.DataContext>
        <local:MyVM/>
    </Window.DataContext>
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <GroupBox Header=" Stationen " Padding="10">
            <DockPanel>
                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="10,0,0,0">
                    <TextBox Text="{Binding StationsBezeichnung}" MinWidth="50" />
                    <Button Content="+" Margin="5,0,0,0" MinWidth="30" Command="{Binding StationAddCommand}"/>
                </StackPanel>
                <ComboBox ItemsSource="{Binding StationenListe}" SelectedIndex="0"/>
            </DockPanel>
        </GroupBox>
        <GroupBox Grid.Row="1" Header=" Routen " Padding="10" Margin="0,20,0,0">
            <DockPanel>
                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="10,0,0,0">
                    <StackPanel>
                        <TextBlock Text="Start:" FontWeight="Bold"/>
                        <ComboBox ItemsSource="{Binding StationenListe}" SelectedIndex="0" SelectedValue="{Binding RouteStart}" />
                        <TextBlock Text="Ziel:" FontWeight="Bold" Margin="0,5,0,0"/>
                        <ComboBox ItemsSource="{Binding StationenListe}" SelectedIndex="0" SelectedValue="{Binding RouteZiel}"/>
                    </StackPanel>
                    <Button Content="+" Margin="5,0,0,0" MinWidth="30" Command="{Binding RouteAddCommand}"/>
                </StackPanel>
                <ListView ItemsSource="{Binding RoutenListe}" SelectedIndex="0" IsSynchronizedWithCurrentItem="True" >
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="Route(n)">
                                <GridViewColumn.CellTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal" Margin="5,0,0,0">
                                            <TextBlock Text="{Binding Start}" TextAlignment="Right" />
                                            <TextBlock Text="-&gt;"/>
                                            <TextBlock Text="{Binding Ziel}" TextAlignment="Right" />
                                        </StackPanel>
                                    </DataTemplate>
                                </GridViewColumn.CellTemplate>
                            </GridViewColumn>
                        </GridView>
                    </ListView.View>
                </ListView>
            </DockPanel>
        </GroupBox>
    </StackPanel>
</Window>

ViewModel:


public class MyVM : INotifyPropertyChanged
{
    /// <summary>
    /// Liste der Stationen
    /// </summary>
    public ObservableCollection<string> StationenListe { get; set; } = new();

    /// <summary>
    /// Liste der einzelnen Routen
    /// </summary>
    public ObservableCollection<StreckenEintrag> RoutenListe { get; set; } = new();


Natürlich nicht! Hier ist man aber auch sparsam!!!!

Aber ich habe das Projekt ja als Link!

P
257 Beiträge seit 2010
vor einem Jahr

Hallo

Da man aus unerklärlichem Grund seine eigenen Einträge nach kurzer Zeit nicht mehr bearbeiten kann!!!

Kein Desktop-Video senden kann!!!

Hier als Nachtrag eine Programm-Demonstration in Form einer Desktop-Aufnahme.

190 Beiträge seit 2012
vor einem Jahr

Hallo

Da man aus unerklärlichem Grund seine eigenen Einträge nach kurzer Zeit nicht mehr bearbeiten kann!!!

siehe hier: Hello World! myCSharp auf .NET

Ebenfalls anders ist, dass Beiträge in Zukunft nur noch innerhalb eines gewissen Zeitraums editieren werden können, der derzeit 30 Minuten beträgt. Der Zeitraum ist nicht fix; wir müssen auch lernen, welcher Zeitraum hier sinnvoll ist.
Der Grund ist, dass in der Vergangenheit zu oft der Kontext selbst nach Wochen editiert wurde, sodass der Verlauf eines Themas nur noch schwer nachvollzogen werden konnte.

  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF