PDA

View Full Version : سوال: تعريف يك ShortCut سراسري در ويندوز [اين مشكل رو چطوري رفع كنم؟]



newgoldenman
سه شنبه 01 بهمن 1387, 15:54 عصر
سلام به همگي دوستان
راستش خيلي گشتم كه بتونم براي برنامم، يدونه ShortCut بسازم، كه از هر جايي توي ويندوز اون كليد زده شد، برنامه ي من برام يه كاري انجام بده.
در نهايت فهميدم كه بايد Keybord رو Hook كنم! پس از گشت و گذارهايي طولاني، يه نمونه پيدا كردم كه كار ميكرد خودش، ولي توي برنامه ي من اين error رو ميده:

A callback was made on a garbage collected delegate of type 'PersianDate!GlobalHookKeyBoard.globalKeyboardHook +keyboardHookProc::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

اين از كلاس Hook كردن كه من استفاده كردم:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace GlobalHookKeyBoard
{
/// <summary>
/// A class that manages a global low level keyboard hook
/// </summary>
class globalKeyboardHook
{
#region Constant, Structure and Delegate Definitions
/// <summary>
/// defines the callback type for the hook
/// </summary>
public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);

public struct keyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}

const int WH_KEYBOARD_LL = 13;
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_SYSKEYDOWN = 0x104;
const int WM_SYSKEYUP = 0x105;
#endregion

#region Instance Variables
/// <summary>
/// The collections of keys to watch for
/// </summary>
public List<Keys> HookedKeys = new List<Keys>();
/// <summary>
/// Handle to the hook, need this to unhook and call the next hook
/// </summary>
IntPtr hhook = IntPtr.Zero;
#endregion

#region Events
/// <summary>
/// Occurs when one of the hooked keys is pressed
/// </summary>
public event KeyEventHandler KeyDown;
/// <summary>
/// Occurs when one of the hooked keys is released
/// </summary>
public event KeyEventHandler KeyUp;
#endregion

#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
/// </summary>
public globalKeyboardHook()
{
hook();
}

/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
/// </summary>
~globalKeyboardHook()
{
unhook();
}
#endregion

#region Public Methods
/// <summary>
/// Installs the global hook
/// </summary>
public void hook()
{
IntPtr hInstance = LoadLibrary("User32");
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
}

/// <summary>
/// Uninstalls the global hook
/// </summary>
public void unhook()
{
UnhookWindowsHookEx(hhook);
}

/// <summary>
/// The callback for the keyboard hook
/// </summary>
/// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
/// <param name="wParam">The event type</param>
/// <param name="lParam">The keyhook event information</param>
/// <returns></returns>
public int hookProc(int code, int wParam, ref keyboardHookStruct lParam)
{
if (code >= 0)
{
Keys key = (Keys)lParam.vkCode;
if (HookedKeys.Contains(key))
{
KeyEventArgs kea = new KeyEventArgs(key);
if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
{
KeyDown(this, kea);
}
else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null))
{
KeyUp(this, kea);
}
if (kea.Handled)
return 1;
}
}
return CallNextHookEx(hhook, code, wParam, ref lParam);
}
#endregion

#region DLL imports
/// <summary>
/// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
/// </summary>
/// <param name="idHook">The id of the event you want to hook</param>
/// <param name="callback">The callback.</param>
/// <param name="hInstance">The handle you want to attach the event to, can be null</param>
/// <param name="threadId">The thread you want to attach the event to, can be null</param>
/// <returns>a handle to the desired hook</returns>
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);

/// <summary>
/// Unhooks the windows hook.
/// </summary>
/// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
/// <returns>True if successful, false otherwise</returns>
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);

/// <summary>
/// Calls the next hook.
/// </summary>
/// <param name="idHook">The hook id</param>
/// <param name="nCode">The hook code</param>
/// <param name="wParam">The wparam.</param>
/// <param name="lParam">The lparam.</param>
/// <returns></returns>
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);

/// <summary>
/// Loads the library.
/// </summary>
/// <param name="lpFileName">Name of the library</param>
/// <returns>A handle to the library</returns>
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
#endregion
}
}


اين هم روشي كه من ازش استفاده كردم توي كلاس فرم برنامم،عيناً همون كارهايي كه توي اون مثال codeproject ديدم.

namespace PersianDate
{
public partial class Form1 : Form
{
globalKeyboardHook myHookKeyBoard = new globalKeyboardHook();
public Form1()
{
InitializeComponent();

}
private void GlobalHookKeyBoard_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("F12 Pressed");
e.Handled = true;
}

private void Form1_Load(object sender, EventArgs e)
{
myHookKeyBoard.HookedKeys.Add(Keys.F12);
myHookKeyBoard.KeyDown += new KeyEventHandler(GlobalHookKeyBoard_KeyDown);
}
}

اين نمونه رو از توي codeproject (http://www.codeproject.com/KB/cs/CSLLKeyboardHook.aspx) پيدا كردم. نمونه ي خودش كار ميكنه، ولي وقتي عيناً كارهاي اونو، توي برنامم اعمال كردم(كه نوشتم براتون) براي من اون error رو ميده.

آيا كسي ميتونه كمكم كنه در اين زمينه؟ خدايي خيلي توي Google گشتم. از اين مدل خطا، خيلي ها داشتن، روش حل هم به روش "تئوري" گفتن همه جا! ولي هيچ جا كسي يك خط كد يا يك تغيير كوچيك كه اين مشكل رو برطرف كنه، نداده...

شرمنده ميكنيد اگه مشكل منو حل كنيد دوستان.يا اينكه يه روشي يا كلاس Hook كردني كه خودتون تاحالا استفاده كرده و كار كرده براتون، به من هم معرفي كنيد.
خيلي خيلي ازتون ممنونم.

اينم لينك نمونه اي كه توي CodeProject ازش حرف زدم:
http://www.codeproject.com/KB/cs/CSLLKeyboardHook.aspx

vcldeveloper
سه شنبه 01 بهمن 1387, 16:10 عصر
خيلي گشتم كه بتونم براي برنامم، يدونه ShortCut بسازم، كه از هر جايي توي ويندوز اون كليد زده شد، برنامه ي من برام يه كاري انجام بده.
برای این کار نیاز به هوک کردن کیبورد نیست. تابع RegisterHotKey ویندوز را فراخوانی کنید تا میانبر صفحه کلیدی که مدنظرتان هست در سیستم ثبت شود. اون وقت هر زمان که این میانبر توسط کاربر فرستاده بشه، ویندوز برای پنجره برنامه شما پیام WM_HOTKEY ارسال میکنه، که شما می تونید با نوشتن یک Message handle به این پیام پاسخ بدید.
فراخوانی توابع API در دات نت با استفاده از PInvoke صورت میگیره. هندل کردن پیام های ویندوز را نمیدونم توی #C چطور انجام میدن، و آیا اصلا همچین قابلیتی داره یا نه، ولی مسلما کاربرانی که بیشتر با #C کار کردند، می تونند بهتون کمک کنند که چطور با #C یک پیام ویندوز را هندل کنید.

newgoldenman
سه شنبه 01 بهمن 1387, 23:18 عصر
ممنونم از توجهتون آقاي کشاورز. ولي چي بگم والا! فكر كنم توي بچه هاي سايت برنامه نويس، حتماً حداقل يكيشون تا بحال به صوال من و خواسته ي من برخوردند، و حتماً به پاسخ هم رسيدند. خدايي خيلي گشتم و يه عالمه كلاس و ... تست كردم. اما هر كدوم يه جوري مشكل دارم باهاشون، يا اينكه توي تعريف كليدها بايد از ثابت ها و .... به جاي استفاده از مثلاً Keys.F12 استفاده كنم. كه اينطوري برام سخته يكمي!

ممنون ميشم كسي از دوستان بتونه پاسخي براي خواسته ي من داشته باشه:
تعريف يك ShortCut سراسري در برنامه، بطوري كه اگر برنامه در حالت Focus نباشه هم كار بكنه!

newgoldenman
چهارشنبه 02 بهمن 1387, 00:00 صبح
به به!
خدا رو شكر. بالاخره خودم به جواب رسيدم.
خيلي ساده و دقيقاً به همون سادگي كه فكرشو ميكردم. اينم نمونه كد براي كساني كه دوست دارند از اين روش استفاده كنند:


public partial class Form1 : Form
{
const int HOTKEY_ID = 31197; //any number to be used as an id within this app
const int WM_HOTKEY = 0x0312;

public enum KeyModifiers //enum to call 3rd parameter of RegisterHotKey easily
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
//API Imports
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(
IntPtr hWnd, // handle to window
int id, // hot key identifier
KeyModifiers fsModifiers, // key-modifier options
Keys vk // virtual-key code
);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(
IntPtr hWnd, // handle to window
int id // hot key identifier
);

protected override void WndProc(ref Message msg)
{
// Listen for operating system messages.
switch (msg.Msg)
{
case WM_HOTKEY:
// this is the block the app turns in if the hotkey has been pressed
//so do your f@cking hotkey stuff here :-D
MessageBox.Show("F11 Pressed!");
break;
}
base.WndProc(ref msg);
}
//globalKeyboardHook myHookKeyBoard = new globalKeyboardHook();
public Form1()
{
InitializeComponent();
bool bcheck = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.None, Keys.F11);

}
}

از آقاي كشاورز عزيز هم ممنون و سپاسگذارم كه وقت گذاشتند و راه حل معرفي كردند. كه با استفاده از همين چند لغتي كه به لاتين نوشتند، جستجوهاي جديدي كردم و بالاخره به جواب رسيدم: خواستن توانست است.
دقيقاً هم حرف ايشون درست بود. كه هيچ نيازي به هوك كردن نيست و ...

موفق باشيد.
(از مديران ممنون ميشم كه اين تاپيك رو به عنوان "تاپيك به عنوان راه حل انتخاب گرديد!" معرفي كنند.)

milligator
جمعه 05 شهریور 1389, 11:11 صبح
به به!
خدا رو شكر. بالاخره خودم به جواب رسيدم.
خيلي ساده و دقيقاً به همون سادگي كه فكرشو ميكردم. اينم نمونه كد براي كساني كه دوست دارند از اين روش استفاده كنند:


public partial class Form1 : Form
{
const int HOTKEY_ID = 31197; //any number to be used as an id within this app
const int WM_HOTKEY = 0x0312;

public enum KeyModifiers //enum to call 3rd parameter of RegisterHotKey easily
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
//API Imports
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(
IntPtr hWnd, // handle to window
int id, // hot key identifier
KeyModifiers fsModifiers, // key-modifier options
Keys vk // virtual-key code
);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(
IntPtr hWnd, // handle to window
int id // hot key identifier
);

protected override void WndProc(ref Message msg)
{
// Listen for operating system messages.
switch (msg.Msg)
{
case WM_HOTKEY:
// this is the block the app turns in if the hotkey has been pressed
//so do your f@cking hotkey stuff here :-D
MessageBox.Show("F11 Pressed!");
break;
}
base.WndProc(ref msg);
}
//globalKeyboardHook myHookKeyBoard = new globalKeyboardHook();
public Form1()
{
InitializeComponent();
bool bcheck = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.None, Keys.F11);

}
}

از آقاي كشاورز عزيز هم ممنون و سپاسگذارم كه وقت گذاشتند و راه حل معرفي كردند. كه با استفاده از همين چند لغتي كه به لاتين نوشتند، جستجوهاي جديدي كردم و بالاخره به جواب رسيدم: خواستن توانست است.
دقيقاً هم حرف ايشون درست بود. كه هيچ نيازي به هوك كردن نيست و ...

موفق باشيد.
(از مديران ممنون ميشم كه اين تاپيك رو به عنوان "تاپيك به عنوان راه حل انتخاب گرديد!" معرفي كنند.)

سلام
از کدتون ممنون
در قسمت base.WndProc(ref msg); دائم خودش را اجرا میکنه.نمیشه یه طور دیگه نوشتش که فقط موقع زدن کلید میانبر برنامه بیدار بشه.تقریبا مثل thread.sleep , thread.wakeup
سوال دیگه اینکه برای تعریف چند کلید میانبر که با زدن هر کدوم کار مشخصی را انجام بده.
من کلید ها را تعریف کردم ولی در قسمت switch (msg.Msg) من case WM_HOTKEY2 هم تعریف کردم ولی هر بار فقط case WM_HOTKEY اجرا میشه و case WM_HOTKEY2 اجرا نمیشه



const int HOTKEY_ID = 31197; //any number to be used as an id within this app

const int HOTKEY2_ID = 31198; //any number to be used as an id within this app

const int WM_HOTKEY = 0x0312;

const int WM_HOTKEY2 = 0x0313;

//***********************************

bool bcheck = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.Alt, Keys.Oemcomma);
bool bcheck2 = RegisterHotKey(Handle, HOTKEY2_ID, KeyModifiers.Alt, Keys.OemQuestion);

ali_autumnal
جمعه 05 شهریور 1389, 11:22 صبح
سلام

این فایل قبلا توسط یکی از دوستان تو همین سایت گذاشته بودن که کاملا درست عمل میکنه.

میتونید ازش استفاده کنید.

موفق باشید
علی پاییزی

milligator
یک شنبه 07 شهریور 1389, 13:54 عصر
سلام
از کدتون ممنون
در قسمت base.WndProc(ref msg); دائم خودش را اجرا میکنه.نمیشه یه طور دیگه نوشتش که فقط موقع زدن کلید میانبر برنامه بیدار بشه.تقریبا مثل thread.sleep , thread.wakeup
سوال دیگه اینکه برای تعریف چند کلید میانبر که با زدن هر کدوم کار مشخصی را انجام بده.
من کلید ها را تعریف کردم ولی در قسمت switch (msg.Msg) من case WM_HOTKEY2 هم تعریف کردم ولی هر بار فقط case WM_HOTKEY اجرا میشه و case WM_HOTKEY2 اجرا نمیشه



const int HOTKEY_ID = 31197; //any number to be used as an id within this app

const int HOTKEY2_ID = 31198; //any number to be used as an id within this app

const int WM_HOTKEY = 0x0312;

const int WM_HOTKEY2 = 0x0313;

//***********************************

bool bcheck = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.Alt, Keys.Oemcomma);
bool bcheck2 = RegisterHotKey(Handle, HOTKEY2_ID, KeyModifiers.Alt, Keys.OemQuestion);




پس از کلنجار رفتن با این کد متوجه شدم که همه ی اشکال بر می گرده به قسمت قرمز رنگ.
در واقع const int WM_HOTKEY فقط باید مقدار 0x0312 را داشته باشه تا اجرا بشه.وگرنه اصلا تشخیص نمیده.
حالا سوال اینجاس که مقدار 0x0312 برای ثابت const int WM_HOTKEY از کجا حاصل میشه؟
مثال های دیگر




private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x020A;
private const int WM_NCCALCSIZE =0x0083;
private const int WM_PAINT =0x000F;
private const int WM_SIZE =0x0005;

milligator
یک شنبه 07 شهریور 1389, 14:41 عصر
مشکل multi hot key را حل کردم



public partial class Form1 : Form
{
const int HOTKEY_ID = 31197; //any number to be used as an id within this app

const int HOTKEY2_ID = 31195;
const int WM_HOTKEY = 0x0312;

public enum KeyModifiers //enum to call 3rd parameter of RegisterHotKey easily
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
//API Imports
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(
IntPtr hWnd, // handle to window
int id, // hot key identifier
KeyModifiers fsModifiers, // key-modifier options
Keys vk // virtual-key code
);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(
IntPtr hWnd, // handle to window
int id // hot key identifier
);

protected override void WndProc(ref Message msg)
{
// Listen for operating system messages.
switch (msg.Msg)
{
case WM_HOTKEY:


switch (msg.WParam.ToInt64())
{
case(HOTKEY_ID):
MessageBox.Show("F11 Pressed!");
break;
case(HOTKEY2_ID):

MessageBox.Show("F12 Pressed!");
break;

}


break;
}
base.WndProc(ref msg);
}
//globalKeyboardHook myHookKeyBoard = new globalKeyboardHook();
public Form1()
{
InitializeComponent();
bool bcheck = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.None, Keys.F11);
bool bcheck2 = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.None, Keys.F12);

}
}





کافیه که قسمت های قرمز رنگ را اعمال کنید

با تشکر از newgoldenman

kamran00f
شنبه 10 مهر 1389, 15:16 عصر
به به!
خدا رو شكر. بالاخره خودم به جواب رسيدم.
خيلي ساده و دقيقاً به همون سادگي كه فكرشو ميكردم. اينم نمونه كد براي كساني كه دوست دارند از اين روش استفاده كنند:


public partial class Form1 : Form
{
const int HOTKEY_ID = 31197; //any number to be used as an id within this app
const int WM_HOTKEY = 0x0312;

public enum KeyModifiers //enum to call 3rd parameter of RegisterHotKey easily
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
//API Imports
[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(
IntPtr hWnd, // handle to window
int id, // hot key identifier
KeyModifiers fsModifiers, // key-modifier options
Keys vk // virtual-key code
);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(
IntPtr hWnd, // handle to window
int id // hot key identifier
);

protected override void WndProc(ref Message msg)
{
// Listen for operating system messages.
switch (msg.Msg)
{
case WM_HOTKEY:
// this is the block the app turns in if the hotkey has been pressed
//so do your f@cking hotkey stuff here :-D
MessageBox.Show("F11 Pressed!");
break;
}
base.WndProc(ref msg);
}
//globalKeyboardHook myHookKeyBoard = new globalKeyboardHook();
public Form1()
{
InitializeComponent();
bool bcheck = RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.None, Keys.F11);

}
}

از آقاي كشاورز عزيز هم ممنون و سپاسگذارم كه وقت گذاشتند و راه حل معرفي كردند. كه با استفاده از همين چند لغتي كه به لاتين نوشتند، جستجوهاي جديدي كردم و بالاخره به جواب رسيدم: خواستن توانست است.
دقيقاً هم حرف ايشون درست بود. كه هيچ نيازي به هوك كردن نيست و ...

موفق باشيد.
(از مديران ممنون ميشم كه اين تاپيك رو به عنوان "تاپيك به عنوان راه حل انتخاب گرديد!" معرفي كنند.)

ممنون از این تاپیکی که گذاشتید، مشکل منو حل کرد ولی با یه مقدار کمی تغییر! نمی دونم چرا اون جوری که شما گفتید برای من اون HotKey فقط یه بار کار میکرد تو برنامه یعنی انگار وقتی که از HotKey استفاده میشد خودش UnRegister میشد. تا اینکه این تغییر رو تو کد برنامه دادم که با هربار استفاده از HotKey دوباره همونو Register میکرد(خط قرمز رنگ کد پایین). این طوری برنامم درست شد. حتی آخر برنامه هم که میخواستم Unregister کنمش Error میداد ولی توضیحاتی تو Error نبود که ببینم چی میگه. احتمالا می خواسته بگه که همچین چیزی Register نشده اصلا! کسی میدونه مشکل چیه و چرا این اتفاق واسه من افتاده و آیا مشکل خاصی پیش میاد با این طرز کد نوشتن من؟!

البته شرط چک کردن HotKey به نظرم صحیح نبود که اونم تو کد زیر اصلاح کردم که فک نمی کنم تاثیری داشته باشه تو سوالی که پرسیدم.




protected override void WndProc(ref Message msg)
{

if(msg.Msg == WM_HOTKEY)
// Listen for operating system messages.
switch (msg.Wparam.ToInt64())
{
case HOTKEY_ID:
// this is the block the app turns in if the hotkey has been pressed
MessageBox.Show("F11 Pressed!");

RegisterHotKey(Handle, HOTKEY_ID, KeyModifiers.None, Keys.F11);
break;
}
base.WndProc(ref msg);
}