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进行设置),这样可以完美避开前两种方案的问题。

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