बीओ स्टोलनिट्ज़ के पास इसके लिए एक मार्कअप एक्सटेंशन का उपयोग करने के बारे में एक अच्छा ब्लॉग पोस्ट था , शीर्षक के तहत "मैं WPF शैलियों में कई कैसे सेट कर सकता हूं?"
वह ब्लॉग अब मृत हो गया है, इसलिए मैं यहां पोस्ट पुन: प्रस्तुत कर रहा हूं
WPF और सिल्वरलाइट दोनों ही "बेस्ड" प्रॉपर्टी के माध्यम से एक स्टाइल को दूसरी स्टाइल से प्राप्त करने की क्षमता प्रदान करते हैं। यह सुविधा डेवलपर्स को अपनी विरासत को वर्ग विरासत के समान एक पदानुक्रम का उपयोग करके व्यवस्थित करने में सक्षम बनाती है। निम्नलिखित शैलियों पर विचार करें:
<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>
इस सिंटैक्स के साथ, एक बटन जो RedButtonStyle का उपयोग करता है, उसकी अग्रभूमि संपत्ति रेड में सेट होगी और इसकी मार्जिन संपत्ति 10 पर सेट होगी।
यह सुविधा लंबे समय से WPF में है, और यह सिल्वरलाइट 3 में नया है।
यदि आप किसी तत्व पर एक से अधिक शैली सेट करना चाहते हैं तो क्या होगा? इस समस्या के समाधान के लिए न तो WPF और न ही सिल्वरलाइट प्रदान करता है। सौभाग्य से WPF में इस व्यवहार को लागू करने के तरीके हैं, जिनके बारे में मैं इस ब्लॉग पोस्ट में चर्चा करूंगा।
WPF और सिल्वरलाइट मार्कअप एक्सटेंशन का उपयोग उन मूल्यों के साथ प्रदान करने के लिए करते हैं जिन्हें प्राप्त करने के लिए कुछ तर्क की आवश्यकता होती है। XAML में उनके आसपास घुंघराले कोष्ठक की उपस्थिति से मार्कअप एक्सटेंशन आसानी से पहचाने जा सकते हैं। उदाहरण के लिए, {बाइंडिंग} मार्कअप एक्सटेंशन में डेटा स्रोत से मान प्राप्त करने और परिवर्तन होने पर इसे अपडेट करने के लिए तर्क होते हैं; {StaticResource} मार्कअप एक्सटेंशन में कुंजी के आधार पर संसाधन शब्दकोश से मान हथियाने के लिए तर्क सम्मिलित हैं। सौभाग्य से, हमारे लिए, WPF उपयोगकर्ताओं को अपने स्वयं के कस्टम मार्कअप एक्सटेंशन लिखने की अनुमति देता है। यह विशेषता अभी तक सिल्वरलाइट में मौजूद नहीं है, इसलिए इस ब्लॉग में समाधान केवल WPF पर लागू है।
दूसरों ने मार्कअप एक्सटेंशन का उपयोग करके दो शैलियों को मर्ज करने के लिए महान समाधान लिखे हैं। हालांकि, मैं एक ऐसा समाधान चाहता था जो असीमित संख्या में शैलियों को मर्ज करने की क्षमता प्रदान करता हो, जो थोड़ा सा पेचीदा है।
मार्कअप एक्सटेंशन लिखना सीधा है। पहला चरण एक ऐसी क्लास बनाना है जो मार्कअपएक्स्टेंशन से निकलती है, और यह इंगित करने के लिए मार्कअपएक्स्टेंशन रीटर्नटाइप विशेषता का उपयोग करें कि आप अपने मार्कअप एक्सटेंशन से लौटाए गए मान को टाइप स्टाइल का होना चाहते हैं।
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
मार्कअप एक्सटेंशन में इनपुट निर्दिष्ट करना
हम अपने मार्कअप एक्सटेंशन के उपयोगकर्ताओं को मर्ज करने के लिए शैलियों को निर्दिष्ट करने का एक सरल तरीका देना चाहते हैं। अनिवार्य रूप से दो तरीके हैं जिसमें उपयोगकर्ता एक मार्कअप एक्सटेंशन के लिए इनपुट निर्दिष्ट कर सकता है। उपयोगकर्ता गुण सेट कर सकता है या निर्माणकर्ता को पैरामीटर पास कर सकता है। चूंकि इस परिदृश्य में उपयोगकर्ता को असीमित संख्या में शैलियों को निर्दिष्ट करने की क्षमता की आवश्यकता होती है, मेरा पहला दृष्टिकोण एक ऐसा निर्माता बनाना था जो "परमेस" कीवर्ड का उपयोग करके किसी भी संख्या में तार लेता है:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
मेरा लक्ष्य निम्नानुसार इनपुट लिखने में सक्षम होना था:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
कॉमा को अलग-अलग स्टाइल की को अलग करते हुए नोटिस करें। दुर्भाग्य से, कस्टम मार्कअप एक्सटेंशन असीमित पैरामीटरों का समर्थन नहीं करते हैं, इसलिए यह दृष्टिकोण एक संकलन त्रुटि का परिणाम देता है। यदि मुझे पहले से पता था कि मैं कितने शैलियों का विलय करना चाहता हूं, तो मैं एक एक्सएएमएल सिंटैक्स का उपयोग कर सकता था, जिसमें एक कंस्ट्रक्टर वांछित संख्या ले सकता है:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
वर्कअराउंड के रूप में, मैंने तय किया कि कंस्ट्रक्टर पैरामीटर को एक सिंगल स्ट्रिंग लें जो स्पेस द्वारा अलग किए गए स्टाइल नामों को निर्दिष्ट करता है। वाक्य-विन्यास बहुत बुरा नहीं है:
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.");
}
}
मार्कअप विस्तार के उत्पादन में गिना जा रहा है
एक मार्कअप एक्सटेंशन के आउटपुट की गणना करने के लिए, हमें "ProvValue" नामक मार्कअपटेक्स्टेंशन से एक विधि को ओवरराइड करना होगा। इस पद्धति से लौटाया गया मान मार्कअप एक्सटेंशन के लक्ष्य में सेट किया जाएगा।
मैंने स्टाइल के लिए एक विस्तार विधि बनाकर शुरुआत की जो दो शैलियों को मर्ज करना जानता है। इस विधि का कोड काफी सरल है:
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];
}
}
ऊपर दिए गए तर्क के साथ, पहली शैली को दूसरी से सभी जानकारी शामिल करने के लिए संशोधित किया गया है। यदि विरोध होता है (जैसे दोनों शैलियों में एक ही संपत्ति के लिए एक सेटर है), दूसरी शैली जीतती है। ध्यान दें कि शैलियों और ट्रिगर्स को कॉपी करने से अलग, मैंने टारगेटप्राइप और बेस्डऑन मूल्यों के साथ-साथ किसी भी संसाधन को दूसरी शैली में भी ध्यान में रखा। मर्ज किए गए शैली के टारगेट टाइप के लिए, मैंने जो भी प्रकार का उपयोग किया है वह अधिक व्युत्पन्न है। यदि दूसरी शैली में एक आधार शैली है, तो मैं शैलियों की अपनी पदानुक्रम को पुन: विलय कर देता हूं। यदि इसके पास संसाधन हैं, तो मैं उन्हें पहली शैली में कॉपी करता हूं। यदि उन संसाधनों को {StaticResource} का उपयोग करने के लिए संदर्भित किया जाता है, तो वे इस मर्ज कोड को निष्पादित करने से पहले सांख्यिकीय रूप से हल हो जाते हैं, और इसलिए उन्हें स्थानांतरित करना आवश्यक नहीं है। मैंने इस कोड को उस स्थिति में जोड़ा है जब हम डायनामिक स्रोत का उपयोग कर रहे हैं।
ऊपर दिखाए गए विस्तार विधि निम्न सिंटैक्स सक्षम बनाता है:
style1.Merge(style2);
यह सिंटैक्स उपयोगी है बशर्ते कि मेरे पास ProvValue के भीतर दोनों शैलियों के उदाहरण हों। खैर, मैं नहीं। कंस्ट्रक्टर से मिलने वाली सभी स्टाइल्स की एक सूची है। अगर कोई निर्माता मानकों में पैरामीटर के लिए समर्थन था, मैं वास्तविक शैली उदाहरणों प्राप्त करने के लिए निम्नलिखित वाक्य रचना के लिए इस्तेमाल किया जा सकता था:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
लेकिन वह काम नहीं करता है। और यहां तक कि अगर परमेस लिमिटेशन मौजूद नहीं था, तो हम शायद मार्कअप एक्सटेंशन की एक और सीमा को मार देंगे, जहां हमें स्थैतिक संसाधनों को निर्दिष्ट करने के लिए विशेषता सिंटैक्स के बजाय गुण-तत्व सिंटैक्स का उपयोग करना होगा, जो क्रिया और बोझिल है (मैं इसे समझाता हूं) पिछले ब्लॉग पोस्ट में बग बेहतर )। और अगर उन दोनों सीमाओं का अस्तित्व नहीं था, तब भी मैं सिर्फ उनके नाम का उपयोग करते हुए शैलियों की सूची लिखूंगा - यह हर एक के लिए एक स्टेटिक स्रोत से पढ़ने के लिए कम और सरल है।
समाधान कोड का उपयोग करके एक StaticResourceExtension बनाने के लिए है। टाइप स्ट्रिंग और एक सेवा प्रदाता की शैली कुंजी को देखते हुए, मैं वास्तविक शैली के उदाहरण को पुनः प्राप्त करने के लिए StaticResourceExtension का उपयोग कर सकता हूं। यहाँ वाक्य रचना है:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
अब हमारे पास ProvValue विधि लिखने के लिए आवश्यक सभी टुकड़े हैं:
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 मार्कअप एक्सटेंशन के उपयोग का एक पूरा उदाहरण है:
<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" />