2016年8月19日 星期五

用c#寫一個按下PrintScreen立即存檔的程式



c#寫一個按下PrintScreen立即存檔的程式

本文介紹一個用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()

打開螢幕的方法是送一個命令代碼給作業系統這是從很久以前Win32Message 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


沒有留言:

張貼留言