欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#?wpf實現截屏框熱鍵截屏的示例代碼

 更新時間:2023年09月07日 09:05:27   作者:CodeOfCC  
這篇文章主要為大家詳細介紹了C#?wpf實現截屏框熱鍵截屏的相關知識,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以學習一下

前言

在《C# wpf 使用DockPanel實現截屏框》中我們實現了一個截屏框,接下來就要實現相應的截屏功能了。獲取截屏區(qū)域然后使用GDI+截屏,在這里不少的細節(jié)需要處理,比如響應熱鍵彈出截屏界面、點擊拖出截屏框、截屏區(qū)域任意反向拖動、處理不同dpi下的坐標位置等等。

一、實現步驟

1、響應熱鍵

我們直接使用win32 api的RegisterHotKey和UnregisterHotKey即可。在Window的SourceInitialized事件中注冊熱鍵,如下是注冊alt+d為熱鍵的示例代碼

[System.Runtime.InteropServices.DllImport("user32")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint controlKey, uint virtualKey);
[System.Runtime.InteropServices.DllImport("user32")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

HotKey是對RegisterHotKey、UnregisterHotKey做了封裝的對象,網上可以搜到此處略。

 private void Window_SourceInitialized(object sender, EventArgs e)
 {
     //注冊alt+d熱鍵,0x44為d,其他虛擬鍵值請查看:https://learn.microsoft.com/zh-tw/windows/win32/inputdev/virtual-key-codes
     HotKey k = new HotKey(this, HotKey.KeyFlags.MOD_ALT, 0x44);
     k.OnHotKey += K_OnHotKey;
     Visibility = Visibility.Collapsed;
 }

2、截屏顯示

(1)獲取屏幕區(qū)域

我們需要使用win32 api獲取屏幕區(qū)域,采用wpf的方法取得的屏幕分辨率是基于dpi的,就算是用PointToScreen進行轉換,在程序運行過程中改了系統(tǒng)dpi后依然會不準確,所以需要直接取得屏幕的實際像素分辨率,用于gdi+截屏。

  const int DESKTOPVERTRES = 117;
        const int DESKTOPHORZRES = 118;
        [DllImport("gdi32.dll")]
        static extern int GetDeviceCaps(
   IntPtr hdc, // handle to DC  
   int nIndex // index of capability  
   );
        [DllImport("user32.dll")]
        static extern IntPtr GetDC(IntPtr ptr);
        [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
        static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
        /// <summary>  
        /// 獲取真實設置的桌面分辨率大小  
        /// </summary>  
        static Size DESKTOP
        {
            get
            {
                IntPtr hdc = GetDC(IntPtr.Zero);
                Size size = new Size();
                size.Width = GetDeviceCaps(hdc, DESKTOPHORZRES);
                size.Height = GetDeviceCaps(hdc, DESKTOPVERTRES);
                ReleaseDC(IntPtr.Zero, hdc);
                return size;
            }
        }

(2)截取并顯示

利用上面步驟獲取到的截屏區(qū)域,結合《C# wpf 使用GDI+實現截屏》里的簡單截屏即完成。取得Bitmap對象后,參考我的另一篇文章《C# wpf Bitmap轉換成WriteableBitmap(BitmapSource)的方法》將其轉換為轉換成wpf對象,然后通過ImageBrush賦值為控件的Background即可以顯示在控件上。

//截屏并顯示到窗口
void Snapshot()
{
    //獲取桌面實際分辨率,可以解決程序運行后修改dpi,截圖區(qū)域不正常的問題
    var leftTop = new Point(0, 0);
    var rightBottom = new Point(DESKTOP.Width, DESKTOP.Height);
    var bitmap = Snapshot((int)leftTop.X, (int)leftTop.Y, (int)(rightBottom.X - leftTop.X), (int)(rightBottom.Y - leftTop.Y));
    var bmp = BitmapToWriteableBitmap(bitmap);
    bitmap.Dispose();
    //顯示到窗口
    grdGlobal.Background = new ImageBrush(bmp);
}

3、自動捕獲窗口

qq和微信的截屏都有自動捕獲窗口功能,我們也可以自己實現這種功能。

(1)獲取系統(tǒng)所有窗口

通過win32 api可以枚舉系統(tǒng)所有窗口,我們需要將所有窗口的位置大小記錄下來,網上可以找到WindowList相關代碼此處略。

//獲取桌面所有窗口
_windows = WindowList.GetAllWindows();
IntPtr hwnd = new WindowInteropHelper(this).Handle;
//去除不可見窗口以及自己
_windows.RemoveAll((ele) => { return !ele.isVisible || ele.Handle == hwnd; });

(2)根據鼠標位置搜索窗口

//窗口是以z順序排列的查找到第一個匹配的窗口即可
var screenPoint = grdGlobal.PointToScreen(point);
foreach (var window in _windows)
{
    if (window.rect.Contains(screenPoint))
    //獲取在鼠標所在區(qū)域的窗口
    {
        try
        {
            if (window.rect.Right > window.rect.Left && window.rect.Bottom > window.rect.Top)
            //
            {
                var topLeft = grdGlobal.PointFromScreen(window.rect.TopLeft);
                var bottomRight = grdGlobal.PointFromScreen(window.rect.BottomRight);
                Thickness thickness = new Thickness(topLeft.X, topLeft.Y, grdGlobal.ActualWidth - bottomRight.X, grdGlobal.ActualHeight - bottomRight.Y);
                 //修正邊界
                if (thickness.Left < 0) thickness.Left = 0;
                if (thickness.Top < 0) thickness.Top = 0;
                if (thickness.Right < 0) thickness.Right = 0;
                if (thickness.Bottom < 0) thickness.Bottom = 0;
                //將截屏框顯示在窗口位置
                leftPanel.Width = thickness.Left;
                topPanel.Height = thickness.Top;
                rightPanel.Width = thickness.Right;
                bottomPanel.Height = thickness.Bottom;
                break;
            }
        }
        catch { }
    }
}

(3)效果預覽

4、點擊拖出截屏框

出現截屏界面之后,參考qq或微信的實現,第一次點擊是可以拖出截屏框框選的。如果是采樣繪制的方法很簡單,直接繪制矩形就可以了。但是基于控件要實現這個功能需要一定的技巧,在《C# wpf 使用DockPanel實現截屏框》的基礎上實現這個功能。

(1)移動到點擊位置

在鼠標按下事件或移動實現中

//將截屏框移動到點擊位置
leftPanel.Width = p.X;
topPanel.Height = p.Y;
rightPanel.Width = grdGlobal.ActualWidth - p.X;
bottomPanel.Height = grdGlobal.ActualHeight - p.Y;

(2)模擬按下事件

接著上面的代碼,thumb為右下角拖動點。

//手動觸發(fā)截屏框滑塊拖動事件
MouseButtonEventArgs downEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{ RoutedEvent = FrameworkElement.MouseLeftButtonDownEvent };
thumb.RaiseEvent(downEvent);

(3)修正偏移

由于是模擬的點擊事件,可能會出現鼠標不在Thumb上的情況,此時需要對thumb位置進行修正,在Thumb的DragStarted事件中記錄偏移。

//滑塊需要的偏移量
Point? _thumbOffset;
var thumb = sender as FrameworkElement;
if (!new Rect(0, 0, thumb.ActualWidth, thumb.ActualHeight).Contains(new Point(e.HorizontalOffset, e.VerticalOffset)))
//鼠標起始位置超出了控件范圍,則記錄中心點偏移在拖動時修正
{
    _thumbOffset = new Point(e.HorizontalOffset - thumb.ActualWidth / 2, e.VerticalOffset - thumb.ActualHeight / 2);
}

在Thumb的DragDelta事件中添加修正邏輯

var horizontalChange = e.HorizontalChange;
var verticalChange = e.VerticalChange;
if (_thumbOffset != null)
//修正偏移
{
    horizontalChange += _thumbOffset.Value.X;
    verticalChange += _thumbOffset.Value.Y;
}

(4)效果預覽

5、反向拖動

這一步不是必須的,但是有的話操作體驗會更好,比如qq和微信的截圖就支持反向拖動。如果我們使用gdi或gdi+繪制截屏框則天然支持反向拖動,因為RECT的大小可以為負數。但是基于控件則有一定的難度了,由于控件寬高不能為負數,我們需要實現事件轉移機制,依然是在《C# wpf 使用DockPanel實現截屏框》的基礎上實現這個功能。

(1)判斷邊界

原本《C# wpf 使用DockPanel實現截屏框》的邏輯的Thumb到了邊界就不進行任何操作了,現在要拓展為到達邊界則進行事件轉移。橫向的Thumb

if (width >= 0)
{
    leftPanel.Width = left >= 0 ? left : 0;
    rightPanel.Width = right >= 0 ? right : 0;
}
else{
//此處將事件轉移到反方向的Thumb
}

縱向的Thumb

if (height >= 0)
{
    topPanel.Height = top >= 0 ? top : 0;
    bottomPanel.Height = bottom >= 0 ? bottom : 0;
}
else
{
//此處將事件轉移到反方向的Thumb
}

(2)事件轉移

//當前的Thumb觸發(fā)鼠標彈起事件,結束拖動
MouseButtonEventArgs upEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{ RoutedEvent = FrameworkElement.MouseLeftButtonUpEvent };
thumb.RaiseEvent(upEvent);
//反方向的Thumb觸發(fā)鼠標按下事件,開始拖動
MouseButtonEventArgs downEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left)
{ RoutedEvent = FrameworkElement.MouseLeftButtonDownEvent };
t.RaiseEvent(downEvent);

(3)修正邊界

完成上述兩步之后已經可以做到反向拖動了,但是會有個問題,當多動過快的時截屏框的位置會發(fā)生移動,要解決這個問題則需要在事件轉移時修正邊界位置,即使兩條邊重合。橫向的Thumb

if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
//從左到右轉移的修正
{
    leftPanel.Width = grdGlobal.ActualWidth - rightPanel.Width;
}
else
//從右到左轉移的修正
{
    rightPanel.Width = grdGlobal.ActualWidth - leftPanel.Width;
}

縱向的Thumb

 if (thumb.VerticalAlignment == VerticalAlignment.Top)
 //從上到下轉移的修正
 {
     topPanel.Height = grdGlobal.ActualHeight - bottomPanel.Height;
 }
 else
 //從下到上轉移的修正
 {
     bottomPanel.Height = grdGlobal.ActualHeight - topPanel.Height;
 }

(4)效果預覽

6、截取圖片

由于前面截取是整個桌面的圖像,保存時需要根據截屏框截取畫面,我們使用WriteableBitmap對象就可以實現。

//獲取截屏框的圖片
WriteableBitmap GetClipImage()
{
    var bursh = grdGlobal.Background as ImageBrush;
    if (bursh != null)
    {
        //裁剪
        //全屏圖片
        var screenWb = bursh.ImageSource as WriteableBitmap;
        //獲取截取區(qū)域
        var leftTop = clipRect.PointToScreen(new Point(0, 0));
        var rightBottom = clipRect.PointToScreen(new Point(clipRect.ActualWidth, clipRect.ActualHeight));
        var rect = new Int32Rect((int)leftTop.X, (int)leftTop.Y, (int)(rightBottom.X - leftTop.X), (int)(rightBottom.Y - leftTop.Y));
        //創(chuàng)建截取圖片對象
        var wb = new WriteableBitmap(rect.Width, rect.Height, 0, 0, screenWb.Format, null);
        //寫入截取區(qū)域數據
        wb.WritePixels(rect, screenWb.BackBuffer, screenWb.PixelHeight * screenWb.BackBufferStride, screenWb.BackBufferStride, 0, 0);
        return wb;
    }
    return null;
}

7、設置粘貼板

直接使用Clipboard.SetImage即可,參數類型為BitmapSource,是WriteableBitmap的基類。

 Clipboard.SetImage(GetClipImage());

二、關于dpi

1、適配不同dpi

有處理dpi不同的情況,在任意dpi下都能正常截圖。

2、不支持dpi實時修改

(1)現象

程序啟動后實時修改dpi,截屏顯示的畫面會模糊,主要原因是不同api之間的dpi計算不統(tǒng)一。系統(tǒng)dpi實時修改后wpf界面會響應oloaded自動調整大小,但部分程序內部的dpi(比如getWindowRect)是不會變化的,尤其是渲染圖片依然按照程序啟動時的dpi去計算,所以會進行縮放,顯示的畫面必然模糊。

這里舉一個具體的例子流程如下:

win11 分辨率1920x1080

1、初始系統(tǒng)dpi為120(1.25倍)

2、程序啟動

3、程序dpi為120

5、全屏窗口大小1536x864,通過winapi獲取則是1920x1080,截屏1920x1080顯示,截屏畫面無損

6、系統(tǒng)dpi設置為96(1倍)

7、此時程序dpi為120

8、全屏窗口大小1920x1080,通過winapi獲取則是2400x1350,截屏1920x1080顯示,截屏畫面模糊。

按像素點繪制,畫面顯示在左上角無法充滿窗口。

(2)嘗試的解決方案

筆者采樣了多種方式嘗試解決

1、提前縮放圖片再顯示。

2、參考微軟解決dpi問題的方法。

3、使用gdi+的graphics直接通過hdc以像素點為單位繪制。

4、使用gdi的bitblt進行hdc拷貝。

以上方法都沒效果畫面依然模糊。

3、建議

需要支持dpi實時改變,可以將截圖功能作為單獨的程序,響應熱鍵后再啟動。

三、效果預覽

1、截屏粘貼到qq

2、截屏保存到文件

總結

本文介紹了wpf截屏框熱鍵截屏的方法。需要實現的功能還是比較多的,而且有些功能難度也不小,幾經嘗試才找到合適的實現方法,至于實時改變dpi的模糊的問題,這個目前的結論是無法解決,這并不是wpf的局限,用c++ mfc也不行,除非存在一個設置程序全局dpi的winapi接口筆者沒有發(fā)現。所以這個問題目前只能通過獨立程序啟動解決。但是總的來說實現的效果是很不錯的,尤其是反向拖動,通過事件轉移的方式實現,界面操作還是很流暢。

到此這篇關于C# wpf實現截屏框熱鍵截屏的示例代碼的文章就介紹到這了,更多相關C# wpf截屏內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論