Trigger für Custom-Property

  • WPF

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

    Trigger für Custom-Property

    Hallo allerseits.

    Zu aller erst muss ich mal sagen, WPF ist noch ziemliches Neuland für mich. Deswegen häng ich hier glaube ich an was, das wahrscheinlich recht einfach umzusetzen ist, aber ich habs jetzt auch nach einigem Suchen nicht hinbekommen.
    Aber jetzt mal zu meinem Problem. Ich habe in meinem MainWindow eine neue Property angelegt, sie sieht so aus:

    C-Quellcode

    1. public static readonly DependencyProperty ScrolledOutProperty = DependencyProperty.Register("ScrolledOut", typeof(bool), typeof(MainWindow));
    2. public bool ScrolledOut
    3. {
    4. get
    5. {
    6. return (bool)GetValue(ScrolledOutProperty);
    7. }
    8. set
    9. {
    10. SetValue(ScrolledOutProperty, value);
    11. }
    12. }

    Nun habe ich über XAML einen Style für alle Buttons angelegt, dieser sieht so aus (ich hab die unwichtigen Stellen ausgelassen):

    XML-Quellcode

    1. <Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
    2. <Setter Property="Template">
    3. <Setter.Value>
    4. <ControlTemplate TargetType="{x:Type Button}">
    5. <Grid>
    6. <Grid Margin="0,10,0,10">
    7. <!-- weiterer Code -->
    8. <Label Grid.Row="1" Content="{TemplateBinding Content}" FontSize="16" HorizontalAlignment="Center" Foreground="White" FontWeight="Light" Visibility="Hidden"/>
    9. </Grid>
    10. <!-- weiterer Code -->
    11. </Grid>
    12. </ControlTemplate>
    13. </Setter.Value>
    14. </Setter>
    15. </Style>
    Ich möchte nun, dass das Label (Zeile 8) sichtbar (Visibility = Visible) wird, sobald "ScrolledOut" true wird und umgekehrt.
    Mit einem normalen Trigger geht das nicht, deswegen hab ich folgendes ausprobiert:

    XML-Quellcode

    1. <Style x:Key="HideOnScolledOut" TargetType="{x:Type Label}">
    2. <Style.Triggers>
    3. <DataTrigger Binding="{Binding ScrolledOut}" Value="True">
    4. <Setter Property="Visibility" Value="Visible" />
    5. </DataTrigger>
    6. <DataTrigger Binding="{Binding ScrolledOut}" Value="False">
    7. <Setter Property="Visibility" Value="Hidden" />
    8. </DataTrigger>
    9. </Style.Triggers>
    10. </Style>
    Und diesen Syle dann auf das Label angewendet:

    XML-Quellcode

    1. <Label Grid.Row="1" Content="{TemplateBinding Content}" FontSize="16" HorizontalAlignment="Center" Foreground="White" FontWeight="Light" Visibility="Hidden" Style="{StaticResource HideOnScolledOut}" DataContext="{Binding ElementName=MyWindow}"/>
    Wobei "MyWindow" eben der Name des Fensters ist.
    Das kompiliert auch ohne Fehler, allerdings passiert gar nichts.
    Weiß jemand, woran das liegt, oder ob ich generell einen ganz anderen Ansatz wählen sollte?
    Das Fenster wird in den sichtbaren Bereich "eingeflogen" und da noch teils transparent. Erst wenn es vollständig sichtbar ist verliert es seine Transparenz und erst dann sollen die Texte sichtbar sein.
    Das hat keinen praktischen Nutzen sondern ist ein Designgimmick. Und ich hab extra WPF dafür genommen, weil das mit WinForms ja nicht geht.

    Für was das jetzt genau gut ist ist aber eigentlich egal, mir geht es nur darum in dem Label auf eine Änderung von "ScrolledIn" zu reagieren.
    ou - Fenster einfliegen - feature-wichtig - must have! ;)

    naja - generell sehr unschön, im Codebehind eine Property anzulegen, also nach MVVM sollteman das besser vermeiden.

    Und scheint mir auch vlt. nicht schwer, weil fliegt ja vmtl. über ein StoryBoard ein, und das StoryBoard kann glaub auch Visible setzen von beliebigen UiElementen.
    Wie legt man denn eine Property in XAML an? Ich kenne das nur im Codebehind (wie gesagt, bin in WPF noch nicht so weit).
    Und das Problem ist halt, dass das eben keine UIElemente an sich sind, sondern ein Template für einen Button.
    Und von wegen Storyboard, ich hab nicht mit Blend gearbeitet. Geht das auch in VS?
    Das Viewmodel besteht aus ganz gewöhnlichen Klassen - und da machste halt Properties rein.
    Im Wpf-Tut-Bereich gibts recht viel dazu, nur zu Storyboard hamwa glaub noch nix.

    aber die MVVM-Geschichte findich eh viel wichtiger.

    zu Storyboard gugge dann (nachdem du MVVM geschnackelt hast): galasoft.ch/mydotnet/articles/article-2006102701.aspx
    Das ViewModel ist eine normale Klasse. Diese sollte das INotifyPropertyChanged Interface implementieren und auch verwenden.
    ViewModels sollten übrigens auch in quasi einer Hierarchie aufgebaut sein. Es empfiehlt sich ein MainViewModel zu erstellen und dort das Singleton Pattern zu verwenden. Von diesem MainViewModel gehen alle anderen ViewModels aus. Ich kenne leider keinen Ersatz für den ?? Operator welcher das Ganze erheblich abkürzt deshalb und da du eh c# kannst hier nen kleines Beispiel in C#.

    Spoiler anzeigen

    Quellcode

    1. [code=c]
    2. public class PropertyChangedBase : INotifyPropertyChanged
    3. {
    4. public bool SetProperty<T>(T value, ref T field, Expression<Func<object>> property)
    5. {
    6. if(field == null || !field.Equals(value))
    7. {
    8. field = value;
    9. OnPropertyChanged(GetPropertyName(property));
    10. return true;
    11. }
    12. return false;
    13. }
    14. public string GetPropertyName(Expression<Func<object>> property)
    15. {
    16. //...
    17. }
    18. }
    19. public class ViewModelBase
    20. {
    21. public MainViewModel Main
    22. {
    23. get { return MainViewModel.Instance; }
    24. }
    25. }
    26. public class MainViewModel : ViewModelBase
    27. {
    28. static MainViewModel _instance;
    29. public static MainViewModel Instance
    30. {
    31. get { return _instance ?? (_instance = new MainViewModel()); }
    32. }
    33. ViewModel1 _viewmodel1;
    34. public ViewModel1 ViewModel1
    35. {
    36. get { return _viewmodel1 ?? (_viewmodel1 = new ViewModel1()); }
    37. set { SetProperty(value, ref _viewmodel1, () => ViewModel1); }
    38. }
    39. ViewModel2 _viewmodel2;
    40. public ViewModel2 ViewModel2
    41. {
    42. get { return _viewmodel2 ?? (_viewmodel2 = new ViewModel2()); }
    43. set { SetProperty(value, ref _viewmodel2 () => ViewModel2); }
    44. }
    45. private MainViewModel() { }
    46. }
    47. public class ViewModel1 : ViewModelBase
    48. {
    49. ViewModel1_1 _viewmodel11;
    50. public ViewModel1_1 ViewModel11
    51. {
    52. get { return _viewmodel11 ?? (_viewmodel11 = new ViewModel1_1()); }
    53. set { SetProperty(value, ref _viewmodel11, () => ViewModel11); }
    54. }
    55. }
    56. public class ViewModel2 : ViewModelBase
    57. {
    58. }
    59. public class ViewModel1_1 : ViewModelBase
    60. {
    61. }
    [/code]


    Zudem Storyboards sind für Animationen in WPF zuständig und ersparen dir die ganzen Timer etc. Hier ist ein Artikel welchen ich zwar nicht gelesen habe, welcher aber sehr gute Bewertungen hat: codeproject.com/Articles/36452…-using-Storyboards-in-WPF


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    Vielen Dank euch beiden. Ich werd mir das gleich alles mal in Ruhe ansehen.
    In der Zwischenzeit fände ich es aber hilfreich, wenn sich jemand noch meine Problems annehmen könnte. ;)

    @thefiloe
    Was macht den der ??-Operator? Den kenn ich gar nicht.
    ich guck mir grad den Code an - grausig.

    was soll solch Storyboard?

    XML-Quellcode

    1. <Storyboard>
    2. <DoubleAnimation Name="da" Storyboard.TargetName="flagImage" Storyboard.TargetProperty="Width" From="200" To="200" Duration="0:0:0.1" Completed="DoubleAnimation_Completed"/>
    3. </Storyboard>
    Da wird die Breite animiert von 200 nach 200 (also garnet), und dann nach 0.1s ein Codebehind-Event ausgelöst, wo dann eigentlich die Flagge animiert wird (durch sauteure Bildwechsel).

    Das Storyboard hat also nur die Funktion eines Timers, der ins Codebehind reinwirkt, und da nehme man dann besser gleich im Codebehind einen Timer, und verzichte auf den Xaml-Brainfuck.

    Peinlich für Codeproject, mussich sagen.

    naja - komisch: Der Artikel zeigt ganz annere Sachen als die Sample-App, mit sieht aus nach sinnvollem Einsatz des Storyboards.

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

    Er macht quasi das hier:

    C-Quellcode

    1. ViewModel1 _viewmodel1;
    2. public ViewModel1 ViewModel1
    3. {
    4. get
    5. {
    6. if(_viewmodel1 == null)
    7. _viewmodel1 = new ViewModel1();
    8. return _viewmodel1;
    9. }
    10. }


    Und btw. @ErfinderDesRades das von 200 auf 200 mag peinlich sein, doch ob du es glaubst oder nicht es dreht sich im Zusammenhang mit WPF nicht immer automatisch auch alles um MVVM. WPF verpflichtet nicht zu MVVM. Und man kann WPF auch ohne MVVM verwenden. Ich denke, wir alle wissen, dass du ein absoluter Fan von MVVM bist, jedoch geht es auch ohne. Ich persönlich verwende auch lieber MVVM doch es muss nicht sein. Und so hat es durchaus seine Berechtigung auch Features welche nicht direkt im Kontext zu MVVM stehen zu erwähnen. Btw. man kann Events auch für AttachedProperties verwenden, falls du solche kennst.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    Meine Kritik hat mit MVVM garnix zu tun.
    Storyboard und Animation geht auch ohne MVVM - sehe ich auch so, insbesondere bei dem Flaggen-Beispiel.

    Aber BrainFuck ist BrainFuck, und wennman Timer-Funktionalität haben will, soll man Timer nehmen - nicht Storyboard.
    Dassis ein mit fast BestNote gerateter Artikel auf ungefähr dem neuesten Stand (April dieses Jahres).
    Und wennman Anfängern den Sinn von was nahebringen will, darf man kein Unsinn verzapfen, findich.
    Also ich hab mir jetzt mal ein bisschen was dazu durchgelesen. Allerdings verstehe ich immer noch nicht wirklich, wie jetzt XAML und Codebehind zusammenarbeiten müssen.
    Was muss das ViewModel alles können, damit ich auf eine Änderung von "ScrolledOut" reagieren kann? Kann das einfach eine Klasse sein, die diese Eigenschaft weiterreicht und dabei INotifyPropertyChanged implementiert? Und wie binde ich dann die Visibility-Eigenschaft an diese Klasse?
    Also du musst eine Instanz des ViewModels in den DataContext setzen. Der DataContext wird auf alle in der Hierarchie unten liegenden Controls vererbt(es sei denn in der Hierarchie wird der DataContext irgendwo manuell geändert). Heißt ganz einfach. Hat das Fenster den DataContext xyz hat es ein Button, in StackPanel und DockPanel + Pipapo ebenfalls DataContext xyz es sei denn z.B. der StackPanel hat DataContext 123 so haben auch alle Controls die dem StackPanel untergeordnet sind DataContext 123. Nimmst du mein Beispiel von oben so setzt du im XAML folgendes ein(ist aber nur in etwa, da ich das nur in Notepad geschrieben habe):

    XML-Quellcode

    1. <Window ...
    2. xmlns:mynamespace = "...mynamespace... des viewmodels"
    3. DataContext={x:Static mynamespace:MainViewModel.Instance} ... >
    4. <Window.Ressources>
    5. <BooleanToVisibilityConverter x:Key="converter"/>
    6. </Window.Ressources>
    7. <Label Visiblity={Binding MyBooleanProperty, Converter={StaticRessource converter}}/>
    8. </Window>


    Im MainViewModel erstellt du nun eine Eigenschaft mit Namen MyBooleanProperty und vom Typ Boolean. Das ist alles. Damit das Binding jedoch Änderungen der Property MyBooleanProperty mitbekommt musst du bei einer Änderung das Event von INotifyPropertyChanged feuern. Das ist alles. So kommunizierst du vom ViewModel zur View.

    Wenn du Skype hast, kannst du mich auch mal in Skype adden(Name steht im Profil). Dann kann ich dir das ein wenig besser erklären.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    Das Codebehind fällt weg. Es sei denn du willst die MainWindow-Instanz als DataContext der MainWindow-Instanz setzen(also this.DataContext = this). Das solltest du aber besser lassen. Im Codebehind steht nur noch der generierte Konstruktor und sonst gar nichts. Die meisten Events werden durch Commands ersetzt. Die restlichen Events durch AttachedProperties. So sieht es MVVM vor.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    ich bin jetzt verwirrt: Auf einmal erklärt theFiloe hier den MVVM-Pattern, und ich bin der Ansicht, dasser hier garnet angemessen ist.
    Eine Property "ScrolledOut" sollte es garnet geben, nicht im Codebehind und nicht im Viewmodel.
    Weil das Sichtbarmachen eines Labels oder was auch immer, am Ende der Flieger-Animation gehört logisch in diese Animation mit heinein.
    Und so ist Storyboard ja auch konzipiert, dass mehrere Animationen ins selbe Storyboard kommen.

    Also erstmal ein Storyboard mit Flieger-Animation machen - das geht ganz in Xaml.
    Dann eine TriggerAnimation da mit hineinmachen, die die Visibility schaltet.

    Also ich scheine jetzt bisserl widersprüchlich zu post#8, aber hier gibts ja noch garkeine Daten, und kein Viewmodel, hier gibts ja nur View, und kannman komplett im View abhandeln, also im Xaml.
    Ich erkläre nur wie man eine Property bindet, da er diese Frage geäußert hat. Wie das mit Storyboards funktioniert hast a) du ihm schon erklärt und b) gibt es dazu jede menge Artikel. Auch ja und c) verstehe ich ehrlich gesagt immer noch nicht ganz was der TE denn nun animieren möchte. Somit kann ich nicht wirklich sagen was er dazu braucht. Bis jetzt habe ich nur versucht MVVM zu erklären, da er zusätzlich zu deiner Erklärung noch ein paar Fragen hatte.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.