Button mit Icon in Abhängigkeit von Objekt-Inhalt

  • WPF
  • .NET 5–6

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von MasterQ.

    Button mit Icon in Abhängigkeit von Objekt-Inhalt

    Moin,

    ich hoffe, dass ich dieses mal nicht wieder was eiinfaches übersehen habe.

    In einem Formular gibt es ein Grid mit gebundener ItemSource. In einigen Spalten stehen Werte aus der Datenquelle, in anderen Button. Ich benötige jetzt für einen Button ein Icon, das vom Inhalte des Objektes abhängt, das in den anderen Spalten der Zeile abhängt.

    Also, im Grid sind in den Zeilen Daten aus einem Auftragsobjekt angezeigt und Buttons mit verschiedenen Funktionalitäten.



    Für einen Button müsste das Icon ebenfalls in Abhängigkeit von Objekteigenschaften gewählt werden. Mein Ansatz sieht so aus:

    XML-Quellcode

    1. <DataGridTemplateColumn Width="50">
    2. <DataGridTemplateColumn.CellTemplate>
    3. <DataTemplate>
    4. <Button
    5. Command="{Binding DataContext.MenuItemClickCommand,
    6. RelativeSource={RelativeSource FindAncestor,
    7. AncestorType={x:Type UserControl}}}"
    8. CommandParameter="ZuVonLeitung"
    9. Width="auto"
    10. ToolTip="{x:Static p:Translate.ZurFreigabe}"
    11. Style="{StaticResource IconButtonInlayStyle}"
    12. IsEnabled="{Binding Content,
    13. RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}},
    14. Converter={StaticResource IsEnabledConverter},
    15. ConverterParameter='Worker.Freigabe'}">
    16. <Button.Content>
    17. <Image Source="{Binding XYZ,
    18. RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}},
    19. Converter={StaticResource weiterzurückConverter}}"
    20. Style="{StaticResource IconButtonIconStyle}" />
    21. </Button.Content>
    22. </Button>
    23. </DataTemplate>
    24. </DataGridTemplateColumn.CellTemplate>
    25. </DataGridTemplateColumn>



    Wie müsste das Binding der Source des Image aussehen? Das mit dem IsEnabled klappt, aber das Image sitzt eine Ebene tiefer und da findet die RelativeSource nicht das Auftragsobjekt sondern den Button.

    Der Vollständigkeit halber hier mein Konverter


    C#-Quellcode

    1. public class WeiterZurückConverter : IValueConverter
    2. {
    3. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    4. {
    5. var o = value as OrderOpen;
    6. if(o!=null)
    7. {
    8. if (!o.LabHasFinished) return "/Images/arrow-thick-right-8x.png";
    9. else return "/Images/arrow-thick-left-8x.png";
    10. }
    11. return "/Images/loop-8x.png";
    12. }
    13. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    14. {
    15. throw new NotImplementedException();
    16. }
    17. }



    Gruß

    MQ
    ich würde dem Viewmodel ein Property ImageSource angedeihen lassen vom Typ ImageSource (oder BitmapSource).
    dann kannste einfach binden:

    XML-Quellcode

    1. <Button.Content>
    2. <Image Source="{Binding ImageSource" />
    3. </Button.Content>
    (Josh Smith hat mal gesagt: "Converter brauche ich nicht - das Viewmodel ist mein Converter")

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

    Das Problem ist, dass dieses Binding dann für alle Einträge in der Liste gleich ist. Ich kann im Viewmodel nicht für jede Zeile unterschiedliche Werte zurückgeben. Irgendwie benötige ich im Viewmodel eine Rückmeldung, welcher Eintrag in der Liste gerade gerändert wird, um den entsprechend richtigen Wert zurückzugeben.

    Ich habe noch etwas mit dem AncenstorLevel rumgespielt und das folgende tut's offensichtlich

    XML-Quellcode

    1. <Button.Content>
    2. <Image Source="{Binding Content,
    3. RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}, AncestorLevel=2},
    4. Converter={StaticResource weiterzurückConverter}}"
    5. Style="{StaticResource IconButtonIconStyle}" />
    6. </Button.Content>




    Hier allerdings jetzt mit drei unterschiedlichen Icons, je nach Status des jeweiligen Auftrages.

    MasterQ schrieb:

    Ich kann im Viewmodel nicht für jede Zeile unterschiedliche Werte zurückgeben. Irgendwie benötige ich im Viewmodel eine Rückmeldung, welcher Eintrag in der Liste gerade gerändert wird
    Versteh ich nicht.
    Bei mir würde beim gezeigten Binding genau dasjenige Element addressiert, was sich an der Stelle in der Liste befindet. ist das nicht identisch mit dem "welcher Eintrag in der Liste gerade gerändert wird"? (was immer das bedeutet)

    ErfinderDesRades schrieb:


    XML-Quellcode

    1. <Button.Content>
    2. <Image Source="{Binding ImageSource" />
    3. </Button.Content>



    C#-Quellcode

    1. ​public string ImageSource => "/Images/bild";


    sowas gibt bei jedem Aufruf den gleichen Wert zurück, so dass in jeder Zeile das gleiche Bild erscheinen würde.

    Will ich das nicht, muss ich im Getter doch wissen, welche Zeile grad dran ist, oder? Wie soll das gehen?

    Wie würde bei dir das Property ImageSource denn aussehen?
    In Grundlagen - MVVM: "Binding-Picking" im Xaml-Editor gibt es ein Viewmodel mit einer Image-Property.
    Natürlich nicht As String, weil ein String ist kein ImageSource.
    Es geht um Commands, die ein Image haben sollen, was man dann auf Buttons anzeigen kann:

    VB.NET-Quellcode

    1. Public Class SpecialFolderCommand
    2. Public Property Command() As relaycommand
    3. Public Property Image() As ImageSource
    4. Private _BrowseTarget As String
    5. Public ReadOnly Property Caption() As String
    6. Get
    7. Return Path.GetFileName(_BrowseTarget)
    8. End Get
    9. End Property
    10. Public Sub New(ByVal enm As Environment.SpecialFolder, ByVal image As ImageSource, ByVal callback As Action(Of String))
    11. _BrowseTarget = Environment.GetFolderPath(enm)
    12. Command = New relaycommand(Sub() callback(_BrowseTarget))
    13. Me.Image = image
    14. End Sub
    15. End Class
    Und natürlich muss man dem Element, was ein bestimmtes Bild vorhalten soll, dieses auch zuweisen - etwa bei der Initialisierung.
    Das Element muss aber keinesfalls wissen, in welcher Zeile es angezeigt wird - wozu soll das gut sein?
    In dem Tut gibts auch Video, und bischen Erläuterungen zum Code - was nicht selbsterklärend ist. Ansonsten fragen.

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

    Wir reden aneinander vorbei.

    1) Die ImageSource darf ein String sein, der auf einen Pfad des Bildes zeigt. Das geht in WPF

    2) Ich habe eine Liste mit Zeilen. In einer Zeile werden Daten z.B. von einem Auftragsobjekt angezeigt, sagen wir Kunde, Datum und Bestellartikel oder was auch immer.

    Ein solcher Auftrag könnte drei Stati haben, "offen", "in Bearbeitung" und "abgeschlossen."

    Wenn der Auftrag "offen" ist, soll der Button einen Pfeil nach rechts anzeigen, bei "in Bearbeitung" einen Pfeil nach links und "abgeschlossen" irgendetwas anderes. In meinem Beispiel oben (Post 3) rede ich über die vorletzte Spalte mit den Pfeilen. Du siehst, dass in jeder Zeile der Liste der Button ein anderes Icon hat, ja nach Status des in dieser Zeile angezeigten Auftrages. Die Binding Property von Source des Image muss wissen, welchen Status der Auftrag hat, um das richtige Icon bzw. den richtigen Pfad zurückzugeben. Ich rede über verschiedene Icons aufgrund der Zeile und nicht der Spalte!!

    Die Einträge bleiben in der Liste, bis alle Bearbeitungsschritte abgeschlossen sind. Damit ändert sich das Icon im Verlauf der Bearbeitung. Hinter dem Button stehen unterschiedliche Funktionalitäten, die mit einem Command abgefangen werden. Im Command weiß ich für welches Element ich was tun muss, denn mit dem Klick auf den Button wird auch SelectedItem geändert, auf das ich zugreifen und auslesen kann, wie der Status ist und was daraufhin zu tun ist. Das geht beim Icon nicht, da hier SelectedItem nicht involviert ist.

    Das ist/war meine Absicht. Mit einem Converter geht das gut und mit einem "einfachen" Binding auf eine Property wüsste ich immer noch nicht, wie das gehen sollte.

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

    MasterQ schrieb:

    Wenn der Auftrag "offen" ist, soll der Button einen Pfeil nach rechts anzeigen, bei "in Bearbeitung" einen Pfeil nach links und "abgeschlossen" irgendetwas anderes. In meinem Beispiel oben (Post 3) rede ich über die vorletzte Spalte mit den Pfeilen. Du siehst, dass in jeder Zeile der Liste der Button ein anderes Icon hat, ja nach Status des in dieser Zeile angezeigten Auftrages. Die Binding Property von Source des Image muss wissen, welchen Status der Auftrag hat, um das richtige Icon bzw. den richtigen Pfad zurückzugeben. Ich rede über verschiedene Icons aufgrund der Zeile und nicht der Spalte!!


    MasterQ schrieb:

    Ich rede über verschiedene Icons aufgrund der Zeile und nicht der Spalte!!
    Das tust du grad nicht.
    Lies deinen eigenen Text: Welches Icon der Button anzeigt hängt nicht von der Zeile ab, sondern vom AuftragStatus.
    Ja, wie gesagt: Gib deinem Auftrag-Viewmodel eine Image-Property, die je nach Status ein anderes Image bereitstellt. Wenn du das mit Strings bewerkstelligen willst - nurzu! (Das ist aber imperformant, weil das BindingSystem für jedes Element die Bild-Datei neu einliest - obwohl es immer dasselbe Bild ist. Aber erstmal kommts drauf an, dasses funzt). Auf jeden Fall muss die Property NotifyPropertyChanged auslösen, wenn der AuftragStatus wechselt.
    Und auf jeden jeden Fall hat das nichts mit der Zeile zu tun, in der der Auftrag dargestellt wird, sondern mit dem AuftragStatus, und der ist im Auftrag ja selbst drinne - daher brauchts dafür keinen Zeilen-Index.

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


    MasterQ schrieb:

    Ich rede über verschiedene Icons aufgrund der Zeile und nicht der Spalte!!
    Das tust du grad nicht.
    Lies deinen eigenen Text: Welches Icon der Button anzeigt hängt nicht von der Zeile ab, sondern vom AuftragStatus.


    OK, ich hatte unsauber formuliert. Jede Zeile zeigt Daten zu einem Auftrag mit unterschiedlichem Status.


    Ja, wie gesagt: Gib deinem Auftrag-Viewmodel eine Image-Property, die je nach Status ein anderes Image bereitstellt.

    Wir drehen uns im Kreis!

    Nochmal anders formuliert! Wie sieht eine solche Image-Property konkret aus, die auf den Status des Auftrages zugreifen kann?

    Dein Beispiel aus dem FilePicker, wenn ich das richtig verstehe, bietet kein Binding, sondern definiert einen Konstruktor, in dem das Icon gesetzt wird. Das ist ein anderer Ansatz. Oder ich versteh gar nix mehr.



    Wenn du das mit Strings bewerkstelligen willst - nurzu! (Das ist aber imperformant, weil das BindingSystem für jedes Element die Bild-Datei neu einliest.


    Guter Punkt!

    MasterQ schrieb:

    Wie sieht eine solche Image-Property konkret aus, die auf den Status des Auftrages zugreifen kann?
    Hmm - kommt mir eiglich nicht soo geheimnisvoll vor:

    VB.NET-Quellcode

    1. Private Shared _AuftragImages As New ImageList("pack://application:,,,/FolderBrowser;component/Images",
    2. "AuftragAnfrage.png", "AuftragErteilt.png", "AuftragInBearbeitung.png", " AuftragErledigt.png")
    3. Public ReadOnly Property AuftragImage As ImageSource
    4. Get
    5. Return _AuftragImages(Me.AuftragStatus)
    6. End Get
    7. End Property
    8. Private _AuftragStatus As Integer
    9. Public Property AuftragStatus As Integer
    10. Get
    11. Return _AuftragStatus
    12. End Get
    13. Set(value As Integer)
    14. If ChangePropIfDifferent(value, _AuftragStatus, NameOf(AuftragStatus)) Then
    15. RaisePropChanged(NameOf(AuftragImage))
    16. End If
    17. End Set
    18. End Property
    so in der Art.
    Also das ist jetzt gebaut mit der Infrastruktur des Tuts, und zwar so, dass wenn der AuftragStatus sich ändert, dass dann auch fürs Image das NotifyPropertyChanged raist.
    Ähnlicher Code findet sich auch im DirectoryNode, nur ists bei einem FileBrowser ja so, dass Files oder Ordner ja nie ihr Image wechseln - daher gibts da fürs Image natürlich auch ein raisePropChanged.
    Aber zB für Directory.IsSelected - ok - das ist nun kein Image.
    Der Knackpunkt ist doch Zeile 6 in deinem Beispiel, genauer das Me.AuftragsStatus.

    Soweit ich verstanden habe, legt man diesen Code im ViewModel ab, d.h. Me, bzw. this ist ist das ViewModel. Das ViewModel hat aber keinen AuftragsStatus, sondern nur eine List<ObservableCollection> AuftragsDaten.

    Die Listenelemente, die haben einen AuftragsStatus.

    ...

    Aber so langsam dämmert es mir, glaube ich. Die Property ImageSource ist bei Dir Teil eines Listenelementes und nicht des ViewModels direkt wie bei mir, oder?

    Dann liegt darin das gegenseitige Missverständnis.

    OK, Gotcha!

    Gruß

    MQ

    MasterQ schrieb:

    Aber so langsam dämmert es mir, glaube ich. Die Property ImageSource ist bei Dir Teil eines Listenelementes und nicht des ViewModels direkt wie bei mir, oder?
    Jaklar. Also die Listenelement-Klassen sind natürlich wesentlicher Bestandteil des Viewmodels.
    "Viewmodel" - damit ist immer ein (zusammenhängendes) System von Klassen gemeint, und Klassen von Listen-Elementen, die angezeigt werden - sind Viewmodel - gehören dazu.
    "Viewmodel direkt" ist ein komischer Begriff, der mir keinen rechten Sinn ergibt. Vermutlich meinst du damit, was ich als "MainViewmodel" bezeichne, die zentrale Klasse, die die Anwendungslogik insgesamt und als ganzes modelliert, und die alle Teilbereiche dieser Anwendungslogik als Properties beinhaltet (und die Properties ggfs. weitere Props etc.).
    Üblicherweise (bei mir jdfs) ist der DataContext des MainWindows ans MainViewmodel gebunden.

    Also es gibt das Viewmodel - ein System von Klassen, die Bindings unterstützen.
    Eine dieser Klassen ist das MainViewmodel, wo alles zusammenläuft.
    "Direkte" (und indirekte) Viewmodelse gibts nicht.
    Aber gugge Tut-Code: Viewmodel ist alles im Ordner Viewmodel, und das erbt alles von Viewmodelbase - ach, bei mir heissst die Basis auch manchmal NotifyPropertyChanged, weil das ist die Hauptaufgabe der Basisklasse aller Viewmodel-Klassen.
    Gerne gugge auch mein Tut "Anwendungsstruktur" im Wpf-Bereich.
    Das Folderbrowser-Beispiel ist ja nur ein Dialog - eiglich keine richtige Anwendung. Im BindingPicking-Tut gehts ja auch nicht "direkt" um MVVM und MVVM-Anwendungsstruktur, sondern ums Setzen von Bindings. Das BindingPicking-Tut habich hier nur rausgesucht, weil du nach einem ImageSource-Beispiel frugtest.
    Und ist auch eines meiner ersten Werke - Im "Anwendungsstruktur-Tut" ist die Binderei noch bisserl geschickter angefasst, indem an Statische Properties gebunden wird.

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