Panel for drawing graphics and scrolling - c#

I want to be able to use a Panel or similar to draw graphics onto a Winform. I cannot seem to see anything regarding adding scrollbars if the graphics become larger than the control?
Is it possible to do this with a Panel or is there a similar control that will allow it?

Set the AutoScroll property to true and the AutoScrollMinSize property to the size of the image. The scrollbars will now automatically appear when the image is too large.
You'll want to inherit your own class from Panel so that you can set the DoubleBuffered property to true in the constructor. Flicker would be noticeable otherwise. Some sample code:
using System;
using System.Drawing;
using System.Windows.Forms;
class ImageBox : Panel {
public ImageBox() {
this.AutoScroll = true;
this.DoubleBuffered = true;
}
private Image mImage;
public Image Image {
get { return mImage; }
set {
mImage = value;
if (value == null) this.AutoScrollMinSize = new Size(0, 0);
else {
var size = value.Size;
using (var gr = this.CreateGraphics()) {
size.Width = (int)(size.Width * gr.DpiX / value.HorizontalResolution);
size.Height = (int)(size.Height * gr.DpiY / value.VerticalResolution);
}
this.AutoScrollMinSize = size;
}
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
if (mImage != null) e.Graphics.DrawImage(mImage, 0, 0);
base.OnPaint(e);
}
}

I'm not 100% sure what you're trying to accomplish, but here is a similar SO question that might help you.
You could also try using a PictureBox that you would manually change its size as the graphics get larger. Then set your form AutoScroll to true.

Related

Display zoomed in part of pictureBox in another pictureBox by hovering cursor c#

I am working on a program that displays the liveView image from a Nikon camera in a pictureBox. I want to be able to hover with the cursor over the image and display a zoomed in area around the cursor in another picturebox. I would also like to add a crosshair instead of mouse pointer. The only solution I have found so far is the following:
zoom an image in a second picturebox following cursor
It does exactly what I want, however I can not get it to work. More specifically, nothing is showing up in picZoom. In the example, images are loaded while in my case, a video stream is shown. That might be the reason why I am not getting it to work. I am relatively new to c#, and did not fully understand the example.
Lets say I have picBox where I receive the video stream. How do I show a portion of picBox around the cursor (let's say a rectangle around the cursor of dimensions x,y) in picZoom with a crosshair as in the example. I do not need to worry about differing dimensions of picBox and picZoom since they will not vary. I also want to be able to vary the degree of zoom by a factor zoomFactor.
If anyone could give me pointers or a solution, it would be greatly appreciated. Also, sorry if my question is poorly formatted, I am new to the forum.
Thank you!
Alexander
I would approach it like this
using System;
using System.Drawing;
using System.Windows.Forms;
namespace MagnifierExample
{
class Magnifier : PictureBox
{
public Magnifier()
{
Visible = false;
}
PictureBox source;
private Point sourcePoint;
Cursor oldCursor;
public PictureBox Source
{
get
{
return source;
}
set
{
if (source != null)
{
source.MouseEnter -= Source_MouseEnter;
source.MouseLeave -= Source_MouseLeave;
source.MouseMove -= Source_MouseMove;
source.Cursor = oldCursor;
}
source = value;
if (source != null)
{
source.MouseEnter += Source_MouseEnter;
source.MouseLeave += Source_MouseLeave;
source.MouseMove += Source_MouseMove;
oldCursor = source.Cursor;
source.Cursor = Cursors.Cross;
}
}
}
private void Source_MouseEnter(object sender, EventArgs e)
{
Visible = true;
BringToFront();
}
private void Source_MouseLeave(object sender, EventArgs e)
{
Visible = false;
}
private void Source_MouseMove(object sender, MouseEventArgs e)
{
sourcePoint = e.Location;
Location = new Point(source.Location.X + e.X - Width / 2, source.Location.Y + e.Y - Height / 2);
Invalidate();
}
protected override void WndProc(ref Message m)
{
const int WM_NCHITTEST = 0x0084;
const int HTTRANSPARENT = (-1);
if (!DesignMode && m.Msg == WM_NCHITTEST)
{
m.Result = (IntPtr)HTTRANSPARENT;
}
else
{
base.WndProc(ref m);
}
}
protected override void OnPaint(PaintEventArgs pe)
{
if (!DesignMode && Source != null && Source.Image != null)
{
Rectangle destRect = new Rectangle(0, 0, Width, Height);
// IMPORTANT: This calculation will depend on the SizeMode of the source and the amount you want to zoom.
// This does 2x zoom and works with PictureBoxSizeMode.Normal:
Rectangle srcRect = new Rectangle(sourcePoint.X - Width / 4, sourcePoint.Y - Height / 4, Width / 2, Height / 2);
pe.Graphics.DrawImage(Source.Image, destRect, srcRect, GraphicsUnit.Pixel);
}
else
{
base.OnPaint(pe);
}
}
}
}
To hook it up all you have to do is add a Magnifier to your form, give it a size, and set its Source to the PictureBox you want it to magnify.
this.magnifier1.Source = this.pictureBox1;
I used the approach in this answer to ignore messages from the magnifier window.
Importantly, I only coded up the zoom math for the PictureBoxSizeMode.Normal SizeMode of the source PictureBox. But I think this is a pretty good start.
Not 100% sure what you wanted for crosshairs, but this uses the built-in crosshair cursor.

(C# Winforms) Drawing a circle on a TextBox

Sorry for my poor English.
I have a user control which includes two text boxes. I wanna draw a circle over that.
I tried to use a transparent panel like below. (This code is from Drawing circles on top of a form)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DrawCircle(int x, int y, int transparency, Graphics graphics)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
Pen pen = new Pen(Color.Red, 5)
graphics.DrawEllipse(pen, new Rectangle(x, y, 90, 90));
pen.Dispose();
graphics.Dispose();
}
private void TransparentPanel1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(10, 10, 255, e.Graphics);
}
private void Form1_Load(object sender, EventArgs e)
{
transparentPanel1.Enabled = false;
transparentPanel1.Paint += TransparentPanel1_Paint;
transparentPanel1.BringToFront();
}
}
public class TransparentPanel : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e);
}
}
However, it doesn't work.
When I use normal panel rather than transparent panel, the background color covers the entire textbox so I can't see the text. I don't want that.
I don't need editing text when the circle appeared, so this textbox can be replaced with label. (But I still need editing text when the circle doesn't exist.)
How can i draw a circle on a textbox? (Circle can be replaced with 'Circle Image file'. But the background of circle still need to be transparent.)
You can do this by setting the Region property as a ring.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WinForm
{
public partial class Form1 : Form
{
TextBox textBox1;
TextBox textBox2;
Panel circle;
public Form1()
{
//InitializeComponent();
textBox1 = new TextBox { Parent = this, Width = 100, Left = 20, Top = 20, Height = 80, AutoSize = false };
textBox2 = new TextBox { Parent = this, Width = 100, Left = 20, Top = textBox1.Bottom };
ShowCircle();
}
void ShowCircle()
{
circle = new Panel
{
Parent = this,
BackColor = Color.Red,
Top = textBox1.Top,
Left = textBox1.Left,
Width = textBox1.Width,
Height = textBox1.Height + textBox2.Height
};
using (var path = new GraphicsPath())
{
var rect = new Rectangle(0, 0, circle.Width, circle.Height);
path.AddEllipse(rect);
rect.Inflate(-5, -5);
path.AddEllipse(rect);
circle.Region = new Region(path);
circle.BringToFront();
}
}
}
}
I have had a similiar problem and the best solution I came up with was to make a screenshot from the parent panel and show the image in a another panel. This panel was made visible while the other one containing all controls was made invisible. I used this to show a loading screen without using a modal form.
I found the code in an old VB.NET application and hope that the translated code works:
class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
internal static Image PrintControl(Control ctrl)
{
using (Graphics controlGraphics = ctrl.CreateGraphics())
{
Bitmap bmp = new Bitmap(ctrl.Size.Width, ctrl.Size.Height, controlGraphics);
using (Graphics bmpGraphics = Graphics.FromImage(bmp))
{
IntPtr dc = bmpGraphics.GetHdc();
PrintWindow(ctrl.Handle, dc, 0);
bmpGraphics.ReleaseHdc(dc);
return bmp;
}
}
}
}
Given you have Panel1 which contains the TextBox controls and Panel2 as a overlay (that contains the screenshot with the red circle) you could use a code like this:
private void ShowRedCircle()
{
Image bmp = NativeMethods.PrintControl(this.panel1);
using (Graphics bmpGraphics = Graphics.FromImage(bmp))
using (Pen sPen = new Pen(Color.Red))
{
bmpGraphics.DrawEllipse(sPen, new Rectangle(10, 10, 90, 90));
this.panel2.BackgroundImage = bmp;
}
this.panel2.Visible = true;
this.panel1.Visible = false;
}
When you want to remove the circle just change the visibility of the panels again. You should consider to dispose the BackgroundImage of panel2 in this case.

Issue with transparent images with a transparent background

I'm having a problem with displaying transparent images with a transparent background. The transparent background takes the color of the underlying control and that is fine ... bu the problem is that some details (lines) on the underlying background are being covered the the images as can be seen in the image below.
Here is the code I am using.... This is the code for the notes....
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Media;
using System.Drawing;
namespace Simpe_Piano_new
{
class MusicNote: PictureBox
{
public SoundPlayer sp = new SoundPlayer();
Timer tmr = new Timer();
public int pitch; //The no. of the music key (e.g. the sound freuency).
public int noteDuration; //Shape of note.
public string noteShape;
public MusicNote(int iPitch, int iNoteDuration)
: base()
{
pitch = iPitch;
noteDuration = iNoteDuration;
Size = new Size(40, 40);
}
public void ShowNote()
{ if (this.noteDuration == 1) noteShape = "Quaver.png";
if (this.noteDuration == 4) noteShape = "Crotchet.png";
if (this.noteDuration == 7) noteShape = "minim.png";
if (this.noteDuration == 10) noteShape = "DotMin.png";
if (this.noteDuration == 12) noteShape = "SemiBreve.png";
this.BackgroundImage = Image.FromFile(noteShape);
this.BackColor = Color.Transparent;
Location = new Point((pitch * 40) - 40, 100);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
public void PlaySound()
{
sp.SoundLocation = this.pitch + ".wav";
sp.Play();
}
public void StopSound()
{
sp.SoundLocation = this.pitch + ".wav";
sp.Stop();
}
public void Play()
{
sp.SoundLocation = this.pitch + ".wav";
sp.Play();
//Time to play the duration
tmr.Interval = noteDuration;
tmr.Start();
tmr.Tick += new System.EventHandler(ClockTick);
}
void ClockTick(object sender, EventArgs e)
{
sp.Stop();
tmr.Stop();
}
}
}
This is the code for the underlying control..the music staff
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace Simpe_Piano_new
{
public class MusicStaff: Panel
{
Pen myPen;
Graphics g;
public MusicStaff()
{
this.Size = new Size(1000, 150);
this.Location = new Point(0, 0);
this.BackColor = Color.Transparent;
this.Paint += new PaintEventHandler(DrawLines);
}
private void DrawLines(object sender, PaintEventArgs pea)
{
myPen = new Pen(Color.Black, 1);
g = this.CreateGraphics();
for (int i = 1; i < 6; i++)
{
g.DrawLine(myPen, 0, (this.Height / 6) * i, this.Width, (this.Height / 6) * i);
}
}
}
}
I have found that C# does not handle transparency really well...
Any help would be greatly appreciated..
add the top control "MusicNote" in the children of the underlying control "MusicStaff"
something like that after -Initializing all components-
// mStaff: the MusicStaff object
// mNote: the MusicNote object
mStaff.Children.Add(mNote);
in old scenario, the form is the parent of both of them, so they display the form background in any transparent area
after modifying the parent of the "MusicNote", it displays the "MusicStaff" background in the transparent area
I hope that help!
Two mistakes. PictureBox supports transparent images well, as long as you set its BackColor property to Color.Transparent. Which you did for the MusicStaff but not for the MusicNote. Layered transparency does not work, you don't need MusicStaff to be transparent, just the picture boxes.
This kind of transparency is simulated by asking the Parent to paint itself into the control to provide the background pixels. Which is your second mistake, you use CreateGraphics() in your DrawLines() method. Which draws directly to the screen, not the control surface. You must use pea.Graphics here.
Do note that the value-add you get from using PictureBox is a very low one. Controls are expensive and you'll easily burn up hundreds of them to display a sheet of music. You'll notice, it will become slow to paint itself. You avoid this by having MusicStaff just paint the notes itself, using Graphics.DrawImage() gets the job done. Transparency effects are now much simpler as well, just layers of paint. Which is the way WPF does it. The only inconvenience you'll have to deal with is that mouse hit testing isn't as simple anymore, you need to map the panel's MouseDown event's coordinates to a note. Just keep a List that keeps track where every note is displayed. You'll use that for painting as well as mouse hit testing.

Datagridview to draw wafer map

Currently I'm using C# datagridview to draw a wafer map which contains >500 rows and >700 columns.
However, there are a few issues:
Slow performance. As i need to adjust the column width, I have to loop through and assign individually.
for (int i = 0; i < this.dgvCompleteMapGrid.Columns.Count; i++)
{
this.dgvCompleteMapGrid.Columns[i].Width = 8;
}
I only need to draw the cell border for cells with value as a wafer map has an almost round shape. I'm using cellpainting event:
if (e.Value != null) {
if (e.Value.ToString() == "")
{
e.AdvancedBorderStyle.All = DataGridViewAdvancedCellBorderStyle.None;
}
else
{
if (e.Value.ToString() == "1")
{
e.CellStyle.BackColor = Color.Lime;
}
else
{
e.CellStyle.BackColor = Color.Red;
}
//check if the top border set None for neighbor empty value cell
if (e.AdvancedBorderStyle.Top.ToString() == "None")
{
e.AdvancedBorderStyle.Top = DataGridViewAdvancedCellBorderStyle.Single;
}
//repeat the same for left right and bottom
}
}
However, it seems like it will draw multiple border duplicate for most of the cells.
Is Datagridview recommended? I tried to draw rectangle on a panel and the performance is even worse.
Here's my Picturebox that allows for pixellated resizing (versus interpolated). E.g. similar to zooming in on a Microsoft Paint picture.
using System.Windows.Forms;
namespace WireWorld
{
public class ZoomablePicturebox : PictureBox
{
public ZoomablePicturebox()
{
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
pe.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
pe.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
base.OnPaint(pe);
}
}
}
To generate the bitmap, I use something like this:
// Pick width and height for your case
Bitmap MyBitmap = new Bitmap(width, height);
using (var g = Graphics.FromImage(retVal))
g.Clear(BackColor); // Pick a background fill color
// Draw your points, whatever your loop needs to be and your point definition is
foreach (MyPointDef point in _Points)
{
MyBitmap.SetPixel(point.X, point.Y, point.Color);
}
Then I put a picture box in a panel on my form. The panel then provides the scrolling. I can set the picture and zoom as follows:
canvasPB.SizeMode = PictureBoxSizeMode.Zoom; // Ensures picture is resized as we resize picturebox
canvasPB.Image = MyBitmap;
canvasPB.Height = canvasPB.Image.Height * Zoom; // Zoom is 1, 2, 3, etc, for 1X, 2X, ...
canvasPB.Width = canvasPB.Image.Width * Zoom;
canvasPB being the name of the Picturebox on my form I'm trying to use as my canvas.

Custom control in a UserControl does not render correctly

In this picture...
... you can see next to each "Line Color" label there is a colored circle.
The colored circle is, in my project, a Swatch. Here is the entire code file for Swatch:
public class Swatch : System.Windows.Forms.Panel
{
/*private int _Radius = 20;
[System.ComponentModel.Category("Layout")]
public int Radius
{
get { return _Radius; }
set { _Radius = value; }
} */
private System.Drawing.Color _BorderColor = System.Drawing.Color.Transparent;
[System.ComponentModel.Category("Appearance")]
public System.Drawing.Color BorderColor
{
get { return _BorderColor; }
set { _BorderColor = value; }
}
private System.Drawing.Color _FillColor = System.Drawing.Color.Blue;
[System.ComponentModel.Category("Appearance")]
public System.Drawing.Color FillColor
{
get { return _FillColor; }
set { _FillColor = value; }
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
System.Drawing.Rectangle RealRect = new System.Drawing.Rectangle(e.ClipRectangle.Location, e.ClipRectangle.Size);
RealRect.Inflate(-1, -1);
int Radius = Math.Min(RealRect.Size.Height, RealRect.Size.Width);
System.Drawing.Rectangle SqRect = new System.Drawing.Rectangle();
SqRect.Location = RealRect.Location;
SqRect.Size = new System.Drawing.Size(Radius, Radius);
System.Drawing.Drawing2D.CompositingQuality PrevQual = e.Graphics.CompositingQuality;
using (System.Drawing.SolidBrush Back = new System.Drawing.SolidBrush(this.FillColor))
{
using (System.Drawing.Pen Pen = new System.Drawing.Pen(new System.Drawing.SolidBrush(this.BorderColor)))
{
//e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.FillEllipse(Back, SqRect);
e.Graphics.DrawEllipse(Pen, SqRect);
}
}
e.Graphics.CompositingQuality = PrevQual;
}
public Swatch()
{
this.SetStyle(System.Windows.Forms.ControlStyles.UserPaint, true);
this.SetStyle(System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true);
this.SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor, true);
this.DoubleBuffered = true;
}
}
Each row is a UserControl which consists of a TableLayoutPanel, labels, a Swatch control, and a NumericUpDown box.
There about 10 rows and they are placed in TableLayoutPanel, which sits inside a TabPage on a tab control. The tab page has AutoScroll set to true so that overflow causes the tab page to scroll.
The problem is that whenever I run the application and scroll up and down, the Swatches (the colored circles) tear and show all sorts of artifacts, as seen in the picture above. I'd like to have clean scrolling with no rendering artifacts.
I've tried using SetStyle (as suggested here Painting problem in windows form) but it has had no effect.
The UserControl (each row) has DoubleBuffered set to true, and that too has had no effect either.
I fear I am missing something rather obvious.
The problem is that you calculate the radius of the circle based on the clipping rectangle. So when the line is only partially visible a bad value is resulted.
You should calculate it based on the real rectangle, the one provided by the base class, and let it being clipped normally.

Categories