可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
An alternative snipping tool solution was provided in this posting: .NET Equivalent of Snipping Tool
Now it's necessary to make it work for selected screens (on multi-monitor systems).
The code has been modified accordingly:
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
But it refuses to work as expected. The snipper doesn't appear on the selected screen, instead it appears on the first one, regardless of the "screen" parameter in "Snip" function.
How to make it work correctly?
UPDATE: The latest snipper version appears on correct screen, but blank.
UPDATE X2 : The code above has been updated to reflect the latest version, which now works properly.
回答1:
LoveDotNet, I believe there is a small problem with your source code, the following line:
Graph.CopyFromScreen(area.X, area.Y, area.Y, area.Y, BitmapSize)
Should be:
Graph.CopyFromScreen(area.X, area.Y, 0, 0, BitmapSize)
Also, just a quick tip to anyone who wants to use this code you can invoke it like the following:
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
Also, when you create your SnippingTool form, be sure to change the StartPosition
property to Manual
.
Big Edit:
I did some work to support multiple monitors at once, which doesn't require the developer to select which monitor to use (this clones the "Snipping Tool" a little closer).
Basically I'm iterating through all of the screens to find the minimum X
and Y
coordinates, and the largest Right
and Bottom
, this lets us evaluate the full size of the "Virtual Monitor":
I've tested it with my configuration which is:
Primary 1280x800
Secondary 1280x1024 w/ -224 X offset
Code:
'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
And you can call it like this:
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:
You need to set the StartPosition of the form to Manual
before setting the Location of the SnippingTool, otherwise it gets placed on the primary screen no matter what. Do this either in the constructor or in your using statement and it should fix your issue.
StartPosition = FormStartPosition.Manual;
回答3:
I don't see you doing anything wrong. I can't test this, don't have the setup right now. Bounds is a bit tricky, there's a bunch of code behind it that ensures that the form can't be displayed off-screen. As an alternative, you could set the Location property instead and override OnLoad() in SnippingTool to set the WindowState property.
回答4:
I have create the helper class to capture the selected area on specific screen where control reside. It works on multiple screens.
The idea is taken from multiple online resources that basically Freeze the Screen and put into PictureBox .NET control.
Here's the codes:
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
}
And you can use it like this:
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();
回答5:
The solution is to use Screen.WorkingArea
property rather than Screen.Bounds
.
The second option yields incorrect results with Graphics.CopyFromScreen
.
Code snippet above has been updated with fully functional version.