Сделайте инструмент .NET snipping совместимым с несколькими мониторами - PullRequest
2 голосов
/ 24 октября 2010

В этой публикации было представлено альтернативное решение для снятия инструмента: .NET Эквивалент снайперского инструмента

Теперь необходимо настроить его на выбранные экраны (в системах с несколькими мониторами).

Код был изменен соответственно:

Public Class SnippingTool


    Private Shared _Screen As Screen

    Private Shared BitmapSize As Size

    Private Shared Graph As Graphics


    Public Shared Function Snip(ByVal screen As Screen) As Image

        _Screen = screen

        Dim bmp As New Bitmap(screen.Bounds.Width, screen.Bounds.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb)

        Dim gr As Graphics = Graphics.FromImage(bmp)

        Graph = gr


        gr.SmoothingMode = Drawing2D.SmoothingMode.None '###

        BitmapSize = bmp.Size


        Using snipper = New SnippingTool(bmp)

            snipper.Location = New Point(screen.Bounds.Left, screen.Bounds.Top)

            If snipper.ShowDialog() = DialogResult.OK Then
                Return snipper.Image
            End If

        End Using

        Return Nothing


    End Function



    Public Sub New(ByVal screenShot As Image)
        InitializeComponent()
        Me.BackgroundImage = screenShot
        Me.ShowInTaskbar = False
        Me.FormBorderStyle = FormBorderStyle.None


        'Me.WindowState = FormWindowState.Maximized

        Me.DoubleBuffered = True
    End Sub
    Public Property Image() As Image
        Get
            Return m_Image
        End Get
        Set(ByVal value As Image)
            m_Image = Value
        End Set
    End Property
    Private m_Image As Image


    Private rcSelect As New Rectangle()
    Private pntStart As Point

    Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
        ' Start the snip on mouse down
        If e.Button <> MouseButtons.Left Then
            Return
        End If
        pntStart = e.Location
        rcSelect = New Rectangle(e.Location, New Size(0, 0))
        Me.Invalidate()
    End Sub
    Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
        ' Modify the selection on mouse move
        If e.Button <> MouseButtons.Left Then
            Return
        End If
        Dim x1 As Integer = Math.Min(e.X, pntStart.X)
        Dim y1 As Integer = Math.Min(e.Y, pntStart.Y)
        Dim x2 As Integer = Math.Max(e.X, pntStart.X)
        Dim y2 As Integer = Math.Max(e.Y, pntStart.Y)
        rcSelect = New Rectangle(x1, y1, x2 - x1, y2 - y1)
        Me.Invalidate()
    End Sub


    Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
        ' Complete the snip on mouse-up
        If rcSelect.Width <= 0 OrElse rcSelect.Height <= 0 Then
            Return
        End If
        Image = New Bitmap(rcSelect.Width, rcSelect.Height)
        Using gr As Graphics = Graphics.FromImage(Image)
            gr.DrawImage(Me.BackgroundImage, New Rectangle(0, 0, Image.Width, Image.Height), rcSelect, GraphicsUnit.Pixel)
        End Using
        DialogResult = DialogResult.OK
    End Sub
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        ' Draw the current selection
        Using br As Brush = New SolidBrush(Color.FromArgb(120, Color.White))
            Dim x1 As Integer = rcSelect.X
            Dim x2 As Integer = rcSelect.X + rcSelect.Width
            Dim y1 As Integer = rcSelect.Y
            Dim y2 As Integer = rcSelect.Y + rcSelect.Height
            e.Graphics.FillRectangle(br, New Rectangle(0, 0, x1, Me.Height))
            e.Graphics.FillRectangle(br, New Rectangle(x2, 0, Me.Width - x2, Me.Height))
            e.Graphics.FillRectangle(br, New Rectangle(x1, 0, x2 - x1, y1))
            e.Graphics.FillRectangle(br, New Rectangle(x1, y2, x2 - x1, Me.Height - y2))
        End Using
        Using pen As New Pen(Color.Red, 3)
            e.Graphics.DrawRectangle(pen, rcSelect)
        End Using
    End Sub
    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
        ' Allow canceling the snip with the Escape key
        If keyData = Keys.Escape Then
            Me.DialogResult = DialogResult.Cancel
        End If
        Return MyBase.ProcessCmdKey(msg, keyData)
    End Function

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        MyBase.OnLoad(e)
        Me.Size = New Size(_Screen.Bounds.Width, _Screen.Bounds.Height)
        Dim area = _Screen.WorkingArea
        Graph.CopyFromScreen(area.X, area.Y, area.Y, area.Y, BitmapSize)
    End Sub

End Class

Но он отказывается работать, как ожидалось. Снайпер не появляется на выбранном экране, вместо этого он появляется на первом, независимо от параметра «screen» в функции «Snip». Как заставить его работать правильно?

ОБНОВЛЕНИЕ : На правильном экране появляется последняя версия сниппера, но пустая.

ОБНОВЛЕНИЕ X2 : приведенный выше код был обновлен, чтобы отразить последнюю версию, которая теперь работает должным образом.

Ответы [ 5 ]

3 голосов
/ 30 октября 2012

LoveDotNet, я считаю, что есть небольшая проблема с вашим исходным кодом, следующая строка:

Graph.CopyFromScreen(area.X, area.Y, area.Y, area.Y, BitmapSize)

Должно быть:

Graph.CopyFromScreen(area.X, area.Y, 0, 0, BitmapSize)

Кроме того, просто быстрый совет всем, кто хочет использовать этот код, вы можете вызвать его следующим образом:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim img As Image = SnippingTool.Snip(Screen.AllScreens(0)) 'Set to (1) for secondary monitor'
End Sub

Кроме того, при создании формы SnippingTool обязательно измените StartPosition свойство для Manual.

Большое редактирование:

Я проделал некоторую работу по поддержке нескольких мониторов одновременно, что не требует от разработчика выбора, какоймонитор для использования (это клонирует «Snipping Tool» немного ближе).

В основном я перебираю все экраны, чтобы найти минимальные X и Y координаты и самые большие Right и Bottom, это позволяет нам оценить полный размер «Виртуального монитора»:

Я протестировал его с моей конфигурацией:

Primary 1280x800

Secondary 1280x1024 w/ -224 X offset

enter image description here

Код:

'SnippingTool Code: Place this in a new form (set the StartUp Property to Manual)'
Public Class SnippingTool


Private Shared _Screen As Screen

Private Shared BitmapSize As Size

Private Shared Graph As Graphics

Private Structure MultiScreenSize
    Dim minX As Integer
    Dim minY As Integer
    Dim maxRight As Integer
    Dim maxBottom As Integer
End Structure


Private Shared Function FindMultiScreenSize() As MultiScreenSize

    Dim minX As Integer = Screen.AllScreens(0).Bounds.X
    Dim minY As Integer = Screen.AllScreens(0).Bounds.Y

    Dim maxRight As Integer = Screen.AllScreens(0).Bounds.Right
    Dim maxBottom As Integer = Screen.AllScreens(0).Bounds.Bottom

    For Each aScreen As Screen In Screen.AllScreens
        If aScreen.Bounds.X < minX Then
            minX = aScreen.Bounds.X
        End If

        If aScreen.Bounds.Y < minY Then
            minY = aScreen.Bounds.Y
        End If

        If aScreen.Bounds.Right > maxRight Then
            maxRight = aScreen.Bounds.Right
        End If

        If aScreen.Bounds.Bottom > maxBottom Then
            maxBottom = aScreen.Bounds.Bottom
        End If
    Next
    Dim m_MultiScreenSize As MultiScreenSize
    With m_MultiScreenSize
        .minX = minX
        .minY = minY
        .maxBottom = maxBottom
        .maxRight = maxRight
    End With
    Return m_MultiScreenSize
End Function
Public Shared Function Snip() As Image
    Dim m_MultiScreenSize As MultiScreenSize = FindMultiScreenSize()

    Dim bmp As New Bitmap(m_MultiScreenSize.maxRight - m_MultiScreenSize.minX, _
                          m_MultiScreenSize.maxBottom - m_MultiScreenSize.minY, _
                          System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
    Dim gr As Graphics = Graphics.FromImage(bmp)

    Graph = gr


    gr.SmoothingMode = Drawing2D.SmoothingMode.None 
    BitmapSize = bmp.Size


    Using snipper = New SnippingTool(bmp)

        snipper.Location = New Point(m_MultiScreenSize.minX, m_MultiScreenSize.minY)

        If snipper.ShowDialog() = DialogResult.OK Then
            Return snipper.Image
        End If

    End Using

    Return Nothing


End Function



Public Sub New(ByVal screenShot As Image)
    InitializeComponent()
    Me.BackgroundImage = screenShot
    Me.ShowInTaskbar = False
    Me.FormBorderStyle = FormBorderStyle.None

    Me.DoubleBuffered = True
End Sub
Public Property Image() As Image
    Get
        Return m_Image
    End Get
    Set(ByVal value As Image)
        m_Image = Value
    End Set
End Property
Private m_Image As Image


Private rcSelect As New Rectangle()
Private pntStart As Point

Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
    ' Start the snip on mouse down'
    If e.Button <> MouseButtons.Left Then
        Return
    End If
    pntStart = e.Location
    rcSelect = New Rectangle(e.Location, New Size(0, 0))
    Me.Invalidate()
End Sub
Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
    ' Modify the selection on mouse move'
    If e.Button <> MouseButtons.Left Then
        Return
    End If
    Dim x1 As Integer = Math.Min(e.X, pntStart.X)
    Dim y1 As Integer = Math.Min(e.Y, pntStart.Y)
    Dim x2 As Integer = Math.Max(e.X, pntStart.X)
    Dim y2 As Integer = Math.Max(e.Y, pntStart.Y)
    rcSelect = New Rectangle(x1, y1, x2 - x1, y2 - y1)
    Me.Invalidate()
End Sub


Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
    ' Complete the snip on mouse-up'
    If rcSelect.Width <= 0 OrElse rcSelect.Height <= 0 Then
        Return
    End If
    Image = New Bitmap(rcSelect.Width, rcSelect.Height)
    Using gr As Graphics = Graphics.FromImage(Image)
        gr.DrawImage(Me.BackgroundImage, New Rectangle(0, 0, Image.Width, Image.Height), _
                     rcSelect, GraphicsUnit.Pixel)
    End Using
    DialogResult = DialogResult.OK
End Sub
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    ' Draw the current selection'
    Using br As Brush = New SolidBrush(Color.FromArgb(120, Color.White))
        Dim x1 As Integer = rcSelect.X
        Dim x2 As Integer = rcSelect.X + rcSelect.Width
        Dim y1 As Integer = rcSelect.Y
        Dim y2 As Integer = rcSelect.Y + rcSelect.Height
        e.Graphics.FillRectangle(br, New Rectangle(0, 0, x1, Me.Height))
        e.Graphics.FillRectangle(br, New Rectangle(x2, 0, Me.Width - x2, Me.Height))
        e.Graphics.FillRectangle(br, New Rectangle(x1, 0, x2 - x1, y1))
        e.Graphics.FillRectangle(br, New Rectangle(x1, y2, x2 - x1, Me.Height - y2))
    End Using
    Using pen As New Pen(Color.Red, 3)
        e.Graphics.DrawRectangle(pen, rcSelect)
    End Using
End Sub
Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
    ' Allow canceling the snip with the Escape key'
    If keyData = Keys.Escape Then
        Me.DialogResult = DialogResult.Cancel
    End If
    Return MyBase.ProcessCmdKey(msg, keyData)
End Function

Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
    MyBase.OnLoad(e)
    Dim m_MultiScreenSize As MultiScreenSize = FindMultiScreenSize()
    Me.Size = New Size(m_MultiScreenSize.maxRight - m_MultiScreenSize.minX, _
                       m_MultiScreenSize.maxBottom - m_MultiScreenSize.minY)

    Graph.CopyFromScreen(m_MultiScreenSize.minX, m_MultiScreenSize.minY, 0, 0, BitmapSize)
End Sub
 End Class

И вы можете назвать это лиКе это:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim img As Image = SnippingTool.Snip()
    img.Save("C:\ScreenShot.png")
End Sub
2 голосов
/ 08 ноября 2010

Вам необходимо установить StartPosition формы на Manual перед настройкой местоположения SnippingTool, в противном случае он будет размещен на основном экране, несмотря ни на что.Сделайте это либо в конструкторе, либо в вашем операторе using, и это должно исправить вашу проблему.

StartPosition = FormStartPosition.Manual;
1 голос
/ 09 апреля 2015

Я создал вспомогательный класс для захвата выбранной области на определенном экране, где находится элемент управления.Он работает на нескольких экранах.

Displays

Идея взята из нескольких онлайн-ресурсов, которые в основном замораживают экран и помещают в элемент управления PictureBox .NET.

Воткоды:

public class CaptureScreen : IDisposable
{
    readonly Control control;
    readonly Pen penSelectedAreaScreenShot;

    Form frmPictureBox = null;
    PictureBox pictureBoxScreenShot = null;
    Point selectedScreenShotStartPoint;
    Size selectedScreenShotSize;
    bool isMouseDownSelectedScreenShot = false;

    public event Action<object, Bitmap> SelectedScreenAreaCaptured;

    public event Action<object, Exception> ScreenCaptureFailed;

    public CaptureScreen(Control control)
    {
        if (control == null)
        {
            throw new ArgumentNullException("control");
        }

        this.control = control;

        this.penSelectedAreaScreenShot = new Pen(Color.Red, 1);
        this.penSelectedAreaScreenShot.DashStyle = DashStyle.Dot;
    }

    public void BeginStart()
    {
        #region Setup the Picture Box for ScreenShot

        if (this.frmPictureBox != null)
        {
            this.frmPictureBox.Close();
            this.frmPictureBox.Dispose();
            this.frmPictureBox = null;
        }

        if (this.pictureBoxScreenShot != null)
        {
            this.pictureBoxScreenShot.Dispose();
            this.pictureBoxScreenShot = null;
        }

        this.frmPictureBox = new Form
        {
            BackColor = Color.Black,
            Cursor = Cursors.Cross,
            FormBorderStyle = FormBorderStyle.None,
            StartPosition = FormStartPosition.CenterParent,
            TopLevel = true,
            TopMost = true,
            Top = 0,
            Left = 0,
            WindowState = FormWindowState.Maximized,
            KeyPreview = true
        };

        this.pictureBoxScreenShot = new PictureBox
        {
            Location = new Point(0, 0),
            SizeMode = PictureBoxSizeMode.Zoom
        };
        this.frmPictureBox.Controls.Add(this.pictureBoxScreenShot);

        #endregion

        #region Capture the Screen

        Bitmap screenShotBitmap = null;
        Graphics graphics = null;
        MemoryStream stream = null;
        try
        {
            Screen currentScreen = Screen.FromControl(this.control);

            screenShotBitmap = new Bitmap(currentScreen.Bounds.Width, currentScreen.Bounds.Height);

            graphics = Graphics.FromImage(screenShotBitmap as Image);
            graphics.CopyFromScreen(currentScreen.Bounds.X, currentScreen.Bounds.Y, 0, 0, screenShotBitmap.Size);

            stream = new MemoryStream();
            screenShotBitmap.Save(stream, ImageFormat.Png);

            this.pictureBoxScreenShot.Size = screenShotBitmap.Size;
            this.pictureBoxScreenShot.Image = Image.FromStream(stream);
        }
        catch (Exception ex)
        {
            if (this.ScreenCaptureFailed != null)
            {
                this.ScreenCaptureFailed(this, ex);
            }
        }
        finally
        {
            if (stream != null)
            {
                stream.Close();
                stream.Dispose();
                stream = null;
            }

            if (graphics != null)
            {
                graphics.Dispose();
                graphics = null;
            }

            if (screenShotBitmap != null)
            {
                screenShotBitmap.Dispose();
                screenShotBitmap = null;
            }
        }

        #endregion

        this.frmPictureBox.KeyDown += this.frmPictureBox_KeyDown;

        this.pictureBoxScreenShot.MouseMove += this.pictureBoxScreenShot_MouseMove;
        this.pictureBoxScreenShot.MouseDown += this.pictureBoxScreenShot_MouseDown;
        this.pictureBoxScreenShot.MouseUp += this.pictureBoxScreenShot_MouseUp;

        this.frmPictureBox.Show(this.control);
    }

    public void Exit()
    {
        if (this.frmPictureBox != null)
        {
            this.frmPictureBox.Close();
            this.frmPictureBox.Dispose();
            this.frmPictureBox = null;
        }

        if (this.pictureBoxScreenShot != null)
        {
            this.pictureBoxScreenShot.Dispose();
            this.pictureBoxScreenShot = null;
        }

        this.isMouseDownSelectedScreenShot = false;
    }

    [DebuggerStepThrough]
    void frmPictureBox_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Escape)
        {
            this.Exit();
        }
    }

    void pictureBoxScreenShot_MouseMove(object sender, MouseEventArgs e)
    {
        if (this.pictureBoxScreenShot.Image == null)
        {
            this.Exit();
            return;
        }

        if (this.isMouseDownSelectedScreenShot)
        {
            this.pictureBoxScreenShot.Refresh();

            this.selectedScreenShotSize = new Size(
                e.X - this.selectedScreenShotStartPoint.X,
                e.Y - this.selectedScreenShotStartPoint.Y);

            // Draw the selected area rectangle.
            this.pictureBoxScreenShot.CreateGraphics().DrawRectangle(this.penSelectedAreaScreenShot,
                this.selectedScreenShotStartPoint.X, this.selectedScreenShotStartPoint.Y,
                this.selectedScreenShotSize.Width, this.selectedScreenShotSize.Height);
        }
    }

    void pictureBoxScreenShot_MouseDown(object sender, MouseEventArgs e)
    {
        if (!this.isMouseDownSelectedScreenShot)
        {
            if (e.Button == MouseButtons.Left)
            {
                this.selectedScreenShotStartPoint = new Point(e.X, e.Y);
            }

            this.pictureBoxScreenShot.Refresh();

            this.isMouseDownSelectedScreenShot = true;
        }
    }

    void pictureBoxScreenShot_MouseUp(object sender, MouseEventArgs e)
    {
        if (this.pictureBoxScreenShot.Image == null)
        {
            this.Exit();
            return;
        }

        isMouseDownSelectedScreenShot = false;

        this.frmPictureBox.Hide();

        // Check whether there is something get selected.
        if (this.selectedScreenShotSize.Width > 0 && this.selectedScreenShotSize.Height > 0)
        {
            Bitmap selectedAreaBitmap = null;
            Graphics graphics = null;
            Bitmap screenShotBitmap = null;

            try
            {
                selectedAreaBitmap = new Bitmap(this.selectedScreenShotSize.Width, this.selectedScreenShotSize.Height);

                graphics = Graphics.FromImage(selectedAreaBitmap);

                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                graphics.CompositingQuality = CompositingQuality.HighQuality;

                screenShotBitmap = new Bitmap(this.pictureBoxScreenShot.Image, this.pictureBoxScreenShot.Size);
                graphics.DrawImage(screenShotBitmap, 0, 0, new Rectangle(this.selectedScreenShotStartPoint, this.selectedScreenShotSize), GraphicsUnit.Pixel);

                if (this.SelectedScreenAreaCaptured != null)
                {
                    this.SelectedScreenAreaCaptured(this, selectedAreaBitmap);
                }
                else
                {
                    Clipboard.SetImage(selectedAreaBitmap);
                    MessageBox.Show(this.control, "Selected Screen is copied to Clipboard.");
                }
            }
            catch (Exception ex)
            {
                if (this.ScreenCaptureFailed != null)
                {
                    this.ScreenCaptureFailed(this, ex);
                }
            }
            finally
            {
                if (screenShotBitmap != null)
                {
                    screenShotBitmap.Dispose();
                    screenShotBitmap = null;
                }

                if (graphics != null)
                {
                    graphics.Dispose();
                    graphics = null;
                }

                if (selectedAreaBitmap != null)
                {
                    selectedAreaBitmap.Dispose();
                    selectedAreaBitmap = null;
                }
            }
        }

        this.Exit();
    }

    #region IDisposable Member

    public void Dispose()
    {
        try
        {
            this.Exit();
        }
        catch { }
    }

    #endregion

}

И вы можете использовать это так:

   CaptureScreen captureScreen = new CaptureScreen(this);

   // If event not implement, then by default it will copied to clipboard.
   //captureScreen.SelectedScreenAreaCaptured += delegate(object am_sender, Bitmap am_selectedAreaBitmap)
   //{
        //string destFilename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), String.Format("Capture_{0}.png", DateTime.Now.ToString("yyyyMMddHHmmss")));
        //am_selectedAreaBitmap.Save(destFilename, System.Drawing.Imaging.ImageFormat.Png);                
   //};

   // Implements this to handle the exception that occurs during screen capture.
   //captureScreen.ScreenCaptureFailed += delegate(object am_sender, Exception am_ex)
   //{
        //MessageBox.Show(this, am_ex.Message, "Unable to Capture the Screen", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
   //};

   captureScreen.BeginStart();
1 голос
/ 24 октября 2010

Я не вижу, что вы делаете что-то не так. Я не могу проверить это, сейчас у меня нет настроек. Границы немного сложны, за ними есть куча кода, который гарантирует, что форма не может быть отображена вне экрана. В качестве альтернативы вы можете вместо этого установить свойство Location и переопределить OnLoad () в SnippingTool, чтобы установить свойство WindowState.

0 голосов
/ 13 ноября 2010

Решением является использование свойства Screen.WorkingArea вместо Screen.Bounds.Второй вариант дает неверные результаты с Graphics.CopyFromScreen.Приведенный выше фрагмент кода обновлен до полнофункциональной версии.

...