DCSIMG
[Tapuz .Net] Winfrosm DateTimerPicker ValueChanged and Windows Messages - Justin myJustin = new Justin( Expriences.Current );

[Tapuz .Net] Winfrosm DateTimerPicker ValueChanged and Windows Messages

שאלה:

אני רוצה ליצור מתודה שתפעל כל פעם שנבחר תאריך ב-DateTimePicker.
הכוונה היא לא כל פעם שנבחר תאריך שונה (ValueChanged) אלא יכול להיבחר לדוגמה גם אותו תאריך שהיה קודם.
יש אפשרות לעשות כך? אולי בשיטה עוקפת?

 

תשובה:

זאת שאלה מצויינת שבאמת תאפשר לנו להיכנס לעומק הקורה עם Windows Forms.

בואו נראה קודם מה השלבים שנעבור עד פתרון הבעיה.

 image

 

נתחיל בלהדגים את הבעיה.
נפתח טופס Winform חדש.

image

נגרור לטופס פקד DateTimerPicker.

image

 image

כמו כן, נוסיף לטופס TextBox שאליו נדפיס טקסט.

image

נירשם לאירוע ה-ValueChanged של ה-DateTimePicker שלנו ע"י לחיצה כפולה על ValueChanged.

image

נוסיף בהרשמה לאירוע קוד שידפיס את התאריך הנבחר לתוך ה-TextBox שלנו.

       private void dateTimePicker1_ValueChanged(object sender, EventArgs e)

        {

            tbxConsole.Text += dateTimePicker1.Value.ToShortDateString() + Environment.NewLine;

        }

 

בואו נריץ את האפליקציה.

image

נבחר את תאריך ה-25.2.2008.

image

image

נכתוב בתוך ה-DateTimerPicker שלנו את התאריך 27.2.008.

image

עכשיו ננסה לבחור שוב את ה-27.2.2008.

image

image

שימו לב אחרי שבחרנו את ה-27.2.2008 אחרי שהוא כבר היה התאריך הנבחר - שום דבר לא השתנה.

שזה אומר - ValueChanged יקפוץ אך ורק כאשר Value באמת משתנה.
השאלה שאנחנו מנסים לענות - איך לתפוס כל קביעה של תאריך ולא רק שינוי תאריך.

 

נפתח את Reflector על DateTimePicker במתודת ה-OnValueChanged שמריצה את ValueChanged.
(Reflector --> System.Windows.Forms --> System.WindowsForms.dll --> System.Windows.Forms --> DateTimePicker)

נבחר שאנחנו רוצים לראות מי קורא למתודת ה-OnValueChanged.

image

 image

 

אפשר לראות שיש 3 פונקציות בתוך הפריימוורק שמרימות את האירוע.
הראשונה היא ResetValue והגיוני שהיא תרים אירוע שינוי ל-Value.
השנייה היא set_value שמתייחסת ל-Setter של המאפיין (גם באנגלית: Property) בשם Value. גם פה הגיוני שהאירוע ירוץ.
והשלישי הוא WmDateTimerChange שמקבל איזה פרמטר מוזר מסוג &Message.

 

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

image

נבדוק מה הקוד שיש בתוך המתודה הזאת בפריימוורק.

image

image

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

עכשיו שמצאנו את האשם הראשי - נמצא את השותפים לפשע.

במתודה הזאת אני לא רואה שום נקודת התערבות טובה.

נראה מי קורה למתודה הזאת (דרך Analyze כפי שעשינו קודם לכן).

image

 

אפשר לראות כאן שרשרת של פונקציות של WndProc שקורה ל-WmReflectCommand שקורה ל-WmDateTimeChanged.

אני עדיין בכלל לא יודע מה זה WndProc ומה זה &Message ובכלל מה זה Wm.

נראה מה התוכן של שתי המתודות האלו.

image

image

אם נעקוב אחרי התהליך נראה ש-WndProc מקבל איזה פרמטר מוזר ואם הוא מקבל Message.Msg == 0x204e הוא יקרה ל-WmReflectCommand.
ב-WmReflectCommand אם נקבל IParamCode.code == -759 אז נקרא ל-WmDateTimeChange שבתורה אנחנו יודעים שמרים את אירוע ה-ValueChanged.

 

זאת השרשרת שגילינו.

image

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

 

זהו, זהינו לחלוטין את האשמים בבעיה שיש לנו.

עכשיו פתרון - הנקודת התערבות היחידה שיש לנו היא לדרוס את מתודת ה-WndProc בראש הערימה.

 

נפתח פרוייקט חדש מסוג Windows Control Library ב-Visual Studio 2008.

image

image

 

ניצור מחלקה (גם באנגלית: Class) חדשה בשם myDateTimerPicker בתוך הפרוייקט Windows Forms Control Library החדש שלנו.

image

image

 

נדאג לשנות את הקובץ ברירת מחדל כך שהמחלקה תירש (גם באנגלית: Inherit) את DateTimerPicker.

using System.Windows.Forms;

 

namespace WinControls_VS2008Testing

{

    public class myDateTimerPicker : DateTimePicker

    {

    }

}

 

בתור התחלה לפתרון, נרצה לחשוף אירוע חדש בשם ValueChanging שירוץ לפני ValueChanged.

    public class myDateTimerPicker : DateTimePicker

    {

        public event EventHandler ValueChanging;

 

        public void OnValueChanging(EventArgs e)

        {

            if (ValueChanging != null)

            {

                ValueChanging(this, e);

            }

        }

    }

הקוד יחסית בסיסי, הוספנו אירוע בשם ValueChanged ומתודת OnValueChanging שתעלה את האירוע.

 

בשלב זה נרצה להתערב הכי נמוך שאפשר בהיררכיה שמצאנו, שזו מתודת ה-WndProc.

    public class myDateTimerPicker : DateTimePicker

    {

        protected override void WndProc(ref Message m)

        {

            base.WndProc(ref m);

        }

 

        public event EventHandler ValueChanging;

 

        public void OnValueChanging(EventArgs e)

        {

            if (ValueChanging != null)

            {

                ValueChanging(this, e);

            }

        }

    }

אז דרסנו את WndProc.
כרגע הוא רץ בדיוק אותו דבר כמו קודם ועוד אין שינוי.

נממש את שני התנאים מקודם לכן (שני ה-Ifים בתוך WndProc ו-WmReflectCommand).

using System;

using System.Runtime.InteropServices;

using System.Windows.Forms;

 

namespace WinControls_VS2008Testing

{

    public class myDateTimerPicker : DateTimePicker

    {

        protected override void WndProc(ref Message m)

        {

            if ((m.Msg == 0x204e)

                && (m.HWnd == base.Handle)

                && (((NMHDR)m.GetLParam(typeof(NMHDR))).code == -759)

                )

            {

            }

 

            base.WndProc(ref m);

        }

 

        [StructLayout(LayoutKind.Sequential)]

        private struct NMHDR

        {

            public IntPtr hwndFrom;

            public IntPtr idFrom;

            public int code;

        }

 

 

        public event EventHandler ValueChanging;

 

        public void OnValueChanging(EventArgs e)

        {

            if (ValueChanging != null)

            {

                ValueChanging(this, e);

            }

        }

    }

}

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

וכשלב אחרון בפתרון, נעלה בתנאים אלו את אירוע ה-OnValueChanging שלנו.

   public class myDateTimerPicker : DateTimePicker

    {

        protected override void WndProc(ref Message m)

        {

            if ((m.Msg == 0x204e)

                && (m.HWnd == base.Handle)

                && (((NMHDR)m.GetLParam(typeof(NMHDR))).code == -759)

                )

            {

                OnValueChanging(new EventArgs());

            }

 

            base.WndProc(ref m);

        }

 

        [StructLayout(LayoutKind.Sequential)]

        private struct NMHDR

        {

            public IntPtr hwndFrom;

            public IntPtr idFrom;

            public int code;

        }

 

 

        public event EventHandler ValueChanging;

 

        public void OnValueChanging(EventArgs e)

        {

            if (ValueChanging != null)

            {

                ValueChanging(this, e);

            }

        }

    }

 

בואו נראה שהפתרון שלנו באמת פועל.

צריך לדאוג להוסיף Reference בין שני הפרוייקטים.

image

image

 

אחרי שנקמפל את כל ה-Solution נוכל לראות ב-Toolbox של הטופס שלנו את myDateTimePicker מתחת ל-Windows Forms Control Library שלנו.

image

 

נשים myDateTimePicker על הטופס.
(שלנו זה התחתון)

image

נירשם לאירוע ה-ValueChanging ושם נדאג להדפיס ל-TextBox.

image

        private void myDateTimerPicker1_ValueChanging(object sender, EventArgs e)

        {

            tbxConsole.Text += myDateTimerPicker1.Value.ToShortDateString() + Environment.NewLine;

        }

 

נריץ את האפליקציה.

נבחר מחדש את התאריך של היום.

image

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

image

 

עכשיו בואו נבין מה בכלל הולך כאן.

מה זה ה-Message הזה שעובר הלוך ושוב.

Windows Forms בכלל הוא ברובו שכבה של קוד דוט-נטי מעל אלמנטים חלונאיים סטנדרטיים.

כלומר, במערכת ההפעלה קיים איזה Unmanaged DateTimerPicker שכל אפליקציה יכולה לגשת אליו, לאתחל אותו ולהירשם לקבל Messages.
Winforms היא שכבת ביינים בין הסיוט שהוא תכנות Win32 לבין קוד-נטי.

image

למעשה Winforms יוצר כדי לטווח בין תכנות Object-Oriented שהוא הרבה יותר אינטואטיבי לבין תכנות Win32 שהוא הרבה יותר פרוצדוראלי.

מדי פעם לפעם, צריך לדעת איזה הודעות עוברת בין Winforms ו-Win32 כדי להרחיב פקדים קיימים, כמו במקרה הזה.

 

נסכם מה שעשינו.

image

 

ניתן להוריד את הקוד שעבדנו עליו מ- http://www.JustinAngel.Net/files/Example-DateTimerPicker-ValueChanging.zip.

 

 

קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=112949617

Published Monday, March 03, 2008 1:27 PM by Justin-Josef Angel [MVP]

Comments

No Comments