Laden...

Style überschreibt eigene Control-Klasse?

Erstellt von GeneVorph vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.713 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren
Style überschreibt eigene Control-Klasse?

Hallo,

ich versuche derzeit meine ersten Gehversuche in WPF etwas aufzufrischen und eigene Styles anzuwenden. Dabei bin ich auf ein Problem gestoßen, bei dem ich nicht weiterkomme.

Ausgangspunkt:
für meine App habe ich drei ToggleButtons, von denen aber jeweils nur einer aktiv sein können soll. Das Verhalten ist also eher das von RadioButtons. Kurz: da ich das Verhalten von RadioButtons mit der Funktionalität und Optik von ToggleButtons brauche, habe ich eine Klasse RadioToggleButton erstellt, die von RadioButton erbt. Sieht dann so aus:


public class RadioToggleButton : RadioButton
    {
        protected override void OnToggle()
        {
            if (IsChecked == true) IsChecked = IsThreeState ? (bool?)null : (bool?)false;
            else IsChecked = IsChecked.HasValue;
        }
    }

Die drei RadioToggleButtons habe ich in XAML folgendermaßen erstellt:

 <Grid DockPanel.Dock="Left" Background="Gray" Width="80">
            <StackPanel>
                <local:RadioToggleButton x:Name="tglButton1" Background="Gray" Height="80" Content="Option 1" Click="tglButton1_Click" Style="{StaticResource {x:Type ToggleButton}" />
                <local:RadioToggleButton x:Name="tglButton2" Background="Gray" Height="80" Content="Option 2" Click="tglButton2_Click" Style="{StaticResource {x:Type ToggleButton}}" />
                <local:RadioToggleButton x:Name="tglButton3" Background="Gray" Height="80" Content="Option 3" Click="tglButton3_Click" Style="{StaticResource {x:Type ToggleButton}}" />
            </StackPanel>
        </Grid>

Das Ergebnis sieht dann aus wie in Bild 1.

Nun habe ich versucht etwas mit Styles herumzuspielen, und zwar folgendermaßen:

<Window.Resources>
        <Style x:Key="TestStyle" TargetType="local:RadioToggleButton">
            <Setter Property="Background" Value="Red"/>
            <Style.Triggers>
                <Trigger Property="IsChecked" Value="True">
                    <Setter Property="Background" Value="Green"/>

                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

Wenn ich nun aber den Style auf meine RadioToggleButtons anwenden möchte...

<StackPanel>
                <local:RadioToggleButton x:Name="tglButton1" Background="Gray" Height="80" Content="Option 1" Click="tglButton1_Click" Style="{StaticResource TestStyle" /> <!-- ... -->

Dann bekommen meine RadioToggleButtons zusätzlich normale RadioButton-Felder (s. Bild 2). Das war natürlich nicht meine Absicht.

Habe ich lediglich was falsch implementiert oder liegt ein Denkfehler anderer Art vor?

Dank und Gruß
Vorph

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

sorry - hier ist Bild Nr 2

T
156 Beiträge seit 2010
vor 4 Jahren

Hi,

es reicht nicht, einfach nur von RadioButton zu erben und in der vererbten Klasse Methoden zu überschreiben. Denn so änderst Du nur das Verhalten.

Was Du machen möchtest, ist, dass Du das Aussehen verändern willst (ja, und das Verhalten).

Da kommst Du nicht drum rum, das ControlTemplate zu überschreiben.
Eine gute Übersicht findest Du hier: RadioButton Styles und Templates

lG, Marko

W
955 Beiträge seit 2010
vor 4 Jahren

Es wäre besser MVVM zu verwenden: Dann kann man bei den ToggleButtons bleiben und setzt im ViewModel die jeweils anderen beiden auf false wenn einer geklickt wird.
Was soll das überhaupt werden? Baust du ein TabControl nach?

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

@trashkid2000: Danke für den Link und den Hinweis mit dem ControlTemplate. Jetzt weiß ich zumindest, in welche Richtung ich dabei gehen muss.

@witte: Danke auch für deinen Hinweis. Möglicherweise versuche ich diese Variante - es hätte den Vorteil keinen eigenen Button entwerfen zu müssen.

Was soll das überhaupt werden?

Ehrlich gesagt eine Fingerübung. Ich code rein zum Spaß (wenn ich alle paar Monate mal wirklich dazu komme). Ich hatte die Idee, so ein Side-Menu zu machen, bei dem sich - wenn man ein Menupunkt ausgewählt hat - rechts davon ein Panel öffnet mit den Submenüpunkten (Im Bild 2 siehst du das Panel, weil ich zufällig den Button auch geclickt hatte).

W
955 Beiträge seit 2010
vor 4 Jahren

Dann kannst du doch einfach ein TabControl verwenden, mit den Tabs an der Seite. Und danach TabItem.Header stylen.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Dann kannst du doch einfach ein TabControl verwenden

😁 Danke - das war mein erster Versuch. Dann dachte ich:"let's go sophisticated" - einfach um mal zu schauen, wie ich die Idee noch umsetzen kann.

Übrigens: ich hab's jetzt so gemacht, dass in meinem ViewModel die Logic das Verhalten der Buttons steuert (wird ein Button geklickt, werden die anderen deaktiviert). Obwohl ich mir nicht 100% sicher bin, ob das Steuern einer Logik im ViewModel, die die Optik beeinflusst, nicht ein Bruch der Trennung von Design und Busisnesslogik ist. Weil ja der Code quasi Styleaspekte festlegt ... streng genommen ... vielleicht hab ich jetzt auch nur n Knoten im Hirn ?(

5.657 Beiträge seit 2006
vor 4 Jahren

Ist schon richtig so. Das ViewModel repräsentiert auch die Logik der Benutzeroberfläche. Schau mal in [Artikel] MVVM und DataBinding, da gibt es auch noch ein paar andere Beispiele dazu.

Weeks of programming can save you hours of planning

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

OK, nach einem ersten Erfolg, nun Ernüchterung.

Kurz nochmal zur angestrebten Funktionalität: Ich klicke "Option 1" und ein Panel gleitet rechts der Buttons auf (zu sehen in Bild 2). Noch ein Klick auf "Option 1), und das Panel gleitet wieder zu. Klicke ich zuerst Option 1 und dann Option 2, sollte das Panel idealerweise nochmals aufgleiten (sieht besser aus, als wenn es direkt geöffnet bleibt...IMHO).

Ich habe den Ansatz mit den ToggleButtons nochmals aufgegriffen und bin zu folgendem Ergebnis gekommen:

  • ich kann nun Option 1 klicken und das Panel gleitet auf. Bei nochmaligem Klicken schließt es.
  • ich kann Option 1 und dann Option 2 klicken. Das Panel öffnet sich, gleichzeitig bleibt aber natürlich der Checked-State von Option 1 erhalten. Das sollte so eigentlich nicht sein. Wenn ich progammatisch den State auf "Unchecked" ändere (also per Code), dann schließt das Panel natürlich und Option zwei verharrt ohne Funktion im State "Checked"

Bevor ich euch meinen COde zeige: zwei Ideen hatte ich noch:
a) ich steige auf einfache Buttons um, und versuche den Style (Background) des Buttons über einen Bool zu steuern, der dafür sorgt, dass die Optik stimmt.
b doch nochmal auf die RAdioButtons zurückkommen. )trashkid2000 schrieb: das ControlTemplate überschreiben. Das entpuppte sich aber als zu schwierig für mich (ich hab's nicht hinbekommen, dass meine Radiobuttons am Schluss wie die normalen Buttons aussahen^^)

Hier mein XAML-Code (dafür habe ich den Code im ViewModel auskommentiert, da nun der XAML-Code Visibility/Collapsed für das Panel übernimmt):

<StackPanel>
                <ToggleButton x:Name="tglButton1" Content="Option 1" Height="80" Click="tglButton1_Click">
                    <ToggleButton.Triggers>
                        <EventTrigger RoutedEvent="ToggleButton.Checked">
                            <BeginStoryboard>
                                <Storyboard x:Name="HidePanel">
                                    <DoubleAnimation Storyboard.TargetName="ToggleGrid" Storyboard.TargetProperty="Width" From="0" To="80" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn"></PowerEase>
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>                                   
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                            <BeginStoryboard>
                                <Storyboard x:Name="ShowPanel">
                                    <DoubleAnimation Storyboard.TargetName="ToggleGrid" Storyboard.TargetProperty="Width" From="80" To="0" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn"></PowerEase>
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>                                    
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ToggleButton.Triggers>
                </ToggleButton>
                
<!-- ab hier wiederholt es sich im Prinzip für die ToggleButtons 2 und 3 -->

<ToggleButton x:Name="tglButton2" Content="Option 2" Height="80" Click="tglButton2_Click">
                    <ToggleButton.Triggers>
                        <EventTrigger RoutedEvent="ToggleButton.Checked">
                            <BeginStoryboard>
                                <Storyboard x:Name="Hide">
                                    <DoubleAnimation Storyboard.TargetName="ToggleGrid" Storyboard.TargetProperty="Width" From="0" To="80" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn"></PowerEase>
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                            <BeginStoryboard>
                                <Storyboard x:Name="Show">
                                    <DoubleAnimation Storyboard.TargetName="ToggleGrid" Storyboard.TargetProperty="Width" From="80" To="0" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn"></PowerEase>
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ToggleButton.Triggers>
                </ToggleButton>
                <ToggleButton x:Name="tglButton3" Content="Option 3" Height="80" Click="tglButton3_Click">
                    <ToggleButton.Triggers>
                        <EventTrigger RoutedEvent="ToggleButton.Checked">
                            <BeginStoryboard>
                                <Storyboard x:Name="Hide2">
                                    <DoubleAnimation Storyboard.TargetName="ToggleGrid" Storyboard.TargetProperty="Width" From="0" To="80" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn"></PowerEase>
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                            <BeginStoryboard>
                                <Storyboard x:Name="Show2">
                                    <DoubleAnimation Storyboard.TargetName="ToggleGrid" Storyboard.TargetProperty="Width" From="80" To="0" Duration="0:0:0.3">
                                        <DoubleAnimation.EasingFunction>
                                            <PowerEase EasingMode="EaseIn"></PowerEase>
                                        </DoubleAnimation.EasingFunction>
                                    </DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ToggleButton.Triggers>
                </ToggleButton>
            </StackPanel>

Noch eine letzte Kleinigkeit: ich habe festgestellt, dass ich nun beim Start der App mit ToggleGrid.Visible beginnen muss. Sonst funktioniert es nicht (als hätten die Button-Klicks keine Funktion?). Woran könnte das liegen?

Danke und Gruß
Vorph

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

@MrSparkle Danke für die Links! Ich merke, da gibt es noch Wissenslücken zu füllen.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 4 Jahren

Nach vielen Stunden Recherche zum Thema und Einarbeit ins Thema, hier meine Lösung - falls mal jemand vor dem gleichen Problem steht:

Gewünschtes Verhalten: Im Prinzip möchte ich zwei bis drei ToggleButtons, die sich wie RadioButtons verhalten - immer nur einer kann zu einem Zeitpunkt IsChecked sein.

Die Lösung sieht dann so aus, dass man 2 Styles erstellt: ein Basis-Style für RadioButtons und einen Style für die ToggleButtons. Aber Code sagt ja bekanntlich mehr als tausend Worte:

 
<!-- hier der Basis-Style für die ToggleButtons-->
<Style x:Key="TGLTemplate" TargetType="ToggleButton">           
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ToggleButton">
                            <Border BorderBrush="{TemplateBinding BorderBrush}" 
                                Background="{TemplateBinding Background}">
                                <ContentPresenter HorizontalAlignment="Center"                  
                                              VerticalAlignment="Center"/>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter Property="Background" Value="Blue"/>
                </Trigger>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter Property="Background" Value="Red" />
                    </Trigger>
                </Style.Triggers>           
        </Style>
        
<!-- hier der Basis-Style für die RadioButtons-->
        <Style x:Key="MyTemplate" TargetType="RadioButton">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
<!-- hier der eigentliche "Trick: im ControlTemplate der RadioButtons befindet sich ein ToggleButton, dessen Style auf den ToggleButton-Style referenziert-->
                        <ToggleButton Style="{StaticResource TGLTemplate}" IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                          Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Wenn man das jetzt noch sauber in ein ResourceDictionary auslagert und ein bisschen aufräumt, ist das hübsch MVVM verträglich. Und war so einfach^^

Gruß
Vorph