本文是講述使用C# WPF製作仿QQ截圖工具的方法。
1. 註冊快捷鍵
QQ的截圖工具,當我們按下Ctrl + Alt + A鍵的時候就可以激活截圖程序。
首先第一步就是要註冊快捷鍵。這裏需要引用到“user32.dll”。對於Win32的API,調用起來還是需要dllimport的。
我們聲明一個Hotkey類,導入相應的方法。
class HotKey
{
//調用WIN32的API
[DllImport("user32.dll", SetLastError = true)]
//聲明註冊快捷鍵方法,方法實體dll中。參數爲窗口句柄,快捷鍵自定義ID,Ctrl,Shift等功能鍵,其他按鍵。
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll", SetLastError = true)]
//註銷快捷鍵方法的聲明。
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
在程序開始,Windows_Loaded方法中就要對快捷鍵進行註冊。
方法是首先獲取窗口句柄。可能C#的程序員對於句柄這個概念比較陌生,因爲語言的高度封裝。但是因爲我們調用的是Win32的方法,還是要自己一步一步去做的。
然後再註冊表中註冊一個鍵值,添加hook監聽窗口事件。通過重寫winproc,相應鍵盤快捷鍵。
這一部分都是Win32程序設計的內容。
/// <summary>
/// 窗體建立完成時調用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Handle = new WindowInteropHelper(this).Handle; //獲取窗口句柄
RunHotKey(); //註冊並監聽HotKey
}
/// <summary>
/// 添加快捷鍵監聽
/// </summary>
private void RunHotKey()
{
RegisterHotKey(); //註冊截圖快捷鍵
HwndSource source = HwndSource.FromHwnd(Handle);
if (source != null)
source.AddHook(WndProc); //添加Hook,監聽窗口事件
}
/// <summary>
/// 註冊快捷鍵
/// </summary>
private void RegisterHotKey()
{
//101爲快捷鍵自定義ID,0x0002爲Ctrl鍵, 0x0001爲Alt鍵,或運算符|表同時按住兩個鍵有效,0x41爲A鍵。
bool isRegistered = HotKey.RegisterHotKey(Handle, 101, (0x0002 | 0x0001), 0x41);
if (isRegistered == false)
{
System.Windows.MessageBox.Show("截圖快捷鍵Ctrl+Alt+A被佔用", "警告", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
/// <summary>
/// 重寫WndProc函數,類型爲虛保護,響應窗體消息事件
/// </summary>
/// <param name="hwnd"></param>
/// <param name="msg">消息內容</param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <param name="handled">是否相應完成</param>
/// <returns></returns>
protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
//0x0312表示事件消息爲按下快捷鍵
case 0x0312:
CatchScreen();
break;
}
return IntPtr.Zero;
}
2. 程序思路
當我們使用QQ截圖時,一開始會有一個灰色的遮罩,然後鼠標按下後移動,建立選區,選區中遮罩消失並可以拖動選區。
爲了實現這一功能,我們在設置遮罩時可以考慮如下方法:
首先拷貝當前屏幕作爲底層。定義4個遮罩層,當鼠標按下後,捕捉鼠標移動的位置,實時調整遮罩區的大小和位置。
選取就是沒有遮罩而露出底層的部分。
3. 初始化遮罩
我製作的時候遮罩和底層都使用的是Canvas。
初始化時,將整個屏幕拷貝到底層,添加黑色遮罩。
/// <summary>
/// 獲取本機分辨率
/// </summary>
private void GetScreenSize()
{
Width = SystemParameters.PrimaryScreenWidth;
Height = SystemParameters.PrimaryScreenHeight;
}
/// <summary>
/// 初始化截圖,截取把整個屏幕並顯示
/// </summary>
private void InitializeImage()
{
GetScreenSize();
image = new Bitmap(Convert.ToInt32(Width), Convert.ToInt32(Height)); //設置截圖區域大小爲整個屏幕
using (Graphics g = Graphics.FromImage(image))
{
g.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(Convert.ToInt32(Width), Convert.ToInt32(Height))); //複製當前屏幕到畫板上,即將截屏圖片的內容設置爲當前屏幕
}
BitmapSource bimage = BitmapToBitmapSource(image);
ImageBrush b = new ImageBrush();
b.ImageSource = bimage;
b.Stretch = Stretch.None;
this.Background = b; //將截屏設爲背景
mask.Height = Height;
mask.Width = Width;
InitializeMask(); //添加黑色遮罩
CompositionTarget.Rendering += UpdateSelection; //註冊窗體重繪事件
}
上方的代碼中涉及到了一個Bitmap與BitmapSource的轉化,因爲在WPF程序中控件只能用BitmapSource,所以會比較麻煩,如果是做WInform則直接使用Bitmap。
添加黑色遮罩的代碼在此略去,就是在底層Canva中添加4個Canva作爲遮罩層。
4. 鼠標動作捕捉
截圖中涉及到的鼠標動作爲:鼠標按下表示開始截圖,按下後拖動表示改變選區,鼠標放開表示完成截圖,完成截圖後鼠標按下拖動選區,在選取邊緣按下鼠標後移動縮放選區。
對於這些事件,只需要對於底層Canvas的MouseLeftButtonDown,MouseMove和MouseLeftButtonUp事件進行處理即可。處理時記錄狀態是正在截圖還是截圖完成後調整大小。
對於這些事件的處理就只是根據當前鼠標位置改變4個Canvas遮罩區的大小,代碼省略。
5.選區框的製作
設計如下圖所示的選取框的方法:
鼠標按下時記錄起始位置,鼠標放開記錄終止位置。根據這兩個位置可以確定一個矩形,即上圖中的選區。矩形有4個頂點和邊的4箇中點處分別畫小矩形(System.Windows.Shapes.Rectangle)。記錄矩形的中間位置。設定鼠標移動到特定位置時光標的圖案。
6. 保存圖片
根據選區位置,直接切割底層顯示的原始圖片,獲取截圖,然後保存。這一部分沒什麼說的,直接看代碼。
/// <summary>
/// 保存圖片
/// </summary>
private void SaveImage()
{
//用當前時間作爲文件名
string time = DateTime.Now.ToString();
//去除時間中的非法字符
string filename = "截圖";
foreach (char symbol in time)
{
if (symbol != '/' && symbol != ':' && symbol != ' ')
filename += symbol;
}
if (StartPoint == FinalPoint)
{
System.Windows.MessageBox.Show("未選擇任何像素", "錯誤", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
GetImage();
SaveFileDialog f = new SaveFileDialog();
f.Filter = "位圖格式(*.bmp)|*.bmp|增強型圖元文件(*.wmf)|*.wmf|可交換圖像文件(*.exif)|*.exif|圖形交換格式(*.gif)|*.gif|Windows圖標圖像格式(*.ico)|*.ico|聯合圖像專家組(*.jpeg)|*.jpeg|W3C可移植網絡圖形(*.png)|*.png|標記圖像文件格式(*.tiff)|*.tiff|Windows圖元文件(*.wmf)|*.wmf";
f.FilterIndex = 6;
f.RestoreDirectory = true;
f.FileName = filename;
f.Title = "保存截圖";
System.Windows.Forms.DialogResult b = f.ShowDialog();
if (b == System.Windows.Forms.DialogResult.OK)
{
switch (f.FilterIndex)
{
case 1:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Bmp);
break;
case 2:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Emf);
break;
case 3:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Exif);
break;
case 4:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Gif);
break;
case 5:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Icon);
break;
case 6:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
break;
case 7:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Png);
break;
case 8:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Tiff);
break;
case 9:
bimage.Save(f.FileName, System.Drawing.Imaging.ImageFormat.Wmf);
break;
}
System.Windows.MessageBox.Show("截圖已保存", "保存成功", MessageBoxButton.OK);
}
}
private Bitmap bimage;
/// <summary>
/// 切分並獲取選中部分截圖
/// </summary>
private void GetImage()
{
bimage = new Bitmap(Convert.ToInt32(FinalPoint.X - StartPoint.X), Convert.ToInt32(FinalPoint.Y - StartPoint.Y));
using (Graphics g = Graphics.FromImage(bimage))
{
g.DrawImage(image, new System.Drawing.Rectangle(0, 0, Convert.ToInt32(FinalPoint.X - StartPoint.X), Convert.ToInt32(FinalPoint.Y - StartPoint.Y)), new System.Drawing.Rectangle(Convert.ToInt32(StartPoint.X), Convert.ToInt32(StartPoint.Y), Convert.ToInt32(FinalPoint.X - StartPoint.X), Convert.ToInt32(FinalPoint.Y - StartPoint.Y)), GraphicsUnit.Pixel);
System.Windows.Forms.Clipboard.SetImage(bimage);
}
}