سؤال

في WPF، كيف يمكنني تطبيق أنماط متعددة على ملف FrameworkElement؟على سبيل المثال، لدي عنصر تحكم يحتوي بالفعل على نمط.لدي أيضًا نمط منفصل أود إضافته إليه دون إلغاء النمط الأول.تحتوي الأنماط على TargetTypes مختلفة، لذلك لا يمكنني فقط توسيع أحدهما مع الآخر.

هل كانت مفيدة؟

المحلول

أعتقد أن الإجابة البسيطة هي أنك لا تستطيع أن تفعل (على الأقل في هذا الإصدار من WPF) ما تحاول القيام به.

وهذا يعني أنه يمكن تطبيق نمط واحد فقط على أي عنصر معين.

ومع ذلك، كما ذكر آخرون أعلاه، ربما يمكنك استخدامها BasedOn لمساعدتك.تحقق من القطعة التالية من xaml السائبة.سترى فيه أن لدي نمطًا أساسيًا يقوم بتعيين خاصية موجودة في الفئة الأساسية للعنصر الذي أريد تطبيق نمطين عليه.وفي النمط الثاني الذي يعتمد على النمط الأساسي، قمت بتعيين خاصية أخرى.

إذن الفكرة هنا...هو أنه إذا كان بإمكانك فصل الخصائص التي تريد تعيينها بطريقة أو بأخرى ...وفقًا للتسلسل الهرمي لوراثة العنصر الذي تريد تعيين أنماط متعددة عليه ...قد يكون لديك حل بديل.

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


أتمنى أن يساعدك هذا.

ملحوظة:

شيء واحد على وجه الخصوص أن نلاحظ.إذا قمت بتغيير TargetType في النمط الثاني (في المجموعة الأولى من xaml أعلاه) ل ButtonBase, ، لا يتم تطبيق النمطين.ومع ذلك، قم بمراجعة xaml التالي أدناه للالتفاف على هذا القيد.في الأساس، هذا يعني أنك بحاجة إلى إعطاء النمط مفتاحًا والإشارة إليه بهذا المفتاح.

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

نصائح أخرى

كان لدى بيا ستولنيتز مشاركة مدونة جيدة حول استخدام ملحق العلامات لهذا، تحت عنوان "كيف يمكنني تعيين أنماط متعددة في WPF؟"

لقد توقفت هذه المدونة الآن، لذا أقوم بإعادة إنتاج هذا المنشور هنا


يوفر كل من WPF وSilverlight القدرة على استخلاص نمط من نمط آخر من خلال خاصية "BasedOn".تتيح هذه الميزة للمطورين تنظيم أنماطهم باستخدام تسلسل هرمي مشابه لميراث الفئة.خذ بعين الاعتبار الأنماط التالية:

<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 على Red وخاصية Margin الخاصة به على 10.

كانت هذه الميزة موجودة في WPF لفترة طويلة، وهي جديدة في Silverlight 3.

ماذا لو كنت تريد تعيين أكثر من نمط على عنصر ما؟لا يوفر WPF ولا Silverlight حلاً لهذه المشكلة خارج الصندوق.لحسن الحظ، هناك طرق لتنفيذ هذا السلوك في WPF، والتي سأناقشها في منشور المدونة هذا.

يستخدم WPF وSilverlight امتدادات العلامات لتزويد الخصائص بقيم تتطلب بعض المنطق للحصول عليها.يمكن التعرف بسهولة على امتدادات العلامات من خلال وجود أقواس متعرجة تحيط بها في XAML.على سبيل المثال، يحتوي ملحق الترميز {Binding} على منطق لجلب قيمة من مصدر بيانات وتحديثها عند حدوث تغييرات؛يحتوي ملحق الترميز {StaticResource} على منطق للحصول على قيمة من قاموس الموارد بناءً على المفتاح.لحسن الحظ بالنسبة لنا، يسمح WPF للمستخدمين بكتابة امتدادات الترميز المخصصة الخاصة بهم.هذه الميزة ليست موجودة بعد في Silverlight، وبالتالي فإن الحل الموجود في هذه المدونة ينطبق فقط على WPF.

آحرون لقد كتبوا حلولاً رائعة لدمج نمطين باستخدام ملحقات العلامات.ومع ذلك، أردت حلاً يوفر القدرة على دمج عدد غير محدود من الأنماط، وهو أمر أصعب قليلاً.

إن كتابة ملحق الترميز أمر بسيط.الخطوة الأولى هي إنشاء فئة مشتقة من MarkupExtension، واستخدام سمة MarkupExtensionReturnType للإشارة إلى أنك تنوي أن تكون القيمة التي يتم إرجاعها من ملحق الترميز الخاص بك من النوع Style.

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

تحديد المدخلات إلى ملحق العلامات

نود أن نمنح مستخدمي ملحق الترميز الخاص بنا طريقة بسيطة لتحديد الأنماط التي سيتم دمجها.هناك طريقتان أساسيتان يمكن للمستخدم من خلالهما تحديد المدخلات لامتداد الترميز.يمكن للمستخدم تعيين الخصائص أو تمرير المعلمات إلى المنشئ.نظرًا لأن المستخدم في هذا السيناريو يحتاج إلى القدرة على تحديد عدد غير محدود من الأنماط، كان النهج الأول الذي اتبعته هو إنشاء مُنشئ يأخذ أي عدد من السلاسل باستخدام الكلمة الأساسية "params":

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

كان هدفي أن أتمكن من كتابة المدخلات على النحو التالي:

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

لاحظ الفاصلة التي تفصل بين مفاتيح الأنماط المختلفة.لسوء الحظ، لا تدعم ملحقات العلامات المخصصة عددًا غير محدود من معلمات المُنشئ، لذلك يؤدي هذا الأسلوب إلى خطأ في الترجمة.إذا كنت أعرف مسبقًا عدد الأنماط التي أرغب في دمجها، كان بإمكاني استخدام نفس بناء جملة XAML مع مُنشئ يأخذ العدد المطلوب من السلاسل:

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.");
    }
}

حساب إخراج ملحق العلامات

لحساب مخرجات امتداد الترميز، نحتاج إلى تجاوز طريقة من MarkupExtension تسمى "ProvideValue".سيتم تعيين القيمة التي يتم إرجاعها من هذه الطريقة في هدف ملحق الترميز.

لقد بدأت بإنشاء طريقة ملحقة للنمط تعرف كيفية دمج نمطين.رمز هذه الطريقة بسيط للغاية:

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];
    }
}

مع المنطق أعلاه، يتم تعديل النمط الأول ليشمل جميع المعلومات من الثاني.إذا كانت هناك صراعات (على سبيل المثال.كلا النمطين لهما أداة ضبط لنفس الخاصية)، النمط الثاني يفوز.لاحظ أنه بصرف النظر عن نسخ الأنماط والمشغلات، فقد أخذت أيضًا في الاعتبار قيم TargetType وBasedOn بالإضافة إلى أي موارد قد يحتوي عليها النمط الثاني.بالنسبة إلى TargetType للنمط المدمج، استخدمت أي نوع أكثر اشتقاقًا.إذا كان النمط الثاني يحتوي على نمط BasedOn، فأنا أقوم بدمج التسلسل الهرمي للأنماط بشكل متكرر.إذا كان لديه موارد، فأنا أنسخها إلى النمط الأول.إذا تمت الإشارة إلى هذه الموارد باستخدام {StaticResource}، فسيتم حلها بشكل ثابت قبل تنفيذ رمز الدمج هذا، وبالتالي ليس من الضروري نقلها.لقد أضفت هذا الرمز في حالة استخدامنا DynamicResources.

تتيح طريقة الامتداد الموضحة أعلاه بناء الجملة التالي:

style1.Merge(style2);

يعد بناء الجملة هذا مفيدًا بشرط أن يكون لدي مثيلات لكلا النمطين داخل ProvideValue.حسنًا، لا أفعل ذلك.كل ما أحصل عليه من المُنشئ هو قائمة بمفاتيح السلسلة لتلك الأنماط.إذا كان هناك دعم للمعلمات في معلمات المنشئ، كان بإمكاني استخدام بناء الجملة التالي للحصول على مثيلات النمط الفعلية:

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

لكن هذا لا ينجح.وحتى لو لم تكن حدود المعلمات موجودة، فمن المحتمل أن نواجه قيدًا آخر في امتدادات العلامات، حيث سيتعين علينا استخدام بناء جملة عنصر الخاصية بدلاً من بناء جملة السمة لتحديد الموارد الثابتة، وهو أمر مطول ومرهق (أشرح هذا علة أفضل في مشاركة المدونة السابقة).وحتى لو لم يكن هذان القيدان موجودين، فأنا أفضل كتابة قائمة الأنماط باستخدام أسمائها فقط - فهي أقصر وأسهل في القراءة من StaticResource لكل منها.

الحل هو إنشاء StaticResourceExtension باستخدام الكود.بالنظر إلى مفتاح النمط من نوع السلسلة ومزود الخدمة، يمكنني استخدام StaticResourceExtension لاسترداد مثيل النمط الفعلي.هنا هو بناء الجملة:

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

الآن لدينا كل القطع اللازمة لكتابة طريقة ProvideValue:

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

enter image description here

ولكن يمكنك أن تمتد من آخر..ألق نظرة على خاصية BasedOn

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

لا يوفر WPF/XAML هذه الوظيفة بشكل أصلي، ولكنه يوفر إمكانية التوسعة للسماح لك بفعل ما تريد.

لقد واجهنا نفس الحاجة، وانتهى بنا الأمر إلى إنشاء امتداد XAML Markup Extension الخاص بنا (والذي أطلقنا عليه اسم "MergedStylesExtension") للسماح لنا بإنشاء نمط جديد من نمطين آخرين (والذي، إذا لزم الأمر، يمكن استخدامه عدة مرات في ملف واحد). صف ليرث من المزيد من الأنماط).

نظرًا لوجود خطأ في WPF/XAML، نحتاج إلى استخدام صيغة عنصر الخاصية لاستخدامها، ولكن بخلاف ذلك يبدو أنها تعمل بشكل جيد.على سبيل المثال،

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

لقد كتبت مؤخرا عن ذلك هنا:http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

هذا ممكن عن طريق إنشاء فئة مساعدة لاستخدام الأنماط الخاصة بك ولفها.تم ذكر النمط المركب هنا يوضح كيفية القيام بذلك.هناك عدة طرق، ولكن أسهلها هو القيام بما يلي:

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

امل ان يساعد.

يستخدم AttachedProperty لتعيين أنماط متعددة مثل الكود التالي:

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;
    }
}

الاستخدام:

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

نتيجة:

enter image description here

إذا كنت لا تلمس أي خصائص محددة، فيمكنك الحصول على جميع الخصائص الأساسية والمشتركة للنمط الذي سيكون النوع المستهدف هو FrameworkElement.بعد ذلك، يمكنك إنشاء نكهات محددة لكل أنواع الأهداف التي تحتاجها، دون الحاجة إلى نسخ كل تلك الخصائص الشائعة مرة أخرى.

ربما يمكنك الحصول على شيء مشابه في حالة تطبيق هذا على مجموعة من العناصر عن طريق استخدام StyleSelector، لقد استخدمت هذا للتعامل مع مشكلة مماثلة في استخدام أنماط مختلفة على TreeViewItems اعتمادًا على نوع الكائن المنضم في الشجرة.قد تضطر إلى تعديل الفصل أدناه قليلاً للتكيف مع منهجك الخاص ولكن نأمل أن يساعدك ذلك على البدء

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;
        }
    }
}

ثم تقوم بتطبيق هذا على هذا النحو

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

في بعض الأحيان يمكنك التعامل مع هذا من خلال لوحات التعشيش.لنفترض أن لديك نمطًا يغير المقدمة وآخر يغير حجم الخط، يمكنك تطبيق النمط الأخير على TextBlock، ووضعه في شبكة يكون نمطها هو الأول.قد يكون هذا مفيدًا وقد يكون الطريقة الأسهل في بعض الحالات، على الرغم من أنه لن يحل جميع المشكلات.

عند تجاوز SelectStyle يمكنك الحصول على خاصية GroupBy عبر الانعكاس كما هو موضح أدناه:

    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;
    }

إذا كنت تحاول تطبيق نمط فريد على عنصر واحد فقط كإضافة إلى النمط الأساسي، هناك طريقة مختلفة تمامًا للقيام بذلك وهي IMHO أفضل بكثير من حيث التعليمات البرمجية القابلة للقراءة والصيانة.

من الشائع للغاية الحاجة إلى تعديل المعلمات لكل عنصر على حدة.يعد تحديد أنماط القاموس للاستخدام على عنصر واحد فقط أمرًا مرهقًا للغاية للمحافظة عليه أو فهمه.لتجنب إنشاء أنماط فقط لتعديل العناصر لمرة واحدة، اقرأ إجابتي على سؤالي هنا هنا:

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top