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.
Related
I somehow noticed, that while I'm creating a control in my application, it first appears as weird rectangle and then it "shrinks" to it's correct form.
First thing you'll see after creation
Correctly created object
My question is, why I first see the rectangle in top left corner and then it suddenly changes to it's correct form. It's like split second thing (I had to use Thread.Sleep 'cos it's impossible to screenshot it), but my eyes can still see this and I'm really triggered when it's happening.
This is the code, where I'm creating the control:
var label = new Label
{
AutoSize = true,
TextAlign = ContentAlignment.MiddleLeft,
Font = new Font("Courier New", 9F, FontStyle.Regular,
GraphicsUnit.Point, 238),
Text = keyword,
Margin = new Padding(0, 6, 25, 3),
Padding = new Padding(0, 3, 0, 0)
};
var button = new Button
{
BackgroundImage = Image.FromFile("../../../Images/cross_200x200.png"),
BackgroundImageLayout = ImageLayout.Stretch,
Dock = DockStyle.Right,
Width = 21,
Height = 21,
FlatStyle = FlatStyle.Flat,
};
button.FlatAppearance.BorderSize = 0;
var pan = new Panel
{
AutoSize = true,
Padding = new Padding(0, 0, 1, 1),
BackColor = Color.PowderBlue,
BorderStyle = BorderStyle.FixedSingle,
Tag = keyword
};
button.Click += delegate
{
_keywords.Remove(pan.Controls.OfType<Label>().First().Text);
pan.Dispose();
StatusLabel.Text = $#"Removed {keyword}";
};
pan.Controls.Add(label);
pan.Controls.Add(button);
FlowLayoutPanelMain.Controls.Add(pan);
Everytime a "keyword" is added to FlowLayoutPanel control, at first it's a rectangle in top left corner and immediately after that it's fine.
With help from a friend of mine, we figured out, that this is happening due to Windows Forms old technology (It's probably not happening in .NET Core's WPF) and it's inability of creating controls at runtime. So, the offered solution for this seems to be just .Hide() the control, .Add() it to the FlowLayoutPanel and then simply .Show() it back, now my eyes will be satisfied.
...
pan.Hide();
FlowLayoutPanelMain.Controls.Add(pan);
pan.Show();
...
Try to remove AutoSize property and assign a new Size() to the panel
AutoSize = false,
Worked for me
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.
I am having trouble drawing a line on a TabPage.
I actually have a TabControl inside a TabControl. I have drawn a number of labels which I am using as boxes. I want to draw lines to join them together.
I have tried:
Pen P = new Pen(System.Drawing.Color.Black, 10);
tabname.CreateGraphics().DrawLine(P, 10, 10, 100, 100);
and
Pen P = new Pen(System.Drawing.Color.Black, 10);
tabcontrolname.TabPages[0].CreateGraphics().DrawLine(P, 10, 10, 100, 100);
Neither are displaying the line. I assume the line is being placed somewhere as there are no errors.
Any ideas how I can get it to display on the correct TabPage?
Thank you!
You probably need to override the OnPaint method (or handle the Paint event) to get this to work properly. If you don't your controls will just end up drawing over your lines.
Here's a link to the relevant docs.
Where do you try these codes, in which function? If you are doing it once in the initialization or construction, they will not be displayed as you expect. Whenever the control needs to be redrawn, you need to draw this line too, again. Either override the OnPaint method of the control or register for the Paint event and do the line drawing there.
I was able to get the arrow to show up using the following code:
TabPage.Paint += new PaintEventHandler(TabPage_Paint);
and
protected void TabPage_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
Pen arrow = new Pen(Brushes.Black, 4);
arrow.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
e.Graphics.DrawLine(arrow, 10, 10, 100, 100);
arrow.Dispose();
}
However, when scrolling is initiated the Paint messes up :(
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.
This is a C# desktop application. The DrawStyle property of my ListBox is set to OwnerDrawFixed.
The problem: I override DrawItem to draw text in different fonts, and it works. But when I start resizing the form at the runtime, the selected item is drawn correctly, but the rest of them are not redrawn, causing text looking corrupt for unselected items.
Here's my code:
private void listDevices_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
string textDevice = ((ListBox)sender).Items[e.Index].ToString();
e.Graphics.DrawString(textDevice,
new Font("Ariel", 15, FontStyle.Bold), new SolidBrush(Color.Black),
e.Bounds, StringFormat.GenericDefault);
// Figure out where to draw IP
StringFormat copy = new StringFormat(
StringFormatFlags.NoWrap |
StringFormatFlags.MeasureTrailingSpaces
);
copy.SetMeasurableCharacterRanges(new CharacterRange[] {new CharacterRange(0, textDevice.Length)});
Region[] regions = e.Graphics.MeasureCharacterRanges(
textDevice, new Font("Ariel", 15, FontStyle.Bold), e.Bounds, copy);
int width = (int)(regions[0].GetBounds(e.Graphics).Width);
Rectangle rect = e.Bounds;
rect.X += width;
rect.Width -= width;
// draw IP
e.Graphics.DrawString(" 255.255.255.255",
new Font("Courier New", 10), new SolidBrush(Color.DarkBlue),
rect, copy);
e.DrawFocusRectangle();
}
listDevices.Items.Add("Device001");
listDevices.Items.Add("Device002");
Also, the item that is drawn correctly (the selected one) is flickering on form resizing. No biggie, but if anyone know why.... tnx
Put the following code in the Resize event:
private void listDevices_Resize(object sender, EventArgs e) {
listDevices.Invalidate();
}
This should cause everything to be redrawn.
To stop the flickering, you need double buffering.
To do this, make a new class, derived from ListBox, and put the following in the constructor:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Or just paste this into a code file:
using System.Windows.Forms;
namespace Whatever {
public class DBListBox : ListBox {
public DBListBox(): base() {
this.DoubleBuffered = true;
// OR
// this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
}
Replace "Whatever" with the namespace your project uses, or make it something more useful. AFter compiling, you should be able to add a DBListBox in the form designer.
I repro the problem. There are several mistakes in the code, the font name is "Arial", you should not adjust rect.Width, you forget to call Dispose() on the fonts, brushes and regions. But they don't explain the behavior. There's something wrong with the clipping area that prevents the text from being properly updated. I don't see where that occurs, the Graphics object state is okay.
Graphics.DrawString() is a very troubled method, you should really avoid it. All Windows Forms controls, including ListBox, use TextRenderer.DrawText(). That solves the problem when I use it. I know measuring is more difficult, you could work around that by displaying the IP address at a fixed offset. Looks better too, they'll line up in a column that way.
It flickers because you use e.DrawBackground(). That erases the existing text, you draw the text right back on it. I don't think double-buffering is going to fix that, you'd have to draw the entire item so you don't have to draw the background. Tricky if you can't get the exact size of the text with the large font, a workaround is to draw into a bitmap first.