Problem mit ContextMenu und Binding

  • WPF

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von Artentus.

    Problem mit ContextMenu und Binding

    Tag zusammen,

    ich hab momentan ein kleines Problem und Suchen ergab keine wirklich hilfreichen Treffer.
    Die Ausgangssituation ist folgende, in meinem MainWindow habe ich eine Property Maximized angelegt:

    C#-Quellcode

    1. bool maximized;
    2. public bool Maximized
    3. {
    4. get { return maximized; }
    5. set
    6. {
    7. if (value != maximized)
    8. {
    9. maximized = value;
    10. OnPropertyChanged(new PropertyChangedEventArgs("Maximized"));
    11. }
    12. }
    13. }
    Wie zu sehen ist habe ich INotifyPropertyChanged bereits korrekt implementiert.
    Ich habe im XAML auch schon erfolgreich an diese Property gebunden:

    XML-Quellcode

    1. <Image Width="16" Height="16">
    2. <Image.Style>
    3. <Style TargetType="Image">
    4. <Setter Property="Source" Value="{StaticResource MaximizeIcon}"/>
    5. <Style.Triggers>
    6. <DataTrigger Binding="{Binding Maximized, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Value="True">
    7. <Setter Property="Source" Value="{StaticResource RestoreIcon}"></Setter>
    8. </DataTrigger>
    9. </Style.Triggers>
    10. </Style>
    11. </Image.Style>
    12. </Image>
    Hier wird ein Bild ausgetauscht, ja nach Status dieser Property, und das funktioniert auch so wies soll.

    Nun habe ich ein ContextMenu, in dem ich das selbe mit dem Icon eines Items machen will, der aktuelle Code dafür ist dieser:

    XML-Quellcode

    1. <ContextMenu>
    2. <MenuItem Command="local:Commands.Maximize">
    3. <MenuItem.Style>
    4. <Style TargetType="MenuItem">
    5. <Setter Property="Header" Value="Maximize"/>
    6. <Setter Property="Icon" Value="{StaticResource MaximizeMenuIcon}"/>
    7. <Style.Triggers>
    8. <DataTrigger Binding="{Binding Maximized, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Value="True">
    9. <Setter Property="Header" Value="Restore"/>
    10. <Setter Property="Icon" Value="{StaticResource RestoreMenuIcon}"/>
    11. </DataTrigger>
    12. </Style.Triggers>
    13. </Style>
    14. </MenuItem.Style>
    15. </MenuItem>
    16. </ContextMenu>
    Das ist im Prinzip der selbe Code, aber hier passiert gar nichts, in der Ausgabe erhalte ich die Meldung ​Cannot find source for binding.
    Ich konnte durch Recherche bereits herausfinden, dass es etwas damit zu tun hat, dass das ContextMenu nicht zum selben VisualTree gehört, wie das Fenster, aber ich konnte keine Lösung für das Problem finden. Ich hoffe, dass hier einer der WPF-Pros weiterhelfen kann, ich selbst bin noch nicht sonderlich erfahren in WPF und bin hier mit meinem Wissen am Ende.
    Jo, das Teil ist nicht im Visual Tree drin, sodass das mit dem DataContext vom Parent nicht ganz so klappt, da das nur ein mal übernommen wird und das war's. Wenn sich da dann was ändert, dann kann er ohne den richtigen DataContext ja auch nicht die Binding Source finden.
    Die Lösung dürfte ziemlich einfach sein und Du müsstest nur beim ContextMenuStrip explizit den DataContext angeben, sodass dieser sich auf das Parent-Control bezieht.PlacementTarget erklärt sich dann ja eigentlich von selbst. Das sollte Dein Problem lösen, tut es sonst auch zu 95%.

    XML-Quellcode

    1. <ContextMenu DataContext="{Binding PlacementTarget.DataContext,
    2. RelativeSource={RelativeSource Self}}" >
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Hm, also das funktioniert nicht wirklich, den Artikel hab ich nämlich auch gefunden:
    codeproject.com/Articles/16278…gain-DataContext-Not-Upda
    Das Problem ist, die Eigenschaft ist nicht im ViewModel, also nicht im DataContext, es handelt sich um eine Eigenschaft des MainWindows selbst.
    Nanu, wieso denn das? Normalerweise sollte das alles im VM drin sein, was Bindings angeht.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Nun gut, da hast Du wohl recht, das geht dann hier soweit in Ordnung. Folglich ist das mit dem ContextMenuStrip jedoch auch schwerer.
    Man müsste entscheiden, ob die Logik hier also insofern Sinn macht.

    Hat das Placement-Target denn auch ​RelativeSource=self als DataContext? Weil das sollte das CMS eigentlich auch übernehmen dann. Aber das ist dann doch wieder schwieriger, da kann ich den Thread verstehen :D
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ich hab das 1zu1 so reinkopiert. Der Code macht auch keine Fehler, aber das Binding funktioniert halt trotzdem nicht, selber Fehler (logisch, das Window ist dadurch immer noch kein Ancestor des ContextMenu).
    Ich könnts mir natürlich einfach machen, dem Item nen Namen geben und dann im Codebehind pfuschen, aber das verstößt gegen MVVM. Und die Property ins ViewModel packen geht auch nicht so einfach, weil sie aus einer Funktion, die sich ebenfalls im WIndow befindet, gesetzt wird, welche wiederum über ein Command aufgerufen wird.

    Edit: ich habs jetzt vorerst im Codebehind gelöst. Das finde ich nicht sehr schön, und deswegen lass ich den Thread offen, aber falls kein weiterer Vorschlag mehr kommt funktionierts jetzt wenigstens.

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

    Artentus schrieb:

    Weil es sonst logisch falsch ist, Maximized ist doch eindeutig ne Eigenschaft des Fensters, die hat nix bei den Daten zu suchen.
    Wie man will.
    Viewmodel heißt Viewmodel, weils ein Modell des Views ist. Könnte man auch sagen, dass Maximized da reingehört.
    Das mit der Funktion ebenfalls im Window hab ich nicht verstanden.
    Ich hole wohl mal etwas mehr aus.
    Ich hab bei meinem Fenster den Rahmen und die Titelleiste entfernt, damit ich diese selbst darstellen kann; hat auch funktioniert. Die drei Buttons in der Titelleiste hab ich an entsprechende Commands gebunden (Close ist schon vorhanden, Minimize und Maximize hab ich selbst erstellt):

    XML-Quellcode

    1. <Window.CommandBindings>
    2. <CommandBinding Command="Close" Executed="Close" CanExecute="CanExecuteCommandDefault"/>
    3. <CommandBinding Command="local:Commands.Minimize" Executed="Minimize" CanExecute="CanExecuteCommandDefault"/>
    4. <CommandBinding Command="local:Commands.Maximize" Executed="Maximize" CanExecute="CanExecuteCommandDefault"/>
    5. </Window.CommandBindings>
    CanExecuteCommandDefault ist ne simple Funktion, die einfach nur e.CanExecute und e.Handled auf true setzt, die anderen drei Funktionen führen die entsprechende Aktion aus, also Close, Minimize und Maximize (ich denke mal, der genaue Code ist hier uninteressant, sollte eh klar sein). Die Buttons sind dann an diese Commands gebunden.
    Close und Minimize erfordern keine Änderungen in der GUI, Maximize aber schon, denn es soll sich ja das Icon entsprechend ändern. Dafür hab ich die Maximized-Eigenschaft angelegt, welche aus dem Command heraus gesetzt wird. Der Button ändert das Icon nun auch korrekt, allerdings gibts ja auch noch das Kontextmenü der TItelleiste, und da funktionierts nicht (genauer Sachverhalt ist ja oben bereits beschrieben).

    Wenn jemand ne Idee hat, wie ich das anders/besser lösen könnte, wäre mir das ebenso recht. Mir ist halt keine Möglichkeit bekannt, ohne weiteren Ranzcode aus dem VIewmodell auf das Fenster zuzugreifen, weshalb ich die Funktionen nicht dorthin stecken konnte.