WPF仿QQ截圖實現方法探討

QQ截圖看起來很簡單,但實際做起來比較複雜。下面先看看完成後的效果圖,後續再詳細說明開發歷程!

img

與QQ原生截圖還是挺像的吧。整個開發斷斷續續歷時一週左右,期間更換了三種不同思路,重構了2次代碼才最終有上圖展示效果。

廢話不說了,開始講代碼。

第一種方案(有坑):

參考地址:https://www.cnblogs.com/lonelyxmas/p/10754115.html

Demo下載:https://download.csdn.net/download/asciil/12473318

原理描述:

截圖其實一個透明Windows窗體覆蓋在最頂層,其中用InkCanvas畫布存儲截圖時的屏幕圖片。鼠標在其上按下左鍵拖動時,首先畫矩形(也就是截圖區域高亮部分),其次設置四周蒙層部分大小及位置(分爲上下左右四部分,以Rectangle元素實現)。

原理聽起來其實很簡單,但實際上會有問題。請看下面效果圖

img

看到了嗎,在拖動過程中鼠標處會有白色線條出現。這個現象產生的原因是由於Win10 DPI縮放引起的,通過找資料沒有發現到比較好的解決辦法,所以放棄此思路。(但就在我寫博客時,Windows1909版本更新了,然後我發現同樣的代碼居然沒有這個現象了)

貼上主要部分代碼:

  • 樣式文件(ScreenshotWindowStyle.xaml)

    <Style x:Key="MaskCanvasStyle" TargetType="Rectangle">
            <Setter Property="IsHitTestVisible" Value="False"/>
            <Setter Property="Fill" Value="#40000000"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
        </Style>
    ​
        <Style x:Key="SnapAreaStyle" TargetType="Border">
            <Setter Property="IsHitTestVisible" Value="False"/>
            <Setter Property="BorderThickness" Value="4"/>
            <Setter Property="BorderBrush" Value="DodgerBlue"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsShowTargetArea,RelativeSource={RelativeSource AncestorType=local:ScreenshotWindow}}" Value="True">
                    <Setter Property="IsHitTestVisible" Value="True"/>
                    <Setter Property="BorderThickness" Value="1"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    ​
        <Style x:Key="HotspotStyle" TargetType="Rectangle">
            <Setter Property="Height" Value="5"/>
            <Setter Property="Width" Value="5"/>
            <Setter Property="Fill" Value="DodgerBlue"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="LeftTop">
                    <Setter Property="Margin" Value="-3, -3, 0, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="TopCenter">
                    <Setter Property="Margin" Value="0, -3, 0, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="RightTop">
                    <Setter Property="Margin" Value="0, -3, -3, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="RightCenter">
                    <Setter Property="Margin" Value="0, 0, -3, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="RightBottom">
                    <Setter Property="Margin" Value="0, 0, -3, -3"/>
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="BottomCenter">
                    <Setter Property="Margin" Value="0, 0, 0, -3"/>
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="LeftBottom">
                    <Setter Property="Margin" Value="-3, 0, 0, -3"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="LeftCenter">
                    <Setter Property="Margin" Value="-3, 0, 0, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                </Trigger>
                <DataTrigger Binding="{Binding IsShowTargetArea,RelativeSource={RelativeSource AncestorType=local:ScreenshotWindow}}" Value="True">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    ​
        <Style TargetType="{x:Type local:ScreenshotWindow}">
            <Setter Property="UseLayoutRounding" Value="True"/>
            <Setter Property="WindowStyle" Value="None"/>
            <Setter Property="WindowState" Value="Maximized"/>
            <Setter Property="Topmost" Value="True"/>
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:ScreenshotWindow}">
                        <AdornerDecorator>
                            <Grid>
                                <!--背景畫布-->
                                <InkCanvas Name="PART_CANVAS" EditingMode="None" Background="Transparent"/>
                                <!--蒙層,分上下左右四部分-->
                                <Rectangle Name="PART_MASKAREALEFT" Style="{StaticResource MaskCanvasStyle}"/>
                                <Rectangle Name="PART_MASKAREATOP" Style="{StaticResource MaskCanvasStyle}"/>
                                <Rectangle Name="PART_MASKAREARIGHT" Style="{StaticResource MaskCanvasStyle}"/>
                                <Rectangle Name="PART_MASKAREABOTTOM" Style="{StaticResource MaskCanvasStyle}"/>
                                <!--截圖區域-->
                                <Border Name="PART_TARGETAREA" Style="{StaticResource SnapAreaStyle}">
                                    <Grid>
                                        <Rectangle local:ToolTipAttachProperty.Placement="LeftTop" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="TopCenter" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="RightTop" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="RightCenter" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="RightBottom" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="BottomCenter" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="LeftBottom" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="LeftCenter" Style="{StaticResource HotspotStyle}"/>
                                        <TextBlock Background="#40000000" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Top" Padding="5,2" Text="{TemplateBinding HintText}"/>
                                    </Grid>
                                </Border>
                            </Grid>
                        </AdornerDecorator>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

     

  • 後臺代碼(ScreenshotWindow.cs)

    public class ScreenshotWindow : Window
        {
            internal InkCanvas PART_BackgroupCanvas { get; set; }       //背景畫布
            internal FrameworkElement PART_TargetArea { get; set; }     //截圖區域
            internal FrameworkElement PART_MaskAreaLeft { get; set; }
            internal FrameworkElement PART_MaskAreaTop { get; set; }
            internal FrameworkElement PART_MaskAreaRight { get; set; }
            internal FrameworkElement PART_MaskAreaBottom { get; set; }
    ​
            private int ScreenWidth, ScreenHeight;                      //屏幕寬高
            private System.Drawing.Bitmap ScreenBitmap { get; set; }    //整個屏幕圖片
            private bool IsScreenshot;                                  //是否在截圖中
            private Point MouseDownPoint;                               //鼠標按下時座標點
            private System.Drawing.Rectangle RectTargetArea { get; set; }       //截圖區域矩形位置
    ​
            #region 依賴屬性
            public static readonly DependencyProperty IsShowTargetAreaProperty = DependencyProperty.Register("IsShowTargetArea", typeof(bool), typeof(ScreenshotWindow), new PropertyMetadata(false));
            /// <summary>
            /// 是否顯示截圖區域
            /// </summary>
            public bool IsShowTargetArea
            {
                get { return (bool)GetValue(IsShowTargetAreaProperty); }
                internal set { SetValue(IsShowTargetAreaProperty, value); }
            }
    ​
            public static readonly DependencyProperty HintTextProperty = DependencyProperty.Register("HintText", typeof(string), typeof(ScreenshotWindow), new PropertyMetadata(""));
            /// <summary>
            /// 截圖區域大小提示文字
            /// </summary>
            public string HintText
            {
                get { return (string)GetValue(HintTextProperty); }
                internal set { SetValue(HintTextProperty, value); }
            }
    ​
            #endregion
    ​
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    ​
                PART_BackgroupCanvas = GetTemplateChild("PART_CANVAS") as InkCanvas;
                PART_TargetArea = GetTemplateChild("PART_TARGETAREA") as FrameworkElement;
                PART_MaskAreaLeft = GetTemplateChild("PART_MASKAREALEFT") as FrameworkElement;
                PART_MaskAreaTop = GetTemplateChild("PART_MASKAREATOP") as FrameworkElement;
                PART_MaskAreaRight = GetTemplateChild("PART_MASKAREARIGHT") as FrameworkElement;
                PART_MaskAreaBottom = GetTemplateChild("PART_MASKAREABOTTOM") as FrameworkElement;
            }
    ​
            public ScreenshotWindow()
            {
                DefaultStyleKey = typeof(ScreenshotWindow);
                this.Loaded += ScreenshotWindow_Loaded;
            }
    ​
            private void ScreenshotWindow_Loaded(object sender, RoutedEventArgs e)
            {
                //截取屏幕,用作背景
                ScreenBitmap = ScreenshotHelper.GetScreenBitmap(out ScreenWidth, out ScreenHeight);
                var bitmap = ScreenshotHelper.ConvertToBitmapSource(ScreenBitmap);
                PART_BackgroupCanvas.Background = new ImageBrush(bitmap);
            }
    ​
            #region 截圖操作-鼠標事件
            protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
            {
                MouseDown_Event(e.GetPosition(null));
            }
    ​
            protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
            {
                MouseUp_Event();
            }
    ​
            protected override void OnPreviewMouseMove(MouseEventArgs e)
            {
                MouseMove_Event(e.GetPosition(null));
            }
    ​
            //通過鍵盤移動發露目標區域
            protected override void OnPreviewKeyDown(KeyEventArgs e)
            {
                if (e.Key == Key.Escape) Close();   //ESC鍵關閉窗體
    ​
                if (IsShowTargetArea)
                {
                    switch (e.Key)
                    {
                        case Key.Left:
                            MoveTargetShotArea(-1);
                            break;
                        case Key.Up:
                            MoveTargetShotArea(offsetY: -1);
                            break;
                        case Key.Right:
                            MoveTargetShotArea(1);
                            break;
                        case Key.Down:
                            MoveTargetShotArea(offsetY: 1);
                            break;
                        case Key.Enter:
                            Close();
                            break;
                    }
                }
                base.OnPreviewKeyDown(e);
            }
    ​
            #endregion
    ​
            #region 私有函數
    ​
            private void MouseDown_Event(Point point)
            {
                if (IsShowTargetArea == true) return;   //已經截圖,則不允許再次截圖
    ​
                IsScreenshot = true;    //開始截圖
                MouseDownPoint = point;
            }
    ​
            private void MouseUp_Event()
            {
                IsScreenshot = false;   //停止截圖
            }
    ​
            private void MouseMove_Event(Point currentPoint)
            {
                if (IsScreenshot)
                {
                    IsShowTargetArea = true;
    ​
                    RectTargetArea = new System.Drawing.Rectangle()
                    {
                        X = (int)Math.Min(MouseDownPoint.X, currentPoint.X),
                        Y = (int)Math.Min(MouseDownPoint.Y, currentPoint.Y),
                        Width = (int)Math.Abs(MouseDownPoint.X - currentPoint.X),
                        Height = (int)Math.Abs(MouseDownPoint.Y - currentPoint.Y)
                    };
    ​
                    RefreshTargetShotArea();
                }
            }
    ​
            //移動截圖目標區域
            private void MoveTargetShotArea(int offsetX = 0, int offsetY = 0)
            {
                if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
                {
                    if (offsetX != 0)
                    {
                        offsetX += RectTargetArea.Left;
                        if (offsetX + RectTargetArea.Width >= ScreenWidth)
                            offsetX = ScreenWidth - RectTargetArea.Width;
                        else
                            offsetX = Math.Max(0, offsetX);
    ​
                        RectTargetArea = new System.Drawing.Rectangle()
                        {
                            X = offsetX,
                            Y = RectTargetArea.Y,
                            Width = RectTargetArea.Width,
                            Height = RectTargetArea.Height
                        };
                    }
    ​
                    if (offsetY != 0)
                    {
                        offsetY += RectTargetArea.Top;
                        if (offsetY + RectTargetArea.Height >= ScreenHeight)
                            offsetY = ScreenHeight - RectTargetArea.Height;
                        else
                            offsetY = Math.Max(0, offsetY);
    ​
                        RectTargetArea = new System.Drawing.Rectangle()
                        {
                            X = RectTargetArea.X,
                            Y = offsetY,
                            Width = RectTargetArea.Width,
                            Height = RectTargetArea.Height
                        };
                    }
    ​
                    RefreshTargetShotArea();
                }
            }
    ​
            private void RefreshTargetShotArea()
            {
                HintText = string.Format("{0}x{1}", RectTargetArea.Width, RectTargetArea.Height);
    ​
                //設置截圖區域控件位置
                PART_TargetArea.Margin = new Thickness(RectTargetArea.Left, RectTargetArea.Top, 0, 0);
                PART_TargetArea.Width = RectTargetArea.Width;
                PART_TargetArea.Height = RectTargetArea.Height;
    ​
                //設置蒙層位置
                PART_MaskAreaLeft.Width = PART_TargetArea.Margin.Left;
                PART_MaskAreaLeft.Height = ScreenHeight;
    ​
                PART_MaskAreaTop.Margin = new Thickness(PART_TargetArea.Margin.Left, 0, 0, 0);
                PART_MaskAreaTop.Width = PART_TargetArea.ActualWidth;
                PART_MaskAreaTop.Height = PART_TargetArea.Margin.Top;
    ​
                PART_MaskAreaRight.Margin = new Thickness(PART_TargetArea.Margin.Left + PART_TargetArea.ActualWidth, 0, 0, 0);
                PART_MaskAreaRight.Width = ScreenWidth - PART_TargetArea.Margin.Left - PART_TargetArea.ActualWidth;
                PART_MaskAreaRight.Height = ScreenHeight;
    ​
                PART_MaskAreaBottom.Margin = new Thickness(PART_TargetArea.Margin.Left, PART_TargetArea.Margin.Top + PART_TargetArea.ActualHeight, 0, 0);
                PART_MaskAreaBottom.Width = PART_TargetArea.ActualWidth;
                PART_MaskAreaBottom.Height = ScreenHeight - PART_TargetArea.Margin.Top - PART_TargetArea.ActualHeight;
            }
    ​
            #endregion
        }

    程序員都是一個完美主義者,這個小毛病不能忍受,所以就有了第二種方案的產生。

     

    第二種方案(有坑):

    參考地址:不記得參考地址了

    Demo下載:https://download.csdn.net/download/asciil/12473335

    原理描述:

    其原理與第一種方案基本相同,不同之處在於蒙層不再分爲上下左右四部分,而是用一個InkCanvas畫布來實現。蒙層部分由於是填充滿整個屏幕,所以過程中一直不用處理,只需要將截圖區域中圖像高亮顯示即可。在拖動鼠標時畫矩形(截圖區域也就是高亮部分),然後實時獲取矩形區域下的屏幕圖片賦值給此區域,這樣就實現了區域高亮效果。

    原理聽起來也沒問題,但請看下面效果圖

img

第一種方案下下的白線是沒有了,但又出現了新問題,截圖區域內會有輕微的抖動。這是由於截圖區域座標由double轉爲int後數據數度丟失引起的。

貼上主要部分代碼:

  • 樣式文件(ScreenshotWindowStyle.xaml)

    <Style x:Key="SnapAreaStyle" TargetType="Border">
            <Setter Property="BorderThickness" Value="3"/>
            <Setter Property="BorderBrush" Value="DodgerBlue"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsShowTargetArea,RelativeSource={RelativeSource AncestorType=local:ScreenshotWindow}}" Value="True">
                    <Setter Property="IsHitTestVisible" Value="True"/>
                    <Setter Property="BorderThickness" Value="1"/>
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    ​
        <Style x:Key="HotspotStyle" TargetType="Rectangle">
            <Setter Property="Height" Value="5"/>
            <Setter Property="Width" Value="5"/>
            <Setter Property="Fill" Value="DodgerBlue"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="LeftTop">
                    <Setter Property="Margin" Value="-3, -3, 0, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="TopCenter">
                    <Setter Property="Margin" Value="0, -3, 0, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="RightTop">
                    <Setter Property="Margin" Value="0, -3, -3, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="RightCenter">
                    <Setter Property="Margin" Value="0, 0, -3, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="RightBottom">
                    <Setter Property="Margin" Value="0, 0, -3, -3"/>
                    <Setter Property="HorizontalAlignment" Value="Right"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="BottomCenter">
                    <Setter Property="Margin" Value="0, 0, 0, -3"/>
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="LeftBottom">
                    <Setter Property="Margin" Value="-3, 0, 0, -3"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                    <Setter Property="VerticalAlignment" Value="Bottom"/>
                </Trigger>
                <Trigger Property="local:ToolTipAttachProperty.Placement" Value="LeftCenter">
                    <Setter Property="Margin" Value="-3, 0, 0, 0"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                </Trigger>
                <DataTrigger Binding="{Binding IsShowTargetArea,RelativeSource={RelativeSource AncestorType=local:ScreenshotWindow}}" Value="True">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    ​
        <Style x:Key="SnapImageStyle" TargetType="InkCanvas">
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="EditingMode" Value="None"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsShowTargetArea,RelativeSource={RelativeSource AncestorType=local:ScreenshotWindow}}" Value="True">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    ​
        <Style TargetType="{x:Type local:ScreenshotWindow}">
            <Setter Property="UseLayoutRounding" Value="True"/>
            <Setter Property="WindowStyle" Value="None"/>
            <Setter Property="WindowState" Value="Maximized"/>
            <Setter Property="Topmost" Value="True"/>
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:ScreenshotWindow}">
                        <AdornerDecorator>
                            <Grid>
                                <!--背景畫布-->
                                <InkCanvas Name="PART_CANVAS" EditingMode="None" Background="Transparent"/>
                                <!--蒙層畫布-->
                                <InkCanvas Name="PART_CANVASMASK" EditingMode="None" Background="#40000000"/>
                                <!--截圖區域-->
                                <Border Name="PART_TARGETAREA" Style="{StaticResource SnapAreaStyle}">
                                    <Grid>
                                        <Rectangle local:ToolTipAttachProperty.Placement="LeftTop" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="TopCenter" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="RightTop" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="RightCenter" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="RightBottom" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="BottomCenter" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="LeftBottom" Style="{StaticResource HotspotStyle}"/>
                                        <Rectangle local:ToolTipAttachProperty.Placement="LeftCenter" Style="{StaticResource HotspotStyle}"/>
                                        <InkCanvas Name="PART_IMAGE" Width="{Binding Width,ElementName=PART_TARGETAREA}" Height="{Binding Height,ElementName=PART_TARGETAREA}"
                                                   Style="{StaticResource SnapImageStyle}"/>
                                        <TextBlock Background="#40000000" Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Top" Padding="5,2" Text="{TemplateBinding HintText}"/>
                                    </Grid>
                                </Border>
                            </Grid>
                        </AdornerDecorator>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    • 後臺代碼(ScreenshotWindow.cs)

    public class ScreenshotWindow : Window
        {
            internal InkCanvas PART_BackgroupCanvas { get; set; }       //背景畫布
            internal InkCanvas PART_MaskCanvas { get; set; }            //蒙層畫布
            internal FrameworkElement PART_TargetArea { get; set; }     //截圖區域
            internal InkCanvas PART_TargetImage { get; set; }           //截取到的圖片
    ​
            private int ScreenWidth, ScreenHeight;                      //屏幕寬高
            private System.Drawing.Bitmap ScreenBitmap { get; set; }    //整個屏幕圖片
            private bool IsScreenshot;                                  //是否在截圖中
            private Point MouseDownPoint;                               //鼠標按下時座標點
            private System.Drawing.Rectangle RectTargetArea { get; set; }       //截圖區域矩形位置
    ​
            #region 依賴屬性
            public static readonly DependencyProperty IsShowTargetAreaProperty = DependencyProperty.Register("IsShowTargetArea", typeof(bool), typeof(ScreenshotWindow), new PropertyMetadata(false));
            /// <summary>
            /// 是否顯示截圖區域
            /// </summary>
            public bool IsShowTargetArea
            {
                get { return (bool)GetValue(IsShowTargetAreaProperty); }
                internal set { SetValue(IsShowTargetAreaProperty, value); }
            }
    ​
            public static readonly DependencyProperty HintTextProperty = DependencyProperty.Register("HintText", typeof(string), typeof(ScreenshotWindow), new PropertyMetadata(""));
            /// <summary>
            /// 截圖區域大小提示文字
            /// </summary>
            public string HintText
            {
                get { return (string)GetValue(HintTextProperty); }
                internal set { SetValue(HintTextProperty, value); }
            }
    ​
            #endregion
    ​
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    ​
                PART_BackgroupCanvas = GetTemplateChild("PART_CANVAS") as InkCanvas;
                PART_MaskCanvas = GetTemplateChild("PART_CANVASMASK") as InkCanvas;
                PART_TargetArea = GetTemplateChild("PART_TARGETAREA") as FrameworkElement;
                PART_TargetImage = GetTemplateChild("PART_IMAGE") as InkCanvas;
            }
    ​
            public ScreenshotWindow()
            {
                DefaultStyleKey = typeof(ScreenshotWindow);
                this.Loaded += ScreenshotWindow_Loaded;
            }
    ​
            private void ScreenshotWindow_Loaded(object sender, RoutedEventArgs e)
            {
                //截取屏幕,用作背景
                ScreenBitmap = ScreenshotHelper.GetScreenBitmap(out ScreenWidth, out ScreenHeight);
                var bitmap = ScreenshotHelper.ConvertToBitmapSource(ScreenBitmap);
                PART_BackgroupCanvas.Background = new ImageBrush(bitmap);
            }
    ​
            #region 截圖操作-鼠標事件
            protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
            {
                MouseDown_Event(e.GetPosition(null));
            }
    ​
            protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
            {
                MouseUp_Event();
            }
    ​
            protected override void OnPreviewMouseMove(MouseEventArgs e)
            {
                MouseMove_Event(e.GetPosition(null));
            }
    ​
            //通過鍵盤移動發露目標區域
            protected override void OnPreviewKeyDown(KeyEventArgs e)
            {
                if (e.Key == Key.Escape) Close();   //ESC鍵關閉窗體
    ​
                if (IsShowTargetArea)
                {
                    switch (e.Key)
                    {
                        case Key.Left:
                            MoveTargetShotArea(-1);
                            break;
                        case Key.Up:
                            MoveTargetShotArea(offsetY: -1);
                            break;
                        case Key.Right:
                            MoveTargetShotArea(1);
                            break;
                        case Key.Down:
                            MoveTargetShotArea(offsetY: 1);
                            break;
                        case Key.Enter:
                            Close();
                            break;
                    }
                }
                base.OnPreviewKeyDown(e);
            }
    ​
            #endregion
    ​
            #region 私有函數
    ​
            private void MouseDown_Event(Point point)
            {
                if (IsShowTargetArea == true) return;   //已經截圖,則不允許再次截圖
    ​
                IsScreenshot = true;    //開始截圖
                MouseDownPoint = point;
            }
    ​
            private void MouseUp_Event()
            {
                IsScreenshot = false;   //停止截圖
            }
    ​
            private void MouseMove_Event(Point currentPoint)
            {
                if (IsScreenshot)
                {
                    IsShowTargetArea = true;
    ​
                    RectTargetArea = new System.Drawing.Rectangle()
                    {
                        X = (int)Math.Min(MouseDownPoint.X, currentPoint.X),
                        Y = (int)Math.Min(MouseDownPoint.Y, currentPoint.Y),
                        Width = (int)Math.Abs(MouseDownPoint.X - currentPoint.X),
                        Height = (int)Math.Abs(MouseDownPoint.Y - currentPoint.Y)
                    };
    ​
                    RefreshTargetShotArea();
                }
            }
    ​
            //移動截圖目標區域
            private void MoveTargetShotArea(int offsetX = 0, int offsetY = 0)
            {
                if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
                {
                    if (offsetX != 0)
                    {
                        offsetX += RectTargetArea.Left;
                        if (offsetX + RectTargetArea.Width >= ScreenWidth)
                            offsetX = ScreenWidth - RectTargetArea.Width;
                        else
                            offsetX = Math.Max(0, offsetX);
    ​
                        RectTargetArea = new System.Drawing.Rectangle()
                        {
                            X = offsetX,
                            Y = RectTargetArea.Y,
                            Width = RectTargetArea.Width,
                            Height = RectTargetArea.Height
                        };
                    }
    ​
                    if (offsetY != 0)
                    {
                        offsetY += RectTargetArea.Top;
                        if (offsetY + RectTargetArea.Height >= ScreenHeight)
                            offsetY = ScreenHeight - RectTargetArea.Height;
                        else
                            offsetY = Math.Max(0, offsetY);
    ​
                        RectTargetArea = new System.Drawing.Rectangle()
                        {
                            X = RectTargetArea.X,
                            Y = offsetY,
                            Width = RectTargetArea.Width,
                            Height = RectTargetArea.Height
                        };
                    }
    ​
                    RefreshTargetShotArea();
                }
            }
    ​
            private void RefreshTargetShotArea()
            {
                HintText = string.Format("{0}x{1}", RectTargetArea.Width, RectTargetArea.Height);
    ​
                //設置截圖區域控件位置
                Thickness thickness = new Thickness(RectTargetArea.Left, RectTargetArea.Top, 0, 0);
                PART_TargetArea.SetValue(FrameworkElement.MarginProperty, thickness);
                PART_TargetArea.Width = RectTargetArea.Width;
                PART_TargetArea.Height = RectTargetArea.Height;
    ​
                //高亮顯示截圖部分
                if ((int)RectTargetArea.Width == 0 || (int)RectTargetArea.Height == 0) return;
    ​
                float dpiX, dpiY;
                using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
                {
                    dpiX = graphics.DpiX / 96;
                    dpiY = graphics.DpiY / 96;
                }
                System.Drawing.RectangleF srcRect = new System.Drawing.RectangleF()
                {
                    X = RectTargetArea.Left * dpiX,
                    Y = RectTargetArea.Top * dpiY,
                    Width = RectTargetArea.Width * dpiX,
                    Height = RectTargetArea.Height * dpiY
                };
                var descScreenBitmap = ScreenshotHelper.CutScreenBitmap(ScreenBitmap, srcRect);
                var bitmap = ScreenshotHelper.ConvertToBitmapSource(descScreenBitmap);
                PART_TargetImage.Background = new ImageBrush(bitmap);
            }
    ​
            #endregion
    ​
        }

     

距離我們的目的越來越近了,不能就半途放棄了。一頓搜索及思考後,腦海中出現了第三種方案。

 

第三種方案(完美):

原理描述:

綜合考慮到前兩種方案問題引起的原因,那麼我們是不是可以綜合兩種方案丟棄掉有問題的部分。

具體思路爲用InkCanvas畫布存儲截圖時的屏幕圖片,截圖區域處理邏輯不變,蒙層分上下左右四部採用Canvas畫布實現。鼠標拖動過程中,首先畫矩形(也就是截圖區域高亮部分),然後計算上下左右蒙層X,Y及寬高變化進行賦值(關鍵:不要直接用 元素.LEFT,元素.TOP進行賦值,採用 Canvas.SetLeft及 Canvas.SetToptTop進行設置),這樣可以完美避開前兩種方案的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章