Bea Stollnitz memiliki posting blog yang bagus tentang menggunakan ekstensi markup untuk ini, di bawah judul "Bagaimana saya bisa mengatur beberapa gaya di WPF?"
Blog itu sudah mati sekarang, jadi saya mereproduksi posting di sini
WPF dan Silverlight keduanya menawarkan kemampuan untuk mendapatkan Gaya dari Gaya lain melalui properti "BasedOn". Fitur ini memungkinkan pengembang untuk mengatur gaya mereka menggunakan hierarki yang mirip dengan warisan kelas. Pertimbangkan gaya berikut:
<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>
Dengan sintaks ini, sebuah Tombol yang menggunakan RedButtonStyle akan memiliki properti Foreground diatur ke Red dan properti Margin-nya diatur ke 10.
Fitur ini sudah ada di WPF sejak lama, dan ini baru di Silverlight 3.
Bagaimana jika Anda ingin mengatur lebih dari satu gaya pada elemen? Baik WPF maupun Silverlight tidak memberikan solusi untuk masalah ini di luar kebiasaan. Untungnya ada cara untuk menerapkan perilaku ini di WPF, yang akan saya bahas di posting blog ini.
WPF dan Silverlight menggunakan ekstensi markup untuk menyediakan properti dengan nilai-nilai yang memerlukan beberapa logika untuk diperoleh. Ekstensi markup mudah dikenali dengan adanya kurung keriting yang mengelilinginya di XAML. Misalnya, ekstensi markup {Binding} berisi logika untuk mengambil nilai dari sumber data dan memperbaruinya ketika terjadi perubahan; ekstensi markup {StaticResource} berisi logika untuk mengambil nilai dari kamus sumber daya berdasarkan kunci. Untungnya bagi kami, WPF memungkinkan pengguna untuk menulis ekstensi markup kustom mereka sendiri. Fitur ini belum ada di Silverlight, jadi solusi di blog ini hanya berlaku untuk WPF.
Yang lain telah menulis solusi hebat untuk menggabungkan dua gaya menggunakan ekstensi markup. Namun, saya menginginkan solusi yang memberikan kemampuan untuk menggabungkan jumlah gaya yang tidak terbatas, yang sedikit lebih rumit.
Menulis ekstensi markup sangat mudah. Langkah pertama adalah membuat kelas yang berasal dari MarkupExtension, dan gunakan atribut MarkupExtensionReturnType untuk menunjukkan bahwa Anda bermaksud nilai yang dikembalikan dari ekstensi markup Anda menjadi tipe Style.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
Menentukan input untuk ekstensi markup
Kami ingin memberi pengguna ekstensi markup kami cara mudah untuk menentukan gaya yang akan digabungkan. Pada dasarnya ada dua cara di mana pengguna dapat menentukan input ke ekstensi markup. Pengguna dapat mengatur properti atau meneruskan parameter ke konstruktor. Karena dalam skenario ini pengguna memerlukan kemampuan untuk menentukan jumlah gaya yang tidak terbatas, pendekatan pertama saya adalah membuat konstruktor yang mengambil sejumlah string menggunakan kata kunci "params":
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
Tujuan saya adalah untuk dapat menulis input sebagai berikut:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Perhatikan koma yang memisahkan tombol gaya yang berbeda. Sayangnya, ekstensi markup kustom tidak mendukung sejumlah parameter konstruktor yang tidak terbatas, sehingga pendekatan ini menghasilkan kesalahan kompilasi. Jika saya tahu sebelumnya berapa banyak gaya yang ingin saya gabungkan, saya bisa menggunakan sintaks XAML yang sama dengan konstruktor yang mengambil jumlah string yang diinginkan:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
Sebagai solusinya, saya memutuskan untuk meminta parameter konstruktor mengambil string tunggal yang menentukan nama gaya yang dipisahkan oleh spasi. Sintaksnya tidak terlalu buruk:
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.");
}
}
Menghitung output ekstensi markup
Untuk menghitung output dari ekstensi markup, kita perlu mengganti metode dari MarkupExtension yang disebut "ProvidValue". Nilai yang dikembalikan dari metode ini akan ditetapkan dalam target ekstensi markup.
Saya mulai dengan membuat metode ekstensi untuk Gaya yang tahu cara menggabungkan dua gaya. Kode untuk metode ini cukup sederhana:
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];
}
}
Dengan logika di atas, gaya pertama dimodifikasi untuk memasukkan semua informasi dari yang kedua. Jika ada konflik (misalnya kedua gaya memiliki setter untuk properti yang sama), gaya kedua menang. Perhatikan bahwa selain menyalin gaya dan pemicu, saya juga memperhitungkan nilai TargetType dan BasedOn serta sumber daya apa pun yang mungkin dimiliki gaya kedua. Untuk TargetType dari gaya gabungan, saya menggunakan tipe yang lebih diturunkan. Jika gaya kedua memiliki gaya BasedOn, saya menggabungkan hierarki gaya secara rekursif. Jika memiliki sumber daya, saya menyalinnya ke gaya pertama. Jika sumber daya tersebut dirujuk menggunakan {StaticResource}, sumber daya tersebut diselesaikan secara statis sebelum kode gabungan ini dieksekusi, dan karenanya tidak perlu memindahkannya. Saya menambahkan kode ini jika kami menggunakan DynamicResources.
Metode ekstensi yang ditunjukkan di atas memungkinkan sintaks berikut:
style1.Merge(style2);
Sintaks ini berguna asalkan saya memiliki contoh kedua gaya dalam ProvidValue. Ya saya tidak. Yang saya dapatkan dari konstruktor adalah daftar kunci string untuk gaya tersebut. Jika ada dukungan untuk params di parameter konstruktor, saya bisa menggunakan sintaks berikut untuk mendapatkan contoh gaya aktual:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
Tapi itu tidak berhasil. Dan bahkan jika batasan params tidak ada, kita mungkin akan menemukan batasan lain dari ekstensi markup, di mana kita harus menggunakan sintaks properti-elemen alih-alih sintaks atribut untuk menentukan sumber daya statis, yang verbose dan rumit (saya jelaskan ini bug lebih baik di posting blog sebelumnya ). Dan bahkan jika kedua keterbatasan itu tidak ada, saya masih lebih suka menulis daftar gaya hanya dengan menggunakan nama mereka - lebih pendek dan lebih mudah dibaca daripada StaticResource untuk masing-masing.
Solusinya adalah membuat StaticResourceExtension menggunakan kode. Diberi kunci gaya string tipe dan penyedia layanan, saya bisa menggunakan StaticResourceExtension untuk mengambil contoh gaya aktual. Berikut ini sintaksnya:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Sekarang kita memiliki semua bagian yang diperlukan untuk menulis metode ProvidValue:
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;
}
Berikut ini adalah contoh lengkap penggunaan ekstensi markup MultiStyle:
<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" />