DataTemplate für TabControl bzw. Tabcontrol.ContentTemplate

  • WPF

Es gibt 22 Antworten in diesem Thema. Der letzte Beitrag () ist von BlackTears.

    DataTemplate für TabControl bzw. Tabcontrol.ContentTemplate

    Hallo zusammen,

    ich mein altes Projekt etwas umgearbeitet. Habe mich (so hoffe ich) an die Anweisungen von Sascha vom vorherigen Projekt angelehnt, aber zur Übung neu erstellt (selbst schreiben prägt ein).

    Es gibt nach wie vor die Checkboxen zum anklicken, einen Bereich für die gewählten Produkte und darunter jetzt neu ein TabControl. Das funktioniert auch alles soweit gut. Im TabControl habe ich meine Produktliste gebunden und ein TabControl.ContentTemplate mit einem DataTemplate versehen. Jetzt möchte ich aber, dass diese DataTemplates je nach gewähltem Produkt, anders aufgebaut sind (mit Buttons, Textboxen, Checkboxen und dergleichen). Also bei Produkt 1 soll der Inhalt des Tabs anders aussehen als bei Produkt 2.

    Soweit ich gelernt habe, kann das in verschiedenen DataTemplates dargestellt werden. Alleine das Wie ist mir noch nicht klar. Und wie bring ich einem bestimmten Tab bei, dass er "sein" Template aufrufen muss (ich vermute mit Binding, weiss aber net wie).

    Ich hoffe, ich habe mich verständlich ausgedrückt. Wenn nicht, bitte fragen. Danke im Voraus für Eure Kommentare und Anregungen.

    Im Anhang das Projekt.

    Gruß

    Oli

    Edit: Datei ohne bin-Ordner hochgeladen
    Dateien
    • Checkbox_Test.zip

      (4,41 MB, 165 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „BlackTears“ ()

    Hallo

    Ich werde mal drüber sehen. Bitte editiere deinen Beitrag nochmals und lade ein Zip File hoch welches bereinigt ist. Sprich, ohne bin Ordner damit keine ausführbare exe vorhanden ist, da dies im Forum nicht erwünscht ist.
    Kannst auch in VS Projektmappe bereinigen klicken.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    BlackTears schrieb:

    aber zur Übung neu erstellt (selbst schreiben prägt ein)

    Sehr gut, in dem Punkt gebe ich dir völlig recht.

    BlackTears schrieb:

    Also bei Produkt 1 soll der Inhalt des Tabs anders aussehen als bei Produkt 2.

    Naja, so ganz genau geht das nicht. Wie soll dein Programm oder besser die WPF wissen welches Produkt du wie angezteigt haben willst. Ich nehme mal salopp an das es sich um Verschiedene Produktarten handelt.
    Also Beispielsweise Elektroprodukt, Haushaltsprodukt, Kleidung usw.
    Nur ne Annahme. Also muss du in diese Richtung gehen. Die WPF kann nicht wissen das du das Produkt mit dem Namen XY so und so angezeigt haben willst. Das geht zwar auch mitrtels Triggern und wenn du das so machen möchtest könnte ich dir ein Beispiel machen. Das ist aber eher schlecht. Was wenn ein Produkt unbenannt wird. Oder du machst es mit dem Titel der Produktkategorie. Was ist wenn diese unbenannt wird.

    In den meissten Fällen kann man hier eine Basisklasse schaffen und die verschiedenen Produktklassen davon ableiten lassen. und anschliessend kann man der WPF (richtig von dir) mittels DataTemplates über die Eigenschaft TargetType beibringen das diese doch bitte eine Instanz der Klasse X bitte so und so rendern soll und eine Instanz der Klasse Y aber anders.

    Ich habe dein Beispiel mal so umgebaut das es die Basisklasse BauteilBase gibt. Diese hält alle Eigenschaften welche JEDES Produkt besitzt. Eine Klasse DefaultBauteil. Diese Klasse erbt von BauteilBase und besitzt sonst keinerlei weitere Eigenschaften. Default eben. Eine weitere Klasse war ich so frei hinzuzufügen - die ElektronikBauteil - welche eine weitere Eigenschaft Volt besitzt und dies einfach mal zu demonstrieren.
    Beim erstellen der Produktliste erstelle ich nun eine Reihe von DefaultBauteil wie du, nur ist dazwischen ein ElektronikBauteil:

    VB.NET-Quellcode

    1. With Checkboxliste
    2. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod1", "Pictures/Prod1.png")))
    3. .Add(New Produktlisteneintrag(New ElektronikBauteil("Elektronik1", "Pictures/Prod2.png", 12.2)))
    4. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod3", "Pictures/Prod3.png")))
    5. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod4", "Pictures/Prod4.png")))
    6. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod5", "Pictures/Prod5.png")))
    7. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod6", "Pictures/Prod6.png")))
    8. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod7", "Pictures/Prod7.png")))
    9. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod8", "Pictures/Prod8.png")))
    10. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod9", "Pictures/Prod9.png")))
    11. .Add(New Produktlisteneintrag(New DefaultBauteil("Prod10", "Pictures/Prod10.png")))
    12. .Add(New Produktlisteneintrag(New DefaultBauteil("Eagle", "Pictures/Eagle.png")))
    13. .Add(New Produktlisteneintrag(New DefaultBauteil("Dog", "Pictures/Dog.png")))
    14. .Add(New Produktlisteneintrag(New DefaultBauteil("Cat", "Pictures/Cat.png")))
    15. .Add(New Produktlisteneintrag(New DefaultBauteil("Spider", "Pictures/Spider.png")))
    16. End With


    Und der WPF habe ich gesagt das alles von von BauteilBase erbt immer nur mit einem Bild gerendert werden soll. Außer es handelt sich um ein Elektronikbauteil. Dann mit mach mir auch noch einen TextBlock darunter.

    XML-Quellcode

    1. <TabControl ItemsSource="{Binding Produktliste}">
    2. <TabControl.ContentTemplate>
    3. <DataTemplate>
    4. <ContentControl Content="{Binding}">
    5. <ContentControl.Resources>
    6. <DataTemplate DataType="{x:Type models:BauteilBase}">
    7. <Image Source="{Binding Produktbild}" Width="42" Height="42" Stretch="UniformToFill"/>
    8. </DataTemplate>
    9. <DataTemplate DataType="{x:Type models:ElektronikBauteil}">
    10. <StackPanel>
    11. <Image Source="{Binding Produktbild}" Width="42" Height="42" Stretch="UniformToFill"/>
    12. <TextBlock HorizontalAlignment="Center" Text="{Binding Volt,StringFormat={}{0} Volt}"/>
    13. </StackPanel>
    14. </DataTemplate>
    15. </ContentControl.Resources>
    16. </ContentControl>
    17. </DataTemplate>
    18. </TabControl.ContentTemplate>
    19. </TabControl>


    Ich hoffe ich konnte das so erklären das man verstehen kann wie dieser Mechanismus funktioniert.
    Anbei das Projekt
    Dateien
    • CheckboxTest.zip

      (2,3 MB, 66 mal heruntergeladen, zuletzt: )
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo Sascha,

    danke, danke, danke. Das ist genau die Richtung, die ich brauche. Ich dachte mir schon, dass da noch was fehlt. Mit der Strukturierung eines Projektes bin ich leider auch noch nicht so vertraut, aber das kommt noch. Wenn ich z.B. den Ordner Models anders benennen würde (z. B. Produktmodule), dann müsste ich in der XAML auch den Aufruf ändern, oder?

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type Produktmodule:BauteilBase}">


    Also für jede Produktgruppe eine Klasse, damit in der XAML mittels DataType darauf zugegriffen werden kann. Müsste ich dann an dieser Stelle

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type models:BauteilBase}">


    eigentlich statt BauteilBase das DefaultBauteil aufrufen?

    Soweit komm ich damit super klar, aber eine Frage bleibt dennoch. Was macht diese Zeile bzw. was wird hier gebunden:

    XML-Quellcode

    1. <ContentControl Content="{Binding}">


    Ich werde mein Projekt entsprechend ausarbeiten und hier wieder vorstellen. Kann allerdings etwas dauern, da wir über Ostern Deine schöne Heimatstadt besuchen werden. Aber spätestens Dienstag mach ich mich mit Hochdruck dran.

    Bis dahin wünsche ich Euch, und speziell Dir Sascha, ein frohes Osterfest. Danke, dass es dieses Forum gibt und Menschen, die sich in Ihrer Freizeit mit den Problemen anderer Menschen beschäftigen.

    Gruß

    Oli
    Hallo Oli

    BlackTears schrieb:

    Wenn ich z.B. den Ordner Models anders benennen würde (z. B. Produktmodule), dann müsste ich in der XAML auch den Aufruf ändern, oder?

    Nicht zwingend. Der Ordnername hat erstmal nichts mit dem Namespace zu tun. Allerdings hält man die Namespaces normalerweise syncron mit der Ordnerstruktur.
    Änderst du den Namespace in den codedateien der Model-Klassen musst du den Import im XAML ganz oben auch anpassen. Wie du den Import benennst ist allerdings dann wieder egal.

    BlackTears schrieb:

    eigentlich statt BauteilBase das DefaultBauteil aufrufen?

    Ich habe das absichtlich so gemacht. Warum? Alle Produktklassen erben vn BauteilBase. Jetzt wo du nur zwei Produktarten hast könntest du BeuteilBase auf DefaultBauteil ändern. Ich mache es aber immer deshalb so damit ich ein Sicherheitsnetz habe. erstelle ich eine neue Klasse für eine Neue Produktart und vergesse ein DataTemplate zu erstellen dann greift auf jeden Fall das Template für die Basisklasse (weil die neue Klasse erbt ja von dieser) und es wird mir somit zumindest etwas angezeigt. Anders würde die WPF nun nicht fündig werden und hätte KEIN DataTemplate zum anzeigen parat. Hoffe das was verständlich.

    BlackTears schrieb:

    Was macht diese Zeile bzw. was wird hier gebunden

    Berechtigte Frage. Das DataTemplate hat als DatenContext in diesem Moment eine Instanz eines Produkts. Da wird nun den Content des ContentControls nicht auf eine bestimmte Eigenschaft eines Produkts binden wollen sondern auch wieder gleich auf das ganze Produkt macht man das einfach so das man das Binding direkt so weitergibt.

    BlackTears schrieb:

    Bis dahin wünsche ich Euch, und speziell Dir Sascha, ein frohes Osterfest.

    Danke, wünsche ich dir auch. Viel spaß in Wien.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo Sascha,

    ich habe mir Dein Beispiel mal zerpflückt und komm jetzt soweit gut damit zurecht. Danke dafür erstmal. Jetzt stoss ich allerdings anderweitig wieder an meine Grenzen. In der XAML erstelle ich ja für jeden Eintrag in der Produktliste ein Template in dem auch ein Button liegt:

    Spoiler anzeigen

    XML-Quellcode

    1. <ListBox>
    2. <ItemsControl ItemsSource="{Binding Produktliste}">
    3. <ItemsControl.ItemTemplate>
    4. <DataTemplate>
    5. <Border Padding="5" BorderBrush="Black" BorderThickness="1">
    6. <Grid Width="60">
    7. <Grid.RowDefinitions>
    8. <RowDefinition Height="40"/>
    9. <RowDefinition Height="*"/>
    10. </Grid.RowDefinitions>
    11. <Button>
    12. <Image Source="{Binding Produktbild}" Stretch="Uniform"/>
    13. </Button>


    Den verschiedenen Buttons möchte ich jetzt ein Klick-Ereignis mitgeben, das in der TabControl das entsprechende TabItem aktiv schaltet, also anzeigt. Hab es mal so versucht:

    Spoiler anzeigen

    XML-Quellcode

    1. <Button Click="aktivesProdukt">
    2. <Image Source="{Binding Produktbild}" Stretch="Uniform"/>
    3. </Button>


    In der Sub hab ich mir einfach zum testen mal eine MessageBox ausgeben lassen. Hat auch gut funktioniert. Allerdings weiss ich nicht, welcher Button geklickt wurde, ergo kann ich auch das entsprechende TabItem nicht ansprechen. Dann hab ich noch versucht wie bei den Checkboxen mit AddHandler ein Event zu starten, bin aber auch hier nicht weiter gekommen.

    Kannst Du mir da einen Ansatz verraten, wie ich das ganze am Besten bewerkstellige? :S ?( ?( :S

    Anbei mein aktueller Code.

    Gruß Oli
    Dateien
    • Checkbox_Test.zip

      (2,36 MB, 66 mal heruntergeladen, zuletzt: )
    Hallo

    BlackTears schrieb:

    Kannst Du mir da einen Ansatz verraten, wie ich das ganze am Besten bewerkstellige?


    Also am saubersten macht man es nicht mit einem Click Ereignisshandler sondern mittels Command. Das wäre der WPF Like Weg.
    OK, so gehts auch solange man mit der Code-Behind Klasse als DatenContext arbeitet. Ist dies mal nicht mehr der Fall steht man wieder an.

    Du hast ja im sender den Button. Frage doch den DataContext des Buttons ab um an die Daten zu kommen.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo Sascha,

    also, ich hab das mit dem sender mal versucht und es klappt auch. :thumbsup:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Sub aktivesProdukt(sender As Object, e As RoutedEventArgs)
    2. Dim DatCon As String = CType(sender, Button).DataContext.ToString
    3. Dim Liste As String = String.Empty
    4. Dim pos As Integer = 0
    5. For i = 0 To Produktliste.Count - 1
    6. If Produktliste.Item(i).ToString = DatCon Then
    7. pos = i
    8. End If
    9. If Liste = String.Empty Then
    10. Liste = Produktliste.Item(i).ToString
    11. Else
    12. Liste = Liste & vbCrLf & Produktliste.Item(i).ToString
    13. End If
    14. Next
    15. MessageBox.Show("Verfügbare Bauteile:" & vbCrLf & Liste & vbCrLf & "Gecklickter Button: " & DatCon, "Klick", MessageBoxButton.OK, MessageBoxImage.Information)
    16. tcBauteile.SelectedIndex = pos
    17. End Sub


    Das mit der Liste und DatCon ist nur Spielerei, kann eigentlich entfallen!!

    So, dann hab ich mal das mit den Commands versucht, aber da komm ich nicht weiter. Das Tutorial hat mir leider nicht weitergeholfen. Hab es versucht wie in RelayCommands beschrieben, aber RelayCommands gibbet bei mir nicht. Meine Klasse sieht erstmal so aus:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Public Class ClickCommand
    2. Private _btnClick As ICommand
    3. Public Property btnClick As ICommand
    4. Get
    5. Return _btnClick
    6. End Get
    7. Set(value As ICommand)
    8. _btnClick = value
    9. End Set
    10. End Property
    11. Private Sub New()
    12. btnClick = New RoutedCommand("Click Command", GetType(ClickCommand))
    13. End Sub
    14. End Class


    Artentus schreibt weiter
    ...die ButtonCommand-Eigenschaft an die Command-Eigenschaft des Buttons binden, sofern das ViewModel korrekt als DataContext festgelegt wurde.​


    Wie mache ich das in meinem Fall? Die Tutorials im Netz sind schon ziemlich alt und ich bin nicht sicher, ob das alles noch aktuell so gemacht wird. :( Oder hast Du noch ein Tut dafür?

    Gruß Oli

    P.S.: Wien ist eine geile Stadt, aber diese Touris :D
    Hallo

    In dem von dir verlinkten Tutorial ist aber eh eine implementierung einer RelayCommand-Klasse enthalten. Dies ist der einfachste weg da ohne RelayCommand-Klasse für jedes Command eine eigene Klasse geschrieben werden müsste.

    Hier meine RelayCommand Klasse:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Windows.Input
    2. ''' <summary>
    3. ''' Diese Klasse Implementiert das ICommand Interface, so muss man nicht in jeder Klasse eines ViewModel alles selbst implementieren.
    4. ''' Einfach eine Command wie folgt Instanzieren:
    5. ''' MyCommand = New RelayCommand(AddressOf MyCommand_Execute, AddressOf MyCommand_CanExecute)
    6. ''' </summary>
    7. Public Class RelayCommand : Implements ICommand
    8. #Region " Fields "
    9. ReadOnly _execute As Action(Of Object)
    10. ReadOnly _canExecute As Predicate(Of Object)
    11. #End Region
    12. #Region " Constructors"
    13. ''' <summary>
    14. ''' Erstellt einen neuen Command welcher NUR Executed werden kann.
    15. ''' </summary>
    16. ''' <param name="execute">The execution logic.</param>
    17. ''' <remarks></remarks>
    18. Public Sub New(execute As Action(Of Object))
    19. Me.New(execute, Nothing)
    20. End Sub
    21. ''' <summary>
    22. ''' Erstellt einen neuen Command welcher sowohl die Execute als auch die CanExecute Logik beinhaltet.
    23. ''' </summary>
    24. ''' <param name="execute">Die Logik für Execute.</param>
    25. ''' <param name="canExecute">Die Logik für CanExecute.</param>
    26. ''' <remarks></remarks>
    27. Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
    28. If execute Is Nothing Then
    29. Throw New ArgumentNullException("execute")
    30. End If
    31. _execute = execute
    32. _canExecute = canExecute
    33. End Sub
    34. #End Region
    35. #Region " ICommand Members "
    36. ''' <summary>
    37. ''' Setzt die CanExecute-Methode des ICommand-Interfaces auf True oder False
    38. ''' </summary>
    39. ''' <param name="parameter"></param>
    40. ''' <returns>Gibt zurück ob die Aktion ausgeführt werden kann oder nicht</returns>
    41. ''' <remarks>
    42. ''' Benutzt DebuggerStepThrough from System.Diagnostics
    43. ''' Der Debugger überspringt diese Prozedur also, es sei den es wird explizit ein Haltepunkt gesetzt.
    44. ''' </remarks>
    45. <DebuggerStepThrough>
    46. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    47. Return _canExecute Is Nothing OrElse _canExecute(parameter)
    48. End Function
    49. ''' <summary>
    50. ''' Event welches geworfen wird wenn die Propertie CanExecuteChanged sich ändert.
    51. ''' </summary>
    52. ''' <remarks></remarks>
    53. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    54. AddHandler(value As EventHandler)
    55. If _canExecute IsNot Nothing Then
    56. AddHandler CommandManager.RequerySuggested, value
    57. End If
    58. End AddHandler
    59. RemoveHandler(value As EventHandler)
    60. If _canExecute IsNot Nothing Then
    61. RemoveHandler CommandManager.RequerySuggested, value
    62. End If
    63. End RemoveHandler
    64. RaiseEvent(sender As Object, e As EventArgs)
    65. End RaiseEvent
    66. End Event
    67. ''' <summary>
    68. ''' Führt die Prozedur Execute des ICommand.Execute aus
    69. ''' </summary>
    70. ''' <param name="parameter"></param>
    71. ''' <remarks></remarks>
    72. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    73. _execute(parameter)
    74. End Sub
    75. #End Region
    76. End Class

    Einfach eine Klasse anlegen und den Code reinkopieren. Schon kannst du angenehm Commands binden.
    Anwendung z.b. so:

    VB.NET-Quellcode

    1. Public ReadOnly Property Save As ICommand = New RelayCommand(AddressOf Save_Execute, AddressOf SaveCanExecute)
    2. Private Async Sub Save_Execute(obj As Object)
    3. 'Mach was
    4. End Sub
    5. Private Function SaveCanExecute(obj As Object) As Boolean
    6. 'Return true wenn der Command ausgeführt werden darf oder false wenn nicht
    7. End Function


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    So hab das jetzt mal versucht, bin aber noch nicht ganz schlau.

    Ich habe Deine RelayCommand-Klasse angelegt. Bei der Anwendung, hab ich mich eng an Deinen Code gehalten:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class MainViewModel
    2. Public Property Klick As ICommand
    3. Private Sub Klick_Execute(obj As Object)
    4. 'Mach was
    5. MessageBox.Show("Button geklickt")
    6. End Sub
    7. Private Function KlickCanExecute(obj As Object) As Boolean
    8. 'Return true wenn der Command ausgeführt werden darf oder false wenn nicht
    9. Return True
    10. End Function
    11. Private Sub New()
    12. Klick = New ClickCommand(AddressOf Klick_Execute, AddressOf KlickCanExecute)
    13. End Sub
    14. End Class


    Das sollte erstmal so passen (hoffe ich) und bei einem Klick auf einen Button die MessageBox anzeigen.

    In der XAML habe ich dann das gemacht:

    Spoiler anzeigen

    XML-Quellcode

    1. <Button Command="{Binding Klick}">
    2. <Image Source="{Binding Produktbild}" Stretch="Uniform"/>
    3. </Button>


    Allerdings passiert nix, wenn ich auf einen Button drücke. Ich vermute mal, dass ich das mit dem DataContext noch nicht drin habe, weiss aber leider auch nicht wie. Ich würde das im XAML vermuten, aber wo? :cursing:

    Warum check ich das nicht? :S

    Gruß

    Oli
    Hallo

    Nochmal, du kannst dir die Klasse "ClickCommand" sparen. Dafür ist die RelayCommand-Klasse ja nun da.
    Klick = New RelayCommand(AddressOf Klick_Execute, AddressOf KlickCanExecute)

    Das sollte klappen sofern die der Datenkontext vom View korrekt auf die MainViewModel festgelegt ist. Ansonsten müsstest du ja im Ausgabefenster Bindingfehler sehen.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Moin Sascha,

    da hab ich wohl für etwas Verwirrung gesorgt. Ich habe die Klasse RelayCommand einfach als ClickCommand geschrieben, einfach weil diese schon da war.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Imports System.Windows.Input
    2. ''' <summary>
    3. ''' Diese Klasse Implementiert das ICommand Interface, so muss man nicht in jeder Klasse eines ViewModel alles selbst implementieren.
    4. ''' Einfach eine Command wie folgt Instanzieren:
    5. ''' MyCommand = New RelayCommand(AddressOf MyCommand_Execute, AddressOf MyCommand_CanExecute)
    6. ''' </summary>
    7. Public Class ClickCommand : Implements ICommand
    8. #Region " Fields "
    9. ReadOnly _execute As Action(Of Object)
    10. ReadOnly _canExecute As Predicate(Of Object)
    11. #End Region
    12. #Region " Constructors"
    13. ''' <summary>
    14. ''' Erstellt einen neuen Command welcher NUR Executed werden kann.
    15. ''' </summary>
    16. ''' <param name="execute">The execution logic.</param>
    17. ''' <remarks></remarks>
    18. Public Sub New(execute As Action(Of Object))
    19. Me.New(execute, Nothing)
    20. End Sub
    21. ''' <summary>
    22. ''' Erstellt einen neuen Command welcher sowohl die Execute als auch die CanExecute Logik beinhaltet.
    23. ''' </summary>
    24. ''' <param name="execute">Die Logik für Execute.</param>
    25. ''' <param name="canExecute">Die Logik für CanExecute.</param>
    26. ''' <remarks></remarks>
    27. Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
    28. If execute Is Nothing Then
    29. Throw New ArgumentNullException("execute")
    30. End If
    31. _execute = execute
    32. _canExecute = canExecute
    33. End Sub
    34. #End Region
    35. #Region " ICommand Members "
    36. ''' <summary>
    37. ''' Setzt die CanExecute-Methode des ICommand-Interfaces auf True oder False
    38. ''' </summary>
    39. ''' <param name="parameter"></param>
    40. ''' <returns>Gibt zurück ob die Aktion ausgeführt werden kann oder nicht</returns>
    41. ''' <remarks>
    42. ''' Benutzt DebuggerStepThrough from System.Diagnostics
    43. ''' Der Debugger überspringt diese Prozedur also, es sei den es wird explizit ein Haltepunkt gesetzt.
    44. ''' </remarks>
    45. <DebuggerStepThrough>
    46. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    47. Return _canExecute Is Nothing OrElse _canExecute(parameter)
    48. End Function
    49. ''' <summary>
    50. ''' Event welches geworfen wird wenn die Propertie CanExecuteChanged sich ändert.
    51. ''' </summary>
    52. ''' <remarks></remarks>
    53. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    54. AddHandler(value As EventHandler)
    55. If _canExecute IsNot Nothing Then
    56. AddHandler CommandManager.RequerySuggested, value
    57. End If
    58. End AddHandler
    59. RemoveHandler(value As EventHandler)
    60. If _canExecute IsNot Nothing Then
    61. RemoveHandler CommandManager.RequerySuggested, value
    62. End If
    63. End RemoveHandler
    64. RaiseEvent(sender As Object, e As EventArgs)
    65. End RaiseEvent
    66. End Event
    67. ''' <summary>
    68. ''' Führt die Prozedur Execute des ICommand.Execute aus
    69. ''' </summary>
    70. ''' <param name="parameter"></param>
    71. ''' <remarks></remarks>
    72. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    73. _execute(parameter)
    74. End Sub
    75. #End Region
    76. End Class


    Im Prinzip Deine kopiert, nur ein anderer Klassenname. Sollte ja auch funzen.

    Nofear23m schrieb:

    sofern die der Datenkontext vom View korrekt auf die MainViewModel festgelegt ist


    Ich glaube aber, dass mein Problem hier liegt. Wie im vorigen Post beschrieben, weiss ich da nicht so recht weiter. Hab mir etliche Tuts dazu angesehen, aber ich checks iwie nicht. :S

    Gruß

    Oli

    BlackTears schrieb:

    Ich glaube aber, dass mein Problem hier liegt. Wie im vorigen Post beschrieben, weiss ich da nicht so recht weiter. Hab mir etliche Tuts dazu angesehen, aber ich checks iwie nicht.

    ja, gut. ICH weis jetzt aber auch nicht woran es liegt. Ohne Code, ohne Konsolenausgabe oder sonst was. Woher soll ich jetzt weiter wissen?
    Entweder du postest den Code und den XAML oder das Projekt ohne "bin" Ordner. Ohne irgendwas kann ich ja nur raten.

    Grüße
    Sascha

    PS: Der Klassenname ist schlecht gewählt. Ein Command kann nicht nur ein Click sein, sondern alles mögliche. Du wirst diese Klasse so wie sie ist sicher in Zukunft in Zig Programmen verwenden, besser du benennst diese wieder um.
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo

    Wie schon vermutet hast du hier die falsche Bindung was die Konsole auch brav anzeigt:

    Quellcode

    1. System.Windows.Data Error: 40 : BindingExpression path error: 'Klick' property not found on 'object' ''Bauteil_1' (HashCode=16310625)'. BindingExpression:Path=Klick; DataItem='Bauteil_1' (HashCode=16310625); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

    Du hast den Button auf Ebene des DataTemplates eines Bauteils. Also ist dort der Datenkontext auch das Bauteil. Somit wird das Klick Command im Bauteil gesucht.

    Zwei möglichkeiten:
    1. Den Command in die Basisklasse vom Bauteil verschieben - dann wird er dort auch gefunden.
    2. Das Binding so ändern das der Button als Datenkontext die MainViewModel-Klasse hat. Dann müsstest du aber als Parameter das Bauteil übergeben da du ja sonst nicht weist bei welchem Bauteil der Button geklickt wird.
    Ich hoffe das war verständlich. Sonst probier hald und wenn du nicht weiterkommst frag einfach nach. Kein Problem.


    PS: Behalte die Konsole im Auge. Diese zeigt dir schön Bindingfehler mit alles relevanten Infos an. Tolles Hilfsmittel.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo Sascha,

    Leider zu früh gefreut, hab es doch noch nicht ganz. Hab versucht Deine Vorschläge umzusetzen. Vorschlag 1 ist soweit klar. Hab meine Basisklasse so ergänzt:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Namespace Produktklasse
    2. Public MustInherit Class Bauteil
    3. Public m_Produktname As String
    4. Public m_Produktbild As String
    5. Public Property Produktname As String
    6. Get
    7. Produktname = m_Produktname
    8. End Get
    9. Set(value As String)
    10. m_Produktname = value
    11. End Set
    12. End Property
    13. Property Produktbild As String
    14. Get
    15. Produktbild = m_Produktbild
    16. End Get
    17. Set(value As String)
    18. m_Produktbild = value
    19. End Set
    20. End Property
    21. Public Sub New()
    22. Klick = New RelayCommand(AddressOf Klick_Execute, AddressOf KlickCanExecute)
    23. End Sub
    24. Public Overrides Function ToString() As String
    25. Return $"{Produktname }"
    26. End Function
    27. Public Property Klick As ICommand
    28. Private Sub Klick_Execute(obj As Object)
    29. 'Mach was
    30. MessageBox.Show("Button von Klasse Bauteil geklickt")
    31. End Sub
    32. Private Function KlickCanExecute(obj As Object) As Boolean
    33. 'Return true wenn der Command ausgeführt werden darf oder false wenn nicht
    34. Return True
    35. End Function
    36. End Class
    37. End Namespace


    Da kommt auch immer schön brav die MessageBox durch. Und zwar bei jedem Button. Wie kann ich da aber abfragen, welcher Button geklickt wurde? ?(

    Bei deiner 2. Möglichkeit hab das so versucht:

    Spoiler anzeigen

    XML-Quellcode

    1. <Button DataContext="MainViewModel" Command="{Binding Klick}">
    2. <Image Source="{Binding Produktbild}" Stretch="Uniform"/>
    3. </Button>


    Die MainViewModel sieht so aus:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Public Class MainViewModel
    2. Public Property Klick As ICommand
    3. Private Sub Klick_Execute(obj As Object)
    4. 'Mach was
    5. MessageBox.Show("Button vom MainViewModel geklickt")
    6. End Sub
    7. Private Function KlickCanExecute(obj As Object) As Boolean
    8. 'Return true wenn der Command ausgeführt werden darf oder false wenn nicht
    9. Return True
    10. End Function
    11. Private Sub New()
    12. Klick = New RelayCommand(AddressOf Klick_Execute, AddressOf KlickCanExecute)
    13. End Sub
    14. End Class



    Da kommt aber in der Konsole immer dieser Fehler:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​BindingExpression path error: 'Klick' property not found on 'object' ''String' (HashCode=589461389)'. BindingExpression:Path=Klick; DataItem='String' (HashCode=589461389); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
    2. System.Windows.Data Error: 40 : BindingExpression path error: 'Produktbild' property not found on 'object' ''String' (HashCode=589461389)'. BindingExpression:Path=Produktbild; DataItem='String' (HashCode=589461389); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')


    Das liegt wahrscheinlich da dran (vermute ich):

    Nofear23m schrieb:

    Dann müsstest du aber als Parameter das Bauteil übergeben da du ja sonst nicht weist bei welchem Bauteil der Button geklickt wird.


    Da komm ich grad wieder nicht weiter. Alle meine Versuche schlagen hier fehl.

    Könnte da noch Input brauchen.

    Gruß
    Oli
    Hallo @BlackTears

    BlackTears schrieb:

    Da kommt auch immer schön brav die MessageBox durch. Und zwar bei jedem Button. Wie kann ich da aber abfragen, welcher Button geklickt wurde?

    Bei der Variante wo des Command in der Bauteilklasse ist (oder deren Basisklasse) ist das ja einfach. Da der Code mit der MessageBox in der selben Klasseninstanz abläuft in welcher das Bauteil definiert ist hast du ja direkten Zugriff auf das Bauteil. Also ja komplett simpel.
    MessageBox.Show($"Button von Klasse Bauteil '{Produktname}' geklickt")

    BlackTears schrieb:

    Bei deiner 2. Möglichkeit hab das so versucht

    Da ist der DataContext aber falsch gesetzt (wie es dir die Consolenausgabe ja auch sagt. Du hast einen String anstatt eines Bindings angegeben.
    Also statt DataContext = "MainViewModel" muss ein DataContext="{Binding ....}" rein.
    Nur das dann der CommandParamter mit angegeben werden müsste um das aktuelle Produkt mit in den Command hinein zu bekommen.
    z.b. mit CommandParameter="{Binding}" und dann im Command den Parameter abfragen und damit arbeiten.


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Das mit dem DataContext bring ich nicht hin, wahrscheinlich zu doof. :S

    Ok, dass ich einen String übergeben habe versteh ich noch. Dass der Datacontext über Binding erledigt muss auch. Das ​​DataContext="{Binding...}" muss doch in der XAML bei dem Button gesetzt werden und hat was mit meiner Klasse MainViewModel zu tun, weil da das Klick-Command drinne is.


    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Public Class MainViewModel
    2. Public Property Klick As ICommand
    3. Private Sub Klick_Execute(obj As Object)
    4. 'Mach was
    5. MessageBox.Show("Button vom MainViewModel geklickt")
    6. End Sub
    7. Private Function KlickCanExecute(obj As Object) As Boolean
    8. 'Return true wenn der Command ausgeführt werden darf oder false wenn nicht
    9. Return True
    10. End Function
    11. Private Sub New()
    12. Klick = New RelayCommand(AddressOf Klick_Execute, AddressOf KlickCanExecute)
    13. End Sub
    14. End Class


    Wie aber muss ich das angeben? Hab mir auch Deine Videos über Databindung nochmal angeschaut, aber ich checks einfach net.

    Ich habs versucht mit ​DataContext="{Binding Source=MainViewModel}" Command="{Binding Klick}" in meiner XAML.

    Spoiler anzeigen

    XML-Quellcode

    1. ​<Window
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:Checkbox_Test"
    7. xmlns:pkl="clr-namespace:Checkbox_Test.Produktklasse"
    8. xmlns:My="clr-namespace:Checkbox_Test.My" x:Class="MainWindow"
    9. mc:Ignorable="d"
    10. Title="Irgendwas" Height="450" Width="800">
    11. <StackPanel>
    12. <ItemsControl ItemsSource="{Binding Checkboxliste}">
    13. <ItemsControl.ItemTemplate>
    14. <DataTemplate>
    15. <CheckBox Content="{Binding neuesProdukt}" IsChecked="{Binding IsChecked}"/>
    16. </DataTemplate>
    17. </ItemsControl.ItemTemplate>
    18. </ItemsControl>
    19. <ListBox>
    20. <ItemsControl ItemsSource="{Binding Produktliste}">
    21. <ItemsControl.ItemTemplate>
    22. <DataTemplate>
    23. <Border Padding="5" BorderBrush="Black" BorderThickness="1">
    24. <Grid Width="60">
    25. <Grid.RowDefinitions>
    26. <RowDefinition Height="40"/>
    27. <RowDefinition Height="*"/>
    28. </Grid.RowDefinitions>
    29. <Button DataContext="{Binding Source=MainViewModel}" Command="{Binding Klick}">
    30. <Image Source="{Binding Produktbild}" Stretch="Uniform"/>
    31. </Button>
    32. <StackPanel Grid.Row="1" Orientation="Horizontal">
    33. <Button x:Name="btnBackward" Width="30" Height="30" HorizontalAlignment="Left" Command="{Binding Klick}">
    34. <Image Source="Pictures/Zurueck_56x50.png" Stretch="UniformToFill" />
    35. </Button>
    36. <Button x:Name="btnForward" Width="30" Height="30" HorizontalAlignment="Right" Command="{Binding Klick}">
    37. <Image Source="Pictures/Vorwaerts_56x50.png"/>
    38. </Button>
    39. </StackPanel>
    40. </Grid>
    41. </Border>
    42. </DataTemplate>
    43. </ItemsControl.ItemTemplate>
    44. <ItemsControl.ItemsPanel>
    45. <ItemsPanelTemplate>
    46. <WrapPanel MaxWidth="750"/>
    47. </ItemsPanelTemplate>
    48. </ItemsControl.ItemsPanel>
    49. </ItemsControl>
    50. </ListBox>
    51. <TabControl ItemsSource="{Binding Produktliste}" x:Name="tcBauteile">
    52. <TabControl.ContentTemplate>
    53. <DataTemplate>
    54. <ContentControl Content="{Binding}">
    55. <ContentControl.Resources>
    56. <DataTemplate DataType="{x:Type pkl:Standardbauteil}">
    57. <Image Source="{Binding Produktbild}" Width="42" Height="42" Stretch="UniformToFill"/>
    58. </DataTemplate>
    59. <DataTemplate DataType="{x:Type pkl:Bauteil_1}">
    60. <StackPanel>
    61. <Image Source="{Binding Produktbild}" Width="42" Height="42" Stretch="UniformToFill"/>
    62. <Button Content="{Binding Button}"/>
    63. </StackPanel>
    64. </DataTemplate>
    65. <DataTemplate DataType="{x:Type pkl:Bauteil_2}">
    66. <ListBox>
    67. <UniformGrid Columns="4">
    68. <Image Source="{Binding Produktbild}" Width="42" Height="42" Stretch="UniformToFill"/>
    69. <Button Content="{Binding Button}"/>
    70. <Button Content="{Binding Button}"/>
    71. <Button Content="{Binding Button}"/>
    72. </UniformGrid>
    73. </ListBox>
    74. </DataTemplate>
    75. </ContentControl.Resources>
    76. </ContentControl>
    77. </DataTemplate>
    78. </TabControl.ContentTemplate>
    79. </TabControl>
    80. </StackPanel>
    81. </Window>


    Leider kommen dann jede Menge Fehler in der Konsole raus:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​System.Windows.Data Error: 40 : BindingExpression path error: 'Klick' property not found on 'object' ''String' (HashCode=589461389)'. BindingExpression:Path=Klick; DataItem='String' (HashCode=589461389); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
    2. System.Windows.Data Error: 40 : BindingExpression path error: 'Produktbild' property not found on 'object' ''String' (HashCode=589461389)'. BindingExpression:Path=Produktbild; DataItem='String' (HashCode=589461389); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
    3. System.Windows.Data Error: 40 : BindingExpression path error: 'Klick' property not found on 'object' ''Standardbauteil' (HashCode=7304143)'. BindingExpression:Path=Klick; DataItem='Standardbauteil' (HashCode=7304143); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
    4. System.Windows.Data Error: 40 : BindingExpression path error: 'Klick' property not found on 'object' ''Standardbauteil' (HashCode=7304143)'. BindingExpression:Path=Klick; DataItem='Standardbauteil' (HashCode=7304143); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')


    Da findet er jetzt beim ersten Button das Klick-Command nicht und das Produktbild. Bei den anderen beiden Button findet er ebenfalls das Klick-Command nicht. So weit, so gut (oder auch nicht). Wie setze ich den DataContext richtig? Hat das mit dem CommandParameter zu tun?

    Ich hoffe ich nerve nicht zu arg, versuche auch den Knoten aus meinem Kopf zu bekommen, irgendwie klappt das aber noch nicht so. :saint:

    Gruß

    Oli
    Hallo

    Also ich habe mir das Beispiel nochmals geladen und laut dem Code deines letzten Posts nachgebaut.

    Da fallen mir gleich ein paar Dinge auf.
    1.) Du hast in deinem XAML ein ItemsControl IN einer ListBox. Hat das einen Grund?
    2.) Du hast ein MainViewModel erstellt in welchem du ein Command hast. OK, aber sonst nichts. Alles andere in in der MainWindow.vb. Und genau auf diese ist die View auch gebunden. (Me.DataContext = Me)
    Also wie soll die View diesen Command überhaupt finden? Es kennt das MainViewModel nicht. Es kennt nur die MainWindow Klasse und alles was darin ist.
    Wenn du also mit einer extra Klasse arbeiten möchtest dann musst du auch die View darauf binden. Das habe ich dir nun so gemacht.
    3.) Innerhalb eines DataTemplates des ItemsControl "bist" du in einem Produkt. Somit kannst du nicht einfach simple per Binding auf eine Eben höher Binden (MainViewModel) da du darauf keinen Zugriff hast.
    Willst du also innerhalb eine DataTemplates auf eine höhere Eben binden musst du dir den DataContext erstmal holen. Beispielsweise über FindAscestor eines Objekts wie dem ItemsControl wie ich es in dem Beispiel gemacht habe.

    Schau dir das mal genau an, spiel ruig etwas rum und lese FindAscestor mal nach, braucht man schon des öfteren.

    Grüße
    Sascha
    Dateien
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##