Laden...

SelectedItem-Property einer Combobox an die Property meines ausgewählen Objekts binden

Erstellt von Kriz vor 6 Jahren Letzter Beitrag vor 6 Jahren 3.426 Views
K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren
SelectedItem-Property einer Combobox an die Property meines ausgewählen Objekts binden

Hi zusammen,

folgende Situation:

Ich habe zwei Klassen:


public class EmployeeContract
      {
             public string ContractName {get;set;}
             public Int ContractID {get;set;}
       }

public class Employee
      {
             public string Forname {get; set}
             public EmployeeContract Contract {get; set;}
       }

Die Mitarbeiter sind in einer Datenbank gespeichert und beim Einlesen wird der Contract anhand der ContractID (in der Datenbank mittels ForeignKey gesetzt) zugewiesen.

Dann habe ich meine View:

<ListBox x:Name="lstEmployee" ItemsSource="{Binding Path=ListOfEmployees}" HorizontalAlignment="Left" Height="276" Margin="25,19,0,0" VerticalAlignment="Top" Width="217" />
<TextBox x:Name="txtForename" Text="{Binding SelectedItem.Forname, ElementName=lstEmployee}" HorizontalAlignment="Left" Height="24" Margin="342,21,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="129" VerticalContentAlignment="Center" GotFocus="SelectText"/>
<ComboBox x:Name="cmbContract" ItemsSource="{Binding ListOfContracts, Mode=TwoWay}" SelectedItem="{Binding SelectedItem.Contract, ElementName=lstEmployee, Mode=TwoWay}" HorizontalAlignment="Left" Margin="342,179,0,0" VerticalAlignment="Top" Width="129"/>

Den DataContext setze ich in der CodeBehind mit

DataContext = new ViewModels.VM_EditEmployee(this);

Und das ViewModel sieht so aus:

public class Model_EditEmployee : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ObservableCollection<Common.Employee> ListOfEmployees { get; set; }
        public ObservableCollection<Common.EmployeeContract> ListOfContracts { get; set; }
        public ObservableCollection<Common.EmployeeGroup> ListOfGroups { get; set; }

        public ICommand AddCommand { get; set; }
        private Forms.Employee.frmEditEmployee tmpView;

        public Model_EditEmployee(Forms.Employee.frmEditEmployee newView)
        {
            tmpView = newView;
            AddCommand = new RelayCommand(o => AddEntry());

            ListOfEmployees = Database.Database_Employee.GetListOfEmployee();
            ListOfContracts = Database.Database_Contract.GetListOfContract();
            ListOfGroups = Database.Database_Group.GetListOfGroups();
        }

        protected internal void OnPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
        }

Nun zu meinem Problem:
Die lstEmployee wird korrekt mit den Mitarbeitern gefüllt, sowie die Auswahlmöglichkeiten der cmbContract. Wenn ich einen Mitarbeiter auswähle wird die txtForname auch brav richtig mit dem Vornamen gefüllt, aber die cmbContract bleibt leer. Also man kann zwar die verschiedenen Verträge auswählen, dennoch funktioniert das Binding da nicht.
Wenn ich nun direkt an die SelectedIndex-Property der cmbContract mit der ContractID binde funktioniert das nur solange die ContractID gleich dem Index ist, was nur selten so ist.
Wenn ich die Text-Property der cmbContract an die ContractName binde, dann wird beim auswählen zwar auch die cmbContract korrekt angezeigt, also mit dem richtigen Vertrag, aber das Speichern funktioniert dann wieder nicht, weil das über die ContractID gemacht wird und auch so bleiben soll.

Also: Ich möchte die SelectedItem-Property der cmbContract an die Contract-Property meines ausgewählen Employee binden. Was mache ich falsch?

Vielen Dank für die Hilfe!

709 Beiträge seit 2008
vor 6 Jahren

Mir ist was anderes aufgefallen: Dein ViewModel kennt seine View, was bei einer ordentlichen Trennung nicht der Fall sein sollte.

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

Das habe ich gemacht damit ich den Mitarbeiter auch wieder speichern kann. Wie kann ich denn sonst auf den ausgewählten Mitarbeiter zurückgreifen?

709 Beiträge seit 2008
vor 6 Jahren

Du könntest im ViewModel eine neue Eigenschaft für den ausgewählten Mitarbeiter anlegen und diese an die SelectedItem-Eigenschaft der ListBox binden.
Dann kannst du innerhalb des ViewModels diese Eigenschaft abfragen, da sie durch die View gesetzt wird.

D
985 Beiträge seit 2014
vor 6 Jahren

Beim Binden der ComboBox wird das gesetzte SelectedItem mit den Items der ComboBox verglichen damit der entsprechende Eintrag in der ComboBox angezeigt werden kann. (Es erfolgt ein IndexOf Zugriff auf die Items).

In den Items gibt es einen Eintrag mit der ContractId 4 und der aktuell gewählte Eintrag hat bei Contract auch eine Instanz mit der ContractId 4. Trotzdem sind diese nicht gleich, da du hier wohl unterschiedliche Instanzen hast.

Entweder musst du beim Erstellen der Employee Instanzen dem Contract die gleichen Instanzen aus der ListOfContracts verwenden oder du musst in der EmployeeContract Klasse die Equals/GetHashcode Methoden überschreiben, so dass Instanzen mit der gleichen ContractId auch als gleich erkannt werden.

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

@pinki: Super, funktionier, vielen Dank!

@Sir Rufo, danke für den Tip, funktioniert auch, vielen Dank!

Thema kann geschlossen werden.

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

Moment, es hat sich ein neues "Problem" aufgetan...

Im ViewModel wird in einer Methode ein neuer Mitarbeiter erstellt, wie kann ich denn dann das Binding auf diesen neuen Mitarbeiter setzen?

T
461 Beiträge seit 2013
vor 6 Jahren

Ähem, stell mal ein wenig Code rein, dann sieht man eher woran es hackt...
(zu spät gesehen 😉)

Im Grunde, willst du das er in der ComboBox-Liste vorkommt, füge ihm einfach in die Auswahlliste der ComboBox im ViewModel hinzu. Dann sollte er automatisch in der CB angezeigt werden.

Dasselbe wenn du willst, daß er sofort in der CB als selektiert angezeigt wird. Wenn du das willst, weise ihm einfach auf das Binding-Property SelectedItem zu... (muß sich natürlich vorher in der Auswahlliste befinden...)

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

Erstmal vielen Dank für die tatkräftige Hilfe, nur hab ich jetzt wieder eine neue Problemstellung:
So wie von pinki vorgeschlagen habe ich in meinem ViewModel eine neue Eigenschaft (selectedEmployee) erstellt, die ich an die selectedItem Eigenschaft der ListBox gebunden habe, damit ich auf den ausgewähten Mitarbeiter der ListBox zugreifen kann.
Nun würde ich gern einen neuen Mitarbeiter erstellen, dies mache ich, indem ich eine neue Instanz mit verschiedenen Werten erstelle und diese Instanz dem selectedEmployee zuweise, da ich dachte dass somit ja das Binding direkt auf den neuen Mitarbeiter zeigt und somit alle Textboxen korrekt angezeigt werden. Funktioniert auch super.
Wenn ich dann aber nach dem Speichern (Also ein einfacher Datanbankeintrag) einen anderen Mitarbeiter in der Liste auswähle werden die Textboxen nicht mehr aktualisiert.
Vermutlich liegt es daran, dass ich in dem Moment wo ich dem selectedEmployee einen Wert, also den neuen Mitarbeiter zugewiesen habe, das Binding gelöscht wurde.

Ich habe versucht via Code das Binding wieder richtig zu setzten, das funktioniert aber nicht, da ich dafür im ViewModel auf das View zugreifen müsste und das widerspricht ja dem MVVM-Pattern.

Hat noch jemand eine andere Idee?

709 Beiträge seit 2008
vor 6 Jahren

Bevor du den neuen Mitarbeiter der ans SelectedItem gebundene Eigenschaft zuweist, sollte dieser der Mitarbeiterliste hinzugefügt werden. Damit die ListBox das auch mitbekommt, sollte diese Auflistung eine ObservableCollection sein.

T
461 Beiträge seit 2013
vor 6 Jahren

Wenn ich dann aber nach dem Speichern (Also ein einfacher Datanbankeintrag) einen anderen Mitarbeiter in der Liste auswähle werden die Textboxen nicht mehr aktualisiert.

Ich denke, da mußt du schon etwas mehr Code zeigen. Eigentlich sollte das Speichern keinen Einfluß auf Databindings haben...

Wie sieht bei dir so ein Property aus? Was genau machst du nach dem Speichern?
Hast du (wie schon ich vorher und @pinki schrieb) den neuen Eintrag auch zuvor in die Auswahlliste eingefügt?

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

meine View:

<Window x:Class="Shifter.Forms.Employee.frmEditEmployee"
        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"
        mc:Ignorable="d"
        Title="frmEditEmployee" Height="350.141" Width="497.195" WindowStyle="None" ResizeMode="NoResize" Foreground="Blue" WindowStartupLocation="CenterScreen">        

    <Grid>    
        <ListBox x:Name="lstEmployee" IsEnabled="{Binding NoEditMode}" SelectedItem="{Binding MasterEmployee}" ItemsSource="{Binding Path=ListOfEmployees}" HorizontalAlignment="Left" Height="276" Margin="25,19,0,0" VerticalAlignment="Top" Width="217" />
        <TextBox x:Name="txtForename" Text="{Binding SelectedItem.Forname, ElementName=lstEmployee}" Margin="342,21,0,0" GotFocus="SelectText"/>
        <TextBox x:Name="txtLastname" Text="{Binding SelectedItem.Lastname, ElementName=lstEmployee}" Margin="342,47,0,0" GotFocus="SelectText"/>
        <TextBox x:Name="txtShowingname" Text="{Binding SelectedItem.Showingname, ElementName=lstEmployee}" Margin="342,74,0,0" GotFocus="SelectText"/>
        <TextBox x:Name="txtPersonelNumber" Text="{Binding SelectedItem.EmployeeID, ElementName=lstEmployee}" Margin="342,99,0,0" GotFocus="SelectText"/>
        <DatePicker x:Name="dtpBirthday" SelectedDate="{Binding SelectedItem.Birthday, ElementName=lstEmployee}" HorizontalAlignment="Left" Margin="342,125,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.256,0.417" SelectedDateFormat="Short"/>
        <TextBox x:Name="txtLoan" Text="{Binding SelectedItem.Loan, ElementName=lstEmployee}" Margin="342,151,0,0" TextWrapping="Wrap" GotFocus="SelectText"/>
        <TextBox x:Name="txtPhone" Text="{Binding SelectedItem.Telephone, ElementName=lstEmployee}" Margin="342,229,0,0" TextWrapping="Wrap" GotFocus="SelectText"/>
        <TextBox x:Name="txtEMail" Text="{Binding SelectedItem.EMail, ElementName=lstEmployee}" Margin="342,255,0,0" TextWrapping="Wrap" GotFocus="SelectText"/>
        <ComboBox x:Name="cmbContract" ItemsSource="{Binding ListOfContracts, Mode=TwoWay}" SelectedItem="{Binding SelectedItem.Contract, ElementName=lstEmployee, Mode=TwoWay}" HorizontalAlignment="Left" Margin="342,179,0,0" VerticalAlignment="Top" Width="130"/>
        <ComboBox x:Name="cmbGroup" ItemsSource="{Binding ListOfGroups, Mode=TwoWay}" SelectedItem="{Binding SelectedItem.Group, ElementName=lstEmployee, Mode=TwoWay}" HorizontalAlignment="Left" Margin="342,204,0,0" VerticalAlignment="Top" Width="130"/>
        <CheckBox x:Name="chkHide" Content="MA im Dienstplan ausblenden" IsChecked="{Binding SelectedItem.isHiding, ElementName=lstEmployee}" HorizontalAlignment="Left" Margin="259,280,0,0" VerticalAlignment="Top" ToolTip="Der Mitarbeiter wird nicht im Dienstplan angezeigt (beispielsweise wegen längerer Abwesenheit)" Width="211"/>            

        <Button x:Name="btnAdd" Content="Add" Command="{Binding Path=cmdAdd, Mode=TwoWay}" HorizontalAlignment="Left" Height="35" Margin="25,303,0,0" VerticalAlignment="Top" Width="35">
        </Button>
        <Button x:Name="btnEdit" Content="Edit" Command="{Binding Path=cmdEdit}" HorizontalAlignment="Left" Height="35" Margin="70,303,0,0" VerticalAlignment="Top" Width="35">
        </Button>
        <Button x:Name="btnDelete" Content="Delete" Command="{Binding Path=cmdDelete}" HorizontalAlignment="Left" Height="35" Margin="115,303,0,0" VerticalAlignment="Top" Width="35">
        </Button>
        <Button x:Name="btnCancel" Content="Cancel" Command="{Binding Path=cmdCancel}" HorizontalAlignment="Left" Height="35" Margin="391,303,0,0" VerticalAlignment="Top" Width="35">
        </Button>
        <Button x:Name="btnOK" Content="OK" Command="{Binding Path=cmdOK}" HorizontalAlignment="Left" Height="35" Margin="436,303,0,0" VerticalAlignment="Top" Width="35">
        </Button>
    </Grid>
</Window>

mein ViewModel:

namespace Models
{
    public class VM_EditEmployee : INotifyPropertyChanged
    {
        #region Propertys
        private ObservableCollection<Common.Employee> mListOfEmployees;
        private ObservableCollection<Common.EmployeeContract> mListOfContracts;
        private ObservableCollection<Common.EmployeeGroup> mListOfGroups;
        private Common.Employee mMasterEmployee;
        private bool isNew;
        private Employee.frmEditEmployee EmpoyeeView;

        public event PropertyChangedEventHandler PropertyChanged;

        public ObservableCollection<Common.Employee> ListOfEmployees
        {
            get
            {
                return mListOfEmployees;
            }

            set
            {
                mListOfEmployees = value;
                OnPropertyChanged("ListOfEmployees");
            }
        }
        public ObservableCollection<EmployeeContract> ListOfContracts
        {
            get
            {
                return mListOfContracts;
            }

            set
            {
                mListOfContracts = value;
                OnPropertyChanged("ListOfContracts");
            }
        }
        public ObservableCollection<EmployeeGroup> ListOfGroups
        {
            get
            {
                return mListOfGroups;
            }

            set
            {
                mListOfGroups = value;
                OnPropertyChanged("ListOfGroups");
            }
        }
        public Common.Employee MasterEmployee
        {
            get
            {
                return mMasterEmployee;
            }

            set
            {
                mMasterEmployee = value;
                OnPropertyChanged("MasterEmployee");
            }
        }


        public ICommand cmdAdd { get; set; }
        public ICommand cmdEdit { get; set; }
        public ICommand cmdDelete { get; set; }
        public ICommand cmdCancel { get; set; }
        public ICommand cmdOK { get; set; }

        #endregion

        public VM_EditEmployee(Employee.frmEditEmployee tmpView)
        {
            EmpoyeeView = tmpView;
            cmdAdd = new RelayCommand(o => AddEntry());
            cmdEdit = new RelayCommand(o => EditEntry());
            cmdDelete = new RelayCommand(o => DeleteEntry());
            cmdCancel = new RelayCommand(o => Cancel());
            cmdOK = new RelayCommand(o => SaveEntry());

            ListOfEmployees = Database_Employee.GetListOfEmployee();
            ListOfContracts = Database_Contract.GetListOfContract();
            ListOfGroups = Database_Group.GetListOfGroups();
        }

        protected internal void OnPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
        }

        private void DeleteEntry()
        {
            if (MessageBox.Show("Sure you want to delete?", "Question", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                Database_Employee.DeleteEmployee(MasterEmployee);
                ListOfEmployees = Database_Employee.GetListOfEmployee();
            }
        }

        private void Cancel()
        {

        }

        private void AddEntry()
        {
            isNew = true;
            Common.Employee newEmployee = new Common.Employee()
            {
                Forname = "Max",
                Lastname = "Mustermann",
                Showingname = "Max",
                EmployeeID = 666,
                Birthday = new System.DateTime(1980, 5, 5),
                Loan = "9,50",
                Contract = Database_Contract.GetListOfContract()[0],
                Group = Database_Group.GetListOfGroups()[0],
                Telephone = "012456789",
                EMail = "chris@roedernet.de",
                isHiding = false
            };

            ListOfEmployees.Add(newEmployee);
            MasterEmployee = newEmployee;
        }

        private void EditEntry()
        {
            isNew = false;
        }

        private void SaveEntry()
        {
                if (isNew == true)
                {
                    Database_Employee.CreateEmployee(MasterEmployee);                    
                }
                else                    {
                    Database_Employee.EditEmployee(MasterEmployee);
                }
                
            }
            else // Wenn der EditMode nict aktiv ist
            {
                EmpoyeeView.Close();
            }


        }
    }
}

Der neue Mitarbeiter "newEmployee" wird erst der Auflistung "ListOfEmployees" hinzugefügt und dann der SelectedItem Eigenschaft.

Das Speichern läuft in einer separaten Klasse: Connection öffnen, Datensatz eintragen, Connection schliessen, fertig. Das Speichern funktioniert auch, nur dass eben danach zwar Mitarbeiter in der Listbox ausgewählt werden können, aber die Textboxen und alles andere nicht mehr aktualisieren.

Wenn ich nach dem Speichern auf den neu hinzugefügten Eintrag in der Liste klicke und danach auf einen anderen Eintrag kommt ein "System.ArgumentException: "Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt."" und das Programm hängt sich auf.

5.657 Beiträge seit 2006
vor 6 Jahren

Hi Kriz,

bei dir gibt es sowohl Bindings auf die SelectedItem- als auch auf die MasterEmployee-Eigenschaft. Das sollte wohl auf die gleiche Eigenschaft gebunden werden.

Im Ausgabefenster solltest du daher auch einige BindingFehler sehen.

Mehr zum Thema findest du unter [Artikel] MVVM und DataBinding. Da ist auch beschrieben, wie man die Bindings debuggen kann, und es sollte auch klar werden, warum du im ViewModel keine Referenz auf die View benötigst.

Weeks of programming can save you hours of planning

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

Problem gefunden...

Es lag nicht direkt am Binding, sondern an meiner Klasse Employee. Dort hatte ich die GetHashCode Funktion überschrieben, damit das Binding mit der ListBox funktioniert. GetHashCode hat die Personalnummer wiedergegeben. Beim Binding mit der ListBox hat das gut funktioniert, aber wenn ich einen neuen Mitarbeiter erstellt habe, hat er eine automatische Personalnummer bekommen welche dann geändert wurde und das Binding somit nicht mehr funktionierte.

Dennoch hat mir dein ArtikelMrSparkle gut geholfen, vielen Dank!

Kriz

5.657 Beiträge seit 2006
vor 6 Jahren

Dort hatte ich die GetHashCode Funktion überschrieben, damit das Binding mit der ListBox funktioniert.

Ich befürchte, da hast du irgendetwas falsch verstanden...

Weeks of programming can save you hours of planning

3.003 Beiträge seit 2006
vor 6 Jahren

Was MrSparkle meint:
Implementierungen von GetHashCode() sollten für die gesamte Lebenszeit eines Objekts dasselbe Ergebnis liefern, unabhängig von seinem Inhalt. Du hast die Methode quasi missbraucht, damit eine andere Mechanik (das Binding) schnell funktioniert. Falsche Herangehensweise.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

Ich habe nun der Klasse employee eine weitere Eigenschaft hinzugefügt, die als Wert für die GetHashCode Funktion dient, der Wert wird in der Datenbank gespeichert und ist ja somit für jedes Objekt, bzw für jeden Mitarbeiter immer der gleiche Wert, dieser kann nachträglich auch nicht mehr geändert werden. Ist das falsch?

5.657 Beiträge seit 2006
vor 6 Jahren

Warum willst du überhaupt die GetHashCode-Methode überschreiben?

Weeks of programming can save you hours of planning

P
441 Beiträge seit 2014
vor 6 Jahren

Das klingt für mich als würdest du nach dem Primary Key suchen und stattdessen die Methode GetHashCode verwenden.

T
461 Beiträge seit 2013
vor 6 Jahren

Ich habe nun der Klasse employee eine weitere Eigenschaft hinzugefügt, die als Wert für die GetHashCode Funktion dient, der Wert wird in der Datenbank gespeichert und ist ja somit für jedes Objekt, bzw für jeden Mitarbeiter immer der gleiche Wert, dieser kann nachträglich auch nicht mehr geändert werden. Ist das falsch?

Wolltest du als eindeutigen Schlüssel zum wiederfinden von Objekten den HashCode verwenden?

8o

Jetzt hast du ja schon ein neues Property dafür oder? Warum bezeichnest du in deinem Beitrag hier nicht als 'ID' (Identifikation)? Und laß den HashCode komplett weg...

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

K
Kriz Themenstarter:in
141 Beiträge seit 2017
vor 6 Jahren

Anfangs hatte ich ein Problem mit den Comboboxen (siehe Anfang vom Thema), da wurde mir das mit der GetHashCode-Methode überschreiben auch vorgeschlagen. Das hat dann so gut funktioniert dass ich dachte ich mach das auch mit der Klasse employee damit das Problem gar nicht erst auftritt... habe nun mal testweise die überschriebene Methode auskommentiert und siehe da, es funktioniert trotzdem einwandfrei...
Also die Identifikation läuft nun über die Personalnummer, wie es auch sein sollte...