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" />