Simple graphic problem - c#

I have never really had to worry about how "pretty" my programs are before but I'm working on something for marketing now.... Anyways I imagine this is pretty simple but I can't seem to figure out why this isn't working. Basically I have a panel with a bunch of picture boxes in it and I am drawing colored rectangles behind them to create a pseudo "frame" around the photos. It has a different frame based on whether or not the photo is selected. The default selected photo is in position 0 and on the first time it paints everything looks great. But when the selection is changed, the paint event fires and nothing changes. Here's the code:
private void panelPicSet_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(panelPicSet.BackColor);
foreach (PictureBox picBox in panelPicSet.Controls)
{
if (picBox == selectedPhoto.PictureBox)
g.FillRectangle(new SolidBrush(Color.FromArgb(53, 73, 106)), new Rectangle(new Point(picBox.Location.X - 4, picBox.Location.Y - 4), new Size(picBox.Width + 8, picBox.Height + 8)));
if (picBox == hoveredPicBox)
g.FillRectangle(new SolidBrush(Color.FromArgb(53, 73, 106)), new Rectangle(new Point(picBox.Location.X - 2, picBox.Location.Y - 2), new Size(picBox.Width + 4, picBox.Height + 4)));
else
g.FillRectangle(new SolidBrush(Color.FromArgb(255, 232, 166)), new Rectangle(new Point(picBox.Location.X - 2, picBox.Location.Y - 2), new Size(picBox.Width + 4, picBox.Height + 4)));
}
}

Like I suspected it was an easy answer. I had to call panelPicSet.Invalidate() in the click and mouse enter/ leave events. I had assumed that clearing the graphics object in the paint event was performing the same function but apparently not.

Related

Graphics.DrawImage() clips image when using RotateTransform()

I have following code to draw my border2.bmp in 4 direction
private void Form1_Paint(object sender, PaintEventArgs e)
{
Bitmap border = new Bitmap("border2.bmp");
int borderThick = border.Height;
Graphics g = e.Graphics;
Size region = g.VisibleClipBounds.Size.ToSize();
Rectangle desRectW = new Rectangle(0, 0, region.Width - borderThick, borderThick);
// 1. LEFT - RIGHT border
g.TranslateTransform(30, 30);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
// 2. UP - BUTTOM border
g.ResetTransform();
g.TranslateTransform(50, 50);
g.RotateTransform(90);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
// 3. RIGHT-LEFT border
g.ResetTransform();
g.TranslateTransform(100, 100);
g.RotateTransform(180);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
// 4. BOTTOM - UP border
g.ResetTransform();
g.TranslateTransform(150, 150);
g.RotateTransform(270);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
}
My original image is:
But the result of rotations are not exactly as I expected. 90 degrees is missing the first red line, 270 degrees is missing first black column, and 180 degrees is missing both.
Like image I attached:
PS: you can get border2.bmp at: http://i.imgur.com/pzonx3i.png
Edit:
I tried g.PixelOffsetMode = PixelOffsetMode.HighQuality; as #Peter Duniho comment, but I found it also does't draw correctly.
Example: 4 line is not starting at same position as we expect.
g.TranslateTransform(50, 50);
// LEFT - RIGHT border
g.DrawLine(Pens.Red, 0, 0, 100, 0);
// UP - BOTTOM border
g.RotateTransform(90);
g.DrawLine(new Pen(Color.FromArgb(128, Color.Blue)), 0, 0, 100, 0);
// RIGHT-LEFT border
g.RotateTransform(90);
g.DrawLine(new Pen(Color.FromArgb(128, Color.Green)), 0, 0, 100, 0);
// BOTTOM - UP border
g.RotateTransform(90);
g.DrawLine(new Pen(Color.FromArgb(128, Color.Gray)), 0, 0, 100, 0);
I can't really explain why this happens, except that any graphics API is necessarily going to include optimizations which may lead to imprecise behaviors at times and it seems that you are running into such a situation here.
In your particular example, the problem can be corrected by adding the following statement to the code, before you draw the images:
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
Setting it to Half will also work. It is equivalent to HighQuality (or technically, HighQuality is equivalent to it…but I find HighQuality more descriptive in the code :) ).
This will slow the rendering of the bitmap down somewhat, but probably not in a way that is perceptible to your users.
While the .NET documentation isn't very helpful in terms of describing this setting, the native Win32 docs for the same feature has slightly more detail:
PixelOffsetMode enumeration. From the description, one can infer that with the logical center of the pixel at (0,0), it's possible to lose a pixel on one edge when rotating (and/or gain a pixel on another edge). Switching to Half fixes this.
You need to account some things.
(1) RotateTransforms applies rotation at the current origin (as explained in the Matrix.Rotate documentation
(2) The default MatrixOrder (when not specified by using the overrides with the additional argument) is Prepend. Which means in your case the resulting transformation is rotate, then translate.
For instance, if you put this code inside the paint event:
var g = e.Graphics;
var loc = new Point(128, 128);
var rect = new Rectangle(0, 0, 64, 16);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.FillRectangle(Brushes.Blue, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.RotateTransform(90);
g.FillRectangle(Brushes.Red, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.RotateTransform(180);
g.FillRectangle(Brushes.Green, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.RotateTransform(270);
g.FillRectangle(Brushes.Magenta, rect);
you'll get this
The blue rectangle is not rotated. Now pin virtually the origin (the upper left point) and start rotating clockwise. You'll see that it will exactly match the Red (90), Green (180) and Magenta (270) rectangles.
What all that means is that if you want to form a rectangle, you need to apply additional offset (translation) to the rotated rectangles. It depends how you want to handle the overlapping areas, but for the sample if we want to concat the Red rectangle right to the Blue one, we need to add the Blue rectangle Width + the original rectangle Height in the X direction. For other rotated rectangles you can apply similar additional offset to X, Y or both.
To complete the sample, if we modify the code like this
var g = e.Graphics;
var loc = new Point(128, 128);
var rect = new Rectangle(0, 0, 64, 16);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.FillRectangle(Brushes.Blue, rect);
g.ResetTransform();
g.TranslateTransform(loc.X + rect.Width + rect.Height, loc.Y);
g.RotateTransform(90);
g.FillRectangle(Brushes.Red, rect);
g.ResetTransform();
g.TranslateTransform(loc.X + rect.Width + rect.Height, loc.Y + rect.Width + rect.Height);
g.RotateTransform(180);
g.FillRectangle(Brushes.Green, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y + rect.Width + rect.Height);
g.RotateTransform(270);
g.FillRectangle(Brushes.Magenta, rect);
the new result will be
Once you understand all that, hope you can apply the required corrections to your concrete code.

(ListView?)-Control like in Windows Explorer

I'm wondering if there is any way to make a control like that one in windows explorer's auto start when you plugin a device.
I had thought that this could be a listview-control in a more or less modified way, but I was not able to find anything with Google. I also checked many CodeProject-pages.
Does anyone have an idea where I would be able to get the control or how I could make one myself? (I am not that good with OwnerDraw :P)
Thanks.
Actually tweaking a ListView is not any easier than ownerDrawing it. Here is an example that shows, how simple it really is.
You just script one event (DrawItem) and you are done.
This piece of code assumes:
The LV's View is set to List
You have a suitable ImageList added to your form
You have the LV's ownerDraw set to true
You have added two columns to hold the text shown in the two labels
You have made the 1st column wide enough to hold the whole stuff that gets drawn
You have made the LV's FontSize as large as the Images' Height (say 32)
Assign the appropriate ImageIndex values to the LV's Items
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
Point point0 = new Point(e.Bounds.Left, e.Bounds.Top);
Point point1 = new Point(imageList1.ImageSize.Width + 10, e.Bounds.Top + 5);
Point point2 = new Point(imageList1.ImageSize.Width + 10, e.Bounds.Top + 25);
Size size = new Size(listView1.ClientRectangle.Width, e.Bounds.Height);
Rectangle R = new Rectangle(point0, size);
Font F1 = new Font(listView1.Font.FontFamily, 11f, FontStyle.Bold);
Font F2 = new Font(listView1.Font.FontFamily, 10f);
if (e.Item.Focused) e.Graphics.FillRectangle(Brushes.LightBlue, R);
else if (e.ItemIndex % 2 == 1) e.Graphics.FillRectangle(Brushes.GhostWhite, R);
e.Graphics.DrawImage(imageList1.Images[e.Item.ImageIndex], point0 );
e.Graphics.DrawString(e.Item.Text, F1, Brushes.Black, point1);
e.Graphics.DrawString(e.Item.SubItems[1].Text, F2, Brushes.Black, point2);
F1.Dispose(); F2.Dispose();
}
Note that I have hard-coded a few Colors to paint every other line and also the focused item. These colors really should use the respective System colors. These come to mind:
SolidBrush brush0 = new SolidBrush(SystemColors.ControlLight);
SolidBrush brush1 = new SolidBrush(SystemColors.Highlight);
I am using the Font that is assigned to the LV but with moderate sizes. Obviously more or less anything, especially the various offsets, can be configured to your liking. But using colors from the System.Colors collection is good way to stay in keeping with your users' Windows themes.

Draw line in C# sometimes has missing parts

I'm currently trying to draw some lines in C# with the Graphics class.
My problem is, that sometimes (mostly on the repainting on resizing the form) some parts of the lines are missing.
This is how it looks like then:
This is my code where I draw the lines:
Graphics g = pnlGraph.CreateGraphics();
g.Clear(pnlGraph.BackColor);
Point p1 = new Point((mainNode.Left + (mainNode.Width / 2)), (mainNode.Top + (mainNode.Height / 2)));
Point p2 = new Point((pic.Left + (pic.Width / 2)), (pic.Top + (pic.Height / 2)));
g.DrawLine(new Pen(new SolidBrush(Color.Black), 2), p1, p2);
This code draws some lines from a mainNode in the middle of my panel to some nodes around it.
I'm calling the function to paint the lines on:
Load,
Resize,
Visible state changed
I also tried it in Paint of the form and the panel which didn't work.
Is there any way to fix it or another way of painting these lines?
Thanks for any answer!
Since the answer of #HansPassant has also made some problems we fixed the problem in another way:
We created an Image and filled it with an rectangle of the size of the panel.
After that we draw the lines in the image and draw the image on the panel.
Graphics g = pnlGraph.CreateGraphics();
Image img = new Bitmap(pnlGraph.Width, pnlGraph.Height);
Graphics gi = Graphics.FromImage(img);
gi.DrawRectangle(new Pen(new SolidBrush(pnlGraph.BackColor)), new Rectangle(0, 0, pnlGraph.Width, pnlGraph.Height));
// For every line:
gi.DrawLine(new Pen(new SolidBrush(Color.Black), 2), p1, p2);
// At the end:
g.DrawImage(img, 0, 0, img.Width, img.Height);

DrawBorder doesn't work when passing a custom rectangle to it

I can't seem to make DrawBorder to work when passing a new rectangle object to it:
private void answered_choice_1_paint(object sender, PaintEventArgs e)
{
Size s = new Size(Math.Max(answered_choice_1.Height, icon_correct.Height) + 4, answered_choice_1.Width + 22 + this.default_margin + 4);
Point p = new Point(answered_choice_1.Location.X - 22 - this.default_margin - 2, answered_choice_1.Location.Y - 2);
Rectangle r = new Rectangle(p, s);
if (icon_correct.Location.Y == answered_choice_1.Location.Y)
{
ControlPaint.DrawBorder(e.Graphics, r, Color.Green, ButtonBorderStyle.Solid);
}
}
However, passing a label's rectangle works:
private void answered_choice_1_paint(object sender, PaintEventArgs e)
{
if (icon_correct.Location.Y == answered_choice_1.Location.Y)
{
ControlPaint.DrawBorder(e.Graphics, answered_choice_1.DisplayRectangle, Color.Green, ButtonBorderStyle.Solid);
}
}
As you can see from the code, my intent is to draw a rectangular border around the answered_choice_1 label and icon_correct pictureBox, so the second code excerpt does draw a rectangle but I want to draw the rectangle from the first excerpt.
Edit:
I've narrowed it down to this:
int x,y;
x = answered_choice_1.Location.X - 22 - this.default_margin - 2;
y = answered_choice_1.Location.Y - 2;
Point p = new Point(x, y);
Using the debugger I've found out that answered_choice_1.Location.Y - 2 evaluates to 210 buy y gets the value 0; This is very strange but consistent: if I call a different constructor for the Rectangle r, I get the same outcome.
Any further help would be appreciated.
Second Edit The edit before was wrong, although that's the data that I saw in the Visual Studio IDE. Humberto's comment gave me the final clue to what was going on, and I've approved his answer.
Your "size" calculation looks like it is doing height for width, and width for height:
Size s = new Size(Math.Max(answered_choice_1.Height, icon_correct.Height) + 4,
answered_choice_1.Width + 22 + this.default_margin + 4);
Since it's hard to tell what the rest of the code looks like, I can only guess that reversing it might work:
Size s = new Size(answered_choice_1.Width + 22 + this.default_margin + 4,
Math.Max(answered_choice_1.Height, icon_correct.Height) + 4)
I think you're trying to paint a border around a pair of controls: an icon aligned to the left of a label. Is this the case?
+------------------------------+
| |
| ICON answered_choice_1 |---> border on a 4px margin around both controls
| |
+------------------------------+
^ ^
| 22px |
If so, your painting code has a problem. It's trying to use the "surface" (Graphics instance) of answered_choice_1 to paint outside its area. It won't work.
Instead, you can place the icon and the label inside a Panel, then paint the panel's border whenever you need. Somewhat like you already did, but referring to panel_1 instead of answered_choice_1:
private void panel_1_paint(object sender, PaintEventArgs e)
{
if (icon_correct.Location.Y == answered_choice_1.Location.Y)
{
ControlPaint.DrawBorder(e.Graphics, panel_1.DisplayRectangle, Color.Green, ButtonBorderStyle.Solid);
}
}
Alternatively, you can assign a FixedSingle border style to the panel, but AFAIK the border color will be system defined.

ToolStripMenuItem can't show checkmark and Image (icon) when RenderMode is "System"?

My Windows Forms application has a MenuStrip and some of the menu items (ToolStripMenuItem) have an icon (setting the ToolStripMenuItem.Image property).
When the RenderMode property of the MenuStrip is set to ToolStripRenderMode.System, the checkmark doesn't display when the Checked or CheckOnClick property is true and the menu item has an icon.
It does display when i switch the MenuStrip.RenderMode property to ToolStripRenderMode.Professional or ToolStripRenderMode.RenderManagerMode.
Unfortunately, this is a problem because my app requires:
A ProgressBar in marquee mode, so Application.EnableVisualStyles() is required to get this to work.
The app requires a "flat" visual style, which i accomplished by leaving out the call to Application.EnableVisualStyles() and leaving the default ToolStripRenderMode.RenderManagerMode on the MenuStrip. But then i can't get my marquee ProgressBar!
Setting the RenderMode to ToolStripRenderMode.System solves the look and feel requirement, but takes away the ability to have checked menu items w/icons.
Is there any way to satisfy all my requirements? Am i missing something? Thanks for looking.
Wow, i stumped SO! Now i know i must be working on some serious code.
Anyway, the answer is: implement your own ToolStripRenderer by creating a class that inherits from ToolStripSystemRenderer.
Override the methods that draw the items with your own code. Here's what i was looking for specifically that draws the checked item. It draws a check if there's no image for the ToolStripMenuItem.
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
{
base.OnRenderItemCheck(e);
if (e.Item.Selected)
{
Rectangle rect = new Rectangle(3, 1, 20, 20);
Rectangle rect2 = new Rectangle(4, 2, 18, 18);
SolidBrush b = new SolidBrush(Color.FromArgb(49, 106, 197));
SolidBrush b2 = new SolidBrush(Color.Orange);
e.Graphics.FillRectangle(b, rect);
e.Graphics.FillRectangle(b2, rect2);
e.Graphics.DrawImage(e.Image, new Point(5, 3));
}
else
{
Rectangle rect = new Rectangle(3, 1, 20, 20);
Rectangle rect2 = new Rectangle(4, 2, 18, 18);
SolidBrush b = new SolidBrush(Color.FromArgb(49, 106, 197));
SolidBrush b2 = new SolidBrush(Color.Orange);
e.Graphics.FillRectangle(b, rect);
e.Graphics.FillRectangle(b2, rect2);
e.Graphics.DrawImage(e.Image, new Point(5, 3));
}
}
I did also come across a simpler alternative:
You can simply put your menu items into a ContextMenuStrip and then assign it to the DropDown property of the DropDownButton.
Hope this helps anyone out there who doesn't fancy overriding the Paint method.

Categories