WPF'de birden çok stil nasıl uygulanır?


153

WPF'de, a'ya birden çok stil nasıl uygulayabilirim FrameworkElement? Örneğin, zaten bir tarzı olan bir kontrolüm var. Ayrıca, ilkini patlatmadan eklemek istediğim ayrı bir stilim var. Stiller farklı TargetTypes var, bu yüzden sadece diğeri ile genişletmek olamaz.


OP, ilk tarzının tek bir kontrol için benzersiz olup olmadığını asla belirtmedi. Bu sayfada verilen cevaplar, her iki stili birden fazla kontrolde paylaşma gereğini varsayar. Denetimlerde temel stilleri kullanmanın ve tek tek denetimlerde bağımsız özellikleri geçersiz kılmanın bir yolunu arıyorsanız: şu yanıta bakın: stackoverflow.com/a/54497665/1402498
JamesHoux

Yanıtlar:


154

Bence basit cevap, yapmaya çalıştığınız şeyi (en azından WPF'nin bu versiyonunda) yapamayacağınızdır.

Diğer bir deyişle, belirli bir öğe için yalnızca bir Stil uygulanabilir.

Ancak, yukarıda belirtildiği gibi, belki de size BasedOnyardımcı olmak için kullanabilirsiniz . Aşağıdaki gevşek xaml parçasına bakın. İçinde iki stil uygulamak istediğiniz öğenin temel sınıfında bulunan bir özelliği ayarlayan bir temel stil var olduğunu göreceksiniz. Ve taban stiline dayanan ikinci stilde, başka bir özellik ayarladım.

Yani, buradaki fikir ... ayarlamak istediğiniz özellikleri bir şekilde ayırabiliyorsanız ... birden fazla stil ayarlamak istediğiniz öğenin miras hiyerarşisine göre ... bir geçici çözümünüz olabilir.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <Style x:Key="baseStyle" TargetType="FrameworkElement">
            <Setter Property="HorizontalAlignment" Value="Left"/>
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource baseStyle}">
            <Setter Property="Content" Value="Hello World"/>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Width="200" Height="50"/>
    </Grid>
</Page>


Bu yardımcı olur umarım.

Not:

Özellikle dikkat edilmesi gereken bir şey var. Eğer değiştirirseniz TargetType(yukarıdaki xaml ilk sette) ikinci tarzında için ButtonBase, iki Stiller uygulanan alamadım. Ancak, bu kısıtlamayı aşmak için aşağıdaki xaml adresine göz atın. Temel olarak, Stil'e bir anahtar vermeniz ve bu tuşla referans vermeniz gerektiği anlamına gelir.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <Style x:Key="baseStyle" TargetType="FrameworkElement">
            <Setter Property="HorizontalAlignment" Value="Left"/>
        </Style>
        <Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}">
            <Setter Property="Content" Value="Hello World"/>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/>
    </Grid>
</Page>

10
Unutmayın ... ** Sipariş önemlidir **. derivedStyleSonra gelmelidirbaseStyle
MSFT - SliverNinja

50

Bea Stollnitz bunun için " WPF'de birden çok stil nasıl ayarlayabilirim?" Başlığı altında bir biçimlendirme uzantısı kullanma konusunda iyi bir blog yazısı yayınladı.

Bu blog artık öldü, bu yüzden gönderiyi burada yeniden üretiyorum


WPF ve Silverlight'ın her ikisi de “BasedOn” özelliği ile başka bir Stilden bir Stil türetme olanağı sunar. Bu özellik, geliştiricilerin sınıf kalıtımına benzer bir hiyerarşi kullanarak stillerini düzenlemelerini sağlar. Aşağıdaki stilleri göz önünde bulundurun:

<Style TargetType="Button" x:Key="BaseButtonStyle">
    <Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
    <Setter Property="Foreground" Value="Red" />
</Style>

Bu sözdizimiyle, RedButtonStyle kullanan bir Button öğesinin Foreground özelliği Red ve Margin özelliği 10 olarak ayarlanır.

Bu özellik uzun süredir WPF'de ve Silverlight 3'te yeni.

Bir öğeye birden fazla stil ayarlamak isterseniz ne olur? Ne WPF ne de Silverlight bu sorun için kutudan çıkar çıkmaz bir çözüm sunmaz. Neyse ki, bu blog yazısında tartışacağım WPF bu davranışı uygulamak için yollar vardır.

WPF ve Silverlight, bazı mantık gerektiren değerlere sahip özellikler sağlamak için biçimlendirme uzantılarını kullanır. İşaretleme uzantıları, XAML'de onları çevreleyen kıvırcık parantezlerin varlığıyla kolayca tanınabilir. Örneğin, {Binding} işaretleme uzantısı bir veri kaynağından bir değer almak ve değişiklikler meydana geldiğinde güncellemek için mantık içerir; {StaticResource} işaretleme uzantısı, bir anahtara dayalı olarak kaynak sözlüğünden bir değer almak için mantık içerir. Neyse ki bizim için WPF, kullanıcıların kendi özel biçimlendirme uzantılarını yazmalarına izin veriyor. Bu özellik henüz Silverlight'ta mevcut olmadığından, bu blogdaki çözüm yalnızca WPF için geçerlidir.

Diğerleri , biçimlendirme uzantılarını kullanarak iki stili birleştirmek için harika çözümler yazdı. Ancak, sınırsız sayıda stili birleştirme yeteneğini sağlayan bir çözüm istedim, ki bu biraz daha hileli.

Bir biçimlendirme uzantısı yazmak basittir. İlk adım, MarkupExtension öğesinden türetilen bir sınıf oluşturmak ve MarkupExtensionReturnType özniteliğini kullanarak, biçimlendirme uzantınızdan döndürülen değeri Style türünde olmasını istediğinizi belirtmektir.

[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}

İşaretleme uzantısına girişleri belirtme

İşaretleme uzantımızdaki kullanıcılara birleştirilecek stilleri belirtmek için basit bir yol sunmak istiyoruz. Kullanıcının bir biçimlendirme uzantısına girdi belirleyebilmesi için esasen iki yol vardır. Kullanıcı özellikleri ayarlayabilir veya parametreleri yapıcıya iletebilir. Bu senaryoda kullanıcının sınırsız sayıda stil belirtme yeteneğine ihtiyacı olduğundan, ilk yaklaşımım “params” anahtar sözcüğünü kullanarak istediğiniz sayıda dizeyi alan bir kurucu oluşturmaktı:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Amacım girdileri aşağıdaki gibi yazabilmekti:

<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}"  />

Farklı stil tuşlarını ayıran virgüllere dikkat edin. Ne yazık ki, özel biçimlendirme uzantıları sınırsız sayıda yapıcı parametresini desteklemediğinden, bu yaklaşım bir derleme hatasıyla sonuçlanır. Önceden kaç stil birleştirmek istediğimi bilseydim, aynı sayıda XAML sözdizimini, istenen sayıda dizeyi alan bir kurucu ile birlikte kullanabilirdim:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Geçici bir çözüm olarak, yapıcı parametresinin boşluklarla ayrılmış stil adlarını belirten tek bir dize almasına karar verdim. Sözdizimi çok kötü değil:

private string[] resourceKeys;

public MultiStyleExtension(string inputResourceKeys)
{
    if (inputResourceKeys == null)
    {
        throw new ArgumentNullException("inputResourceKeys");
    }

    this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

    if (this.resourceKeys.Length == 0)
    {
        throw new ArgumentException("No input resource keys specified.");
    }
}

İşaretleme uzantısının çıktısını hesaplama

Bir biçimlendirme uzantısının çıktısını hesaplamak için, MarkupExtension'dan “ProvideValue” adlı bir yöntemi geçersiz kılmamız gerekir. Bu yöntemden döndürülen değer, biçimlendirme uzantısının hedefinde ayarlanır.

Stil için iki stilin nasıl birleştirileceğini bilen bir uzantı yöntemi oluşturarak başladım. Bu yöntemin kodu oldukça basittir:

public static void Merge(this Style style1, Style style2)
{
    if (style1 == null)
    {
        throw new ArgumentNullException("style1");
    }
    if (style2 == null)
    {
        throw new ArgumentNullException("style2");
    }

    if (style1.TargetType.IsAssignableFrom(style2.TargetType))
    {
        style1.TargetType = style2.TargetType;
    }

    if (style2.BasedOn != null)
    {
        Merge(style1, style2.BasedOn);
    }

    foreach (SetterBase currentSetter in style2.Setters)
    {
        style1.Setters.Add(currentSetter);
    }

    foreach (TriggerBase currentTrigger in style2.Triggers)
    {
        style1.Triggers.Add(currentTrigger);
    }

    // This code is only needed when using DynamicResources.
    foreach (object key in style2.Resources.Keys)
    {
        style1.Resources[key] = style2.Resources[key];
    }
}

Yukarıdaki mantıkla, birinci stil, ikinciden gelen tüm bilgileri içerecek şekilde değiştirilir. Çakışmalar varsa (örneğin her iki stilin de aynı özellik için bir ayarlayıcı vardır), ikinci stil kazanır. Stilleri ve tetikleyicileri kopyalamanın yanı sıra, ikinci stilin sahip olabileceği tüm kaynakların yanı sıra TargetType ve BasedOn değerlerini de dikkate aldım. Birleştirilen stilin TargetType'ı için, hangi tür daha türetilmişse kullandım. İkinci stilin BasedOn stili varsa, stil hiyerarşisini yinelemeli olarak birleştiririm. Kaynakları varsa, onları ilk stile kopyalarım. Bu kaynaklara {StaticResource} kullanılarak atıfta bulunulursa, bu birleştirme kodu yürütülmeden önce statik olarak çözümlenir ve bu nedenle bunları taşımak gerekmez. DynamicResources kullanmamız durumunda bu kodu ekledim.

Yukarıda gösterilen uzantı yöntemi aşağıdaki sözdizimini etkinleştirir:

style1.Merge(style2);

ProvideValue içinde her iki stilin örnekleri olması koşuluyla bu sözdizimi yararlıdır. Ben bilmiyorum. Yapıcıdan aldığım tek şey bu stiller için dize anahtarlarının bir listesidir. Yapıcı parametrelerinde parametreler için destek olsaydı, gerçek stil örneklerini almak için aşağıdaki sözdizimini kullanabilirdim:

<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}"/>
public MultiStyleExtension(params Style[] styles)
{
}

Ama bu işe yaramıyor. Params sınırlaması olmasa bile, muhtemelen ayrıntılı ve hantal olan statik kaynakları belirtmek için özellik sözdizimi yerine özellik öğesi sözdizimini kullanmak zorunda kalacağımız biçimlendirme uzantılarının başka bir sınırlamasına çarparız (bunu açıklarım önceki blog yayınında daha iyi hata ). Ve bu sınırlamaların her ikisi de olmasa bile, sadece isimlerini kullanarak stil listesini yazmayı tercih ederim - her biri için bir StaticResource'dan daha kısa ve okumak daha kolaydır.

Çözüm, kodu kullanarak bir StaticResourceExtension oluşturmaktır. Tip dizesi ve servis sağlayıcısının bir stil anahtarı verildiğinde, gerçek stil örneğini almak için StaticResourceExtension'ı kullanabilirim. Sözdizimi şöyledir:

Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;

Şimdi ProvideValue yöntemini yazmak için gereken tüm parçalara sahibiz:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    Style resultStyle = new Style();

    foreach (string currentResourceKey in resourceKeys)
    {
        Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;

        if (currentStyle == null)
        {
            throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
        }

        resultStyle.Merge(currentStyle);
    }
    return resultStyle;
}

MultiStyle biçimlendirme uzantısının kullanımının tam bir örneği:

<Window.Resources>
    <Style TargetType="Button" x:Key="SmallButtonStyle">
        <Setter Property="Width" Value="120" />
        <Setter Property="Height" Value="25" />
        <Setter Property="FontSize" Value="12" />
    </Style>

    <Style TargetType="Button" x:Key="GreenButtonStyle">
        <Setter Property="Foreground" Value="Green" />
    </Style>

    <Style TargetType="Button" x:Key="BoldButtonStyle">
        <Setter Property="FontWeight" Value="Bold" />
    </Style>
</Window.Resources>

<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />

resim açıklamasını buraya girin


3
Gerçekten iyi bir çözüm, ama neden 3 veya + stilini birleştirmek için basit bir çözüm olmadığını anlamıyorum.
Bay Rubix

31

Ama bir başkasından uzatabilirsiniz .. BasedOn özelliği bir göz atın

<Style TargetType="TextBlock">
      <Setter Property="Margin" Value="3" />
</Style>

<Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock" 
       BasedOn="{StaticResource {x:Type TextBlock}}">
     <Setter Property="VerticalAlignment" Value="Top" />
</Style>

bu benim için yeterliydi. tnks!
David Lay

Ancak bu yalnızca her iki stil de aynı
türdeyse

17

WPF / XAML bu işlevselliği yerel olarak sağlamaz, ancak istediğinizi yapmanıza olanak tanıyan genişletilebilirlik sağlar.

Aynı ihtiyaca rastladık ve diğer iki stilde yeni bir Stil oluşturmamıza izin vermek için kendi XAML İşaretleme Uzantımızı ("MergedStylesExtension" olarak adlandırdık) oluşturduk (gerekirse, muhtemelen bir satır daha da fazla stilden devralma).

Bir WPF / XAML hatası nedeniyle, onu kullanmak için özellik öğesi sözdizimini kullanmamız gerekiyor, ancak bunun dışında iyi çalışıyor gibi görünüyor. Örneğin,

<Button
    Content="This is an example of a button using two merged styles">
    <Button.Style>
      <ext:MergedStyles
                BasedOn="{StaticResource FirstStyle}"
                MergeStyle="{StaticResource SecondStyle}"/>
   </Button.Style>
</Button>

Geçenlerde burada yazdım: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/


3

Bu, stillerinizi kullanmak ve sarmak için yardımcı bir sınıf oluşturarak mümkündür. Burada adı geçen CompoundStyle , bunun nasıl yapılacağını gösterir. Birden çok yol vardır, ancak en kolayı aşağıdakileri yapmaktır:

<TextBlock Text="Test"
    local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>

Umarım yardımcı olur.


2

AttachedPropertyAşağıdaki kod gibi birden fazla stil ayarlamak için kullanın :

public class Css
{

    public static string GetClass(DependencyObject element)
    {
        if (element == null)
            throw new ArgumentNullException("element");

        return (string)element.GetValue(ClassProperty);
    }

    public static void SetClass(DependencyObject element, string value)
    {
        if (element == null)
            throw new ArgumentNullException("element");

        element.SetValue(ClassProperty, value);
    }


    public static readonly DependencyProperty ClassProperty =
        DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css), 
            new PropertyMetadata(null, OnClassChanged));

    private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ui = d as FrameworkElement;
        Style newStyle = new Style();

        if (e.NewValue != null)
        {
            var names = e.NewValue as string;
            var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var name in arr)
            {
                Style style = ui.FindResource(name) as Style;
                foreach (var setter in style.Setters)
                {
                    newStyle.Setters.Add(setter);
                }
                foreach (var trigger in style.Triggers)
                {
                    newStyle.Triggers.Add(trigger);
                }
            }
        }
        ui.Style = newStyle;
    }
}

KULLANILDIĞII:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:style_a_class_like_css"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="325">
    <Window.Resources>

        <Style TargetType="TextBlock" x:Key="Red" >
            <Setter Property="Foreground" Value="Red"/>
        </Style>

        <Style TargetType="TextBlock" x:Key="Green" >
            <Setter Property="Foreground" Value="Green"/>
        </Style>

        <Style TargetType="TextBlock" x:Key="Size18" >
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="Margin" Value="6"/>
        </Style>

        <Style TargetType="TextBlock" x:Key="Bold" >
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>

    </Window.Resources>
    <StackPanel>

        <Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
        <Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
        <Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>

    </StackPanel>
</Window>

Sonuç:

resim açıklamasını buraya girin


1

Belirli bir özelliğe dokunmuyorsanız, tüm temel ve ortak özellikleri hedef türünün FrameworkElement olacağı stile alabilirsiniz. daha sonra, tüm bu ortak özellikleri tekrar kopyalamanıza gerek kalmadan, ihtiyacınız olan her hedef türü için özel lezzetler oluşturabilirsiniz.


1

Bir StyleSelector kullanarak öğeleri bir koleksiyona uygulayarak benzer bir şey alabilirsiniz, ben ağaçta ilişkili nesne türüne bağlı olarak TreeViewItems farklı stilleri kullanarak benzer bir sorun yaklaşmak için kullandım. Özel yaklaşımınıza uyum sağlamak için aşağıdaki sınıfı biraz değiştirmeniz gerekebilir, ancak umarım bu başlamanıza yardımcı olur

public class MyTreeStyleSelector : StyleSelector
{
    public Style DefaultStyle
    {
        get;
        set;
    }

    public Style NewStyle
    {
        get;
        set;
    }

    public override Style SelectStyle(object item, DependencyObject container)
    {
        ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container);

        //apply to only the first element in the container (new node)
        if (item == ctrl.Items[0])
        {
            return NewStyle;
        }
        else
        {
            //otherwise use the default style
            return DefaultStyle;
        }
    }
}

Daha sonra bunu böyle uygulayın

 <TreeView>
     <TreeView.ItemContainerStyleSelector
         <myassembly: MyTreeStyleSelector DefaultStyle = "{StaticResource DefaultItemStyle}"
                                         NewStyle = "{StaticResource NewItemStyle}" />
     </TreeView.ItemContainerStyleSelector>
  </ TreeView>

1

Bazen panelleri yuvalayarak buna yaklaşabilirsiniz. Ön Plan'ı değiştiren bir Stiliniz olduğunu ve FontSize'i değiştiren bir Stiliniz olduğunu, ikincisini TextBlock'a uygulayabilir ve Stilinin ilk olduğu bir Izgaraya koyabilirsiniz. Bu, tüm sorunları çözmeyecek olsa da, bazı durumlarda en kolay yol olabilir ve olabilir.


1

SelectStyle'ı geçersiz kıldığınızda, GroupBy özelliğini aşağıdaki gibi yansıma yoluyla alabilirsiniz:

    public override Style SelectStyle(object item, DependencyObject container)
    {

        PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);

        PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);

        if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
        {
            return this.TitleStyle;
        }

        if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
        {
            return this.DateStyle;
        }

        return null;
    }

0

Temel stile ek olarak yalnızca tek bir öğeye benzersiz bir stil uygulamaya çalışıyorsanız, bunu yapmanın tamamen farklı bir yolu vardır, bu IMHO'nun okunabilir ve bakımı kolay kod için çok daha iyidir.

Her bir eleman için parametreleri ayarlamak son derece yaygındır. Sadece tek bir öğede kullanmak için sözlük stillerini tanımlamak, sürdürmek veya anlamlandırmak için son derece zahmetlidir. Sadece bir kerelik eleman ayarları için stil oluşturmayı önlemek için, burada kendi soruma cevabımı okuyun:

https://stackoverflow.com/a/54497665/1402498

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.