Transparent Overlapping Circular Progress Bars (Custom Control) - c#

I am having some trouble with a custom circular progress bar control. I am trying to overlap the two of them at the lower right corner. It has a transparent background, which obviously in WinForms is showing the background, but has no effect on each other.
Here is what I am seeing:
I have been researching on stackoverflow, and have found a few answers to people having this issue with custom picturebox controls. Most of the solutions, seem to have no effect on the circular progress bar control. Some of the solutions I have tried is.
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
I also have the code on the custom control for allowing transparent backgrounds. Obviously, as I stated, this does not effect overlapping controls.
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
There is also a TransparentControl solution on stackoverflow which I saw people using. I have created the control, but either have no idea how to use it, or it doesn't work in my situation. Here is the code from that control.
public class TransparentControl : Panel
{
public bool drag = false;
public bool enab = false;
private int m_opacity = 100;
private int alpha;
public TransparentControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
public int Opacity
{
get
{
if (m_opacity > 100)
{
m_opacity = 100;
}
else if (m_opacity < 1)
{
m_opacity = 1;
}
return this.m_opacity;
}
set
{
this.m_opacity = value;
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle bounds = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Color frmColor = this.Parent.BackColor;
Brush bckColor = default(Brush);
alpha = (m_opacity * 255) / 100;
if (drag)
{
Color dragBckColor = default(Color);
if (BackColor != Color.Transparent)
{
int Rb = BackColor.R * alpha / 255 + frmColor.R * (255 - alpha) / 255;
int Gb = BackColor.G * alpha / 255 + frmColor.G * (255 - alpha) / 255;
int Bb = BackColor.B * alpha / 255 + frmColor.B * (255 - alpha) / 255;
dragBckColor = Color.FromArgb(Rb, Gb, Bb);
}
else
{
dragBckColor = frmColor;
}
alpha = 255;
bckColor = new SolidBrush(Color.FromArgb(alpha, dragBckColor));
}
else
{
bckColor = new SolidBrush(Color.FromArgb(alpha, this.BackColor));
}
if (this.BackColor != Color.Transparent | drag)
{
g.FillRectangle(bckColor, bounds);
}
bckColor.Dispose();
g.Dispose();
base.OnPaint(e);
}
protected override void OnBackColorChanged(EventArgs e)
{
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
this.Invalidate();
base.OnParentBackColorChanged(e);
}
}
Any assistance would be appreciated. This has been driving me nuts for hours. Thanks :)
UPDATE 1: I tried using the following code snippet from examples posted below. This yielded the same results. I still have that blank space between the circular progress bars (as seen in the picture).
Parent.Controls.Cast<Control>()
.Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
.Where(c => c.Bounds.IntersectsWith(this.Bounds))
.OrderByDescending(c => Parent.Controls.GetChildIndex(c))
.ToList()
.ForEach(c => c.DrawToBitmap(bmp, c.Bounds));
Still stumped. :(
UPDATE 2: I tried setting the front circularprogressbar to use the back circularprogressbar as it's parent in the FormLoad. That didn't work out either. It made them transparent to each other, but cut off any part of the top circularprogressbar that wasn't within' the boundaries of the back.
var pts = this.PointToScreen(circularprogressbar1.Location);
pts = circularprogressbar2.PointToClient(pts);
circularprogressbar1.Parent = circularprogressbar2;
circularprogressbar1.Location = pts;

I'm going to give you just a couple of suggestions on how to proceed.
Start off with this bare-bones transparent control (TransparentPanel).
This class is derived from Panel. That's the first choice to make: is Panel the right control to inherit from/extend for this task? Maybe it is, maybe not.
For example, a Panel is a container. Do you need the features of a container, here? Container means a lot. It inherits ScrollableControl and has ContainerControl among its Window styles. It comes with a baggage already.
You could opt for a Label instead, it's light-weight. Or build a UserControl.
I don't think there's an absolute best choice. It depends of what this custom control is used for. You need to try it out.
Note:
To create the rotation effect with the code shown here, you need the TransparentPanel Control shown below, it won't work the same way drawing on the surface of a standard Control.
This Control generates sort of a persistence in the drawn shapes, which won't exist using another type of Control as canvas (not without tweaking it heavily, that is).
class TransparentPanel : Panel
{
internal const int WS_EX_TRANSPARENT = 0x00000020;
public TransparentPanel() => InitializeComponent();
protected void InitializeComponent()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.Opaque |
ControlStyles.ResizeRedraw |
ControlStyles.SupportsTransparentBackColor |
ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get {
var cp = base.CreateParams;
cp.ExStyle |= WS_EX_TRANSPARENT;
return cp;
}
}
}
Other notes:
Here, ControlStyles.SupportsTransparentBackColor is set explicitly. The Panel class already supports this. It's specified anyway because it gives the idea of what this custom control is for just reading at its constructor.
Also, ControlStyles.OptimizedDoubleBuffer is set to false.
This prevents the System to interfere in any way with the painting of the control. There's no caching, the Custom Control is painted new when it's Invalidated. The container Form should preferably have its DoubleBuffer property set to true, but you might want test it without, to see if there's a difference.
This Custom Control (not to be confused with a UserControl) is completely transparent. It doesn't draw its background. But you can paint anything on its surface.
Take the links posted before:
This Translucent Label (no BackGround painting, disabled DoubleDuffering)
Reza Aghaei's transparent Panel (using Opacity in a different way)
TaW's Grid Panel (Color.Transparent and DoubleBuffer)
These notes: Reasons for why a WinForms label does not want to be transparent?
4 different ways to get to the same result. Which one to choose depends on the context/destination.
A design-time advice: when you are testing a custom control functionalities, remember to always rebuild the project. It can happen that a CustomControl, droppen on a Form from the Toolbox, is not updated with the new changes when the project is run.
Also, if you add or remove properties, you need to delete the control, rebuild and drop a new one on the Form.
If you don't, there's a really good chance that your modification/addition are completely ignored and you keep on testing features that never get into play.
An example, using 2 overlapping custom controls.
(using the bare-bones custom TransparentPanel)
This is the test code used to generate these drawings:
Create a new Custom Control using the TransparentPanel class shown before:
Drop two TransparentPanel objects on a test Form
Assign to TransparentPanel1 and TransparentPanel2 the transparentPanel1_Paint and transparentPanel2_Paint event handlers.
Overlap the two transparent Panels, making sure you don't nest them by mistake.
Adapt the rest of the code (you need just a Button, here named btnRotate, assign the btnRotate_Click handler)
private System.Windows.Forms.Timer RotateTimer = null;
private float RotationAngle1 = 90F;
private float RotationAngle2 = 0F;
public bool RotateFigures = false;
public form1()
{
InitializeComponent();
RotateTimer = new Timer();
RotateTimer.Interval = 50;
RotateTimer.Enabled = false;
RotateTimer.Tick += new EventHandler(this.RotateTick);
}
protected void RotateTick(object sender, EventArgs e)
{
RotationAngle1 += 10F;
RotationAngle2 += 10F;
transparentPanel1.Invalidate();
transparentPanel2.Invalidate();
}
private void btnRotate_Click(object sender, EventArgs e)
{
RotateTimer.Enabled = !RotateTimer.Enabled;
if (RotateTimer.Enabled == false)
{
RotateFigures = false;
RotationAngle1 = 90F;
RotationAngle2 = 0F;
}
else
{
RotateFigures = true;
}
}
private void transparentPanel1_Paint(object sender, PaintEventArgs e)
{
if (!RotateFigures) return;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.CompositingMode = CompositingMode.SourceOver;
Rectangle rect = transparentPanel1.ClientRectangle;
Rectangle rectInner = rect;
using (Pen transpPen = new Pen(transparentPanel1.Parent.BackColor, 10))
using (Pen penOuter = new Pen(Color.SteelBlue, 8))
using (Pen penInner = new Pen(Color.Teal, 8))
using (Matrix m1 = new Matrix())
using (Matrix m2 = new Matrix())
{
m1.RotateAt(-RotationAngle1, new PointF(rect.Width / 2, rect.Height / 2));
m2.RotateAt(RotationAngle1, new PointF(rect.Width / 2, rect.Height / 2));
rect.Inflate(-(int)penOuter.Width, -(int)penOuter.Width);
rectInner.Inflate(-(int)penOuter.Width * 3, -(int)penOuter.Width * 3);
e.Graphics.Transform = m1;
e.Graphics.DrawArc(transpPen, rect, -4, 94);
e.Graphics.DrawArc(penOuter, rect, -90, 90);
e.Graphics.ResetTransform();
e.Graphics.Transform = m2;
e.Graphics.DrawArc(transpPen, rectInner, 190, 100);
e.Graphics.DrawArc(penInner, rectInner, 180, 90);
}
}
private void transparentPanel2_Paint(object sender, PaintEventArgs e)
{
if (!RotateFigures) return;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.CompositingMode = CompositingMode.SourceOver;
Rectangle rect = transparentPanel2.ClientRectangle;
Rectangle rectInner = rect;
using (Pen transpPen = new Pen(transparentPanel2.Parent.BackColor, 10))
using (Pen penOuter = new Pen(Color.Orange, 8))
using (Pen penInner = new Pen(Color.DarkRed, 8))
using (Matrix m1 = new Matrix())
using (Matrix m2 = new Matrix())
{
m1.RotateAt(RotationAngle2, new PointF(rect.Width / 2, rect.Height / 2));
m2.RotateAt(-RotationAngle2, new PointF(rect.Width / 2, rect.Height / 2));
rect.Inflate(-(int)penOuter.Width, -(int)penOuter.Width);
rectInner.Inflate(-(int)penOuter.Width * 3, -(int)penOuter.Width * 3);
e.Graphics.Transform = m1;
e.Graphics.DrawArc(transpPen, rect, -4, 94);
e.Graphics.DrawArc(penOuter, rect, 0, 90);
e.Graphics.ResetTransform();
e.Graphics.Transform = m2;
e.Graphics.DrawArc(transpPen, rectInner, 190, 100);
e.Graphics.DrawArc(penInner, rectInner, 180, 90);
}
}

Related

Create vertical scrollbar with multiple pictures

I am creating a project that needs to have a vertical scroll bar with multiple pictures like the server explorer in discord:
For example:
how can I mimic in WinForms in C# (not only having pictures scrolling but also the pictures can have events attached to them?
First you need add a Parent Panel, i'm used the pnServers.
Set property value:
AutoScroll = True;
On Code Behind you can create a List of Rounded Pictures.
private void DiscordServerBarExample_Load(object sender, System.EventArgs e)
{
// Example, in your case this looping is based on return (Database, Api, ...).
for (int i = 1; i <= 10; i++)
{
Panel pnServer = new Panel()
{
Dock = DockStyle.Top,
Height = pnServers.Width,
Padding = new Padding(10)
};
RoundedPictureBox serverImage = new RoundedPictureBox()
{
SizeMode = PictureBoxSizeMode.CenterImage,
Dock = DockStyle.Fill
};
serverImage.Image = Properties.Resources._255352;
pnServer.Controls.Add(serverImage);
pnServers.Controls.Add(pnServer);
}
}
Rounded Picture Box Code:
public class RoundedPictureBox : PictureBox
{
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddEllipse(0, 0, Width - 1, Height - 1);
Region rg = new Region(gp);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawEllipse(new Pen(new SolidBrush(this.BackColor), 1), 0, 0, this.Width - 1, this.Height - 1);
Region = rg;
}
}
}
And this is final result.

Toggle switch control in Windows Forms

I am designing a toggle switch control using CheckBox, but currently my control only draws a circle. How can I draw round shapes like the below image and how can I change the location of the circle based on the value of the control to represent checked and unchecked states like the below image?
Here is my code:
public class MyCheckBox:CheckBox
{
public MyCheckBox()
{
this.Appearance = System.Windows.Forms.Appearance.Button;
this.BackColor = Color.Transparent;
this.TextAlign = ContentAlignment.MiddleCenter;
this.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.FlatAppearance.BorderColor = Color.RoyalBlue;
this.FlatAppearance.BorderSize = 2;
}
protected override void OnPaint(PaintEventArgs e)
{
this.OnPaintBackground(e);
using (var path = new GraphicsPath())
{
var c = e.Graphics.ClipBounds;
var r = this.ClientRectangle;
r.Inflate(-FlatAppearance.BorderSize, -FlatAppearance.BorderSize);
path.AddEllipse(r);
e.Graphics.SetClip(path);
base.OnPaint(e);
e.Graphics.SetClip(c);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
if (this.Checked)
{
using (var p = new Pen(FlatAppearance.BorderColor,
FlatAppearance.BorderSize))
{
e.Graphics.DrawEllipse(p, r);
}
}
}
}
}
I know this is a Windows Forms question. But you may want to take a look at Toggle Switches or read more about Universal Windows App Components.
Anyway, here is an answer for Windows Forms developers. It shows how we can customize rendering of a checkbox to have such appearance.
Currently you are drawing only an ellipse, and it's quite a toggle button. But if you want to show it like the below image, you should first draw a round shape for background, and then based on the Checked value, draw the check circle. Using the code in Example part of the answer you can have a CheckBox with such a UI:
Example
The important thing about this sample is it's completely a CheckBox control and supports check using mouse and keyboard. It also supports data-binding and all other standard features of CheckBox. The code is not perfect, but it is a good start point to have a yes/no toggle switch:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class MyCheckBox : CheckBox
{
public MyCheckBox()
{
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
Padding = new Padding(6);
}
protected override void OnPaint(PaintEventArgs e)
{
this.OnPaintBackground(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var path = new GraphicsPath())
{
var d = Padding.All;
var r = this.Height - 2 * d;
path.AddArc(d, d, r, r, 90, 180);
path.AddArc(this.Width - r - d, d, r, r, -90, 180);
path.CloseFigure();
e.Graphics.FillPath(Checked ? Brushes.DarkGray : Brushes.LightGray, path);
r = Height - 1;
var rect = Checked ? new Rectangle(Width - r - 1, 0, r, r)
: new Rectangle(0, 0, r, r);
e.Graphics.FillEllipse(Checked ? Brushes.Green : Brushes.WhiteSmoke, rect);
}
}
}

Enhanced crosshair cursor possible?

I have found this question (as a few others), but this is the one I have implemented so far:
Crosshair cursor with additional lines in C#
As it states, I can use a stock cursor "cross" directly in the IDE. This is a really good way to do things. The answer specified in the answer above draws a cross on the screen at the given width / height. Eg:
private Cursor crossCursor(Pen pen, Brush brush, int x, int y)
{
var pic = new Bitmap(x, y);
Graphics gr = Graphics.FromImage(pic);
var pathX = new GraphicsPath();
var pathY = new GraphicsPath();
pathX.AddLine(0, y / 2, x, y / 2);
pathY.AddLine(x / 2, 0, x / 2, y);
gr.DrawPath(pen, pathX);
gr.DrawPath(pen, pathY);
IntPtr ptr = pic.GetHicon();
var c = new Cursor(ptr);
return c;
}
My issue is that I want my cross hairs to extend to the Bounds of the viewing area. To provide context here, I have:
//Form
//TableLayoutPanel
//UserControl (fills the TableLayoutPanel visible area)
So how can I adjust my cursor so that the lines extend (much like in CAD pacakages)?
Thanks.
Update: I have tried calling the method from here:
protected override void OnLoad(System.EventArgs e)
{
Cursor = crossCursor(Pens.WhiteSmoke, Brushes.WhiteSmoke, Bounds.Width, Bounds.Height);
}
But it is not Ok because at this point in time Bounds is returning a dimension of 150 by 150 which is not the size of the TableLayoutPanel.
Update: I have adjuted it to use the Resize handler instead and it does improve things:
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Cursor = crossCursor(Pens.WhiteSmoke, Brushes.WhiteSmoke, Bounds.Width, Bounds.Height);
}
The only problem now (and it kind of makes sense I suppose) is that the cursor will only take the full width and height of the view when it is central to the view. As soon as I move about in the view that cursor does not adjust. I always want a horizontal/vertical line through the mouse position (not just the initial cross).
See:
The crosshairs need extending (the thicker red lines). Either I need to constantly create the cursor as the mouse moves or construct the two lines another way. What to do?
I came across this:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/7bdbad6d-1f65-461b-8f0c-6ef4f243fa6b/crosshair-cursor-using-c?forum=csharpgeneral
So, instead of changing the cursor object I now draw lines in the controls MouseMove handler:
Region r = new Region();
r.Union(new Rectangle(0, lastY, this.Width, 1));
r.Union(new Rectangle(lastX, 0, 1, this.Height));
this.Invalidate(r);
this.Update();
Graphics g = Graphics.FromHwnd(this.Handle);
g.DrawLine(Pens.White, 0, e.Y, this.Width, e.Y);
g.DrawLine(Pens.White, e.X, 0, e.X, this.Height);
int intDiameter = 20;//the diameter of this circle
g.DrawEllipse(Pens.White, e.X - intDiameter / 2, e.Y - intDiameter / 2, 20, 20);
//to draw the circle
lastX = e.X;
lastY = e.Y;
It works, but I get noticiable screen flicker doing it this way.
You don't need to create a cursor. You can create a double buffered control and draw cross over control.
using System;
using System.Drawing;
using System.Windows.Forms;
public class DrawingSurface : Control
{
Pen crossPen;
Pen rectanglePen;
Brush rectangleBrush;
public DrawingSurface()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
crossPen = new Pen(Color.Red, 2);
rectangleBrush = new SolidBrush(Color.FromArgb(50, Color.Blue));
rectanglePen = new Pen(Color.Blue, 1);
}
bool mouseDown = false;
Point startPoint = Point.Empty;
Point endPoint = Point.Empty;
protected override void OnMouseDown(MouseEventArgs e)
{
startPoint = e.Location;
mouseDown = true;
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
mouseDown = false;
base.OnMouseUp(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
endPoint = e.Location;
this.Invalidate();
base.OnMouseMove(e);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
if (this.ClientRectangle.Contains(endPoint))
DrawCross(e.Graphics, endPoint);
if (mouseDown)
DrawRectangle(e.Graphics, startPoint, endPoint);
}
void DrawCross(Graphics g, Point point)
{
g.DrawLine(crossPen, new Point(0, point.Y), new Point(Width, point.Y));
g.DrawLine(crossPen, new Point(point.X, 0), new Point(point.X, Height));
}
void DrawRectangle(Graphics g, Point point1, Point point2)
{
var rectangle = new Rectangle(
Math.Min(point1.X, point2.X), Math.Min(point1.Y, point2.Y),
Math.Abs(point1.X - point2.X), Math.Abs(point1.Y - point2.Y));
g.FillRectangle(rectangleBrush, rectangle);
g.DrawRectangle(rectanglePen, rectangle);
}
protected override void Dispose(bool disposing)
{
crossPen.Dispose();
rectanglePen.Dispose();
rectangleBrush.Dispose();
base.Dispose(disposing);
}
}

Custom Control to use Cursor Hand

I've made a custom control in C# and anytime that the user's cursor is hovering over the custom control I want the cursor to be displayed as the 'Hand'. Where do i place the code to do such a thing?
????.Cursor = Cursors.Hand;
in order to make it so the Hand Cursor is being displayed when hovering over this custom control?
namespace CustomRangeBar
{
public partial class RangeBar : UserControl
{
public RangeBar()
{
InitializeComponent();
label1.ForeColor = Color.Black;
this.ForeColor = SystemColors.Highlight; // set the default color the rangeBar
this.Click += new EventHandler(RangeBar_Click);
}
protected float percent = 0.0f; // Protected because we don't want this to be accessed from the outside
// Create a Value property for the rangeBar
public float Value
{
get
{
return percent;
}
set
{
// Maintain the value between 0 and 100
if (value < 0) value = 0;
else if (value > 100) value = 100;
percent = value;
label1.Text = value.ToString();
//redraw the rangeBar every time the value changes
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush b = new SolidBrush(this.ForeColor); //create brush that will draw the background of the range bar
// create a linear gradient that will be drawn over the background. FromArgb means you can use the Alpha value which is the transparency
LinearGradientBrush lb = new LinearGradientBrush(new Rectangle(0, 0, this.Width, this.Height), Color.FromArgb(255, Color.White), Color.FromArgb(50, Color.White), LinearGradientMode.Vertical);
// calculate how much has the rangeBar to be filled for 'x' %
int width = (int)((percent / 100) * this.Width);
e.Graphics.FillRectangle(b, 0, 0, width, this.Height);
e.Graphics.FillRectangle(lb, 0, 0, width, this.Height);
b.Dispose(); lb.Dispose();
}
private void RangeBar_SizeChanged(object sender, EventArgs e)
{
// maintain the label in the center of the rangeBar
label1.Location = new Point(this.Width / 2 - 21 / 2 - 4, this.Height / 2 - 15 / 2);
}
}
}
public void RangeBar_Click(object obj, EventArgs ea)
{
// This get executed if the pictureBox gets clicked
label1.text = "Increment 1";
}
UserControl derives from Control and therefore should already have a Cursor property inherited from that class. Do you not see a Cursor property in code/Properties?

Is it possible to create a non-shaped control?

I'm trying to place an image that has no definitive shape (a hat for example) on top of a different image control.
The thing is, since the control has a definitive shape, it leaves the default background color to cover up the space left blank. The image control is the exact same size of the image.
I tried using control.BackColor = Color.Transparent; but it doesn't seem to work.
Any other suggestions?
You can use Control.Region for this purpose
GraphicsPath path = new GraphicsPath();
path.AddEllipse(control.ClientRectangle);
control.Region = new Region(path);
try this, you can create any shape using GraphicsPath and set it to Region for instance I created ellipse.
Edit
If you just want to set BackColor = Color.Transparent. for some reason some controls doesn't allow this. in such cases you can do the following
public class CustomControl1 : Control
{
public CustomControl1()
{
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
}
Create a descendant of your control and set this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); that should do the trick
If your Image Control (like a PictureBox) is not moved (by holding mouse down and dragging) by user at runtime, you can use this technique which allows you to display images on top of each other. The images should have transparent background:
public class ImageControl : Control {
public ImageControl(){
SetStyle(ControlStyles.Opaque, true);
}
public Image Image {get;set;}
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e){
if(Image != null) e.Graphics.DrawImage(Image, Point.Empty);
}
}
You can use the control above instead of a PictureBox. Moving this control by dragging at runtime causes flicker much. So if you want so I think there is only 1 solution which uses Region. In this approach, you have to turn your Bitmap to a Region and assign this Region for your Control.Region property. The link given by Chris Dunaway is very helpful for you to do this. However I have to say that the Region has not a smooth border as you may expect. That's a shortage of this approach. For your convenience, I'll post the code with a little modification here, this code uses LockBits which will outperform the original code:
public class Util {
//invert will toggle backColor to foreColor (in fact, I mean foreColor here is the Solid Color which makes your image distinct from the background).
public static Region RegionFromBitmap(Bitmap bm, Color backColor, bool invert)
{
Region rgn = new Region();
rgn.MakeEmpty();//This is very important
int argbBack = backColor.ToArgb();
BitmapData data = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int[] bits = new int[bm.Width * bm.Height];
Marshal.Copy(data.Scan0, bits, 0, bits.Length);
//
Rectangle line = Rectangle.Empty;
line.Height = 1;
bool inImage = false;
for (int i = 0; i < bm.Height; i++)
{
for (int j = 0; j < bm.Width; j++)
{
int c = bits[j + i * bm.Width];
if (!inImage)
{
if (invert ? c == argbBack : c != argbBack)
{
inImage = true;
line.X = j;
line.Y = i;
}
}
else if(invert ? c != argbBack : c == argbBack)
{
inImage = false;
line.Width = j - line.X;
rgn.Union(line);
}
}
}
bm.UnlockBits(data);
return rgn;
}
}
//Use the code
//if your Bitmap is a PNG with transparent background, you can get the Region from it like this:
Region rgn = Util.RegionFromBitmap(yourPng, Color.FromArgb(0), false);
//if your Bitmap has a figure with solid color of Black, you can get the Region like this:
Region rgn = Util.RegionFromBitmap(yourPng, Color.Black, true);

Categories

Resources