WPF-Custom Controls Part 1

19 בMarch 2013

תגיות:
אין תגובות

כתיבת קונטרולים תמיד נחשבה לדבר מורכב שהעיסוק בו נשמר
ל”מומחים” או לפחות למפתחים מנוסים. למעשה ב
WPF העסק לא נורא בכלל, צריך
רק להבין כמה דברים בסיסיים ואפשר לרוץ..

נתחיל בזה שנדרש ידע בסיסי בWPF כי אם אנחנו לא יודעים מה
זה
Dependency Property או מה זה Routed Event, או מעולם לא כתבנו Resource
Dictionary
משלנו יהיה קשה להתחיל ישר מכתיבת
קונטרולים, למרות זאת אני אנסה במדריך מקוצר להוכיח שזה קל עד כדי מפתיע.

כדי להתחיל חשוב שנבין קודם כל את המבנה של עץ הירושה הבסיסי של WPF, (הוא לא שונה מכל עץ אחר ב.NET אולי קצת מסועף יותר ממה שהיה בWinForm למשל.)

בראש העץ יושב (נשמע כמו תחילה של שיר..) אובייקט אבסטרקטי שנקרא DispatcherObject

המשמעות שלו זה אובייקט שמנוהל אוטומטית מבחינת תהליכם שנגשים אליו,

מתחתיו אובייקט חשוב מאוד לתהליך שלנו שנקרא DependencyObject, החשיבות שלו עצומה כי רק בתוך DependencyObject אני יכול למקם DependencyProperty ובלי DependencyProperty WPF פשוט לא היה..נסתפק כרגע במידע ש DependencyProperties מספקים ממשק אוטומטי להודעות כאשר הערך
הפנימי משתנה וזה מאפשר
Binding  וכו’.

אני יכול
להשתמש ישירות ב
DependencyObjectאבל חבל על המאמץ. כדי להציג קונטרול
נדרש מהאובייקט לעבור התאמות ושינויים כדי שיוכל לצייר את עצמו וכו’, מה עוד
שהתשתית כבר כתובה כל מה שצריך זה למצוא את האובייקט האולטימטיבי לצרכי ירושה
שמכיל מה שאנחנו צריכים, אבל חסר בו מעט פרטים ואת זה נוסיף לבד. אותה צורת חשיבה שקיימת
בתכנות
.Object Oriented אז אנחנו נרד מהעץ הגבוה שטיפסנו עליו עד לשלב שבו נמצא אובייקט
מתאים.

האובייקט
הבא בהיררכיה הוא
Visual שגם הוא
אבסטרקטי, ואם נתבונן בו נראה כבר תחילת תשתית שמתאימה לציור אלמנטים לכרטיס מסך.
יש לו חלקים שעוסקים בגרפיקה, בטקסט, בגישה ל
Parent (הקונטרול או הפאנל שבתוכו הוא ממוקם), גישה
ל
Children (בהנחה שהוא עצמו Parent של משהוא) ועוד הרבה..

הבא בתור
(מלמעלה למטה) הוא
UIElement כאן נוספת לנו לדוגמא תמיכה בכל האינפוטים של המשתמש כמו
אירועי מקלדת ועכבר, כולל תשתית חשובה של
RaiseEvent שנרחיב עליה בהמשך.

מיד
אחריו  במהלך שמקרב אותנו ליעד הסופי נמצא
FrameworkElement
כאן אנחנו מוסיפים פרטים ראשוניים שמאפשרים לנו
להתייחס לאובייקט ויזואלי ולא לישות לוגית בלבד
. כאן נקבל התייחסות ל:
גובה רוחב וכו’ למעשה רוב השדות שאנו מאתחלים דרך ה
xaml מקורם ב
FrameworkElement הכי מוכר אולי הוא ה DataContext Object.

אנו מגיעים לסוף המסלול הארכיאולוגי עם
אובייקט שנקרא
Control,
זה מכיל את כל מה שהכילו האובייקטים בעץ עם הוספת התייחסות לצבעים ופונטים וכו’.

כאן כבר אפשר לרשת ישירות וליצור את הפקד
המיוחל שלנו.

לפני שנעשה זאת נתן מבט על הציור בראש
הפוסט שמציג שוב את עץ הירושה שמביא אותנו לקלאס
Control. אז סימני השאלה מייצגים
את אותו קונטרול עתידי שנרצה לייצר, בתור התחלה ננסה ליצור קונטרול
“טיפש” בלי כלום שמציג ריבוע צהוב לחלון.

·       
הערה: את דוגמאות הקוד אני מיישם ב VS2012 וזה לא שונה מ VS2010, אלא ברמת התפריטים.

בתפריט של VS מקש ימני על הפרויקט
נבחר
Add/new Item/custom control

למעשה כבר עכשיו כדי שנחליט על שם לקונטרול החדש, אני קראתי לו לצורך הדוגמא MyCustomControl.

קיבלנו את הקוד הבסיסי הבא, שכולל
קונסטרקטור סטטי שחייב להיות שם, ובתוכו רישום לסטייל בסיסי, אם נוריד את השורה
הזאת נקבל את המראה הדיפולטיבי של קונטרול, מאחר שעוד לא דיברנו על עיצוב מראה
לקונטרול החדש נדבר על שורה זאת בהמשך.

  public class MyCustomControl1 : Control

    {

        static MyCustomControl1()

        {

           
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyCustomControl1), new FrameworkPropertyMetadata(typeof(MyCustomControl1)));

        }

                                                                                                                                    }                                            

                                                                           

לאחר שהגדרנו את הקונטרול ניתן בתוכו לעשות
override
לכל הפונקציות שמגיעות מהמוריש, כמו גם להוסיף שדות וכו’. כל ערך שאנחנו מוסיפים
לקונטרול חדש ונרצה שיקבל גישה מבחוץ ויתמוך ב
Data Binding יהיה כמובן Dependency Property. אז איך
מגדירים אחד כזה? ברוב המקרים אם נקיש
propdp ואז פעמיים על מקשTAB נקבל את הסקריפט הבא:

  public int MyProperty

        {

            get { return (int)GetValue(MyPropertyProperty); }

            set { SetValue(MyPropertyProperty, value); }

        }

 

        // Using
a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding,
etc…

        public static readonly DependencyProperty MyPropertyProperty =

            DependencyProperty.Register(“MyProperty”, typeof(int), typeof(ownerclass), new PropertyMetadata(0));

 מה זה אומר? אני לא נכנס ל
Dependency Property  עכשיו, ממש לא.. מה שכן
צריך להחליט מה עושה הפרופרטי הזה, נניח שזה אכן
int שצייצג מספר שנכנס לרקע של הפקד החדש שכאמור
הוא סתם ריבוע צהוב. ועכשיו יהיה לו גם ברקע של טקסט שחור. במקרה כזה נשנה את
הפרופרטי שיראה כך:

 

        public string  
UserBackgroundText

        {

            get { return (string)GetValue(UserBackgroundTextProperty); }

            set { SetValue(UserBackgroundTextProperty, value); }

        }

 

        // Using
a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding,
etc…

        public static readonly DependencyProperty UserBackgroundTextProperty = DependencyProperty.Register(“UserBackgroundText”, typeof(string), typeof(MyCustomControl1), new PropertyMetadata(null));

 

שיניתי
את השם (
UserBackgroundText), את הטייפ (string), את הערך ההתחלתי (null).

אוקיי אז
יש לנו קונטרול מבחינה לוגית. בוא ניגש לתת לו מראה. אני מוסיף קובץ מסוג
Resource Dictionary לפרוייקט וקורא לו MyCustomControl1Dictionary, כדאי לזכור שנהוג להשתמש בקונוונציית שמות
לסטייל של קונטרול שמורכבת משם הקונטרול +
dictionary.

אני
מוסיף בפנים
XMLNS (רפרנס) לעצמי כדי שנוכל לזהות את הקונטרול
כטיפוס.
ומגדיר  Templateלפקד החדש. כך נראה הקוד:

<ResourceDictionary xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

                    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”

                    xmlns:Custom=”clr-namespace:WpfApplication1″>

 

    <Style  TargetType=”{x:Type Custom:MyCustomControl1}”>

        <Setter Property=”Template”>

            <Setter.Value>

                <ControlTemplate
TargetType
=”{x:Type Custom:MyCustomControl1}”>

                    <Grid Background=”Yellow” Width=”100″ Height=”100″>

                    </Grid>

                </ControlTemplate>

            </Setter.Value>

           

        </Setter>

    </Style>

</ResourceDictionary> 

>                                                                                                          

כדי
שנוכל להשתמש בו אני צריך לקשור את המקור של הסטייל ל
App-xaml של האפליקציה כדי שבזמן ריצה יידע מהיכן
לטעון את הסטייל, ואת זה עושים בדרך הפשוטה הבאה:

<Application x:Class=”WpfApplication1.App”

             xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

             xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”      

             StartupUri=”MainWindow.xaml”>

   

    <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source=”MyCustomControl1Dictionary.xaml”/>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>

</Application>

זהו
עכשיו בחלון הראשי של התכנית אני יכול לטעון את ה”פקד” החדש ולהשתמש בו,
שוב נדרש רפרנס עבור הפקד עצמו, (כמו שעשינו בהגדרת ספריית הסטייל לפקד שיזהה את
הטייפ), בסופו של דבר כך נראה הקוד ב
main window  ואם נריץ נקבל ריבוע צהוב
במרכז החלון:

<Window x:Class=”WpfApplication1.MainWindow”

        xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

        xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”

        xmlns:local=”clr-namespace:WpfApplication1″

        Title=”MainWindow” Height=”350″ Width=”525″>

    <Grid>

        <local:MyCustomControl1 Width=”200″ Height=”200″></local:MyCustomControl1>

    </Grid>

</Window>

נותר לנו
רק לכלול את הטקסט השחור ברקע של הפקד החדש, בשיל זה אני חוזר לסטייל של הקונטרול
ושותל
TextBlock בפנים עם Binding Template
Parent
שזה אומר חפש את הפרופרטי הזה באובייקט שאני טמפלייט עבורו. כך
ייראה הסטייל אחרי השינוי:

<ResourceDictionary xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

                    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”

                    xmlns:Custom=”clr-namespace:WpfApplication1″>

 

    <Style  TargetType=”{x:Type Custom:MyCustomControl1}”>

        <Setter Property=”Template”>

            <Setter.Value>

                <ControlTemplate
TargetType
=”{x:Type Custom:MyCustomControl1}”>

                    <Grid Background=”Yellow” Width=”100″ Height=”100″>

                        <TextBlock Text=”{Binding UserBackgroundText,  

                            RelativeSource={RelativeSource TemplatedParent}}” 

                                   HorizontalAlignment=”Center”
VerticalAlignment
=”Center” />

                    </Grid>

                </ControlTemplate>

            </Setter.Value>

           

        </Setter>

    </Style>

   </ResourceDictionary>                                                                                               

וכמובן השינוי האחרון יבוא בגזרת השימוש בפקד שבו אני אאתחל את הערך
הרצוי עבור הקונטרול:

  <Grid>

        <local:MyCustomControl1 Width=”200″ Height=”200″   

                   UserBackgroundText=”NewControl”></local:MyCustomControl1>

    </Grid>

                                                                                                        

זהו בנינו קונטרול ראשון, בפוסט הבא בסדרה נבנה משהו מורכב יותר עם
התאמה ספציפית לדרישות.

הוסף תגובה
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *