I draw images (lets say 200x200) on control in C# depending on how many there are (could be over 100) they get drawn offscreen since they are stacked one above the other. What is the best way to make a dynamic scroll so that i can scroll to the ones that are offscreen.
I was thinking of using a Panel and painting them on there and then just place the Panel on the control. But with the panel being transparent and the control on which the panel is sitting is changing its image (its a map that you can move with draging) the panel lags behind when drawing and creates a ugly jitter effect.
So are there any good solutions where i wont need to implement the whole logic of a scroller myself for such a solution?
PictureBox is doublebuffered and will take care of the flicker.
For your case don't draw onto a control, no matter which, but instead draw into a PictureBox's Image.
Put the PictureBox inside an autoscroll panel and make it a large as the Size you need. No jitter, smooth scrolling..
Here is an example. It randomly draws around 200 small images into the Image of a PictureBox. I create the Image with a large size and I put the PB into a Panel:
Random R = new Random();
private void button1_Click(object sender, EventArgs e)
{
panel1.AutoScroll = true;
pictureBox1.Parent = panel1;
pictureBox1.Location = Point.Empty;
pictureBox1.Image = new Bitmap(3000, 500);
pictureBox1.ClientSize = pictureBox1.Image.Size;
var imgFiles = Directory.GetFiles(#"D:\scrape\", "*.png");
foreach(string file in imgFiles)
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
using (Bitmap bmp = new Bitmap(file))
{
if (bmp.Size.Width < 300)
{
for (int i = 0; i < 10; i++ )
G.DrawImage(bmp, R.Next(2500), R.Next(400));
}
}
}
}
You can easily adapt it to your own project, I'm sure..
Related
I have a background image, then I want to load several others on top of this. Each image is a png with an alpha channel that has different areas non-transparent.
I placed a PicureBox on a form. Then in the code:
private List<PictureBox> layers = new List<PictureBox>();
for (int l = 0; l < 11; l++)
{
Image i = (Image) Properties.Resources.ResourceManager.GetObject(l.ToString());
PictureBox b = new PictureBox
{
Parent = form_picture,
Image = i,
Dock = DockStyle.Fill,
SizeMode = PictureBoxSizeMode.Zoom,
BackColor = Color.Transparent
};
//b.BringToFront();
layers.Add(b);
}
where "form_picture" is the PictureBox placed on the form, and my resource images are named 0,1,2..10.
It shows only the first image or the last one (removing the comment to the BringToFront method).
It doesn't seem a problem of transparency because I correctly see the background image, but only the first or last opaque area of the upper levels.
I'm afraid I'm not using correctly the properties.
IMO, you shouldn't use pictureboxes at all. They're too expensive for what You're trying to do. Use Graphics.DrawImage instead.
The System.Drawing.Image has a PixelFormat property of type System.Drawing.Imaging.PixelFormat that should help you with the alpha channel.
I've seen few questions about this problem, I tried every solution but none of them worked for my case.
My code is working; this image shows what happens when I click on Draw button.
I need to zoom on that drawing.Is it possible to code something like autocad feature "zoom/extent"?
Pen myPen = new Pen(Color.Black);
int centerpointx, centerpointy;
private void pictureBoxDraw_Paint(object sender, PaintEventArgs e)
{
centerpointx = pictureBoxDraw.Size.Width/2;
centerpointy = pictureBoxDraw.Size.Height/2;
myPen.Width = 2;
if (binary > 0)
{
var sizecrestgeo = 40;
var distancearraycrestgeo = new float[sizecrestgeo];
var elevationarraycrestgeo = new float[sizecrestgeo];
for (int i = 0; i < sizecrestgeo; i++)
{
distancearraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[0].Value.ToString());
elevationarraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[1].Value.ToString())*-1;
}
for (int i=0; i < sizecrestgeo-1; i++)
{
e.Graphics.DrawLine(myPen, distancearraycrestgeo[i]+centerpointx, elevationarraycrestgeo[i]+centerpointy, distancearraycrestgeo[i + 1]+centerpointx, elevationarraycrestgeo[i + 1]+centerpointy);
}
}
else
{
}
}
private void buttonDraw_Click_1(object sender, EventArgs e)
{
if (Hd > 0.0001)
{
binary = 1;
pictureBoxDraw.Invalidate();
}
else
{
MessageBox.Show("No data to draw, perform analysis first.");
}
}
private void buttoncleardraw_Click(object sender, EventArgs e)
{
binary = 0;
pictureBoxDraw.Invalidate();
}
}
This is not so hard, provided you know all the puzzle pieces.
Let's start with the obvious one:
You can scale the Graphics object to create zoomed graphics with ScaleTransform.
As I mentioned, this will include the widths of pens, font sizes and also any images you draw (though not the hatches of a HatchBrush).
You also asked about keeping the drawing 'centered'. This is a non-obvious concept: Just what is the center of your drawing surface??
When zooming (just like rotating) you always need to know the center point of the zoom (or the rotation.) By default this is the origin (0,0). I chose the center of the Panel. You may want to pick some other point..
Once you do you can move the origin of the graphics viewport to this point with TranslateTransform.
Once you have achieved all this you almost certainly will want to allow scrolling.
To do so you have two options:
You can keep AutoScroll = false and nest the canvas control inside another control, usually a Panel, which has AutoScroll = true; next make the canvas control big enough to always hold your drawing and you're done.
Or you can turn on AutoScroll for the canvas control and also set a large enough AutoScrollMinSize. If you then add the current scrolling position to the translation you are also done. Let's see this solution in action:
This is the code in the Paint event:
Size sz = panel3.ClientSize;
Point center = new Point(sz.Width / 2, sz.Height / 2);
Graphics g = e.Graphics;
// center point for testing only!
g.DrawEllipse(Pens.Orange, center.X - 3, center.Y - 3, 6, 6);
// you determine the value of the zooming!
float zoom = (trackBar1.Value+1) / 3f;
// move the scrolled center to the origon
g.TranslateTransform(center.X + panel3.AutoScrollPosition.X,
center.Y + panel3.AutoScrollPosition.Y);
// scale the graphics
g.ScaleTransform(zoom, zoom);
// draw some stuff..
using(Pen pen = new Pen(Color.Yellow, 0.1f))
for (int i = -100; i < 100; i+= 10)
g.DrawEllipse(Pens.Yellow, i-22,i-22,44,44);
A few notes:
I draw an orange circle in the center to show this point is invariant.
My coordinates go from the negative to the positive so you can see that this works nicely.
I draw with a tiny pen width; so the width of the drawing only changes once the resulting pen goes over 1 pixel. Anything draw will always be draw with 1 pxiel width, though.
I first translate and then scale so I don't have to calculate scaled poitions.
The only line in the TrackBar's Scroll event is to trigger the Paint event: panel3.Invalidate();
The only settings needed for the Panel are
panel3.AutoScroll = true;
panel3.AutoScrollMinSize = new Size(500, 500); // use the size you want to allow!
However to avoid flicker it is highly recommended to use a DoubleBuffered control, maybe a Panel subclass like this:
class DrawPanel : Panel
{
public DrawPanel() { DoubleBuffered = true; }
}
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Graphics.ScaleTransform() is how you can zoom. Try using something like this inside your paint event handler:
e.Graphics.ScaleTransform(2.0F, 2.0F);
Using a standard Label I aligned my image to the right side.
However there is a small indent (marked in red) that I cannot remove.
So my question: Is there an easy way to correctly align/snap an image to the right edge of a label? Or do I need to edit the label paint method so I can manually draw the image?
The label in question sits inside a "Panel", the following is my code:
label1.BackColor = System.Drawing.Color.Red;
label1.Image = global::TestProject.Properties.Resources.Header;
label1.ImageAlign = System.Drawing.ContentAlignment.MiddleRight;
label1.Size = new System.Drawing.Size(200, 40);
The simple answer appears to be: No there is no easy way to correctly align the image.
The "PictureBox" seems like the preferred component for working with images, however the image can be correctly aligned by editing the paint method of the label:
label1.Paint += new System.Windows.Forms.PaintEventHandler(this.label1_Paint);
And the paint method:
private void label1_Paint(object sender, PaintEventArgs e)
{
Image image = global::TestProject.Properties.Resources.Header;
Graphics g = e.Graphics;
//Align to far right: label width - image width
g.DrawImage(image, this.label1.Width - image.Width, 0);
}
I have two panels on top of each other. The one below is a bit larger than the one on top. I am painting an image by using CreateGraphics() method on the top most panel. (To be clear this image is a connect four grid with transparent holes). Now what I need to do is to add a picture box to the bottom panel and have it show from behind this grid.
I am adding controls of picture box to the bottom grid. And I'm using the BringToFront() method too. What can I do to have the picture box show underneath the grid?
In the following code: chipHolder is the bottom panel, grid is the topmost panel and picBox is the picture box respectively
public void addControl()
{
chipHolder.Controls.Add(picBox);
picBox.BringToFront();
}
// This piece of code is in a mouse_click event of grid
Graphics g = grid.CreateGraphics();
addControl();
// to make the picture move downwards
for (int i = 0; i < newYloc; i++)
{
picBox.Location = new Point(newXloc, picBox.Top + 1);
picBox.Show();
}
// drawing the grid image on the grid panel
protected virtual void grid_Paint(object sender, PaintEventArgs e)
{
Image img = Properties.Resources.grid_fw;
gridGraphics = grid.CreateGraphics();
gridGraphics.DrawImage(img, 0, 0, 650, 550);
}
To have a better picture this is how my panels are. the one selected is the chipHolder panel.
You could try a different approach: don't use a Panel and use a single PictureBox, this way you draw everything in that PictureBox. Thus, you use PictureBox's MouseDown event handler to calculate the (virtual) cell the user has clicked (you need to perform a simple division) and then you draw the chip on the PictureBox. If you want to show the chip falling, you'd need to save a copy of the current Bitmap (the Image property of the PictureBox) and draw the chip on different y coordinates (from 0 to its final position on the grid), this would be just like the double-buffer technique.
Here is a small example (you need a Form with a PictureBox, in this example it's named "pictureBox2"):
public partial class Form1 : Form
{
Bitmap chip = new Bitmap(40, 40, PixelFormat.Format32bppArgb);
public Form1()
{
InitializeComponent();
using (Graphics g = Graphics.FromImage(chip))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillEllipse(new SolidBrush(Color.FromArgb(128, 255, 80, 0)), 1, 1, 38, 38);
}
pictureBox2.Image = new Bitmap(pictureBox2.Width, pictureBox2.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(pictureBox2.Image))
{
g.Clear(Color.Yellow);
}
}
private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Text = e.Location.ToString();
using (Graphics g = Graphics.FromImage(pictureBox2.Image))
{
g.DrawImage(chip, e.Location.X - 20, e.Location.Y - 20);
}
pictureBox2.Invalidate();
}
}
}
If you want controls with real transparency, you should use WPF (it provides better graphics and uses hardware acceleration).
i am trying to move a image in picture box. i added panel to my application and also added picture box in panel. i opened an image.if the image size is big.i want to see the particular portion of image. so how can i move the image up and down (without using scroll bars) to see the particular portion of image?
You can add controls like move left, move right, move up, move down with associated actions to move the image within your picturebox. An example of how to do this for moving the image to the right is shown below. You can implement these action with mouse down and mouse up events so that the user just presses the appropriate buttons to move the picture as he wants. Also note that once you reach the maximum dimensions of the image, you can change the rectangular region to that within image bounds.
int ff = 0; //number of positions to move
Bitmap b2;
private void button1_Click(object sender, EventArgs e)
{
if (ff == 0) { b2 = new Bitmap(pictureBox1.Image);} //original image as bitmap b2
Bitmap b1 = new Bitmap(pictureBox1 .Width ,pictureBox1.Height ); //new bitmap with rectangular region of original image
Rectangle r1 = new Rectangle(ff++, 0, pictureBox1.Width, pictureBox1.Height );
Graphics g = Graphics.FromImage(b1);
g.DrawImage(b2, 0, 0, r1, GraphicsUnit.Pixel);
g.Dispose();
pictureBox1.Image = null;
pictureBox1.Image = (Image)b1;
pictureBox1.Refresh();
}
Not sure if it really answers your question, but this seems like a fun reason to play with Reactive Extensions (Rx). This video demonstrates nicely how well this stuff works with asynchronous events like mouse-input.