معرفی کنترل های Lookless در WPF

سه شنبه 6 بهمن 1394

بسیاری از مهندسین نرم افزار و برنامه نویسان به تازگی به تکنولوژی WPF روی آورده اند. ساده ترین و شهودی ترین راه برای ایجاد یک کنترل با قابلیت استفاده مجدد در WPF، توسط VS2015 برای UserControl فراهم شده است. در این مقاله نشان می دهیم، که کنترل های Lookless انتخاب بهتری نسبت به UserControl ها هستند.

معرفی کنترل های Lookless در WPF

کنترل های Lookless  انعطاف پذیری قابل توجهی را فراهم کرده اند. در این مقاله نشان می دهیم که چگونه از این کنترل ها به صورت مجدد استفاده کنیم.

در بیشتر مقالات WPF به کنترل های Lookless کنترل های سفارشی می گویند. اما ما در این مقاله نمی خواهیم از این اسم استفاده کنیم به دو دلیل:

1. برخی خوانندگان ممکن است با این نام به اشتباه بیفتند و فکر کنند که User Control ها هم کنترل های سفارشی هستند زیرا آنها را نیز می توان به صورت سفارشی ساخت.

2. Visual Studio یک قالب برای کنترل های سفارشی دارد که تقریبا بی فایده و گیج کننده است. بنابراین ما نمی خواهیم با استفاده از آنها خوانندگان را گیج کنیم.

این مقاله برای افرادی است که تازه، کار با WPF را شروع کرده اند و می خواهند در مورد  بهبود شیوه های برنامه نویسی بیشتر بدانند .

 موضوعاتی که در این مقاله مطرح شده اند به صورت زیر هستند:

مثالی از یک User Control

اشکالات استفاده از User Control ها

مثالی برای کنترل Lookless

استفاده از دو قالب(Template) مختلف برای کنترل های Lookless مشابه

ضمیمه 1: ایجاد یک UserControl در Visul Studio

ضمیمه 2: نصب یک propdp Snippet سفارشی

ضمیمه 3: ایجاد یک فایل منبع WPF

 

مثالی از یک UserControl

در WPF ( و دیگر فریم ورک ها) کنترل ها به منظور کپسوله سازی برخی بخش ها با قابلیت استفاده مجدد استفاده می شود.

اجازه دهید از یک مثال برای ایجاد و استفاده از WPF UserControl استفاده کنیم. کد مرتبط با این مثال در نمونه برنامه UserControlSample موجود است.

این پروژه را اجرا کنید و میبینید که پنجره زیر نمایش داده می شود:

 

دو سطر در این پنجره وجود دارد که یکی برای وارد کردن نام و دیگری برای وارد کردن passcode استفاده می شود.

هرکدام از ردیف ها شامل یک برچسب (یک متن غیر قابل ویرایش)، یک متن قابل ویرایش به صورت text box و یک دکمه X می باشد، با کلیک کردن بر روی دکمه متن موجود در text box پاک می شود.

دو سطری که برای وارد کردن نام و passcode قرار داده شده است، توسط یک نمونه ساده از همان کلاس EditableTextAndLabelUserControl ارائه شده است.

در زیر کد MainWindow.xaml وجود دارد که این پنجره را ایجاد کرده است:

<Window x:Class="UserControlSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:UserControlSample"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- user control for entering the name -->
        <local:EditableTextAndLabelUserControl Label="Enter your name:" 
                                               HorizontalAlignment="Left"
                                               VerticalAlignment="Center"
                                               Margin="10,0,0,0"/>
        
        <!-- user control for entering the passcode -->
        <local:EditableTextAndLabelUserControl Label="Enter your passcode:"
                                               HorizontalAlignment="Left"
                                               VerticalAlignment="Center" 
                                               Margin="10,0,0,0"
                                               Grid.Row="1"/>
    </Grid>
</Window>

 

این کد برای EditableTextAndLabelUserControl می باشد و همچنین بخشی از همان پروژه موجود در فایل EditableTextAndLabelUserControl.xaml و فایل EditableTextAndLabelUserControl.xaml.cs می باشد.( برای اطلاع از نحوه ایجاد یک UserControl در Visual Studio ضمیمه 1 را مطالعه کنید).

فایل EditableTextAndLabelUserControl.xaml شامل کد XAML به صورت زیر می باشد:

<UserControl x:Class="UserControlSample.EditableTextAndLabelUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:UserControlSample">
    <StackPanel Orientation="Horizontal"
                VerticalAlignment="Stretch">
        <TextBlock x:Name="TheLabel"
                   Margin="0,0,2,0"/>
        <TextBox x:Name="TheTextBox"
                 Width="100"
                 Margin="2,0"/>
        <Button x:Name="TheClearButton"
                Content="X"
                Width="30"/>
    </StackPanel>
</UserControl>

این برچسب توسط TextBlock نمایش داده می شودکه نام آن The Lable می باشد، متن قابل ویرایش توسط یک TextBox نمایش داده می شود که نام آن TheTextBox می باشد و دکمه ای با کنترل Button نمایش داده می شود که نام آن  ClearButton می باشد.

موتور تجزیه XAML در WPF متغیرهایی مرتبط با نام های کنترل ها ایجاد می کند. این متغیرها از طریق code behind (که در فایل EditableTextAndLabelUserControl.xaml.cs موجود است) در دسترس می باشد.

این کد هم بسیار ساده است:

public partial class EditableTextAndLabelUserControl : UserControl
{
    public EditableTextAndLabelUserControl()
    {
        InitializeComponent();

        TheClearButton.Click += TheClearButton_Click;
    }

    private void TheClearButton_Click(object sender, RoutedEventArgs e)
    {
        Clear();
    }

    public void Clear()
    {
        EditableText = null;
    }

    public string Label
    {
        get
        {
            return TheLabel.Text;
        }

        set
        {
            TheLabel.Text = value;
        }
    }

    public string EditableText
    {
        get
        {
            return TheTextBox.Text;
        }

        set
        {
            TheTextBox.Text = value;
        }
    }
}

 

این کد یک Property رشته ای Public  به صورت Label و EditableText فراهم می کند که به عنوان ابزاری برای برقراری ارتباط با بقیه نرم افزار می باشد و یک متد ( ) Clear که توسط کلیک بر روی دکمه X فراخوانی می شود.( این فراخوانی از طریق رویداد handler انجام می شود).

 

معایب استفاده از UserControl ها 

UserControl ها می توانند ساده ولی قدرتمند باشند. اما آنها یک نقص ذاتی دارند نمایش ظاهری آنها به طور دائمی به کد گره خورده است. نمایش ظاهری از کنترل EditableTextAndLabelUserControl که در بالا بحث شد نمی تواند تغییر کند. این ها همیشه یک TextBox، TextBlock، و یک Button باقی می مانند که به صورت افقی مرتب شده اند.

برای اینکه عادلانه باشد برخی مزایای UserControl ها که می تواند توسط پارامتر دهی بدست آید را به روش مشابه در ادامه کنترل های Lookless مورد بررسی قرار می دهیم. اما مشکلی که همیشه وجود دارد این است که هسته نمایشی آنها ثابت بوده و همیشه با C# code behind آن گره خورده است.

کنترل های Lookless به طو کامل به این محدودیت ها غلبه می کنند. آنها معمولا شامل تعدادی متدهای غیر نمایشی هستند و هیچ نوع محدودیتی را برای پیاده سازی نمایش آن تحمیل نمی کند. پیاده سازی نمایشی برای کنترل های Lookless توسط XAML ایجاد می شد و استایل ها به راحتی می توانند بدون هر نوع تغییری در کد کنترل تغییر کند و یا کنترل Lookless با بقیه اپلیکیشن در تعامل است.

مثالی برای کنترل های Lookless

کد مربوط به مثال کنترل های Lookless در پروژه LooklessControlSample قرار دارد. زمانی که آن را اجرا کنید پنجره ای شبیه به مثال UserControl نمایش داده می شود.

نگاهی به EditableTextAndLabelControl بیندازید که در فایل EditableTextAndLabelControl.cs قرار دارد.(توجه کنید که این یک فایل C# ساده است و هیچ نوع قالب VS ای برای ایجاد آن لازم نیست).

این کلاس از کلاس System.Windows.Controls.Control راه اندازی شده و شامل دو خصوصیت وابستگی رشته ای Lable و EditableText در متد Clear می باشد( که به سادگی خصوصیت EditableText را به null تنظیم می کند.)

public class EditableTextAndLabelControl : Control
{
    #region Label Dependency Property
    public string Label
    {
        get { return (string)GetValue(LabelProperty); }
        set { SetValue(LabelProperty, value); }
    }

    public static readonly DependencyProperty LabelProperty =
    DependencyProperty.Register
    (
        "Label",
        typeof(string),
        typeof(EditableTextAndLabelControl),
        new PropertyMetadata(null)
    );
    #endregion Label Dependency Property


    #region EditableText Dependency Property
    public string EditableText
    {
        get { return (string)GetValue(EditableTextProperty); }
        set { SetValue(EditableTextProperty, value); }
    }

    public static readonly DependencyProperty EditableTextProperty =
    DependencyProperty.Register
    (
        "EditableText",
        typeof(string),
        typeof(EditableTextAndLabelControl),
        new PropertyMetadata(null)
    );
    #endregion EditableText Dependency Property


    public void Clear()
    {
        this.EditableText = null;
    }
} 

 

خواص وابستگی کد ممکن است شبیه به یک ظرف غذای وحشتناک باشد که کسی از آنها استفاده نمی کند اما آنها به راحتی می توانند با قطعه کد propdp ایجاد شوند که در Visual Studio موجود است. و با نوشتن propdp و فراهم کردن یک نام مقدار و نوع پیش فرض مشخص می شود.

شما به منظور تغییر بین بخش  های خصوصیت وابستگی به tab مورد نظر رفته و enter را بعد از این که انجام دادید فشار دهید.

قطعه کد propdp (در فولدر CODE/Snippets قرار دارد) یک نسخه بهبود یافته برای آن قسمتی است که همراه Visual Studio  می آید. این کار کد را بیشتر به صورت عمودی منظم کرده و نام کلاس container را به صورت خودکار ایجاد می کند و یک ناحیه برای هر خصوصیت وابستگی(dependancy Property) ایجاد می کند.

این snippet می تواند در فولدر NetFX30 شما کپی شود تا با کد propdp پیش فرض جایگزین شود. برای راهنمایی بیشتر روی نصب snippet ضمیمه 2 مورد مطالعه قرار دهید.

زمانی که شما یک خصوصیت وابستگی تعریف می کنید اگر این یک ویژگی نرمال با تعدای ویژگی های اضافی باشد می توانید از آن در dependancy Property مشخص  استفاده کنید.

1. تغییرات در اتصال به WPF را اعلام می کند و می تواند به عنوان property منبع اتصال WPF باشد.

2. می توانیم از WPFدر  binding target property استفاده کنیم( خصوصیت های معمولی برای آن نمی تواند استفاده شود).

3. می توان استایل های WPF را تغییر داد.

4. می توان آن ها را متحرک ساخت.

 

در واقع این جالب است که خصوصیت های وابستگی می توانند سرور را به عنوان منبع و هدف اتصال WPF قرار دهد. این برای کنترل بسیار مهم است و به همین دلیل قادر به ارتباط با اپلیکیشن می باشد.

به جهت سهولت کار ما ControlTemplate را برای Lookless Control خود در فایل MainWindow.xaml قرار داده ایم. این در بخش Window.Resources می باشد:

<ControlTemplate x:Key="PlainHorizontalEditableTextTemplate"
                 TargetType="local:EditableTextAndLabelControl">
    <StackPanel Orientation="Horizontal"
                VerticalAlignment="Stretch">
        <!-- bound to the Label dependency property of the control -->
        <TextBlock x:Name="TheLabel"
                   Text="{Binding Path=Label,
                                  RelativeSource={RelativeSource TemplatedParent}}"
                   Margin="0,0,2,0" />
        <!-- bound to the EditableText dependency property of the control -->
        <TextBox x:Name="TheTextBox"
                 Text="{Binding Path=EditableText,
                                Mode=TwoWay,
                                RelativeSource={RelativeSource TemplatedParent}}"
                 Width="100"
                 Margin="2,0" />

        <!-- We use Expression Blend's SDK's EventTrigger and CallMethodAction
             objects to wire Clear() method of the control 
             to the Click event of the button -->
        <Button x:Name="TheClearButton"
                Content="X"
                Width="30">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <ei:CallMethodAction MethodName="Clear"
                                         TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </StackPanel>
</ControlTemplate>

 

توجه داشته باشید که در حال استفاده از مجموعه RelativeSource برای حالت RelativeSource می باشیم، برای مثال:

<TextBox x:Name="TheTextBox"
     Text="{Binding Path=EditableText,
                    Mode=TwoWay,
                    RelativeSource={RelativeSource TemplatedParent}}"  
.../>

 

RelativeSource TemplatedParent دستور اتصال به جستجو برای خصوصیت منبع روی این کنترل می باشد که قالب این کنترل XAML متعلق به آن می باشد . در پروژه ما نام آن EditableTextAndLabelControl می باشد.

این یک مخفف برای اتصال TemplatedParent می باشد – به جای نوشتن تمام این binding ها برای XAMl در بالا، می توانیم آنها را به سادگی زیر بنویسیم :

<TextBox x:Name="TheTextBox"
     Text="{TemplateBinding EditableText}"  
.../> 

باز هم تکرار می کنیم که binding  در بالا کار می کند فقط به خاطر اینکه ما  یک Template برای کنترل را برنامه نویسی می کنیم. یک راه عمومی تر می تواند پیدا کردن شیئ منبع از binding با استفاده از نوع خصوصیت AncestorType از RelativeSource به جای TemplatedParent می باشد:

<TextBox x:Name="TheTextBox"
     Text="{Binding Path=EditableText,
                    Mode=TwoWay,
                    RelativeSource={RelativeSource AncestorType=local:EditableTextAndLabelControl}}"  
.../>

 

کد بالا عمومی تر است زیرا در هر جایی تحت درخت نمایشی از EditableTextAndLabelControl ما کار می کند، و فقط برای قالب های فوری نیست. رویداد کلیک دکمه با متد ( ) Clear مرتبط شده و این از EditableTextAndLabelControl  با قابلیت Expression Blend SDK ایجاد می شود:

<Button x:Name="TheClearButton"
        Content="X"
        Width="30">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <ei:CallMethodAction MethodName="Clear"
                                 TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

 

شما لازم نیست MS Expression Blend را به منظور داشتن این قابلیت نصب کنید. بلکه باید آن را از طریق رفرنس دادن دو dll که در پوشه CODE/ExpressionBlenSDK قرار دارد انجام دهید.

همچنین باید رفرنس ها را با دو فضای نام در هدر فایل XAMl خود تنظیم کنید:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 

همانطور که در بالا نشان داده شده است این قابلیت به شما اجازه می دهد تا هرنوع رویداد مسیریابی شده از هر کنترل را مدیریت کند و هر نوع متد را بر روی شیئ قابل اتصال در رویداد راه اندازی فراخوانی کند.

یک روش رایج تر، نوشتن یک رویداد برای قابلیت C# با استفاده از دستورات WPF می باشد. با این حال دستورات انعطاف کمتری دارند و به دلایل زیر دست و پا گیر هستند:

-دستورات فقط می توانند با رویداد کلیک راه اندازی شوند که برای کنترل های کمی تعریف شده است برای مثال دکمه ها و منو ها در حالی که قابلیت Expression Blend SDK داشته باشند می توانند برای هر رویداد مسیریابی شده روی هر کنترلی استفاده شوند.

-دستورات معمولا نیاز دارند که به کد C# اضافه شوند در حالی که قابلیت Expression Blend SDK  می تواند هر نوع متدی را کد C# به عنوان یک متد طولانی فراخوانی کرده که آرگومان ندارد و یا اگر آرگومان های آنها مشابه است آنها از رویدادهای مسیریابی شده می باشند.

نکته: Expression Blend SDK functionality با تعدادی از بسته های(Package) محبوب دیگر می آید برای مثال اگر شما از Prism استفاده می کنید باید از قبل DLL های Expression Blend SDK را داشته باشید.

کد MainWindow بسیار ساده است و بسیار شبیه به مثال قبلی می باشد:

<!-- user control for entering the name -->
<local:EditableTextAndLabelControl Label="Enter your name:"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Center"
                                   Margin="10,0,0,0"
                                   Template="{StaticResource PlainHorizontalEditableTextTemplate}"/>

<!-- user control for entering the passcode -->
<local:EditableTextAndLabelControl Label="Enter your passcode:"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Center"
                                   Margin="10,0,0,0"
                                   Grid.Row="1"
                                   Template="{StaticResource PlainHorizontalEditableTextTemplate}"/>

 

توجه داشته باشید که ارجاع به ControlTemplate از طریق StaticResource  به صورت زیر است:

 

Template="{StaticResource PlainHorizontalEditableTextTemplate}" 

 

توجه داشته باشید که کد XAML برای هر دو کنترل ها بسیار مشابه است: ما از HorizontalAlignment، VerticalAlignment، Margin و خصوصیت Propertyها برای هردو آنها استفاده می شود. به منظور استفاده مجدد از کد XAML می توانیم از استایل WPF استفاده کنیم که از آن Property ها برای تنظیم استایل های wpf استفاده می کنیم:

<Style x:Key="TheEditableTextAndLabelControlStyle"
       TargetType="local:EditableTextAndLabelControl">
    <Setter Property="HorizontalAlignment"
            Value="Left"/>
    <Setter Property="VerticalAlignment"
            Value="Center"/>
    <Setter Property="Margin"
            Value="10,0,0,0"/>
    <Setter Property="Template"
            Value="{StaticResource PlainHorizontalEditableTextTemplate}"/>
</Style> 

این استایل باید در بخش Windows.Resources  از فایل MainWindow.xaml تحت تعریف Template قرار داده شود( زیرا استایل به الگو یا Template اشاره می کند.)

سپس می توانیم خصوصیت های تعریف شده در فرم استایل کنترل ها را  به جای ایجاد کنترل هایی که به استایل اشاره می کند پاک کنیم:

<!-- user control for entering the name -->
<local:EditableTextAndLabelControl Label="Enter your name:"
                                   Template="{StaticResource PlainHorizontalEditableTextTemplate}"
                                   Style="{StaticResource TheEditableTextAndLabelControlStyle}"/>

<!-- user control for entering the passcode -->
<local:EditableTextAndLabelControl Label="Enter your passcode:"
                                   Grid.Row="1"
                                   Style="{StaticResource TheEditableTextAndLabelControlStyle}" /> 

 

برای ساده تر شدن XAML می توانیم استایلی را به عنوان استایل پیش فرض برای تمام EditableTextAndLabelControl  ایجاد کنیم و دیگر خصوصیت  x:Key را روی آن تنظیم نکنیم. در آن صورت ما حتی می توانیم رفرنس هایی برای چنین استایل هایی از کنترل ها قرار دهیم:

 

<!-- user control for entering the name -->
<local:EditableTextAndLabelControl Label="Enter your name:"
                                   Template="{StaticResource PlainHorizontalEditableTextTemplate}"/>

<!-- user control for entering the passcode -->
<local:EditableTextAndLabelControl Label="Enter your passcode:"
                                   Grid.Row="1"> 

 

در نهایت بهترین کار قرار دادن استایل ها و قالب ها در یک فایل جدا با نام ResourceDictionary  میباشد، که معمولا در یک فایل جدا قرار دارد، بنابراین آنها می توانند از مکان های مختلف در دسترس باشند. در پروژه ها یک فولدر به نام Styles داریم که شامل ResourceDictionarys  برای کنترل های مختلف می باشد.

 

استفاده از دو Tempalate مختلف برای کنترل های Lookless  مشابه  

در مثال بعدی به شما نشان می دهیم که چگونه دو Template کاملا متفاوت برای کنترل های Lookless مشابه ایجاد کنیم. نمونه برنامه این مثال در پروژه ی TwoDifferentTemplatesForTheSameLooklessControlSample قرار دارد.

در اینجا خروجی پروژه به صورت زیر می باشد:

هر دو در سمت راست و چپ دو کنترل EditableTextAndLabelControl مشابه نشان داده ایم اما در سمت چپ از استایل PlainHorizontalEditableTextStyle و در سمت راست از استایل SomeCrazyEditableTextStyle استفاده کرده ایم:

<Grid>
    ...
    <local:EditableTextAndLabelControl Label="Enter your name:"
                                       Style="{StaticResource PlainHorizontalEditableTextStyle}"
                                       Grid.Row="1"/>

    ...

    <local:EditableTextAndLabelControl Label="Enter your name:"
                                       Style="{StaticResource SomeCrazyEditableTextStyle}"
                                       Grid.Column="2"
                                       Grid.Row="1"/>
</Grid>  

هر دو استایل در LooklessControlStyles.xaml Resource Dictionary  تعریف شده است که در فایل Styles قرار دارد :

 

به منظور ایجاد یک Resource Dictionary لطفا  مراحل مشخص شده در ضمیمه 3 را  دنبال کنید.

LooklessControlStyles.xaml Resource Dictionary دو الگو را تعریف می کند:

PlainHorizontalEditableTextTemplate (شبیه به مثال قبلی می باشد) و SomeCrazyEditableTextTemplate ( که مورد جدیدی است).

این نیز خود دو استایل دارد:

PlainHorizontalEditableTextStyle و SomeCrazyEditableTextStyle که این دو Template را متعاقبا استفاده می کنند.

ما بیشتر می خواهیم که از استایل Crazy استفاده کنیم:

<Style TargetType="local:EditableTextAndLabelControl"
       x:Key="SomeCrazyEditableTextStyle">
    <Setter Property="HorizontalAlignment"
            Value="Left" />
    <Setter Property="VerticalAlignment"
            Value="Center" />
    <Setter Property="Margin"
            Value="10,0,0,0" />
    <Setter Property="Template"
            Value="{StaticResource SomeCrazyEditableTextTemplate}" />
</Style>

 

و در اینجا الگوی Crazy را قرار داده ایم:

<ControlTemplate x:Key="SomeCrazyEditableTextTemplate"
                 TargetType="local:EditableTextAndLabelControl">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <!-- bound to the Label dependency property of the control -->
        <Label x:Name="TheLabel"
               Content="{Binding Path=Label,
                                    RelativeSource={RelativeSource TemplatedParent}}"
               Margin="0,0,2,0"
               Background="Red"
               Grid.Row="1"
               RenderTransformOrigin="0.5,0.5">
            <Label.RenderTransform>
                <RotateTransform Angle="-45" />
            </Label.RenderTransform>
        </Label>
        <!-- bound to the EditableText dependency property of the control -->
        <TextBox x:Name="TheTextBox"
                 Text="{Binding Path=EditableText,
                                Mode=TwoWay,
                                RelativeSource={RelativeSource TemplatedParent}}"
                 Width="100"
                 Margin="2,0"
                 Grid.Column="1" />

        <!-- We use Expression Blend's SDK's EventTrigger and CallMethodAction
                 objects to wire Clear() method of the control 
                 to the Click event of the button -->
        <ToggleButton x:Name="TheClearButton"
                      Content="X"
                      Width="30"
                      Grid.Column="2"
                      Grid.Row="1">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <ei:CallMethodAction MethodName="Clear"
                                         TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ToggleButton>
    </Grid>
</ControlTemplate> 

توجه داشته باشید که نه تنها مرتب سازی کنترل ها به صورت متفاوت است بلکه از کنترل های متفاوتی هم استفاده می کنیم، به عنوان مثال به جای TextBlock ما از کنترل Label و به جای Button از ToggleButton استفاده کرده ایم.

همچنین توجه داشته باشید که به منظور ایجاد استایل ها و قالب های تعریف شده در MainWindow.xaml، کد زیر را به بخش Window.Resources اضافه می کنیم:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Styles/LooklessControlStyles.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

اغلب Resource Dictionary ها ارجاع داده شده در یک پروژه متفاوت است. در آن صورت خصوصیت Source از منبع  ادغام شده dictionary باید به طور متفاوت تنظیم شوند به عنوان مثال فرض کنید که ما به Styles/LooklessControlStyles.xaml در یک پروژه متفاوت با نام Controls اشاره می کنیم در اینجا چگونگی مرتبط کردن این خطوط را نشان می دهیم:

<ResourceDictionary Source="/Controls;Component/Styles/LooklessControlStyles.xaml"/> 

توجه داشته باشید که همچنین ما یک پیشوند Component به مسیر مربوطه در پروژه اضافه می کنیم.

 

پارامتر دهی کنترل های Lookless

ما می توانیم قابلیت استفاده مجدد Xaml را با ایجاد خصوصیت های وابستگی از کنترل Lookless  بهبود ببخشیم.

اگر شما پروژه ParametrizingLooklessControl را اجرا کنید و متنی در TextBox بنویسد می بینید که برچسب متن آبی می شود در حالی که متن قابل ویرایش قرمز است:

 

در اینجا محتوای مربوط به فایل MainWindow.xaml وجود دارد:

 

<local:EditableTextAndLabelControl Label="Enter your name:"
                                   Foreground="Blue"
                                   EditableTextForeground="Red"
                                   Style="{StaticResource PlainHorizontalEditableTextStyle}"/> 

 

شما می توانید مشاهده کنید که خصوصیت Foreground از کنترل آن به Blue تنظیم شده است در حالی که خصوصیت EditableTextForeground به Red تنظیم شده است.

هر کنترل WPF دارای خاصیت Foreground است، بنابراین از آنجاییکه کلاس EditableTextAndLabelControl از Control راه اندازی می شود پس خصوصیت Foreground را به صورت خودکار می گیرد.

خصوصیت وابستگی EditableTextForeground وجود داشت اما با این وجود به کلاس EditableTextAndLabelControl اضافه شده است:

#region EditableTextForeground Dependency Property
public Brush EditableTextForeground
{
    get { return (Brush)GetValue(EditableTextForegroundProperty); }
    set { SetValue(EditableTextForegroundProperty, value); }
}

public static readonly DependencyProperty EditableTextForegroundProperty =
DependencyProperty.Register
(
    "EditableTextForeground",
    typeof(Brush),
    typeof(EditableTextAndLabelControl),
    new PropertyMetadata(null)
);
#endregion EditableTextForeground Dependency Property  

 

این Template( تعریف شده در فایل LooklessControlStyles.xaml) به TextBlock های مربوطه متصل شده و خصوصیت های Foreground(پیش زمینه) TextBox به EditableTextForeground به روش زیر مرتبط می باشد:

<!-- bound to the Label dependency property of the control -->
<TextBlock x:Name="TheLabel"
           Foreground="{Binding Path=Foreground,
                                RelativeSource={RelativeSource TemplatedParent}}"
           Text="{Binding Path=Label,
                          RelativeSource={RelativeSource TemplatedParent}}"
           Margin="0,0,2,0" />
<!-- bound to the EditableText dependency property of the control -->
<TextBox x:Name="TheTextBox"
         Foreground="{Binding Path=EditableTextForeground,
                              RelativeSource={RelativeSource TemplatedParent}}"
         Text="{Binding Path=EditableText,
                            Mode=TwoWay,
                            RelativeSource={RelativeSource TemplatedParent}}"
         Width="100"
         Margin="2,0" />  

 

دسترسی نمایشی به کد C# برای کنترل های Lookless

گاهی اوقات (و نه اغلب) لازم است تا به بخش های تعریف شده در یک قالب کنترل از کد C# دسترسی داشته باشیم. تنها مزیت کوچکی که UserControl ها نسبت به کنترل های Lookless دارند این است که دسترسی به تصاویر تعریف شده در XAML برای UserControl ها از کد C# راحتتر است.

با این وجود یک راه نسبتا ساده برای دسترسی به کنترل های Lookless نیز وجود دارد.

پروژه AccessingVisualPartsFromCSharpSample نشان می دهد که این ویژگی چگونه انجام می شود.

اگر پروژه را اجرا کرده وبر روی Enter your name کلیک کنید می توانی مشاهده کنید یک پنجره modal dialog شامل متن label clicked به صورت popp up ظاهر می شود:

 

به منظور ادامه کار با main window باید اولین popup را ببندید.

نگاهی به Template کنترل در فایل LooklessControlStyle.xaml بیاندازید. تنها چیزی که در مقایسه با مثال قبل تغییر کرده است این است که TextBlock به PART_Label تغییر اسم داده است و یک پس زمینه شفاف دارد:

<TextBlock x:Name="PART_TheLabel"
           Background="Transparent"
           Text="{Binding Path=Label,
                          RelativeSource={RelativeSource TemplatedParent}}"
           Margin="0,0,2,0" />

 

پیشوند PART_ برای نام هایی که ممکن است از کد C# در دسترس قرار بگیرد یک عمل مفید و رایج است.

شفافیت برای background از TextBlock به منظور گرفتن بهتر رویداد mouse down روی آن است( در غیر این صورت این فقط زمانی که یک کلیک روی متن ایجاد شود انجام می گیرد).

کد C# مربوطه اضافه شده در فایل EditableTextAndLabelControl.cs قرار دارد:

public class EditableTextAndLabelControl : Control
{
    FrameworkElement _labelElement = null;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        // find _labelElement from the control template
        _labelElement = 
            this.Template.FindName("PART_TheLabel", this) as FrameworkElement;

        // attach event handler to MouseLeftButtonDown event.
        _labelElement.MouseLeftButtonDown += _labelElement_MouseLeftButtonDown;
    }

    private void _labelElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // create and show a modal window with text "label clicked!"
        Window dialogWindow = new Window() { Width = 150, Height = 100 };

        dialogWindow.Content =
            new TextBox { Text = "label clicked!" };
        dialogWindow.ShowDialog();
    }
...
}

ما متد ( ) OnApplyTemplateرا برای اطمینان از اینکه می توانیم بخش های تعریف شده در قالب کنترل را پیدا کنیم باز نویسی کرده ایم. سپس از متد (...) Template.FindName برای پیدا کردن کنترل استفاده می شود.

 

ضمیمه 1: ایجاد یک UserControl  در Visual Studio

به منظور ایجاد یک WPF UserControl در Visual Studio روی پروژه در Solution Explorer راست کلیک کرده و Add->New Item را انتخاب کنید سپس WPF را از قسمت سمت چپ انتخاب کرده و User Control (WPF) را از روی سمت راست انتخاب کنید و نام کنترل را وارد کنید:

ضمیمه 2: نصب یک propdp Snippet سفارشی

شما می توانید propdp snippet سفارشی را نصب کنید که به صورت زیر است(در پوشه CODE/Snippets)

 

Snippets Manager را با رفتن به منوی Tools در Visual Studio باز کرده و بر روی بخش Code Snippets Manager کلیک کنید:

در Snippets Manager زبان را به C# تغییر دهید (با استفاده از drop down در بالا) و بر روی فولدر NetFX30 کلیک کنید:

محل فولدر snippet را از Location field در Snippets Manager کپی کنید. این محل را در فایل propdp Paste کنید که در Explorer قرار دارد.

فایل propdp را با کد خود در محل snippet کپی کنید.

Visual Studio خود را Restart کنید.

 

ضمیمه 3: ایجاد یک فایل WPF Resource Dictionary

شما  می توانید یک فایل ResourceDictionary در Visual Studio با مراحل زیر ایجاد کنید :

بر روی  فولدر یا پروژه در VS Solution Explorer راست کلیک کنید که می خواهید در آن ResourceDictionary ایجاد شود.

پوشه WPF را در سمت چپ و Resource Dictionary (WPF) را در سمت راست انتخاب کنید:

نام مناسب برای فایل Resource Dictionary انتخاب کرده و دکمه Add را بزنید.

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • WPF
  • 2k بازدید
  • 1 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید