I'm trying to write a magnifier application using a winform or wpf windows. The idea is to drag the window over a spot on the screen and magnify it. I know it exists commercially but need to build a customized version. The challenge I'm having is to capture the screen image behind the active application.
I have found code to capture a screen image below. But it includes the active window
{
Rectangle bounds = new Rectangle(this.Location, this.Size);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
Graphics g = Graphics.FromImage(bitmap);
g.CopyFromScreen(this.Location, Point.Empty, bounds.Size);
Bitmap bp2 = new Bitmap(bitmap); // local copy of image...
pictureBox1.Image = bp2;
}
Adding statements to hide the active application correct the image capture, but introduce a screen flicker which I'd like to avoid. (modified code below)
{
this.StartPosition = FormStartPosition.Manual; // get current window location
Point cur = this.Location;
this.Location = new Point(-500, -500); // hide the active app off screen.
Rectangle bounds = new Rectangle(cur, this.Size);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
Graphics g = Graphics.FromImage(bitmap);
g.CopyFromScreen(cur, Point.Empty, bounds.Size);
Bitmap bp2 = new Bitmap(bitmap); // local copy of image...
pictureBox1.Image = bp2;
this.Location = cur; // restore application location
}
Can someone suggest an alternative to capture a screen region, behind an active windows?
Thx.
Wrapping the Magnification API is pretty useful. I created a Winforms control that does this. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto a form.
The TrackMouse property controls which part of the screen is shown magnified. Set to False, it magnifies the area covered by the control, making it act like a magnifying glass. Set to True, it will operate like Windows' Magnifier, following the mouse.
The Magnification property controls the amount of magnification. You can already adjust it by using the mouse wheel.
The form you drop it should have its TopMost property set to True. You might want to tinker with its Region property to make it resemble a spyglass.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class Magnifier : Control {
public Magnifier() {
if (!MagInitialize()) throw new NotSupportedException();
timer = new Timer { Interval = 45 };
timer.Tick += (o, ea) => { if (trackMouse) setSource(false); else Invalidate(); };
}
[DefaultValue(false)]
public bool TrackMouse {
get { return trackMouse; }
set { trackMouse = value; setSource(false); }
}
[DefaultValue(2.0f)]
public float Magnification {
get { return magnification; }
set { magnification = Math.Max(1, value); setSource(true); }
}
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
if (!this.DesignMode) {
cp.ClassName = "Magnifier";
//cp.Style |= MS_SHOWMAGNIFIEDCURSOR;
this.SetStyle(ControlStyles.UserPaint, true);
}
return cp;
}
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (!this.DesignMode) {
setSource(true);
this.FindForm().LocationChanged += ParentLocationChanged;
timer.Start();
}
}
protected override void Dispose(bool disposing) {
if (disposing) {
var frm = this.FindForm();
if (frm != null) frm.LocationChanged -= ParentLocationChanged;
timer.Dispose();
MagUninitialize();
}
base.Dispose(disposing);
}
private void ParentLocationChanged(object sender, EventArgs e) {
if (!trackMouse) setSource(false);
}
protected override void OnSizeChanged(EventArgs e) {
setSource(false);
base.OnSizeChanged(e);
}
protected override void OnMouseWheel(MouseEventArgs e) {
this.Magnification += e.Delta / 100f;
((HandledMouseEventArgs)e).Handled = true;
}
private void setSource(bool newmag) {
if (!this.IsHandleCreated || this.DesignMode) return;
if (newmag) {
var xform = new MAGTRANSFORM();
xform.v11 = xform.v22 = magnification;
xform.v33 = 1.0f;
MagSetWindowTransform(this.Handle, ref xform);
}
Point center;
if (trackMouse) center = Cursor.Position;
else {
var rc = this.RectangleToScreen(this.Bounds);
center = new Point(rc.Left + rc.Width / 2, rc.Top + rc.Height / 2);
}
var scr = Screen.FromPoint(center);
var rect = new RECT();
rect.left = Math.Max(scr.Bounds.Left, center.X - (int)(this.Width / magnification / 2));
rect.top = Math.Max(scr.Bounds.Top, center.Y - (int)(this.Height / magnification / 2));
rect.right = rect.left + (int)(this.Width / magnification);
if (rect.right > scr.Bounds.Right) {
rect.right = scr.Bounds.Right;
rect.left = rect.right - (int)(this.Width / magnification);
}
rect.bottom = center.Y + (int)(this.Height / magnification);
if (rect.bottom > scr.Bounds.Bottom) {
rect.bottom = scr.Bounds.Bottom;
rect.top = rect.bottom - (int)(this.Height / magnification);
}
MagSetWindowSource(this.Handle, ref rect);
this.Invalidate();
}
private Timer timer;
private bool trackMouse;
private float magnification = 2.0f;
private struct RECT {
public int left, top, right, bottom;
}
private struct MAGTRANSFORM {
public float v11, v12, v13;
public float v21, v22, v23;
public float v31, v32, v33;
}
[DllImport("magnification.dll")]
private static extern bool MagInitialize();
[DllImport("magnification.dll")]
private static extern bool MagUninitialize();
[DllImport("magnification.dll")]
private static extern bool MagSetWindowSource(IntPtr hWnd, ref RECT rc);
[DllImport("magnification.dll")]
private static extern bool MagSetWindowTransform(IntPtr hWnd, ref MAGTRANSFORM xform);
private const int MS_SHOWMAGNIFIEDCURSOR = 1;
private const int MS_CLIPAROUNDCURSOR = 2;
private const int MS_INVERTCOLORS = 4;
}
Related
I would like to design a chess board and drag over pieces (shown in pictureBox Controls) all child of the main board (pictureBox1).
Problem I encounter is the transparency is only set to the Parent pictureBox1.
Which shows this effect: The square is showing.
private void CommonPiece_Mouse_Move(object sender, MouseEventArgs e)
{
if (Piece_Selected)
{
int MousePositionX = pictureBox1.PointToClient(Cursor.Position).X;
int MousePositionY = pictureBox1.PointToClient(Cursor.Position).Y;
(sender as PictureBox).Left = MousePositionX - 35;
(sender as PictureBox).Top = MousePositionY - 25;
}
}
What would be a good way to go about it?
There are several solutions for this problem. The one in the following screen recording, is based on drawing png images as movable shapes; it uses the idea that I've explained in How to drag and move shapes in C#. Basically there's only one control -the drawing surface, and all the other stuff are movable drawings.
You can use any image for the pieces by passing an Image to the ImageShape
You can modify the size of the Pieces by setting Height of the shape
It supports snapping to the grid
You can customize the texture, easily by setting the BackgroundImage of the drawing surface
You can change the grid size, by setting GridSize property of the drawing surface
You can change the Color of White and Grid grids, by assigning the color to WhiteColor and BlackColor
It's of course a quick example showing how to draw movable objects, including png images keeping their transparency. You know how to improve it :)
Drawing and moving shapes - Chess pieces
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Windows.Forms;
public interface IShape
{
bool HitTest(Point p);
void Draw(Graphics g);
void Move(int dx, int dy);
Point Location { get; set; }
}
public class ImageShape : IShape
{
public int Height { get; set; } = 100;
public Point Location { get; set; }
private Image _image;
public ImageShape(Image image)
{
_image = image;
}
public void Draw(Graphics g)
{
var r = new Rectangle(Location, new Size(Height, Height));
r.Inflate(-5, -5);
g.DrawImage(_image, r);
}
public bool HitTest(Point p)
{
return new Rectangle(Location, new Size(Height, Height)).Contains(p);
}
public void Move(int dx, int dy)
{
Location = new Point(Location.X + dx, Location.Y + dy);
}
}
public class DrawingSurface : Control
{
public List<IShape> Shapes { get; private set; }
public int GridSize { get; set; } = 100;
public Color WhiteColor = Color.FromArgb(200, Color.White);
public Color BlackColor = Color.FromArgb(120, Color.Black);
IShape selectedShape;
bool moving;
Point previousPoint = Point.Empty;
public DrawingSurface()
{
DoubleBuffered = true;
ResizeRedraw = true;
Shapes = new List<IShape>();
}
protected override void OnMouseDown(MouseEventArgs e)
{
for (var i = Shapes.Count - 1; i >= 0; i--)
if (Shapes[i].HitTest(e.Location))
{
selectedShape = Shapes[i];
break;
}
if (selectedShape != null)
{
moving = true;
previousPoint = e.Location;
Invalidate();
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (moving)
{
var dx = e.X - previousPoint.X;
var dy = e.Y - previousPoint.Y;
selectedShape.Move(dx, dy);
previousPoint = e.Location;
this.Invalidate();
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (moving)
{
int i = (e.X / GridSize) * GridSize;
int j = (e.Y / GridSize) * GridSize;
selectedShape.Location = new Point(i, j);
selectedShape = null;
moving = false;
this.Invalidate();
}
base.OnMouseUp(e);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.InterpolationMode = InterpolationMode.High;
g.SmoothingMode = SmoothingMode.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.CompositingQuality = CompositingQuality.HighQuality;
foreach (var shape in Shapes.Except(new[] { selectedShape }))
shape.Draw(g);
if (selectedShape != null)
selectedShape.Draw(g);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
var g = e.Graphics;
using (var w = new SolidBrush(WhiteColor))
using (var b = new SolidBrush(BlackColor))
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
g.FillRectangle((i + j) % 2 == 0 ? b : w,
i * GridSize, j * GridSize, GridSize, GridSize);
}
}
To add pieces:
private void Form1_Load(object sender, EventArgs e)
{
this.drawingSurface1.Shapes.Add(new ImageShape(
Properties.Resources.KingWhite) { Location = new Point(0, 0) });
this.drawingSurface1.Shapes.Add(
new ImageShape(Properties.Resources.kingBlack) { Location = new Point(0, 100) });
}
Here's an example that makes the PictureBox Non-Rectangular. The PB will literally not exist where the transparent areas were:
using System.Runtime.InteropServices;
namespace CS_Scratch_WindowsFormsApp2
{
public partial class Form1 : Form
{
public const int HT_CAPTION = 0x2;
public const int WM_NCLBUTTONDOWN = 0xA1;
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
public Form1()
{
InitializeComponent();
pbChessPiece.MouseMove += PbChessPiece_MouseMove;
}
private void PbChessPiece_MouseMove(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
if (!DesignMode && e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(pb.Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
private void button1_Click(object sender, EventArgs e)
{
Bitmap bmp = (Bitmap)pbChessPiece.Image;
Region rgn = new Region();
rgn.Union(new Rectangle(0, 0, bmp.Width, bmp.Height));
for(int x=0; x<bmp.Width; x++)
{
for(int y=0; y<bmp.Height; y++)
{
if (bmp.GetPixel(x, y).A == 0)
{
rgn.Exclude(new Rectangle(x, y, 1, 1));
}
}
}
pbChessPiece.Region = rgn;
}
}
}
Example run:
I did something similar, but I did not use Drag & Drop events
On MouseDown I assumed a drag operation started, I've opened a translucent unfocused window on top of the drop area, and started drawing dragged image on that form. Since the form is translucent it can show images with transparency on top of everything. I did not use images, but instead draw rectangles where users could drop. If my memory serves me correct, MouseMove events are forwarded to the dragged component, the original chess piece in your case, or the opened but unfocused form, so I can easily track the location. On MouseUp event I wrapped everything.
Since you can draw the image on the new form you can tilt image chess piece image to the left or right for a nice touch.
I developed a very strict forms designer application using this technique.
You can use the panel instead of using the pictureBox and display the image in the background of the controls. Remember that your images must be "png" and the surrounding of the image must be completely transparent. Also, the background color of the controls of the panels Set by events so that it becomes transparent during drag and returns to the cell color after drag and drop.
Good luck.
Here is the following rectangle below:
When I resize the form, I need this rectangle to match the size of the form.
When changing the width of the rectangle, do not interfere with its visibility within the form.
I'm using the following:
Note:
I did the rectangle manually, but if you have rectangle ready, better yet!
public Form1()
{
InitializeComponent();
this.Paint += Form1_Paint;
this.rectangles = new Dictionary<string, Rectangle>();
this.sizeScreen = this.Size;
this.sizeRectangles = new Size(8, 8);
this.brush = new SolidBrush(Color.Red);
FillLeft();
FillRight();
FillUp();
FillDown();
}
private Size sizeScreen;
private Size sizeRectangles;
private SolidBrush brush;
private Dictionary<string, Rectangle> rectangles;
private void FillLeft()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeScreen.Height,
Width = this.sizeRectangles.Width,
X = 0,
Y = this.sizeRectangles.Height
};
this.rectangles.Add("left", rectangle);
}
private void FillRight()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeScreen.Height,
Width = this.sizeRectangles.Width,
X = this.sizeScreen.Width - (this.sizeRectangles.Width * 5),
Y = this.sizeRectangles.Height
};
this.rectangles.Add("right", rectangle);
}
private void FillUp()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeRectangles.Height,
Width = this.sizeScreen.Width,
X = 0,
Y = this.sizeRectangles.Height
};
this.rectangles.Add("up", rectangle);
}
private void FillDown()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeRectangles.Height,
Width = this.sizeScreen.Width,
X = 0,
Y = this.sizeScreen.Height - (this.sizeRectangles.Height * 11)
};
this.rectangles.Add("down", rectangle);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < this.rectangles.Count; i++)
{
e.Graphics.FillRectangles(this.brush, this.rectangles.Values.ToArray());
}
}
I want to set the rectangle on the form when it is resized
This is the way I'm creating the rectangle, but it does not stay right on the screen, to resize it I do not know
I think this would simplify what you are trying to do:
const int PenWidth = 10;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = this.ClientRectangle;
Pen pen = new Pen(Color.Red, PenWidth);
e.Graphics.DrawRectangle(pen, r);
}
You could even add a margin:
const int PenWidth = 10;
const int PenMargin = 10;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = this.ClientRectangle;
r.Inflate(-PenMargin, -PenMargin);
Pen pen = new Pen(Color.Red, PenWidth);
e.Graphics.DrawRectangle(pen, r);
}
To prevent traces (suggested by Wyck):
private void Form1_Resize(object sender, EventArgs e)
{
Invalidate();
}
Handle the Resize event and call Invalidate in the handler. Create a Pen of the desired color and width and set its Alignment to Inset. Handle the Paint event and in the handler call DrawRectangle passing in the ClientRectangle of the form.
Here is an example.
const float borderWidth = 8.0f;
Pen borderPen = new Pen(Color.Red, borderWidth) { Alignment = System.Drawing.Drawing2D.PenAlignment.Inset };
public Form2()
{
InitializeComponent();
this.Paint += Form2_Paint;
this.Resize += Form2_Resize;
}
private void Form2_Resize(object sender, EventArgs e)
{
Invalidate();
}
private void Form2_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(borderPen, this.ClientRectangle);
}
Apply the following fixes to the code:
Set ResizeRedraw property of the form to true. It sets the underlying style for the form so by each resize it sends the paint message and you don't need to handle Resize event.
Use DrawRectangle and draw using wide pen. So you don't need to fill multiple rectangles.
Set the PenAlignment to Inset. So you don't need to calculate the location of rectangle.
Do dispose the pen when you don't need it.
Example
public Form1()
{
InitializeComponent();
ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var pen = new Pen(Color.Red, PenWidth))
{
pen.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
e.Graphics.DrawRectangle(pen, ClientRectangle);
}
}
i have used this on my current project. when ever you resize the form, it will automatically resize all the object inside your form
i have class named clsResize and i call this on the form load.
1st you have to initialize the class inside the form then create 2 new method.
see example below
public partial class frmNewForm : Form
{
clsResize _form_resize;
public string selectedProd;
public frmNewForm()
{
InitializeComponent();
_form_resize = new clsResize(this);
this.Load += _Load;
this.Resize += _Resize;
}
private void _Load(object sender, EventArgs e)
{
_form_resize._get_initial_size();
}
private void _Resize(object sender, EventArgs e)
{
_form_resize._resize();
}
}
and here is the class that i used.
public class clsResize
{
List<System.Drawing.Rectangle> _arr_control_storage = new List<System.Drawing.Rectangle>();
private bool showRowHeader = false;
public clsResize(Form _form_)
{
form = _form_; //the calling form
_formSize = _form_.ClientSize; //Save initial form size
_fontsize = _form_.Font.Size; //Font size
}
private float _fontsize { get; set; }
private System.Drawing.SizeF _formSize {get;set; }
private Form form { get; set; }
public void _get_initial_size() //get initial size//
{
var _controls = _get_all_controls(form);//call the enumerator
foreach (Control control in _controls) //Loop through the controls
{
_arr_control_storage.Add(control.Bounds); //saves control bounds/dimension
//If you have datagridview
if (control.GetType() == typeof(DataGridView))
_dgv_Column_Adjust(((DataGridView)control), showRowHeader);
}
}
public void _resize() //Set the resize
{
double _form_ratio_width = (double)form.ClientSize.Width /(double)_formSize.Width; //ratio could be greater or less than 1
double _form_ratio_height = (double)form.ClientSize.Height / (double)_formSize.Height; // this one too
var _controls = _get_all_controls(form); //reenumerate the control collection
int _pos = -1;//do not change this value unless you know what you are doing
try
{
foreach (Control control in _controls)
{
// do some math calc
_pos += 1;//increment by 1;
System.Drawing.Size _controlSize = new System.Drawing.Size((int)(_arr_control_storage[_pos].Width * _form_ratio_width),
(int)(_arr_control_storage[_pos].Height * _form_ratio_height)); //use for sizing
System.Drawing.Point _controlposition = new System.Drawing.Point((int)
(_arr_control_storage[_pos].X * _form_ratio_width), (int)(_arr_control_storage[_pos].Y * _form_ratio_height));//use for location
//set bounds
control.Bounds = new System.Drawing.Rectangle(_controlposition, _controlSize); //Put together
//Assuming you have a datagridview inside a form()
//if you want to show the row header, replace the false statement of
//showRowHeader on top/public declaration to true;
if (control.GetType() == typeof(DataGridView))
_dgv_Column_Adjust(((DataGridView)control), showRowHeader);
//Font AutoSize
control.Font = new System.Drawing.Font(form.Font.FontFamily,
(float)(((Convert.ToDouble(_fontsize) * _form_ratio_width) / 2) +
((Convert.ToDouble(_fontsize) * _form_ratio_height) / 2)));
}
}
catch(Exception e)
{
MessageBox.Show(e.Message);
return;
}
}
private void _dgv_Column_Adjust(DataGridView dgv, bool _showRowHeader) //if you have Datagridview
//and want to resize the column base on its dimension.
{
int intRowHeader = 0;
const int Hscrollbarwidth = 5;
if (_showRowHeader)
intRowHeader = dgv.RowHeadersWidth;
else
dgv.RowHeadersVisible = false;
for (int i = 0; i < dgv.ColumnCount; i++)
{
if (dgv.Dock == DockStyle.Fill) //in case the datagridview is docked
dgv.Columns[i].Width = ((dgv.Width - intRowHeader) / dgv.ColumnCount);
else
dgv.Columns[i].Width = ((dgv.Width - intRowHeader - Hscrollbarwidth) / dgv.ColumnCount);
}
}
private static IEnumerable<Control> _get_all_controls(Control c)
{
return c.Controls.Cast<Control>().SelectMany(item =>
_get_all_controls(item)).Concat(c.Controls.Cast<Control>()).Where(control =>
control.Name != string.Empty);
}
}
I have WPF borderless Transparent window.
By using this.DragMove(); I can successfully move the window.
I wanted to restrict window within screen area.
It's also working using below snippet.
private void Window_LocationChanged(object sender, EventArgs e)
{
CheckBounds();
}
private void CheckBounds()
{
var height = System.Windows.SystemParameters.PrimaryScreenHeight;
var width = System.Windows.SystemParameters.PrimaryScreenWidth;
if (this.Left < 0)
this.Left = 0;
if (this.Top < 0)
this.Top = 0;
if (this.Top + this.Height > height)
this.Top = height - this.Height;
if (this.Left + this.Width > width)
this.Left = width - this.Width;
}
But using above code, whenever window reach its max bounds using mouse drag, It starts flickering.
Could anybody suggest how to avoid this flickering?
The best way I know to deal with this issue is to handle the WM_MOVING windows message in your window and adjust the position there. Since the WM_MOVING message is received before the window actually moves and allows the position to be modified, you never see any jitter. Here is an example code-behind for a Window.
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
public partial class MainWindow : Window
{
private HwndSource mSource;
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
mSource = (HwndSource)PresentationSource.FromVisual(this);
mSource.AddHook(WndProc);
}
protected override void OnClosed(EventArgs e)
{
mSource.RemoveHook(WndProc);
mSource.Dispose();
mSource = null;
base.OnClosed(e);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == (int)WindowsMessage.WM_MOVING)
{
// TODO: Substitute realistic bounds
RECT bounds = new RECT() { Left = 0, Top = 0, Right = 1000, Bottom = 800 };
RECT window = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
if (window.Left < bounds.Left)
{
window.Right = window.Right + bounds.Left - window.Left;
window.Left = bounds.Left;
}
if (window.Top < bounds.Top)
{
window.Bottom = window.Bottom + bounds.Top - window.Top;
window.Top = bounds.Top;
}
if (window.Right >= bounds.Right)
{
window.Left = bounds.Right - window.Right + window.Left - 1;
window.Right = bounds.Right - 1;
}
if (window.Bottom >= bounds.Bottom)
{
window.Top = bounds.Bottom - window.Bottom + window.Top - 1;
window.Bottom = bounds.Bottom - 1;
}
Marshal.StructureToPtr(window, lParam, true);
handled = true;
return new IntPtr(1);
}
handled = false;
return IntPtr.Zero;
}
}
Here are the helper objects that are used in the code:
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int Left, Top, Right, Bottom;
}
enum WindowsMessage
{
WM_MOVING = 0x0216
}
P.S. The LocationChanged event (and associated OnLocationChanged override) is called in response to WM_MOVE, which is not called until the window has already moved. There does not seem to be a corresponding OnLocationChanging event.
I'm trying to write a magnifier application using a winform or wpf windows. The idea is to drag the window over a spot on the screen and magnify it. I know it exists commercially but need to build a customized version. The challenge I'm having is to capture the screen image behind the active application.
I have found code to capture a screen image below. But it includes the active window
{
Rectangle bounds = new Rectangle(this.Location, this.Size);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
Graphics g = Graphics.FromImage(bitmap);
g.CopyFromScreen(this.Location, Point.Empty, bounds.Size);
Bitmap bp2 = new Bitmap(bitmap); // local copy of image...
pictureBox1.Image = bp2;
}
Adding statements to hide the active application correct the image capture, but introduce a screen flicker which I'd like to avoid. (modified code below)
{
this.StartPosition = FormStartPosition.Manual; // get current window location
Point cur = this.Location;
this.Location = new Point(-500, -500); // hide the active app off screen.
Rectangle bounds = new Rectangle(cur, this.Size);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
Graphics g = Graphics.FromImage(bitmap);
g.CopyFromScreen(cur, Point.Empty, bounds.Size);
Bitmap bp2 = new Bitmap(bitmap); // local copy of image...
pictureBox1.Image = bp2;
this.Location = cur; // restore application location
}
Can someone suggest an alternative to capture a screen region, behind an active windows?
Thx.
Wrapping the Magnification API is pretty useful. I created a Winforms control that does this. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto a form.
The TrackMouse property controls which part of the screen is shown magnified. Set to False, it magnifies the area covered by the control, making it act like a magnifying glass. Set to True, it will operate like Windows' Magnifier, following the mouse.
The Magnification property controls the amount of magnification. You can already adjust it by using the mouse wheel.
The form you drop it should have its TopMost property set to True. You might want to tinker with its Region property to make it resemble a spyglass.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class Magnifier : Control {
public Magnifier() {
if (!MagInitialize()) throw new NotSupportedException();
timer = new Timer { Interval = 45 };
timer.Tick += (o, ea) => { if (trackMouse) setSource(false); else Invalidate(); };
}
[DefaultValue(false)]
public bool TrackMouse {
get { return trackMouse; }
set { trackMouse = value; setSource(false); }
}
[DefaultValue(2.0f)]
public float Magnification {
get { return magnification; }
set { magnification = Math.Max(1, value); setSource(true); }
}
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
if (!this.DesignMode) {
cp.ClassName = "Magnifier";
//cp.Style |= MS_SHOWMAGNIFIEDCURSOR;
this.SetStyle(ControlStyles.UserPaint, true);
}
return cp;
}
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (!this.DesignMode) {
setSource(true);
this.FindForm().LocationChanged += ParentLocationChanged;
timer.Start();
}
}
protected override void Dispose(bool disposing) {
if (disposing) {
var frm = this.FindForm();
if (frm != null) frm.LocationChanged -= ParentLocationChanged;
timer.Dispose();
MagUninitialize();
}
base.Dispose(disposing);
}
private void ParentLocationChanged(object sender, EventArgs e) {
if (!trackMouse) setSource(false);
}
protected override void OnSizeChanged(EventArgs e) {
setSource(false);
base.OnSizeChanged(e);
}
protected override void OnMouseWheel(MouseEventArgs e) {
this.Magnification += e.Delta / 100f;
((HandledMouseEventArgs)e).Handled = true;
}
private void setSource(bool newmag) {
if (!this.IsHandleCreated || this.DesignMode) return;
if (newmag) {
var xform = new MAGTRANSFORM();
xform.v11 = xform.v22 = magnification;
xform.v33 = 1.0f;
MagSetWindowTransform(this.Handle, ref xform);
}
Point center;
if (trackMouse) center = Cursor.Position;
else {
var rc = this.RectangleToScreen(this.Bounds);
center = new Point(rc.Left + rc.Width / 2, rc.Top + rc.Height / 2);
}
var scr = Screen.FromPoint(center);
var rect = new RECT();
rect.left = Math.Max(scr.Bounds.Left, center.X - (int)(this.Width / magnification / 2));
rect.top = Math.Max(scr.Bounds.Top, center.Y - (int)(this.Height / magnification / 2));
rect.right = rect.left + (int)(this.Width / magnification);
if (rect.right > scr.Bounds.Right) {
rect.right = scr.Bounds.Right;
rect.left = rect.right - (int)(this.Width / magnification);
}
rect.bottom = center.Y + (int)(this.Height / magnification);
if (rect.bottom > scr.Bounds.Bottom) {
rect.bottom = scr.Bounds.Bottom;
rect.top = rect.bottom - (int)(this.Height / magnification);
}
MagSetWindowSource(this.Handle, ref rect);
this.Invalidate();
}
private Timer timer;
private bool trackMouse;
private float magnification = 2.0f;
private struct RECT {
public int left, top, right, bottom;
}
private struct MAGTRANSFORM {
public float v11, v12, v13;
public float v21, v22, v23;
public float v31, v32, v33;
}
[DllImport("magnification.dll")]
private static extern bool MagInitialize();
[DllImport("magnification.dll")]
private static extern bool MagUninitialize();
[DllImport("magnification.dll")]
private static extern bool MagSetWindowSource(IntPtr hWnd, ref RECT rc);
[DllImport("magnification.dll")]
private static extern bool MagSetWindowTransform(IntPtr hWnd, ref MAGTRANSFORM xform);
private const int MS_SHOWMAGNIFIEDCURSOR = 1;
private const int MS_CLIPAROUNDCURSOR = 2;
private const int MS_INVERTCOLORS = 4;
}
I'm tring to build a Control derived class which supports an Opcacity property.
This control could host both text and image and will beable to fade them out and in.
Here is my code:
internal class FadeControl : Control
{
private int opacity = 100;
public FadeControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
public int Opacity
{
get
{
return opacity;
}
set
{
if (value > 100) opacity = 100;
else if (value < 1) opacity = 1;
else opacity = value;
if (Parent != null)
Parent.Invalidate(Bounds, true);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothing
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
protected override void OnPaint(PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Rectangle bounds = new Rectangle(0, 0, Width - 1, Height - 1);
int alpha = (opacity * 255) / 100;
using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
{
if (BackColor != Color.Transparent)
g.FillRectangle(bckColor, bounds);
}
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix33 = (float)alpha / 255;
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
if (BackgroundImage != null)
g.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
if (Text != string.Empty)
{
using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
{
g.DrawString(Text, Font, txtBrush, 5, 5);
}
}
}
}
protected override void OnBackColorChanged(EventArgs e)
{
if (Parent != null)
Parent.Invalidate(Bounds, true);
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
Invalidate();
base.OnParentBackColorChanged(e);
}
}
I've putted the control on a form which has a timer on it.
The timer set the control's opacity from 0 to 100 and back and its working well.
The problem I'm trying to solved is that the control flickers while changing its opacity.
Setting the control toControlStyles.DoubleBuffer will make the control invisible on the form.
Any advice will be welcome.
I was unable to use both a double buffer and WS_EX_TRANSPARENT (0x20) for the transparent background. So I decided to implement the transparent background by copying the content of the parent control and use double buffer to prevent flicker.
The following is the final source code, tested and working:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
internal class FadeControl : Control
{
private int opacity = 100;
private Bitmap backgroundBuffer;
private bool skipPaint;
public FadeControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
public int Opacity
{
get
{
return opacity;
}
set
{
if (value > 100) opacity = 100;
else if (value < 1) opacity = 1;
else opacity = value;
if (Parent != null)
Parent.Invalidate(Bounds, true);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//do nothig
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
private void CreateBackgroundBuffer(Control parent)
{
int offsetX;
int offsetY;
GetOffsets(out offsetX, out offsetY, parent);
backgroundBuffer = new Bitmap(Width + offsetX, Height + offsetY);
}
protected override void OnResize(EventArgs e)
{
var parent = Parent;
if (parent != null)
{
CreateBackgroundBuffer(parent);
}
base.OnResize(e);
}
private void GetOffsets(out int offsetX, out int offsetY, Control parent)
{
var parentPosition = parent.PointToScreen(Point.Empty);
offsetY = Top + parentPosition.Y - parent.Top;
offsetX = Left + parentPosition.X - parent.Left;
}
private void UpdateBackgroundBuffer(int offsetX, int offsetY, Control parent)
{
if (backgroundBuffer == null)
{
CreateBackgroundBuffer(parent);
}
Rectangle parentBounds = new Rectangle(0, 0, Width + offsetX, Height + offsetY);
skipPaint = true;
parent.DrawToBitmap(backgroundBuffer, parentBounds);
skipPaint = false;
}
private void DrawBackground(Graphics graphics, Rectangle bounds)
{
int offsetX;
int offsetY;
var parent = Parent;
GetOffsets(out offsetX, out offsetY, parent);
UpdateBackgroundBuffer(offsetX, offsetY, parent);
graphics.DrawImage(backgroundBuffer, bounds, offsetX, offsetY, Width, Height, GraphicsUnit.Pixel);
}
private void Draw(Graphics graphics)
{
Rectangle bounds = new Rectangle(0, 0, Width, Height);
DrawBackground(graphics, bounds);
int alpha = (opacity * 255) / 100;
using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
{
if (BackColor != Color.Transparent)
{
graphics.FillRectangle(bckColor, bounds);
}
}
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix33 = (float)alpha / 255;
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
if (BackgroundImage != null)
{
graphics.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
}
if (Text != string.Empty)
{
using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
{
graphics.DrawString(Text, Font, txtBrush, 5, 5);
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (!skipPaint)
{
Graphics graphics = e.Graphics;
Draw(graphics);
}
}
protected override void OnBackColorChanged(EventArgs e)
{
if (Parent != null)
{
Parent.Invalidate(Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
Invalidate();
base.OnParentBackColorChanged(e);
}
}
Note that the method CreateParams is no longer present, also I've changed the contructor.
The field skipPaint is to know when not to paint in order to be able to able to tell the parent to draw itself to a bitmap during OnPaint without having infinite recursion.
backgroundBuffer is not to implement double buffering, but to keep a copy of the contents of the parent without the control rendered. It is updated each paint, I know there are more efficient solutions...* yet this approach keeps it simple and shouldn't be a bottleneck unless you have too many of these controls on the same container.
*: A better solution would be to update it each time the parent invalidates. Futhermore shared it among all the FadeControls in the same parent.