My problem is:
System.ComponentModel.Win32Exception: 'Error creating window handle'.
I know I can solve this problem with Dispose(), but when I use it in the program, I'm displaying another error:
System.ObjectDisposedException: 'Can not access a disposed object.
Object name: 'PictureBox'. '
I use the following code:
private void SetUpPuzzle_Click(int parts)
{
Panel P = new Panel
{
Size = new Size(200, 200),
Location = new Point(394, 62),
};
Controls.Add(P);
Control board = P;
int total = parts * parts;
var PB = new PictureBox[total];
var imgarray = new Image[total];
var img = User_Image.Image;
int W = img.Width / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
int H = img.Height / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
int size = 200 / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
for (int x = 0; x < parts; x++)
{
for (int y = 0; y < parts; y++)
{
var index = x * parts + y;
imgarray[index] = new Bitmap(W, H);
using (Graphics graphics = Graphics.FromImage(imgarray[index]))
graphics.DrawImage(img, new Rectangle(0, 0, W, H),
new Rectangle(x * W, y * H, W, H), GraphicsUnit.Pixel);
PB[index] = new PictureBox
{
Name = "P" + index,
Size = new Size(size, size),
Location = new Point(x * size, y * size),
Image = imgarray[index],
SizeMode = PictureBoxSizeMode.StretchImage
};
PB[index].MouseEnter += Images_M_E;
PB[index].MouseLeave += Images_M_L;
PB[index].MouseClick += Form_MouseClick;
*PB[index].Dispose();
*board.Controls.Add(PB[index]);
}
}
}
When I want to create 10,000 objects
This error is displayed.
My problem is:
System.ComponentModel.Win32Exception: 'Error creating window handle'.
Indeed. You are creating way too many controls for a Winforms application.
And disposing of them doesn't really help because you can't use a disposed object any longer..
To have this kind of large puzzle (10k pieces) you need to change from using PictureBoxes (or any other Controls) to display the puzzle pieces to a different approach. This has been suggested in the original question but then you only wanted to have 100 pieces, remember?
The most common approach is this: Keep a list of images (when they are <= 256x256 pixels do put them into an ImageList!) and draw them in the board's Paint event. This will get rid of all the overhead involved with PictureBoxes.
(Aside: One may think this will not be performant with all the DrawImage calls. But all those PictureBoxes also need to draw all the pixels on all their surfaces, so that is no issue. But they also have to carry the overhead of being (under the hood) fully functional windows (see the error message!), which is why the system can only have a limited number of them; always try to keep the number of controls < 1k!)
You will have to move the placement logic to the board's Paint event and will also have to change the event model..:
Instead of having each PictureBox respond to its own events you will have to find a way to do all the work in the board's events. This will have to be diffenrent, depending on the event.
Since we don't know which event you have and what they do and which data they need for their work, it is hard to give all the necessary details, so I'll just point out a few things..:
There will not be a Enter or Leave event you can use. Instead you need to detect entering an area of a piece by testing for it in the MouseMove event. If you keep a List<Rectangle> you can use Rectangle.Contains(e.Location) for this test.
You can detect a MouseClick but then will have to find out which area was clicked. If your Enter and Leave logic from the MouseMove is working you can use its result to know where the Click went.
Similar ideas can be used for all other events; some are simple, some need a little calculation but they will all be fast and pretty easy to implement..
To optimize performance try to make the image n the right size and use Format32bppPArgb as the pixel format, because it is faster to display.
Another option is to pull the pixel data right from the original image in the Paint event with the same calculations you use now to create them. (There is a DrawImage overlay that uses two Rectangles, one to determine the target and one for the source area..) This saves GDI handles, at least if you can't use an ImageList.
Always plan for growth! For a better implementation do create a Piece class. It should hold a Rectangle and an integer index into the ImageList's Images collection. It could also have a method Switch(Piece otherPiece) which would either switch the Rectangles or the indices.
Good luck :-)
I met this exception because endless loop creating new UI control and set its properties. After looped many times, this excption was thrown when change control visible property. I found both User Object and GDI Object (From Task Manager) are quite large.
I guess your issue is similar reason that system resources are exhaust by those UI controls.
I comment PB[index].Dispose(); and it's work.
private void SetUpPuzzle(int parts)
{
// Comment ***********
//Panel P = new Panel
//{
// Size = new Size(200, 200),
// Location = new Point(394, 62),
//};
//Controls.Add(P);
//Control board = P; ***********
int total = parts * parts;
var PB = new PictureBox[total];
var imgarray = new Image[total];
var img = User_Image.Image;
int W =Convert.ToInt32(img.Width / Math.Sqrt(parts));
int H = Convert.ToInt32(img.Height / Math.Sqrt(parts));
int size = Convert.ToInt32(200 / Math.Sqrt(parts));
for (int x = 0; x < parts; x++)
{
for (int y = 0; y < parts; y++)
{
var index = x * parts + y;
imgarray[index] = new Bitmap(W, H);
using (Graphics graphics = Graphics.FromImage(imgarray[index]))
graphics.DrawImage(img, new Rectangle(0, 0, W, H),
new Rectangle(x * W, y * H, W, H), GraphicsUnit.Pixel);
PB[index] = new PictureBox
{
Name = "P" + index,
Size = new Size(size, size),
Location = new Point(x * size, y * size),
Image = imgarray[index],
SizeMode = PictureBoxSizeMode.StretchImage
};
PB[index].MouseEnter += Form1_MouseEnter;
PB[index].MouseLeave += Form1_MouseLeave;
PB[index].MouseClick += Form1_MouseClick;
//Comment
//PB[index].Dispose(); < -----------------
// Add PB in Panel in form
panel1.Controls.Add(PB[index]);
}
}
// after add all refresh panel
panel1.Refresh();
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void Form1_MouseEnter(object sender, EventArgs e)
{
throw new NotImplementedException();
}
Then Call the SetUpPuzzle method in your button like :
private void button1_Click(object sender, EventArgs e)
{
SetUpPuzzle(10);
}
Related
I'm working on a screen sharing project ,and i recieve a small blocks of image from a Socket constantly and need to update them on a certain initial dekstop bitmap i have.
Basically i constantly read data from socket(data which is stored as jpeg image) ,using Image.FromStream() to retrieve the image and copying the recieved block pixels to the full primary bitmap(at a specific position X and Y which i also get from the socket)- that's how the initial image gets updated. But then comes the part where i need to display it on a Picturebox
I handle the Paint event and redrawing it all again-the entire inital image,which is pretty big(1920X1080 in my case).
This is my code:
private void MainScreenThread()
{
ReadData();//reading data from socket.
initial = bufferToJpeg();//first intial full screen image.
pictureBox1.Paint += pictureBox1_Paint;//activating the paint event.
while (true)
{
int pos = ReadData();
x = BlockX();//where to draw :X
y = BlockY();//where to draw :Y
Bitmap block = bufferToJpeg();//constantly reciving blocks.
Draw(block, new Point(x, y));//applying the changes-drawing the block on the big initial image.using native memcpy.
this.Invoke(new Action(() =>
{
pictureBox1.Refresh();//updaing the picturebox for seeing results.
// this.Text = ((pos / 1000).ToString() + "KB");
}));
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
lock (initial)
{
e.Graphics.DrawImage(initial, pictureBox1.ClientRectangle); //draws at picturebox's bounds
}
}
Because i'm aiming at high speed performance(it's kind of a real-time project) , i would like to know if there isn't any method to draw current recieved the block itself on the picturebox instead of drawing the whole initial bitmap again-which seems very inefficient to me...
This is my drawing method(works extremly fast, copying block with memcpy):
private unsafe void Draw(Bitmap bmp2, Point point)
{
lock (initial)
{
BitmapData bmData = initial.LockBits(new Rectangle(0, 0, initial.Width, initial.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, initial.PixelFormat);
BitmapData bmData2 = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int Width = bmp2.Width;
int Height = bmp2.Height;
int X = point.X;
int Y = point.Y;
scan0 = IntPtr.Add(scan0, stride * Y + X * 3);//setting the pointer to the requested line
for (int y = 0; y < Height; y++)
{
memcpy(scan0, scan02 ,(UIntPtr)(Width * 3));//copy one line
scan02 = IntPtr.Add(scan02, stride2);//advance pointers
scan0 = IntPtr.Add(scan0, stride);//advance pointers//
}
initial.UnlockBits(bmData);
bmp2.UnlockBits(bmData2);
}
}
Here are some examples of a full primary bitmap,and other small blocks i'm getting and need to draw over the full one.
Full bitmap:
small block:
small block:
small block:
I'm getting large amount of small blocks per second(30~40) somtimes their bounds are really small(rectangle of 100X80 pixels for example) so redrawing the entire bitmap again is not necessary...Rapidly Refreshing a full screen image would kill the performance...
I hope my explaination was clear.
Looking forward for an answer.
Thanks.
It would be shame to leave that question without some answer. The following is about 10 times faster in my tests when updating small portions of the picture box. What it does basically is smart invalidating (invalidates just the updated portion of the bitmap, considering the scaling) and smart painting (draws only the invalidated portion of the picture box, taken from e.ClipRectangle and considering the scaling):
private Rectangle GetViewRect() { return pictureBox1.ClientRectangle; }
private void MainScreenThread()
{
ReadData();//reading data from socket.
initial = bufferToJpeg();//first intial full screen image.
pictureBox1.Paint += pictureBox1_Paint;//activating the paint event.
// The update action
Action<Rectangle> updateAction = imageRect =>
{
var viewRect = GetViewRect();
var scaleX = (float)viewRect.Width / initial.Width;
var scaleY = (float)viewRect.Height / initial.Height;
// Make sure the target rectangle includes the new block
var targetRect = Rectangle.FromLTRB(
(int)Math.Truncate(imageRect.X * scaleX),
(int)Math.Truncate(imageRect.Y * scaleY),
(int)Math.Ceiling(imageRect.Right * scaleX),
(int)Math.Ceiling(imageRect.Bottom * scaleY));
pictureBox1.Invalidate(targetRect);
pictureBox1.Update();
};
while (true)
{
int pos = ReadData();
x = BlockX();//where to draw :X
y = BlockY();//where to draw :Y
Bitmap block = bufferToJpeg();//constantly reciving blocks.
Draw(block, new Point(x, y));//applying the changes-drawing the block on the big initial image.using native memcpy.
// Invoke the update action, passing the updated block rectangle
this.Invoke(updateAction, new Rectangle(x, y, block.Width, block.Height));
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
lock (initial)
{
var viewRect = GetViewRect();
var scaleX = (float)initial.Width / viewRect.Width;
var scaleY = (float)initial.Height / viewRect.Height;
var targetRect = e.ClipRectangle;
var imageRect = new RectangleF(targetRect.X * scaleX, targetRect.Y * scaleY, targetRect.Width * scaleX, targetRect.Height * scaleY);
e.Graphics.DrawImage(initial, targetRect, imageRect, GraphicsUnit.Pixel);
}
}
The only kind of tricky part is determining the scaled rectangles, especially the one for invalidating, due to floating point to int conversions required, so we make sure it's eventually a little bigger than needed, but not less.
If you just need to draw on top of the canvas, you can draw the initial image just once and then use CreateGraphics() and DrawImage to update the content:
ReadData();
initial = bufferToJpeg();
pictureBox1.Image = initial;
var graphics = pictureBox1.CreateGraphics();
while (true)
{
int pos = ReadData();
Bitmap block = bufferToJpeg();
graphics.DrawImage(block, BlockX(), BlockY());
}
I'll update the answer with a performance comparison as I'm not convinced this will give any major benefit; it will, at least, avoid a double DrawImage though.
I have a chart on which I want to plot a heat map; the only data I have is humidity and temperature, which represent a point in the chart.
How do I get the rectangular type of heat map on the chart in c#?
What I want is similar to picture below :
What I really want is a rectangular region in the chart which is plotted in different color based on the point that i get from the list of points and form the colorful section in the chart.
You have a choice of at least three ways to create a chart with colored rectangles that make up a heat map.
Here is one example
that uses/abuses a DataGridView. While I would not suggest this, the post contains a useful function that creates nice color lists to use in your task.
Then there is the option to draw the chart using GDI+ methods, namely Graphics.FillRectangle. This not hard at all but once you want to get those nice extras a Chart control offers, like scaling, axes, tooltips etc the work adds up.. See below!
So let's have a look at option three: Using the Chart control from the DataVisualization namespace.
Let's first assume that you have created a list of colors:
List<Color> colorList = new List<Color>();
And that you have managed to project your data onto a 2D array of int indices that point into the color list:
int[,] coloredData = null;
Next you have to pick a ChartType for your Series S1 There really is only one I can think of that will help:
S1.ChartType = SeriesChartType.Point;
Points are displayed by Markers. We want the DataPoints not really displayed as one of the standard MarkerTypes.
Square would be ok, if we wanted to display squares; but for rectangles it will not work well: Even if we let them overlap there will still be points at the borders that have a different size because they don't fully overlap..
So we use a custom marker by setting the MarkerImage of each point to a bitmap of a suitable size and color.
Here is a loop that adds the DataPoints to our Series and sets each to have a MarkerImage:
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
This takes some explaining: To set a MarkerImage that is not at a path on the disk, it has to reside in the Chart's Images collection. This means is needs to be of type NamedImage. Any image will do, but it has to have a unique name string added to identify it in the NamedImagesCollection . I chose the names to be 'NI1', 'NI2'..
Obviously we need to create all those images; here is a function to do that:
void createMarkers(Chart chart, int count)
{
// rough calculation:
int sw = chart.ClientSize.Width / coloredData.GetLength(0);
int sh = chart.ClientSize.Height / coloredData.GetLength(1);
// clean up previous images:
foreach(NamedImage ni in chart1.Images) ni.Dispose();
chart.Images.Clear();
// now create count images:
for (int i = 0; i < count; i++)
{
Bitmap bmp = new Bitmap(sw, sh);
using (Graphics G = Graphics.FromImage(bmp))
G.Clear(colorList[i]);
chart.Images.Add(new NamedImage("NI" + i, bmp));
}
}
We want all markers to have at least roughly the right size; so whenever that size changes we set it again:
void setMarkerSize(Chart chart)
{
int sx = chart1.ClientSize.Width / coloredData.GetLength(0);
int sy = chart1.ClientSize.Height / coloredData.GetLength(1);
chart1.Series["S1"].MarkerSize = (int)Math.Max(sx, sy);
}
This doesn't care much about details like the InnerPlotPosition, i.e. the actual area to draw to; so here is some room for refinement..!
We call this when we set up the chart but also upon resizing:
private void chart1_Resize(object sender, EventArgs e)
{
setMarkerSize(chart1);
createMarkers(chart1, 100);
}
Let's have a look at the result using some cheap testdata:
As you can see resizing works ok..
Here is the full code that set up my example:
private void button6_Click(object sender, EventArgs e)
{
List<Color> stopColors = new List<Color>()
{ Color.Blue, Color.Cyan, Color.YellowGreen, Color.Orange, Color.Red };
colorList = interpolateColors(stopColors, 100);
coloredData = getCData(32, 24);
// basic setup..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA");
chart1.Series.Clear();
Series S1 = chart1.Series.Add("S1");
chart1.Legends.Clear();
// we choose a charttype that lets us add points freely:
S1.ChartType = SeriesChartType.Point;
Size sz = chart1.ClientSize;
// we need to make the markers large enough to fill the area completely:
setMarkerSize(chart1);
createMarkers(chart1, 100);
// now we fill in the datapoints
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
// S1.Points[pt].Color = coloredData[x, y];
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
}
A few notes on limitations:
The point will always sit on top of any gridlines. If you really needs those you will have to draw them on top in one of the the Paint events.
The labels as shown are referring to the integers indices of the data array. If you want to show the original data, one way would be to add CustomLabels to the axes.. See here for an example!
This should give you an idea of what you can do with a Chart control; to complete your confusion here is how to draw those rectangles in GDI+ using the same colors and data:
Bitmap getChartImg(float[,] data, Size sz, Padding pad)
{
Bitmap bmp = new Bitmap(sz.Width , sz.Height);
using (Graphics G = Graphics.FromImage(bmp))
{
float w = 1f * (sz.Width - pad.Left - pad.Right) / coloredData.GetLength(0);
float h = 1f * (sz.Height - pad.Top - pad.Bottom) / coloredData.GetLength(1);
for (int x = 0; x < coloredData.GetLength(0); x++)
for (int y = 0; y < coloredData.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colorList[coloredData[x,y]]))
G.FillRectangle(brush, pad.Left + x * w, y * h - pad.Bottom, w, h);
}
}
return bmp;
}
The resulting Bitmap looks familiar:
That was simple; but to add all the extras into the space reserved by the padding will not be so easy..
i am trying to build a windows application in .net which draw fractal image inside the panel.It take end points of line as starting point of next line.But problem is, diagram is going outside of the panel.How do i fix drawing inside the panel
static int start_x, start_Y;
static int end_x, end_Y;
static int my_angle = 0;
static int my_length = 0;
private void Canvas_Paint(object sender, PaintEventArgs e)
{
start_x = Canvas.Width / 2;
start_Y = Canvas.Height / 2;
for (int i = 0; i < 400; i++)
{
draw_T();
}
}
public void draw_T()
{
Pen mypen = new Pen(Color.Green, 2F);
my_angle = my_angle + (45);
my_length = 100 + (1);
end_x = (int)(start_x + Math.Cos(my_angle * .0174539676) * my_length);
end_Y = (int)(start_Y + Math.Sin(my_angle * .0174539676) * my_length);
Point[] points =
{
new Point (start_x,start_Y),
new Point (end_x,end_Y)
};
Point[] points1 =
{
new Point ((end_x+start_x)/2,(end_Y+start_Y)/2),
new Point (end_x+50,end_Y-100)
};
start_x = end_x;
start_Y = end_Y;
Graphics g = Canvas.CreateGraphics();
g.DrawLines(mypen, points);
g.DrawLines(mypen, points1);
}
I'm not sure how you graphic is supposed to look but I can give you a couple of hints.
At general one first: Do make use of e.Graphics parameter! Change
public void draw_T()
To
public void draw_T(Graphics g)
and delete the line.
Graphics g = Canvas.CreateGraphics();
Change the call to
draw_T(e.Graphics);
You are leaking GDI resource by creating all those Graphcs with disposing of them and and lose time by creating them when you already have the one from the Paint event.
Next you should add a NumericUpDown for testing your algorithm and script it like this:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Canvas.Invalidate();
}
To work you now change the loop to
for (int i = 0; i < numericUpDown1.Value; i++)
And watch your graphics develop.
Another test could be to introduce a second pen color for the second series of point.
To play around further you could add another NumericUpDown and tie my_lengthto it..
In the end you'll see that length needs to be smaller than 101 or the Canvas needs to be as large as 700 pixels.
BTW: Neither my_angle nor my_length need to be declared at class level since they are always set in the method and used nowhere else and no other variable needs to static either, at least from what you show us..
I have a WinForm C# app. It displays an image 720x360 pixels size on my UserControl in the onpaint event.
I am comparing constantly the current image with the new image that comes every 1/10 second.
I had been using Validate() to force the onpaint event to redraw the new image.
What I decided to do instead was Invalidate and pass regions in.
So, I compare the current image with the new image. Look for differences. Create a new rectangle object and then use the Union method to add the new image.
This is my code:
//I use EMGU to compare 2 images.
Image<Bgr, byte> _diffBetweenCurrentAndPrevious = newImage.AbsDiff(currentFrame);
//I enumerate through the resultant image to list the differences beyond a threshold.
for (int y = 0; y < 576; y++)
{
for (int x = 0; x < 720; x++)
{
//if beyond a threshold then note it
motionRegions.Union(new Rectangle(x, y, 1, 1));
}
}
I assign the new image:
newImage =(Bitmap) currentFrame.Clone();
I then call:
Invalidate(motionRegions);
Which will call this:
protected override void OnPaint(PaintEventArgs pe)
{
Graphics g = pe.Graphics;
if (newImage != null)
{
pe.Graphics.DrawImageUnscaled(newImage, 0, 0);
}
}
The problem is I do not seem to be improving on response time / RAM when just re-drawing what I need to redraw.
Am I doing something wrong?
Thanks
Is it possible to Draw any Form (without overridding the Paint method) in grayscale.
If I show a Form in a Modal() Dialog, I wan't do show its parent as grayscale.
I noticed this in the Visual Studio Extension Manager. If a progressbar is downloading a package, the underlying window is grayed out.
I am thinking of this:
private void Button1_Click(object sender, EventArgs e)
{
using (var dialog = new Form2())
{
SetGrayscale(this, true);
dialog.ShowDialog();
SetGrayscale(this, false);
}
}
Update
Just setting Form.Enabled = false; is not what I intended. That does not look as good as a grayscale representation of my form.
I think the compiz window decorator for Linux did this with apps that are unresponsive.
As has already been said the way to do this is to overlay another control / form on top of your existing form and have it render a grayscale version of this on top, you could either do this using an additional form placed exactly over the original form, or using something like a Panel positioned on top of all other controls.
Here is a working example of how you might do this when placing another form exactly over the client area of the first. How to use it
using (Grayscale(this))
{
MessageBox.Show("Test");
}
Implementation
public static Form Grayscale(Form tocover)
{
var frm = new Form
{
FormBorderStyle = FormBorderStyle.None,
ControlBox = false,
ShowInTaskbar = false,
StartPosition = FormStartPosition.Manual,
AutoScaleMode = AutoScaleMode.None,
Location = tocover.PointToScreen(tocover.ClientRectangle.Location),
Size = tocover.ClientSize
};
frm.Paint += (sender, args) =>
{
var bmp = GetFormImageWithoutBorders(tocover);
bmp = ConvertToGrayscale(bmp);
args.Graphics.DrawImage(bmp, args.ClipRectangle.Location);
};
frm.Show(tocover);
return frm;
}
private static Bitmap ConvertToGrayscale(Bitmap source)
{
var bm = new Bitmap(source.Width, source.Height);
for (int y = 0; y < bm.Height; y++)
{
for (int x = 0; x < bm.Width; x++)
{
Color c = source.GetPixel(x, y);
var luma = (int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11);
bm.SetPixel(x, y, Color.FromArgb(luma, luma, luma));
}
}
return bm;
}
private static Bitmap GetControlImage(Control ctl)
{
var bm = new Bitmap(ctl.Width, ctl.Height);
ctl.DrawToBitmap(bm, new Rectangle(0, 0, ctl.Width, ctl.Height));
return bm;
}
private static Bitmap GetFormImageWithoutBorders(Form frm)
{
// Get the form's whole image.
using (Bitmap wholeForm = GetControlImage(frm))
{
// See how far the form's upper left corner is
// from the upper left corner of its client area.
Point origin = frm.PointToScreen(new Point(0, 0));
int dx = origin.X - frm.Left;
int dy = origin.Y - frm.Top;
// Copy the client area into a new Bitmap.
int wid = frm.ClientSize.Width;
int hgt = frm.ClientSize.Height;
var bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.DrawImage(wholeForm, 0, 0,
new Rectangle(dx, dy, wid, hgt),
GraphicsUnit.Pixel);
}
return bm;
}
}
Note that:
The implementation of Paint is fairly poor - really it should use double buffering so that the grayscale image is pre-rendered to a buffered graphics context so the Paint method just needs to paint the pre-drawn buffer contents. See Custom Drawing Controls in C# – Manual Double Buffering
ConvertToGrayscale is a tad on the slow side, but can probably be sped up
Things will go wrong if someone manages to move the original form for any reason
The image is static, if the base control gets redrawn then ideally the top form should redraw too. I'm not sure how best to detect when a portion of another form has been invalidated.
If I find the time I'll try and fix some of those problems, but the above at least gives you the general idea.
Note that in WPF this would be a lot easier.
Sources:
How to convert a colour image to grayscale
Get the image of a control or form, or a form's client area in C#
I don't think there is a way to do it directly - I think all forms are rendered with sRGB.
A hacky way could be to overlay the form with a copy of it as an image (this is simple to do with Control.DrawToBitMap) and then pass it through a simple GDI matrix to desaturate https://web.archive.org/web/20141230145627/http://bobpowell.net/grayscale.aspx.
Try something like this which would work for most simple controls (you would need to recurse into containers to switch all controls correctly).
private void button1_Click(object sender, EventArgs e)
{
using (var dialog = new Form())
{
Dictionary<Control, Tuple<Color, Color>> oldcolors = new Dictionary<Control, Tuple<Color, Color>>();
foreach (Control ctl in this.Controls)
{
oldcolors.Add(ctl, Tuple.Create(ctl.BackColor, ctl.ForeColor));
// get rough avg intensity of color
int bg = (ctl.BackColor.R + ctl.BackColor.G + ctl.BackColor.B) / 3;
int fg = (ctl.ForeColor.R + ctl.ForeColor.G + ctl.ForeColor.B) / 3;
ctl.BackColor = Color.FromArgb(bg, bg, bg);
ctl.ForeColor = Color.FromArgb(fg, fg, fg);
}
dialog.ShowDialog();
foreach (Control ctl in this.Controls)
{
ctl.BackColor = oldcolors[ctl].Item1;
ctl.ForeColor = oldcolors[ctl].Item2;
}
}
}