Combobox in Detailview für Auswahl aus übergeordneter Tabelle

  • WPF

Es gibt 8 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Combobox in Detailview für Auswahl aus übergeordneter Tabelle

    Hallo Leuts,
    ich hänge an folgendem Problem:
    Ich habe eine Form mit einer Listbox, die eine Tabelle Personen über eine ListCollectionView mit Databinding präsentiert. Die Tabelle enthält auch eine Property ID_Anreden, die auf die Anredentabelle verweisen soll.
    Beim Auswählen eines ListboxItems, werden dessen Eigenschaften (Name, Vorname.... ID_Anrede) in eine Instanz NewPerson kopiert, damit sie bearbeitet werden können oder mit Button "Neu" wird eine leere NewPerson-Instanz erzeugt. Für die Auswahl der Anrede wollte ich eine Combobox ebenfalls mit Databinding erwenden. In XAML siehts jetzt so aus:

    XML-Quellcode

    1. <ComboBox Grid.Column="2" Grid.Row="2"
    2. ItemsSource="{Binding Path=AnredenView}"
    3. DisplayMemberPath="Anrede"
    4. SelectedItem="{Binding Path=NewPerson.Anreden, UpdateSourceTrigger=PropertyChanged}"
    5. Name="CB_Anrede" />

    Die Anzeige der Einträge aus der Tabelle in der Combobox Anreden funzt, und in einer Messagebox werden beim Speichern die korrekten Werte ausgegeben. Hier mal die Speichersub:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub SaveCommand(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
    2. Dim _p As Personen
    3. If lbPersonen.SelectedIndex = -1 Then
    4. 'neue Person Adden
    5. DataInstanz.context.AddPerson(DataInstanz.NewPerson)
    6. Else
    7. _p = CType(lbPersonen.SelectedItem, Personen)
    8. _p.ID_Anrede = DataInstanz.NewPerson.ID_Anrede
    9. _p.Vorname = DataInstanz.NewPerson.Vorname
    10. _p.Name = DataInstanz.NewPerson.Name
    11. _p.Geb = DataInstanz.NewPerson.Geb
    12. _p.Color = DataInstanz.NewPerson.Color
    13. End If
    14. With DataInstanz.NewPerson
    15. 'Probeweise Ausgabe der NewPerson
    16. MessageBox.Show(String.Format("ID= {0} Vorname= {1} Name= {2} Anrede= {3}", _
    17. .ID, .Vorname, .Name, .ID_Anrede))
    18. MessageBox.Show(String.Format("ID= {0} Vorname= {1} Name= {2} Anrede= {3}", _
    19. _p.ID, _p.Vorname, _p.Name, _p.ID_Anrede))
    20. End With
    21. Application.DataInstanz.PersonenView.Refresh()
    22. Application.DataInstanz.Speichern.Execute(Nothing)
    23. lbPersonen.SelectedIndex = -1
    24. DataInstanz.NewPerson = New Personen
    25. tb_Vorname.Focus()
    26. tb_Vorname.SelectAll()
    27. End Sub

    Messageboxausgabe:
    Trotzdem kommt die Fehlermeldung bei context.SubmitChanges():

    Ich hab nochmal das Projekt angehängt, damit man den Vehler nachfollziehen kann: DispatcherPro.zip

    Für Tipps wär ich denkbar dankbar

    Gute Nacht,
    Vatter
    :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup:
    Wieso machst du das so kompliziert?
    Die ComboBox kannst du auch in den DataGrid hauen.
    Somit hast du alles zentralisiert.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    naja - dassis halt ein DetailView - kann man ja so wollen.

    Aber ich finds auch überkompliziert, v.a. den CodeBehind fund ich unnötig:
    Im MainWindow habich die TabItems per Xaml draufgemacht, und da diese komischen CommandBindings einem CodeBehind aufzwingen, habich die rausgeschmissen, und die Add- und Delete- Funktionalität ins Viewmodel verlegt (wo sie hingehört).

    Das Adden und Deleten habich auch nicht über den DataContext gelöst, sondern geadded wird den CollectionViews und gut.
    An die DB geht das erst, wenn Save geklickst wird.
    Da reduziert sich also einiges:

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.IO
    3. Public Class MainModel
    4. #Region "PropertyChanged (bislang üflüssig)"
    5. Implements INotifyPropertyChanged
    6. Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    7. Private Sub OnPropertyChanged(ByVal Propertyname As String)
    8. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
    9. End Sub
    10. #End Region
    11. #Region "New Instanzen (üflüssig)"
    12. Private _newPerson As New Personen
    13. Public Property NewPerson As Personen
    14. Get
    15. Return _newPerson
    16. End Get
    17. Set(ByVal value As Personen)
    18. _newPerson = value
    19. OnPropertyChanged("NewPerson")
    20. End Set
    21. End Property
    22. Private _newAnrede As Anreden
    23. Public Property NewAnrede() As Anreden
    24. Get
    25. Return _newAnrede
    26. End Get
    27. Set(ByVal value As Anreden)
    28. _newAnrede = value
    29. OnPropertyChanged("NewAnrede")
    30. End Set
    31. End Property
    32. #End Region
    33. Public Property PersonenView As New ListCollectionView({New Personen()}) With {.Filter = Function(o) True}
    34. Public Property AnredenView As New ListCollectionView({New Anreden()}) With {.Filter = Function(o) True}
    35. Public Sub New()
    36. If Not System.ComponentModel.DesignerProperties.GetIsInDesignMode(New DependencyObject) Then
    37. Laden.Execute(Nothing)
    38. End If
    39. End Sub
    40. Private _DataFile As New FileInfo("Daten\MyDB.sdf")
    41. Public Property context As MyDataDataContext
    42. 'Command DB Laden
    43. Public Property Laden As New RelayCommand( _
    44. Sub()
    45. If context IsNot Nothing Then context.Dispose()
    46. context = New MyDataDataContext("Data Source=" & _DataFile.FullName)
    47. If context.DatabaseExists() Then
    48. context.Log = Console.Out
    49. PersonenView = New ListCollectionView(context.Personen.GetList)
    50. AnredenView = New ListCollectionView(context.Anreden.GetList)
    51. OnPropertyChanged("context")
    52. OnPropertyChanged("PersonenView")
    53. OnPropertyChanged("AnredenView")
    54. Else
    55. context.CreateDatabase()
    56. End If
    57. End Sub)
    58. 'Command DB Speichern
    59. Public Property Speichern As New RelayCommand(Sub() context.SubmitChanges())
    60. 'Command Neue Person
    61. Public Property AddPerson As New RelayCommand( _
    62. Sub() PersonenView.AddNewItem(New Personen() With { _
    63. .Anreden = DirectCast(AnredenView(0), Anreden), _
    64. .Name = "Rumpel", _
    65. .Vorname = "stilzchen", _
    66. .Geb = Date.Now}))
    67. Public Property DeletePerson As New RelayCommand( _
    68. Sub() PersonenView.RemoveAt(PersonenView.CurrentPosition), _
    69. Function(obj) PersonenView.CurrentPosition >= 0)
    70. End Class


    (und wieder zeigt sich, dasses besser ist, DB-Tabellen singular zu benennen. Weil

    VB.NET-Quellcode

    1. PersonenView.AddNewItem(New Personen())
    ist sehr mißverständlich, denn es ist ja nur eine Person, die geadded wird.
    Und ebenso mißverständlich ist ab nun jeder Umgang mit dieser Entität.
    Dateien
    Vielen Dank für deine wieder einmal erfolgreichen Bemühungen. Wobei ich noch nicht begriffen habe, wo die Fehlerursache lag. Ich muß wohl mit den Bindings ein Kuddelmuddel veranstaltet haben.
    Nun ist es aber so, dass die Bindung der Textboxen an die Personenview nicht mehr das Hinzufügen einer neuen "Personen" :) ermöchlicht. dafür hatte ich die separate Instanz NewPerson vorgesehen. Die dient quasi als temp-Variabele. Aber es sollte ja kein vollständiger Code sein, sondern ein Anschubs. Dafür dickes Danke.
    Ich habe auch festgestellt, dass meine Manipulationen in der AnredenListbox des uc_Anreden sich auf die Personenview ausgewirkt haben und zu merkwürden Effekten führte.

    ErfinderDesRades schrieb:

    und wieder zeigt sich, dasses besser ist, DB-Tabellen singular zu benennen

    Naja, das halt ich eher für Ansichtssache. Beispühl: Das ListboxItem der PersonenView soll in Spalte 1 die anrede (natürlitsch Klartext) anzeigen. Das Binding dafür:

    XML-Quellcode

    1. ItemsSource="{Binding Path=PersonenView}"
    2. <TextBlock Grid.Column="0" Text="{Binding Path=Anreden.Anrede}" />
    Es wird also aus der Personenview über die Beziehung auf Tabelle Anreden und deren Eigenschaft Anrede verwiesen. In diesem Zusammenhang find ichs stimmig. Natürlich klingt Add(Personen) mistverständlich. Aber die textfarbe sacht ja eindeutig, dass es ne Klassenbezeichnung ist. Oda ich müsst die Tabele Anrede und die Property Text nennen...

    Die Command hatte ich absichtlich im Codebehind, weil es mir nicht gelang, die CanExecute-Methode im Mainmodel zu aktivieren. Irgendwie haben die ans Comand gebundenen Steuerelemente das entsprechende Event nicht aufgerufen. Also hab ichs in den Codebehind gelegt und von da die entsprechenden Methoden im Mainmodel aufgerufen. Ich find es nämlich ausgesprochen nützlich, den Speicherbutton erst zu aktivieren, wenn die Eingaben korrekt sind.

    Ich werds mal in die von dir vorgeschlagene Richtung verfeinern.

    Schönes WE

    Vatter
    :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup:

    Vatter schrieb:

    Irgendwie haben die ans Comand gebundenen Steuerelemente das entsprechende Event nicht aufgerufen.

    Dassis bestimmt ein Bug in deiner RelayCommand-Implementation.

    XML-Quellcode

    1. ItemsSource="{Binding Path=PersonenView}"
    2. <TextBlock Grid.Column="0" Text="{Binding Path=Anreden.Anrede}" />
    ich finde im Code garnix derartiges.
    Auch sieht das - jetzt im Zusammenhang mit der ItemsSource - so aus, als verfüge eine Person über mehrere Anreden.


    Bei mir wäre ein Textbloc vlt. so gebunden:

    XML-Quellcode

    1. ItemsSource="{Binding Path=AnredenView}"
    2. <TextBlock Grid.Column="0" Text="{Binding Path=Anrede.Text}" />
    Beachte: die View im ViewModel benenne ich schon plural, aber da drin die Anreden ist jede einzelne singular. Und Anrede.Anrede - naja. Hat den Vorteil, dass mans nicht mit anneren Text-Properties verwechseln kann.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Nuja, entgegen deiner eindringlichen Mahnung, dieses ViewModel-Dingens Tool zu verwenden (Da kommich nich mit klar, weils anscheinend nur mit vb2008 funzen tut) hab ich nur die Relay-Command-Klasse drin. Wahrscheinlich muss ich der jeweiligen Form sagen, dass sie an diese Klasse gebunden wird oder so. Da hab ich kein Plan.
    Und mit meinem (Designfehlerhaften) Codebehind gehts ja zur Not erstma. Und die Daten fass ich ja in der Mainmodel-Klasse zentral an, so dass GUI und Daten für meine Begriffe strikt genug getrennt sind.
    :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup:
    ich hab das Tool jetzt auch rausgeschmissen, und mir ein eigenes RelayCommand gemacht (hauptsächlich abgeschrieben von Josh Smith, aber auch bischen erweitert):

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Namespace System.Windows.Input
    3. Public Class RelayCommand : Inherits CommandBase
    4. Private ReadOnly _execute As Action
    5. Private ReadOnly _canExecute As Func(Of Boolean) = Function() Enabled
    6. <DebuggerStepThrough()> _
    7. Public Sub New(ByVal execute As Action, Optional ByVal canExecute As Func(Of Boolean) = Nothing)
    8. If execute Is Nothing Then Throw New ArgumentNullException("execute")
    9. Me._execute = execute
    10. If canExecute.NotNull Then Me._canExecute = canExecute
    11. End Sub
    12. <DebuggerStepThrough()> _
    13. Public Overrides Function CanExecute(ByVal parameter As Object) As Boolean
    14. Return _canExecute()
    15. End Function
    16. <DebuggerStepThrough()> _
    17. Public Overrides Sub Execute(ByVal parameter As Object)
    18. Me._execute()
    19. End Sub
    20. End Class
    21. Public Class RelayCommand(Of T) : Inherits CommandBase
    22. Private ReadOnly _execute As Action(Of T)
    23. Private ReadOnly _canExecute As Predicate(Of T) = Function(itm As T) Enabled
    24. <DebuggerStepThrough()> _
    25. Public Sub New(ByVal execute As Action(Of T), Optional ByVal canExecute As Predicate(Of T) = Nothing)
    26. If execute Is Nothing Then Throw New ArgumentNullException("execute")
    27. Me._execute = execute
    28. If canExecute.NotNull Then Me._canExecute = canExecute
    29. End Sub
    30. <DebuggerStepThrough()> _
    31. Public Overrides Function CanExecute(ByVal parameter As Object) As Boolean
    32. Return _canExecute(DirectCast(parameter, T))
    33. End Function
    34. <DebuggerStepThrough()> _
    35. Public Overrides Sub Execute(ByVal parameter As Object)
    36. Me._execute(DirectCast(parameter, T))
    37. End Sub
    38. End Class
    39. Public MustInherit Class CommandBase : Inherits NotifyPropertyChanged : Implements ICommand
    40. Private EventDisabled As New Counter 'verhindert Ereigniskette beim gegenseitigen Aufruf von CanExecute und Setter der Enabled-Property
    41. 'das CanExecuteChanged-Event vereinigt sich mit dem statischen CommandManager.RequerySuggested-Event. Eiglich müsste es LaunchRequery heißen oderso
    42. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    43. AddHandler(ByVal value As EventHandler)
    44. AddHandler CommandManager.RequerySuggested, value
    45. End AddHandler
    46. RemoveHandler(ByVal value As EventHandler)
    47. RemoveHandler CommandManager.RequerySuggested, value
    48. End RemoveHandler
    49. RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
    50. CommandManager.InvalidateRequerySuggested()
    51. End RaiseEvent
    52. End Event
    53. Public Shared ReadOnly EnabledChangedArgs As New PropertyChangedEventArgs("Enabled")
    54. Public MustOverride Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
    55. Public MustOverride Function CanExecute(ByVal parameter As Object) As Boolean
    56. Private _Enabled As Boolean = True
    57. Public Property Enabled() As Boolean
    58. Get
    59. Return _Enabled
    60. End Get
    61. Set(ByVal value As Boolean)
    62. If ChangePropIfDifferent(value, EnabledChangedArgs, _Enabled) Then
    63. If EventDisabled.Up = 0 Then RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
    64. EventDisabled.Down()
    65. End If
    66. End Set
    67. End Property
    68. Private Function ICanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
    69. If EventDisabled.Up = 0 Then Enabled = CanExecute(parameter)
    70. EventDisabled.Down()
    71. Return _Enabled
    72. End Function
    73. End Class
    74. End Namespace
    Nee - hat kein Zweck.
    Da hängt noch meine NotifyPropertyChanged - Klasse dran, und daran wiederum einige Extensions - im grunde mein kompletter Tool-Ersatz - wird also bei dir net laufen :(.
    Oh Weia, da mussich länger drüber brüten. Die 1. relaycommand-klasse entspricht ja in etwa dem, was ich auch von Josh Smith geklaut hab...
    Ich muß mich aber zunächst mal mit den Binding-Grundlagen weiter beschäftigen, damit ich das auch beherrsche und nich nur hinschreib.
    Ich hab jetz das CurrentChanged_Event der PersonenView abboniert und kopiere da die Properties des CurrentItem in das NewPerson-Objekt. Die Textboxen funzen da, aber mit der Combobox komm ich wieder nicht klar. Egal, wie ich SelectedItem binde, eine parallel dazu an NewPerson.ID_Anrede gebundene Textbox zeigt keine Reaktion.
    Ich probier mal bei Gelegenheit nochmal rum. Jetz mussich erstma Oppa sein :D
    :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup: