用c#寫一個按下PrintScreen立即存檔的程式
本程式須在 "有畫面" 時候才能使用,如果因螢幕保護而回到登入畫面],就無畫面可以抓取!程式中用到KeyHook類別[1],用來跟系統鍵盤掛勾(或稱攔截),監聽是否PrintScreen按鍵被按下,Hook()函數如下:
public void Hook()
{
if (m_HookHandle == 0)
{
using (Process
curProcess = Process.GetCurrentProcess())
using (ProcessModule
curModule = curProcess.MainModule)
{
m_KbdHookProc = new HookProc(KeyHooker.KeyboardHookProc);
m_HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, m_KbdHookProc,
GetModuleHandle(curModule.ModuleName), 0);
}
if (m_HookHandle == 0)
{
MessageBox.Show("呼叫 SetWindowsHookEx 失敗!");
return;
}
}
}
|
攔截函數 KeyboardHookProc() 掛勾後,每次按鍵被按下,都會被呼叫,可用來判斷按鍵:
public static int KeyboardHookProc(int
nCode, IntPtr wParam, IntPtr lParam)
{
const int
WM_KEYDOWN = 0x100;
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
Keys vkCode = (Keys)kbd.vkCode;
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) //Key
Pressed
{
if (vkCode == Keys.PrintScreen)
{
IsPrintScreenPressed = true;
}
}
return CallNextHookEx(m_HookHandle, nCode, wParam,
lParam);
}
|
為了避免重複執行,主程式在Form_Load事件中先判斷是否已經有proc執行,然後才跟keyboard掛勾:
private void
PrintScreenForm_Load(object sender, EventArgs e)
{
// 判斷是否已有程式在執行
string proc = Process.GetCurrentProcess().ProcessName;
Process[] processes = Process.GetProcessesByName(proc);
if (processes.Length > 1)
{
MessageBox.Show("PrintScreen
" + proc + " 已經在執行", "重複執行錯誤", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Close();
}
LoadConfig();
KeyHookHandler.Hook();
timerKeyboard.Enabled = true;
} //PrintScreenForm_Load()
|
本程式並非在按下PrintScreen後產生call back 回主程式呼叫,而是用計時器去檢查按鍵PrintScreen是否被按下,是的話就呼叫ScreenCapture()函數:
private void
timerKeyboard_Tick(object sender, EventArgs e)
{
if (KeyHooker.IsPrintScreenPressed)
//有鍵盤輸入
{
ScreenCapture();
KeyHooker.IsPrintScreenPressed = false;
}
} //imerKeyboard_Tick()
|
ScreenCapture() 會抓取所有螢幕畫面存檔,如果有2個螢幕亦可同時抓取。這裡要注意的是,如果螢幕在休眠中,要先打開螢幕,否則畫面不會更新。
private void
ScreenCapture()
{
int MonitorIndex = 0; //default
monitor number
// Turn on screen before capture to enable buffer refresh
SetMonitorState(MonitorState.ON);
Thread.Sleep(100); //wait
monitor on
using (Bitmap
bmpScreenCapture = new Bitmap(Screen.AllScreens[MonitorIndex].Bounds.Width,
Screen.AllScreens[MonitorIndex].Bounds.Height))
{
using (Graphics
g = Graphics.FromImage(bmpScreenCapture))
{
g.CopyFromScreen(Screen.AllScreens[MonitorIndex].Bounds.X,
Screen.AllScreens[MonitorIndex].Bounds.Y,
0, 0,
bmpScreenCapture.Size,
CopyPixelOperation.SourceCopy);
}
BuildPath();
String FileName = "PrintScreen"
+ DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".jpg";
String
BmpFileName = FilePath + "\\"+
FileName;
bmpScreenCapture.Save(BmpFileName);
if (chkSound.Checked)
{
SoundPlayer simpleSound = new SoundPlayer(@"shutter.wav");
simpleSound.Play();
}
//if (chkBalloonTips.Checked && this.WindowState
== FormWindowState.Minimized)
{
notifyIcon.BalloonTipText = FileName;
notifyIcon.ShowBalloonTip(200);
}
}
} //ScreenCapture()
|
打開螢幕的方法是送一個命令代碼給作業系統,這是從很久以前Win32的Message Queue沿襲而來的程式架構,為了使用SendMessage(),在C#中需使用DllImport來引用非C#語言寫的系統user32.dll:
private int
SC_MONITORPOWER = 0xF170;
private uint
WM_SYSCOMMAND = 0x0112;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr
hWnd, uint Msg, IntPtr
wParam, IntPtr lParam);
private void
SetMonitorState(MonitorState state)
{
SendMessage(this.FindForm().Handle,
WM_SYSCOMMAND, (IntPtr)SC_MONITORPOWER, (IntPtr)state);
} //SetMonitorState(
|
畫面縮小到圖示與回復,主程式要使用Form_Resize() 事件,NotifyIcon則需使用MouseDoubleClick()
事件:
private void
PrintScreenForm_Resize(object sender, EventArgs e)
{
if (this.WindowState
== FormWindowState.Minimized)
{
this.ShowInTaskbar = false;
notifyIcon.Visible = true;
notifyIcon.BalloonTipText = "Print
Screen";
notifyIcon.ShowBalloonTip(300);
this.Hide();
}
} //PrintScreenForm_Resize()
private void
notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
this.WindowState = FormWindowState.Normal;
this.ShowInTaskbar
= true;
notifyIcon.Visible = false;
this.Show();
//this.BringToFront();
} //notifyIcon_MouseDoubleClick()
|
將使用者設定存檔,每次程式執行時自動載入上次的設定,設定檔PrintScreen.cfg的內容:
RootPath=C:\Users\ghosty\Desktop
BalloonTips=True
Sound=True
|
Config的載入與存檔:
void LoadConfig()
{
if (!File.Exists(ConfigFileName))
{
// Config not exists
RootPath = Environment.GetEnvironmentVariable("HOMEDRIVE")
+ Environment.GetEnvironmentVariable("HOMEPATH")
+ @"\ScreenCapture";
SaveConfig();
}
else
{
StreamReader ConfigFileStream = new StreamReader(ConfigFileName);
String Line;
while ((Line = ConfigFileStream.ReadLine()) != null)
{
if (Line[0] == '#')
{
//skip comment line
}
else if
(Line.Contains("RootPath="))
{
RootPath = Line.Substring("RootPath=".Length);
}
else if
(Line.Contains("BalloonTips="))
{
chkBalloonTips.Checked = Convert.ToBoolean(
Line.Substring("BalloonTips=".Length));
}
else if
(Line.Contains("Sound="))
{
chkSound.Checked = Convert.ToBoolean(Line.Substring("Sound=".Length));
}
}
ConfigFileStream.Close();
}
tbFilePath.Text = RootPath;
} // LoadConfig()
void SaveConfig()
{
StreamWriter ConfigFileStream = new StreamWriter(ConfigFileName);
ConfigFileStream.WriteLine("RootPath="
+ RootPath);
ConfigFileStream.WriteLine("BalloonTips="
+ chkBalloonTips.Checked);
ConfigFileStream.WriteLine("Sound="
+ chkSound.Checked);
ConfigFileStream.Close();
} //SaveConfig()
|
剩下的就是UI元件的事件處理:
private void
btnBrowse_Click(object sender, EventArgs e)
{
folderBrowserDialog.ShowDialog();
RootPath = tbFilePath.Text = folderBrowserDialog.SelectedPath;
SaveConfig();
} //btnBrowse_Click()
private void
chkBalloonTips_Click(object sender, EventArgs e)
{
SaveConfig();
} //chkBalloonTips_Click()
private void
chkSound_Click(object sender, EventArgs e)
{
SaveConfig();
} //chkSound_Click()
|
參考資料:
[1] “C# 全域鍵盤掛鉤(Global Keyboard Hook)範例”, http://www.dotblogs.com.tw/huanlin/archive/2008/04/23/3320.aspx
沒有留言:
張貼留言