DCSIMG
August 2007 - Posts - Yoav Blob

Yoav Blob

August 2007 - Posts

How to add hot keys to your program.

בהמשך לפוסט על בדיקת שגיאות כתיב בכל מקום.

הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.

הפתרונות לבעיה מספר אחת:

עם הבעיה התמודדתי בהתחלה ע"י שימוש בספריה ManagedHooks שמאפשרת רישום לאירועי המערכת. כאשר האירוע מתקבל בתוכנה שלי, אני בודק האם המקש שנלחץ הוא המקש שאני מחכה לו. במידה וכן, אני מפעיל את בדיקת השגיאות.

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

טריק יפה ששינתי מתוך הדוגמא שנמצאת בCodeProject הוא בצורה שבה אני מפעיל את הפונקציה RegisterHotKey בדוגמא שקראתי הייתה מתודה שידעה להמיר מהEnum של net.

המתודה נראית ככה:

/// <summary>
/// Calculates the character code of alphanumeric key of the Keys enum instance
/// </summary>
/// <param name="k">An instance of the Keys enumaration</param>
/// <returns>The character code of the alphanumeric key</returns>
public static byte CharCodeFromKeys(Keys k)
{
byte charCode = 0;
if ((k.ToString().Length == 1) || ((k.ToString().Length > 2) && (k.ToString()[1] == ',')))
charCode = (byte)k.ToString()[0];
else if ((k.ToString().Length > 3) && (k.ToString()[0] == 'D') && (k.ToString()[2] == ','))
charCode = (byte)k.ToString()[1];
return charCode;
}

לאחר דיבאג קצר ראיתי שהמתודה לא עובדת כאשר הKey שלי הוא לא אות.

במאמר שהופיע באתר, הוסבר שצריך לקחת מתוך הערך של k את ה4 סיביות הראשונות שהם מייצגות את המקש, ושאר הסיביות מיצגות את Shift Alt Ctrl .

אז החלפתי את הפונקיה בשורה אחת פשוטה:

/// <summary>
/// Calculates the character code of alphanumeric key of the Keys enum instance
/// </summary>
/// <param name="k">An instance of the Keys enumaration</param>
/// <returns>The character code of the alphanumeric key</returns>
public static byte CharCodeFromKeys(Keys k)
{
return (byte)((int)k & 0xFFFF);
}
ע"י שימוש פעולות בינאריות לקחתי רק את ה4 סיביות הראשונות של המספר.
 
דוגמאת קוד לשימוש בRegister ניתן למצוא בקוד המלא של CheckSpellingEveryWhere.
הצצה קצרה לקוד:
RegisterHotKey(this.Handle, id, ShortcutInput.Win32ModifiersFromKeys(cmd.HotKeys), ShortcutInput.CharCodeFromKeys(cmd.HotKeys));

Get User Focus Window From Other Program

בהמשך לפוסט על בדיקת שגיאות כתיב בכל מקום.

הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.

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

הבעיה השניה נראתה לי פשוטה. מנסיוני, ידעתי שWindows API מכיל את המתודה GetFocus

בגלל זה ניסיתי את הקוד הפשוט הבא:

[DllImport("user32.dll")]
public static extern IntPtr GetFocus();
public void TryGetFoucsWindow()
{
 MessageBox.Show(GetFocus().ToString());
}

אבל בכל פעם שבדקתי תמיד חזר לי הערך 0.
לאחר מחקר קצר בMSDN הבנתי שGetFocus וGetActiveWindow עבודות כאשר החלון נמצא בתוך הThread שלי, ואז מצאתי את המודה בAttachThreadInput. מקריאה בMSDN ראיתי שבכדי להפעיל את המתודה צריך להעביר לה את הThread Id.

Thread Id ? מאיפה זה הגיע?! ואז מצאתי את המתודות הבאות:
1. GetWindowThreadProcessId - מחזירה את הThreadId של חלון.
2. GetCurrentThreadId - מחזירה את הThreadId של האפליקציה שלי.

לסיום כתבתי את הקוד הבא:

[DllImport("user32.dll")]
public static extern
IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

[DllImport("user32.dll")]
public static extern
IntPtr AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, int fAttach);

[DllImport("user32.dll")]
public static extern
IntPtr GetFocus();

[DllImport("user32.dll")]
public static extern
IntPtr GetActiveWindow();

[DllImport("user32.dll")]
public static extern
IntPtr GetForegroundWindow();

[DllImport("Kernel32.dll")]
public static extern
IntPtr GetCurrentThreadId();

public void TryGetFoucsWindow()
{

IntPtr activeWindow = GetForegroundWindow();
IntPtr threadID1 = GetCurrentThreadId();
IntPtr threadID2 = GetWindowThreadProcessId(activeWindow, IntPtr.Zero);

if (threadID1 != threadID2
{
        AttachThreadInput(threadID1, threadID2, 1);
        IntPtr focusWindow = GetFocus();
        AttachThreadInput(threadID1, threadID2, 0);
        MessageBox.Show(focusWindow);
}

}

 החתימות הnet. לכל המתודות מתוך הWin32 Api הגיעו מתוך Pinvoke.net

GetWindowText the working way

בהמשך לפוסט על בדיקת שגיאות כתיב בכל מקום.

הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.

והפעם קריאה של הטקסט מתוך החלון.

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

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

לאחר חיפוש באינטרנט קראתי על המתודה GetDlgItemText אבל גם היא לא נתנה את הפתרון,לכן פניתי לפתרון הבסיסי ביותר, שימוש בשליחת הודעות ברמת מערכת הפעלה מקריאה בMSDN ידעתי שניתן לבצע שליחה של הודעה WM_GETTEXT לחלון ולקבל את תוכן החלון כתשובה.

בMSDN הופיעה החתימה הבא:

Syntax:

To send this message, call the SendMessage function as follows.


lResult = SendMessage(
    // returns LRESULT in lResult


   (HWND) hWndControl,
    // handle to destination control


   (UINT) WM_GETTEXT,
    // message ID


   (WPARAM) wParam,
    // = (WPARAM) () wParam;


   (LPARAM) lParam
    // = (LPARAM) () lParam;


);

Parameters

wParam Specifies the maximum number of TCHARs to be copied, including the terminating null character.

Windows NT/2000/XP:ANSI applications may have the string in the buffer reduced in size (to a minimum of half that of the wParam value) due to conversion from ANSI to Unicode.

lParam Pointer to the buffer that is to receive the text.

Return Value

The return value is the number of TCHARs copied, not including the terminating null character.

זה נראה פשוט לבצע את הקריאה מתוך קוד ++C שזאת הדרך המקורית להשתמש בAPI אבל מתוך NET. זה לא כזה טריוויאלי. 

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

[DllImport("user32.dll")]
public static extern
IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, StringBuilder sb);

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

Check spelling every where

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

אז החלטתי לפתור את הבעיה אחת ולתמיד ולהוסיף את היכולת לבדיקה של שגיאות כתיב לכל חלון במחשב.

הבעיות שהתמודדתי איתן היו:
1. הפעלה של התוכנית ע"י צירוף מקשים, כאשר האפליקציה שלי לא נמצאת בפוקוס.
2. קבלה של החלון שעכשיו המשתמש נמצא בתוכו ע"י שימוש בWin32 Api.
3. קבלת הטקסט מתוך החלון של המשתמש.

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

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

וכל הקוד של התוכנה כנ"ל לשימוש עצמי.