Stop Painting Yourself

11 בנובמבר 2008

תגיות:
4 תגובות


בוקר טוב.


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


תמיד אהבתי מחשבים, אבל לתחום התכנות הגעתי לפני כארבע שנים, כשמכללת סלע בשיתוף עם ג'וינט ישראל עשו (ועושים) מסלולי לימוד לחרדים כדי לעזור לנו לצאת לשוק העבודה (מה שבאמת קורה), מאז ועד היום( וגם בעתיד הקרוב והרחוק) אני נמצא בסלע, בשנה וחצי האחרונים הייתי בחברת QualiSystems מטעם חברת סלע, QualiSystems מפתחת מוצר מדהים של בדיקות אוטומטיות לחומרה,


עיקר העבודה שלי היא ב Web וב Win אני מקווה בקרוב גם להתחיל לפתח ב Silverlight, ואני גם מרצה במכללת סלע (שזה דבר נהדר להיות מרצה, כי זה גורם להיכנס לפינות הכי קטנות בכל נושא).


 


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


נחשוב על הסיטואציה הבאה: נניח שיש לכם Form כלשהו ואתם רוצים לסגור אותו ולפתוח אותו בלי שהמשתמש ישים לב, או שאתם עושים עבודה על Control וכל עוד שה Control לא גומר להיטען אתם רוצים שהוא לא יצייר את עצמו על הדף כי אתם מקבלים בזמן הטעינה מין מסך אפור, או כל סיבה אחרת שעולה בראשכם שאתם רוצים לעצור את ה Paint של control מסוים.


 למעשה יש Controls כמו TreeView שיש להם שני מתודות מענינות BeginUpdate ו EndUpdate


משתמשים בהם אם משנים את ה Tree בקוד, ואם השינוי לוקח מספיק זמן (אם אתם בונים Tree גדול) רואים את ה tree נבנה, כדי לפתור את זה צריך לפני השינוי לקרוא ל BeginUpdate ואחרי השינוי לקרוא ל EndUpdate, זה יגרום שהמשתמש לא יראה שה tree נבנה, אלא בבת אחת הוא יראה את ה Tree החדש, זה גם כן משפר את הביצועים בצורה משמעותית כי ה Tree לא צריך לצייר את עצמו, אלא רק לבנות אותו.


הקוד יראה כך:




   1         private void BuildTree_Click(object sender, EventArgs e)


   2         {


   3             try


   4             {


   5                 treeView1.BeginUpdate();


   6 


   7                 // any logic here…


   8             }


   9             finally


   10            {


   11                 treeView1.EndUpdate();


   12            }


   13         }





 


 כמובן שצריך לכתוב את EndUpdate בבלוק finally כדי שלא משנה מה יתרחש בזמן יצירת ה Tree הוא יחזור לצייר את עצמו.


למרבה הצער המתודות BeginUpdate ו EndUpdate מופיעות רק אצל חלק מה controls, אז כדי להבין מה צריך לעשות כדי לכתוב מתודה כזאת לבד ל Controls אחרים, כמובן צריך לפתוח את ה Reflector, אז הדבר היחיד שהמתודה עושה, זה להפעיל את base.BeginUpdateInternal שזה מתודה שנמצאת אצל Control ולמרבה הצער היא מוגדרת ב Internal, מה שגורם שאנחנו לא יכולים להפעיל את זה לבד.


אז כמובן שנצטרך לכתוב מתודה בעצמנו עבור Controls אחרים, אז נמשיך להסתכל ב Reflector על המימוש של BeginUpdateInternal


 




   1         internal void BeginUpdateInternal()


   2         {


   3             if (this.IsHandleCreated)


   4             {


   5                 if (this.updateCount == 0)


   6                 {


   7                     this.SendMessage(11, 0, 0);


   8                 }


   9                 this.updateCount = (short)(this.updateCount + 1);


   10             }


   11         }



אוקיי, נתעלם כרגע מכל הקוד, ונתייחס רק לשורה מספר 7,



this.SendMessage(11, 0, 0);



בתירגום חופשי זה אומר: "תודיע למערכת ההפעלה שיפסיק לצייר אותי", למעשה אנחנו שולחים ל windows את ה message מספר 11, שזה Paint עבור this עם הארגומנט false


למעשה עבור כל Control שיש על ה form מתרחש Paint כל הזמן, אם תעשו override על OnPaint של Control ותשימו Breakpoint, תראו שכל הזמן אתם עוצרים שם, כי Windows מציירת כל הזמן את כל ה Controls מחדש.


ברגע ששולחים את ה message אז OnPaint עבור ה Control מפסיק להתרחש, ולכן ב Tree אחרי הקריאה ל BeginUpdate, אפשר לשנות את מבנה ה Tree מבלי שהמשתמש ישים לב וכמובן זה יעבוד הרבה יותר מהר.


 


אז, אנחנו רוצים לאפשר את הפעולה גם עבור Controls אחרים,


נוכל להשתמש ב Extension Methods כדי שלכל control יהיו BeginUpdate ו EndUpdate


להלן הקוד:


 


 




    1 public static class BeginEndUpdate


    2 {


    3     [DllImport("user32.dll", CharSet = CharSet.Auto)]


    4      private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);


    5 


    6     /// <summary>


    7     /// Disables any redrawing of the control


    8     /// </summary>


    9     /// <param name="control"></param>


   10     public static void BeginUpdate(this Control control)


   11     {


   12         SendMessage(new HandleRef(control, control.Handle), 11, 0, 0);


   13     }


   14 


   15     /// <summary>


   16     /// Enables the redrawing of the control


   17     /// </summary>


   18     /// <param name="control"></param>


   19     public static void EndUpdate(this Control control)


   20     {


   21         SendMessage(new HandleRef(control, control.Handle), 11, -1, 0);


   22         control.Invalidate(true);


   23     }


   24 }



והקוד יראה כך:


 



   1         private void BuildControl_Click(object sender, EventArgs e)


   2         {


   3             try


   4             {


   5                 form1.BeginUpdate();


   6                


   7                 // any logic here…


   8             }


   9             finally


   10            {


   12                 form1.EndUpdate();


   13            }


   14         }



 


 


 


 


 


 

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

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

4 תגובות

  1. Doron Ben-David11 בנובמבר 2008 ב 12:15

    פוסט מצויין, וברוך הבא :).

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

    הגב
  2. Shlomo12 בנובמבר 2008 ב 15:31

    תודה על ההערה,
    צודק בקשר ל OnPaint

    הגב
  3. יוסי גולדברג18 בנובמבר 2009 ב 11:46

    יפה

    הגב