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!
Mir ist was anderes aufgefallen: Dein ViewModel kennt seine View, was bei einer ordentlichen Trennung nicht der Fall sein sollte.
Das habe ich gemacht damit ich den Mitarbeiter auch wieder speichern kann. Wie kann ich denn sonst auf den ausgewählten Mitarbeiter zurückgreifen?
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.
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.
@pinki: Super, funktionier, vielen Dank!
@Sir Rufo, danke für den Tip, funktioniert auch, vielen Dank!
Thema kann geschlossen werden.
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?
Ä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... 😄
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?
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.
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... 😄
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.
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
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
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
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)
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?
Warum willst du überhaupt die GetHashCode-Methode überschreiben?
Weeks of programming can save you hours of planning
Das klingt für mich als würdest du nach dem Primary Key suchen und stattdessen die Methode GetHashCode verwenden.
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... 😄
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...