מתחילים לבנות
כעת, נתחיל לכתוב את התוכנה שלנו ונתנסה עם הפונקציה הראשונה שלנו, ה-RegisterHotKey. נפתח פרויקט Windows Forms חדש וניצור מחלקה חדשה בשם WinAPI. מחלקה זו תכיל את כל הפונקציות אשר קשורות לממשק תכנות היישומים של Windows. בכדי שנוכל להשתמש בפונקצית ה-RegisterHotKey עלינו להכריז עלייה לפני. אתר מצוין שבו נשתמש בכדי שנוכל למצוא כיצד מכריזים על פונקציות WinAPI ומשתמשים בהן נקרא Pinvoke. נחפש באתר את הפונקציה RegisterHotKey ונמצא כי אנו מכריזים עליה בדרך הבאה:
כעת, נתחיל לכתוב את התוכנה שלנו ונתנסה עם הפונקציה הראשונה שלנו, ה-RegisterHotKey. נפתח פרויקט Windows Forms חדש וניצור מחלקה חדשה בשם WinAPI. מחלקה זו תכיל את כל הפונקציות אשר קשורות לממשק תכנות היישומים של Windows. בכדי שנוכל להשתמש בפונקצית ה-RegisterHotKey עלינו להכריז עלייה לפני. אתר מצוין שבו נשתמש בכדי שנוכל למצוא כיצד מכריזים על פונקציות WinAPI ומשתמשים בהן נקרא Pinvoke. נחפש באתר את הפונקציה RegisterHotKey ונמצא כי אנו מכריזים עליה בדרך הבאה:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
את ההכרזה הזאת נעתיק למחלקת ה-WinAPI שיצרנו. יש לשים לב כי יופיעו לנו מספר שגיאות משום שעלינו לייבא את התחום InteropServices. בראש הקוד של קובץ ה-WinAPI, באזור שבו אנו מייבאים ספריות שונות נייבא את התחום InteropServices, אשר נותן לנו את היכולת להשתמש בפונקציונליות של תוכניות שאינן נכתבו תחת .Net. ניתן לקרוא עוד על תחום זה בקישור הבא.
using System.Runtime.InteropServices;
בכדי שנוכל לגשת לפונקציה RegisterHotKey, עלינו להפוך את ההכרזה ל-public. לכן, נשנה את הכרזת הפונקציה באופן הבא:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
כעת נוכל לגשת לפונקציה זו דרך הטופס הראשי של התוכנה. כברירת מחדל שמו של טופס זה הוא Form1 אך לשם נוחות נשנה אותו ל-Main (הטופס הראשי של התוכנה). נשתמש בפונקצית ה-RegisterHotKey בכדי להורות למערכת ההפעלה לשלוח לחלון ה-Main שלנו הודעה כאשר המשתמש לחץ על קיצור מקשים מסוים. בכדי שנוכל לעשות זאת עלינו להבין מה כל פרמטר בפונקציה אומר:
- hWnd– פרמטר זה הוא המזהה של החלון שאליו ההודעה תישלח (ההודעה שהמשתמש לחץ על צירוף מקשים).
- id– פרמטר זה הוא בעצם תעודת הזהות של אותו צירוף המקשים. במילים אחרות ניתן לכל צירוף מקשים מזהה מסוים, באמצעות מזהה זה נוכל לזהות איזה צירוף מקשים נלחץ.
- fsModifiers– ה-Modifier שעליו נצטרך ללחוץ עם צירוף מקש נוסף. Modifier יכול להיות מספר שמאפיין את אחד מהמקשים הבאים: Alt, Control, Shift או Win.
- vk– הכפתור הנוסף (Virtual Key) שהמשתמש יצטרך ללחוץ.
לכל כפתור במקלדת יש מספר שמאפיין אותו. למשל, המאפיין של X הוא 89. לא נצטרך לזכור או להכריז על המאפיינים וזאת מפני שמיקרוסופט כבר הכינו לנו Enum אשר מכיל בתוכו את כל המקשים בשם Keys. בשביל כפתורי ה-Modifiers ניצור Enum חדש אשר מכיל בתוכו את כל כפתורי ה-Modifiers. לכן, אם נרצה שהמשתמש ילחץ על הצירוף Win + X עלינו להקליד בפרמטר fsModifiers את המאפיין Win ובפרמטר vk את המאפיין X.
כאשר התוכנית שלנו תעלה, עלינו לרשום את צירוף המקשים שרצינו. לכן עלינו לכתוב במתודה שנקראת ברגע שהטופס שלנו עולה את הקוד שאחראי לקרוא לפונקצית ה-RegisterHotKey. לחיצה על הטופס שלנו ובחירת הצלמית בצורת הברק בחלון ה-Properties תציג לנו את כל האירועים אשר נוכל להגיב אליהם. נלחץ פעמיים על האירוע Load ותיווצר לנו המתודה Main_Load אשר תגיב לאירוע העלייה של החלון.
כעת עלינו להוסיף את ה-Enum אשר מכיל את כל כפתורי ה-Modifiers, ובנוסף עלינו להוסיף גם Enum אשר מכיל את ה-ID של כל קיצור מקשים. במקרה שלנו יש לנו 3 קיצורי מקשים: הורדת שקיפות, העלאת שקיפות והחזרה לברירת מחדל. נכתוב את ה-Enums שציינו בראש המחלקה כך:
enum Modifiers
{
Alt = 0x1,
Control = 0x2,
Shift = 0x4,
Win = 0x8
}
enum HotKeyID
{
IncreaseTransparent = 0,
DecreaseTransparent = 1,
Restore = 2
}
את המאפיין של כל Modifier ניתן למצוא בדוגמאות המצוינות בעמוד ה-Pinvoke של הפונקציה RegisterHotKey. ה-Enum של ה-HotKeyID מכיל את ה-ID של כל קיצור מקשים, אשר נקבעו באופן שרירותי.
כעת נכתוב את הקוד הבא במתודת ה-Main_Load (שמגיבה לעליית החלון):
WinAPI.RegisterHotKey(this.Handle, (int) HotKeyID.IncreaseTransparent, (uint)Modifiers.Win, (uint)Keys.Z);
WinAPI.RegisterHotKey(this.Handle, (int) HotKeyID.DecreaseTransparent, (uint)Modifiers.Win, (uint)Keys.X);
WinAPI.RegisterHotKey(this.Handle, (int) HotKeyID.Restore, (uint) Modifiers.Win, (uint) Keys.C);
הקוד הנ"ל רושם את שלושת צירופי המקשים באמצעות הפונקציה RegisterHotKey. כל פרמטר מכיל את המאפיינים הבאים:
- הפרמטר הראשון- ה-Handle של החלון שלנו (Main).
- הפרמטר השני- ה-ID שנתנו לכל צירוף מקשים.
- הפרמטר השלישי- המאפיין של כפתור ה-Win.
- הפרמטר הרביעי- הכפתור הנוסף שהמשתמש ילחץ בכדי לקרוא לצירוף המקשים הספציפי. למשל, הפרמטר בשורה הראשונה הוא Z, מה שאומר שהכפתור Win + Z יעלה את רמת השקיפות של החלון (בהתאם ל-ID שנתנו בפרמטר השני).
כאשר נסגור את התוכנה, נרצה להסיר את כל קיצורי המקשים שרשמנו. בכדי לעשות זאת, עלינו להשתמש בפונקציה UnregisterHotKey שעושה בדיוק ההפך. הפרמטר הראשון הוא ה-Handle של החלון שאליו קיצור המקשים משוייך, הפרמטר השני הוא ה-ID של קיצור המקשים שאנו רוצים להסיר. כמו בפונקציה הקודמת, גם כאן עלינו להכריז על פונקציה זו במחלקת ה-WinAPI.
נשתמש באתר Pinvoke בכדי למצוא כיצד מכריזים על פונקציה זו. לאחר שהכרזנו על פונקציה זו נגיב לאירוע הסגירה של החלון שלנו באמצעות הוספת המתודה Main_Closing בדיוק כפי שהוספנו את המתודה Main_Load. במתודת ה-Main_Closing נוסיף את השורות הבאות:
נשתמש באתר Pinvoke בכדי למצוא כיצד מכריזים על פונקציה זו. לאחר שהכרזנו על פונקציה זו נגיב לאירוע הסגירה של החלון שלנו באמצעות הוספת המתודה Main_Closing בדיוק כפי שהוספנו את המתודה Main_Load. במתודת ה-Main_Closing נוסיף את השורות הבאות:
WinAPI.UnregisterHotKey(this.Handle, (int) HotKeyID.IncreaseTransparent);
WinAPI.UnregisterHotKey(this.Handle, (int)HotKeyID.DecreaseTransparent);
WinAPI.UnregisterHotKey(this.Handle, (int)HotKeyID.Restore);
כאשר התוכנה שלנו תעלה, כל קיצורי המקשים ירשמו. ברגע שהיא תיסגר, קיצורי המקשים שלנו ימחקו.
עד כאן הכל נשמע טוב, אבל כיצד אנחנו מגיבים כאשר צירוף מקשים מסוים נלחץ? בכדי שנוכל להגיב לצירוף מקשים עלינו להיזכר בכמה עקרונות. אמרנו כי כל חלון במערכת ההפעלה Windows מקבל הודעות. הודעה מורכבת כך:
כאשר השתמשנו בפונקצית ה-RegisterHotKey אמרנו בעצם למערכת ההפעלה לשלוח הודעת WM_HOTKEY לחלון שלנו כל פעם שקיצור המקשים שרשמנו יתרחש. הפרמטר MSG (הפרמטר השני) יכיל WM_HOTKEY (מספר המתאר התרחשות של קיצורי מקשים). מחלקת ה-Main שלנו (הטופס הראשי של התוכנה שלנו) יורשת את המחלקה Form, אשר מתארת חלון/טופס. מחלקת ה-Form מגיבה לכל האירועים אשר מתרחשים באופן אוטומטי "מאחורי הקלעים". אם כך, כיצד אנו נוכל "להביט" בהודעות לפני שהמחלקה מספיקה לטפל בהן? לשם כך עלינו "לדרוס" את המתודה שאחראית לנהל את ההודעות אשר מתקבלות, הנקראת WndProc. נעתיק את המתודה הבאה למחלקת ה-Main שלנו:
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
}
"דרסנו" את המתודה WndProc, כך שבמקום שהיא תעבוד מאחורי הקלעים במחלקה המקורית שלה (Form), אנחנו נחליט מה לעשות עם כל הודעה והודעה שמתקבלת. במקרה הזה קראנו למתודה המקורית של WndProc שתנהל את ההודעות המתקבלות. אז מה בעצם עשינו אם ההודעות מתנהלות אותו הדבר? עצם העובדה שכתבנו את המתודה מחדש בתוך המחלקה, תאפשר לנו "להציץ" להודעות ולהגיב רק להודעות ששייכות אלינו. ההודעות שאנו רוצים להגיב אליהן הן הודעות מסוג WM_HOTKEY, המספר המאפיין הודעה מסוג זה הוא 0x312 (המספר מוצג בהקסדצימלי). נוסיף את הקבוע WM_HOTKEY במחלקה WinAPI עם הערך 0x312. נכריז על הקבוע כך:
public const int WM_HOTKEY = 0x312;
כעת, נחזור למתודה WndProc של מחלקת ה-Main שלנו. כפי שאמרנו פרמטר ה-MSG אמור להכיל את הקבוע WM_HOTKEY. בנוסף לכך, הפרמטר WPARAM יכיל את ה-ID של צירוף המקשים שנלחץ. כעת נכתוב שוב את המתודה WndProc אך הפעם נגיב להודעות הרלוונטיות אלינו:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WinAPI.WM_HOTKEY: //User pressed on one of the hot keys
switch ((HotKeyID) m.WParam)
{
case HotKeyID.IncreaseTransparent:
MessageBox.Show("Transparent Increased!");
break;
case HotKeyID.DecreaseTransparent:
MessageBox.Show("Transparent Decreased!");
break;
case HotKeyID.Restore:
MessageBox.Show("Transparent Back To Normal!");
break;
}
break;
default:
base.WndProc(ref m);
break;
}
}
כפי שניתן ליראות, בדקנו האם ההודעה רלוונטית אלינו (אם פרמטר ה-MSG שלה מכיל את הקבוע WM_HOTKEY) ואחר כך בדקנו מהו ה-ID של אותו צירוף מקשים. כעת, כאשר נריץ את התוכנה נראה כי קופצות לנו הודעות אשר מציגות את מה שאמור ליקרות, לא משנה אם החלון שלנו ממוזער או אם אנחנו נמצאים בפוקוס על תוכנה אחרת בכלל. לאחר שהרצנו את התוכנה וראינו שהיא מזהה את קיצורי המקשים, ניתן לגשת לחלק הבא: הפיכת החלון הנוכחי לשקוף.
בכדי שנוכל להבין כיצד להפוך חלון לשקוף, עלינו לקרוא קצת יותר על חלונות בסביבת Windows. לכל חלון יש תכונה הנקראת Window Style. תכונה זו קובעת איך החלון יראה ואיך הוא יתנהג. אנו קובעים כיצד החלון יראה על ידי הכנסת קבועים לתכונה זו. קבוע הוא בסך הכל מספר בעל משמעות, כל קבוע ישנה דבר אחר בחלון. התכונה המתקדמת יותר של Window Style נקראת Window Extended Style ומכילה קבועים יותר מתקדמים, כמו הקבוע אשר מאפשר לנו להשתמש ב-Alpha Blending וכך לקבוע את רמת השקיפות. כאשר אנו רוצים לגרום לחלון להיות שקוף עלינו להוסיף לתכונת ה-Extended Style את הקבוע WS_EX_LAYERED.
כעת, בכדי שנוכל לקבוע את רמת שקיפות החלון עלינו לקרוא לפונקצית API אשר זה הוא תפקידה. כאשר אנו רוצים לספק מספר קבועים עלינו לחבר ביניהם.במקרים מסוימים נרצה ליישם מספר קבועים (לשנות מספר דברים בחלון). לכן כל מה שעלינו לעשות הוא לחבר בין הקבועים.
הפונקציות אשר נשתמש בהן בכדי לקבוע את שקיפות החלון נקראות SetWindowLong ו-SetLayeredWindowAttributes. הפונקציה הראשונה מאפשרת לנו לשנות את תכונת ה-Extended Style של חלון שנרצה (ובכך לאפשר לו להפוך להיות שקוף). הפונקציה השנייה מאפשרת לנו לקבוע את ערך השקיפות של החלון. בנוסף נשתמש בפונקציה GetWindowLong בכדי להשיג את הקבועים הנוכחיים של תכונת ה-Extended Style, בכדי שברגע שנשתמש בפונקצית ה-SetWindowLong נוסיף רק את הקבוע WM_EX_LAYERED בנוסף לקבועים הקיימים (על ידי חיבור בין הקבועים, כפי שציינו) וכך לא "נדרוס" קבועים אחרים.
עלינו להשתמש גם בפונקציה GetLayeredWindowAttributes בכדי להשיג את רמת השקיפות הנוכחית של החלון. בכדי להשיג את ה-Handle של החלון הנוכחי שהמשתמש נמצא בו עלינו להשתמש בפונקציה GetForegroundWindow.
עלינו להשתמש גם בפונקציה GetLayeredWindowAttributes בכדי להשיג את רמת השקיפות הנוכחית של החלון. בכדי להשיג את ה-Handle של החלון הנוכחי שהמשתמש נמצא בו עלינו להשתמש בפונקציה GetForegroundWindow.
נכריז על פונקציות ה-API הנ"ל במחלקת ה-WinAPI בדיוק כפי שלמדנו בתחילת המדריך (באתר Pinvoke). נציין כי פונקציות כמו SetWindowLong ו-GetWindowLong הן פונקציות אשר אינן תואמות למערכות מסוג 64 ביט (כפי שמצוין בתיעוד של הפונקציות). במצב כזה, על כל פונקציה עלינו ליצור פונקציה ראשית אשר מצביעה על פונקציה המתאימה לסוג מערכת ההפעלה שלה.