Laden...

WPF: Default-Style für Elemente in einem Custom Control wird nicht gefunden

Erstellt von p!lle vor 8 Jahren Letzter Beitrag vor 2 Jahren 5.941 Views
p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren
WPF: Default-Style für Elemente in einem Custom Control wird nicht gefunden

Hallo,

ich bin immer noch mit den Eigenarten von WPF bzw. Styles beschäftigt.
EDIT: Ich habe das Problem weiter unten (gleicher Thread) nochmal neu beschrieben: WPF: Default-Style für Elemente in einem Custom Window wird nicht geladen

Dazu habe ich mir ein kleines Testprojekt angelegt, welches folgende 3 Dateien hat (Code auszugsweise):

[STAThread]
public static void Main(string[] args)
{
    var app = new Application();
    app.Run(new MainWindow());
}

<Window.Resources>
    <ResourceDictionary Source="Themes/Styles.xaml" />
</Window.Resources>
    
<Button Content="Button1" Width="200" Height="50" />
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Red" />
    </Style>
</ResourceDictionary>

Starte ich dieses Testprojekt, wird wie erwartet ein Fenster mit einem roten Button angezeigt. Soweit, so gut.

Nun füge ich ein Custom Window hinzu:

static CustomWindow()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow)));
}
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:theme_test2="clr-namespace:ThemeTest2">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Themes/Styles.xaml" />
    </ResourceDictionary.MergedDictionaries>
    
    <Style x:Key="{x:Type theme_test2:CustomWindow}" TargetType="{x:Type Window}" BasedOn="{StaticResource {x:Type Window}}">
        <Setter Property="Content">
            <Setter.Value>
                <Button Content="Button1" Width="200" Height="50"  />
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Dazu noch eine Generic.xaml wo der Custom Window-Style gebunden wird.

Starte ich das Projekt nun (in der App.cs wird jetzt natürlich ein CustomWindow statt eines MainWindow genutzt), dann sehe ich zwar das Custom Window, aber der Button ist NICHT rot.
Das hätte ich jetzt allerdings erwartet. Er "zieht" den Default-Style für den Button aus der Styles.xaml nicht, obwohl diese Datei eingebunden wurde. Definiere ich z.B. Farben in der Styles.xaml und nutze diese (z.B. über StaticResource), dann findet er sie in der Datei.

Meine Vermutung warum es nicht geht, ist der Visual Tree - das Einbinden außerhalb des Styles gehört evtl. nicht mehr dazu?!
DENN: Binde ich die Resource direkt im Style ein, funktioniert es:

<Style x:Key="{x:Type theme_test2:CustomWindow}" TargetType="{x:Type Window}" BasedOn="{StaticResource {x:Type Window}}">
        <Style.Resources>
            <ResourceDictionary Source="Themes/Styles.xaml" />
        </Style.Resources>
        <Setter Property="Content">
...

Kann jemand evtl. zuverlässig etwas dazu sagen?

301 Beiträge seit 2009
vor 8 Jahren

Probier mal folgendes :


// Achtung hier nicht den Static Ctor nehmen
public CustomWindow()
{
      SetResourceReference(StyleProperty, typeof (CustomWindow));         
}

Ansonsten würde ich deiner Vermutung zustimmen ich hatte schon einmal das gleich Problem.

5.299 Beiträge seit 2008
vor 8 Jahren

von einem Datentyp CustomWindow hab ich noch nie gehört - was ist das für ein Dingens?

Der frühe Apfel fängt den Wurm.

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

@KroaX: Hat leider keinen positiven Effekt.

@ErfinderDesRades: ala Custom Control.

5.299 Beiträge seit 2008
vor 8 Jahren

sagt mir nix, zumindest nichts eindeutiges.

Auf CodeProjekt fand ich einen hochgerateten Artikel zum erstellen eines CustomControls, aber in seinem Inhalt gab es nur ein paar Templates, die an einen Radiobutton gemacht wurden.

Ist was du mit "CustomControl" bezeichnest, am ende ein ganz normales Wpf-Window?

Der frühe Apfel fängt den Wurm.

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

Es ist so, dass das Window wie ein CustomControl "getrennt" und dann schlussendlich über die Generic.xaml zusammengeführt wird.

Das Problem was ich oben beschrieben habe, lässt sich allerdings auch mit Custom Controls nachstellen, wie ich eben festgestellt habe. Ich fasse das nochmal neu zusammen.

5.299 Beiträge seit 2008
vor 8 Jahren

von "wie ein CustomControl getrennt" hab ich auch noch nie was gehört.
Vlt. verschafft mir die Frage so mehr durchblick: Welche Basisklasse hat dein CustomControlWindow?

Der frühe Apfel fängt den Wurm.

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

Wie beim CustomControl gibt es eine Klasse, die von Window abgeleitet wurde. Und dazu gibt es dann für dieses Fenster irgendwo einen Style. Ala CustomControl. 😉

Wie eben schon angedeutet, gibt es das Problem mit klassisches CustomControls allerdings auch, wofür ich grade einen Beitrag vorbereite. Dann kommen wir vom untypischen CustomWindow weg. 🙂

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

Um das Problem verständlicher dazustellen, hier einmal eine Version mit einem Custom Control.
Das Testprojekt ist minimal gehalten:*App.xaml (mit StartupUri) *MainWindow.xaml

<views:CustomControl Width="200" Height="50"  />

*CustomControl.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:views="clr-namespace:ThemeTest3.Views">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/ThemeTest3;component/Themes/Styles.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <Style x:Key="{x:Type views:CustomControl}" TargetType="{x:Type Control}" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Button Content="Button1" Width="200" Height="50" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

*CustomControl.cs (mit DefaultStyleKeyProperty.OverrideMetadata) *Generic.xaml (für CustomControl) *Styles.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Red" />
    </Style>

</ResourceDictionary>

Meine Erwartung beim Starten des Projektes wäre gewesen, dass der Button rot ist. Ist er aber nicht, obwohl die Styles.xaml als Resource in der CustomControl.xaml eingebunden wurde. Nutzt man allerdings z.B. mit x:Key definierte Farben aus der Styles.xaml, werden diese auch gefunden.

Lediglich den DefaultStyle findet er nicht. Meine Vermutung ist, dass diese Ebene nicht zum VisualTree gehört.

Der Button wird rot, wenn ich die Styles.xaml als Resource direkt im Style für das CC, im Fenster oder in der Applikation eintrage (welche alle zum VisualTree gehören).

5.299 Beiträge seit 2008
vor 8 Jahren

kannst du das Projekt vlt. zippen und anhängen?

Ich kann deine Angaben nicht nachbauen.
Insbesondere, dass in der CustomControl.Xaml auf den Typ Views:CustomControl verwiesen wird, funktioniert nicht.

Der frühe Apfel fängt den Wurm.

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

MainWindow, CustomControl.xaml und CustomControl.cs liegen in einem extra "Views"-Unterordner. 😉

5.299 Beiträge seit 2008
vor 8 Jahren

Das dachte ich mir - aber wie gesagt: Es funktioniert nicht.

Edit: nee - das dachte ich mir nicht. In meiner Wpf-Welt würde es eine Datei CustomControl.cs nicht geben, jdfs. nicht im Views-Ordner.
Im Views-Ordner wären nur .Xaml-Dateien und ggfs auch .Xaml.cs - Dateien.

Der frühe Apfel fängt den Wurm.

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

Das ist ein TESTprojekt, sieh es mir nach wenn ich die Datei da im Views-Ordner habe. 😉

Was mich aber stutzig macht, warum gibt es in deiner WPF-Welt keine CustomControl.cs?
ZIP mache ich fix fertig.

301 Beiträge seit 2009
vor 8 Jahren

Hab dir mal eine angepasste Version angehängt wo es geht.

Es ist halt wichtig zu wissen wie Resourcen in WPF ermittelt werden. Der gängige Weg mit externen Libraries ist über die Themes/Generic.xaml

Ansonsten geht es auch wenn du alle Resourcen in der App.Resources lädst

Ich suche dir gleich mal einen Artikel dazu raus

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

@KroaX: Nach meinem Verständnis bindet man die Generic.xaml niemals nirgendwo ein.

Im Endeffekt kann ich dann auch die Styles.xaml in der App.xaml eintragen (was anderes machst du ja auch nicht). Dass das geht, weiß ich ja.

301 Beiträge seit 2009
vor 8 Jahren

This only applies to any custom controls you have defined i.e. classes derived from Control, directly or indirectly. You can change the default style for a standard control by deriving from it and calling DefaultStyleKeyProperty.OverrideMetadata in the static constructor, but you then have to supply the full style including ControlTemplate.

Alles andere muss explizit in der App.xaml eingebunden werden

Siehe auch : http://stackoverflow.com/questions/1228875/what-is-so-special-about-generic-xaml

p!lle Themenstarter:in
1.040 Beiträge seit 2007
vor 8 Jahren

Jein, nicht unbedingt. 🙂 Man bindet Styles da ein, wo man sie benötigt. In der Applikation, im Fenster, im Control.

Das Verständnisproblem hat die CustomControl.xaml ausgelöst, dessen Basis ein ResourceDictionary bildet. Darin ist dann ja irgendwo der Style für das CC definiert. Und ich bin bisher immer davon ausgegangen, wenn ich im ResourceDictionary andere Ressourcen bekannt mache, gehören die mit zum VisualTree des Controls. Tun sie aber nicht. 😉

Sprich

<Window x:Class="ThemeTest3.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:ThemeTest3.Views"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ResourceDictionary Source="/ThemeTest3;component/Themes/Styles.xaml" />
    </Window.Resources>

    ...
</Window>

und

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:views="clr-namespace:ThemeTest3.Views">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/ThemeTest3;component/Themes/Styles.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <Style x:Key="{x:Type views:CustomControl}" TargetType="{x:Type Control}" >
        <Setter Property="Template">
            ...
        </Setter>
    </Style>
</ResourceDictionary>

hatten für mich immer die gleiche Bedeutung, was das Einbinden der Ressourcen angeht.
Aber es funktioniert nicht 1:1 gleich. Auf mit x:Key-definierte Einträge kann man in beiden Fällen zugreifen, Defaultstyles für Standardcontrols werden aber nur im Oberen gefunden.
Im Unteren muss man die Ressourcen nochmal explizit im Style einbinden (oder eben in der App 😉)

301 Beiträge seit 2009
vor 8 Jahren

Es funktioniert schon so wie du dir das vorstellst.

This only applies to any custom controls you have defined

Das ist der einzige Unterschied. Ersetz deinen Button mal durch ein weiteres Custom Control und teste nochmal 😃

Die Standardcontrols von WPF neu zu definieren funktioniert nur dann global wenn du sie in der App.xaml oder den Resourcen eines gewünschten Controls einbindest. So wie du das schon richtig erkannt hast 😃.

T
64 Beiträge seit 2018
vor 2 Jahren

Der Beitrag ist schon etwas älter, aber vielleicht lest ihr ja noch mit 🙂
Wie funktioniert das ganze, wenn das Control Template keinen Button enthält, sondern sowas in der Art:


<Style x:key="{x:Type custom:OverviewButton}" TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Aqua"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid>
                        <Rectangle Grid.ColumnSpan="3" Grid.RowSpan="2" RadiusX="10" RadiusY="10" Fill="AliceBlue" Margin="10,20"/>
                        <Grid Margin="10,20">
                            
                            <Grid Width="30" Height="50" Background="yellow" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="20,0">
                                <Grid.Effect>
                                    <DropShadowEffect BlurRadius="20" RenderingBias="Quality" ShadowDepth="1"/>
                                </Grid.Effect>
                                <materialDesign:PackIcon Kind="{TemplateBinding Logo}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0, 5"/>
                            </Grid>
                            <TextBlock Text="{TemplateBinding Heading}" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="10" FontFamily="Calibri" FontSize="25"/>
                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,20,10,0">
                                <TextBlock  Text="{TemplateBinding Text}" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Calibri" FontSize="55"/>
                            </StackPanel>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Dann funktioniert das mit den Default Style für einen Button ja nicht. Ich bräuchte da etwas Hilfe 🙂
Ich möchte das kompllete Template als Button ansehen und wenn ich den DefaultStyle eines Button ändere, soll in diesem Fall die Fill Value geändert werden.
Kann man das der Value mit einem Binding sagen?

Lg TheSoulT