本文是讲述使用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程序设计的内容。
///
/// 窗体建立完成时调用
///
///
///
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Handle = new WindowInteropHelper(this).Handle; //获取窗口句柄
RunHotKey(); //注册并监听HotKey
}
///
/// 添加快捷键监听
///
private void RunHotKey()
{
RegisterHotKey(); //注册截图快捷键
HwndSource source = HwndSource.FromHwnd(Handle);
if (source != null)
source.AddHook(WndProc); //添加Hook,监听窗口事件
}
///
/// 注册快捷键
///
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);
}
}
///
/// 重写WndProc函数,类型为虚保护,响应窗体消息事件
///
///
/// 消息内容
///
///
/// 是否相应完成
///
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。
初始化时,将整个屏幕拷贝到底层,添加黑色遮罩。
///
/// 获取本机分辨率
///
private void GetScreenSize()
{
Width = SystemParameters.PrimaryScreenWidth;
Height = SystemParameters.PrimaryScreenHeight;
}
///
/// 初始化截图,截取把整个屏幕并显示
///
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. 保存图片
根据选区位置,直接切割底层显示的原始图片,获取截图,然后保存。这一部分没什么说的,直接看代码。
///
/// 保存图片
///
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;
///
/// 切分并获取选中部分截图
///
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);
}
}