I am using a ComboBox in Text and DropDown mode (the default) and I want to have an ItemHeight of X (e.g. 40) but have the ComboBox's Height set to Y (e.g. 20).
The reason for this is that I am intending to use the ComboBox for a Quick Search feature in which the user keys in text and detailed results are rendered in the list items. Only one line of input is required.
Unfortunately, Winforms automatically locks the ComboBox's Height to the ItemHeight and I can see no way to change this.
How can I make the ComboBox's Height differ to the ItemHeight?
What you have to do is, first of all, change DrawMode from Normal to OwnerDrawVariable. Then you've got to handle 2 events: DrawItem and MeasureItem . They would be something like:
private void comboBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
e.ItemHeight = 40; //Change this to your desired item height
}
private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
ComboBox box = sender as ComboBox;
if (Object.ReferenceEquals(null, box))
return;
e.DrawBackground();
if (e.Index >= 0)
{
Graphics g = e.Graphics;
using (Brush brush = ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
? new SolidBrush(SystemColors.Highlight)
: new SolidBrush(e.BackColor))
{
using (Brush textBrush = new SolidBrush(e.ForeColor))
{
g.FillRectangle(brush, e.Bounds);
g.DrawString(box.Items[e.Index].ToString(),
e.Font,
textBrush,
e.Bounds,
StringFormat.GenericDefault);
}
}
}
e.DrawFocusRectangle();
}
Related
The code below works perfectly to draw text items in my listbox (articlesLB) to a certain color based on some conditions. But when the text is longer than the size of the listbox, the horizontal scrollbar fails to show. I've implemented the MeasureItemEventHandler and DrawItemEventHandler. Can any one see why this is not working?
articlesLB.DrawMode = DrawMode.OwnerDrawFixed;
articlesLB.MeasureItem += new MeasureItemEventHandler(articlesLB_MeasureItem);
articlesLB.DrawItem += new DrawItemEventHandler(articlesLB_SetColor);
private void articlesLB_MeasureItem(object sender, MeasureItemEventArgs e)
{
int textwidth = TextRenderer.MeasureText(articlesLB.Items[e.Index].ToString(), articlesLB.Font).Width;
articlesLB.HorizontalExtent = textwidth;
}
private void articlesLB_SetColor(object sender, DrawItemEventArgs e)
{
if (e.Index < 0) return;
//if the item state is selected them change the back color
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
e = new DrawItemEventArgs(e.Graphics,
e.Font,
e.Bounds,
e.Index,
e.State ^ DrawItemState.Selected,
e.ForeColor,
Color.Yellow); //Choose the color
// Draw the background of the ListBox control for each item.
e.DrawBackground();
Brush myBrush = Brushes.Black;
// check some boolean flags to see if either is checked
if (EditAOSideRB.Checked == true || EditAPinSideRB.Checked == true)
{
string articleTitle = ((ListBox)sender).Items[e.Index].ToString();
if (articleTitle.Contains("* ") == true)
{
// set the brush to green
myBrush = Brushes.DarkGreen;
}
else
{
// set the brush to red
myBrush = Brushes.Red;
}
}
// Draw the current item text
e.Graphics.DrawString(articlesLB.Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
// If the ListBox has focus, draw a focus rectangle around the selected item.
e.DrawFocusRectangle();
}
You should set DrawMode to OwnerDrawVariable.
According to MSDN for ListBox.DrawMode:
This event is used by an owner-drawn ListBox. The event is only raised
when the DrawMode property is set to DrawMode.OwnerDrawFixed or
DrawMode.OwnerDrawVariable. You can use this event to perform the
tasks needed to draw items in the ListBox. If you have a
variable-sized item (when the DrawMode property is set to
DrawMode.OwnerDrawVariable), before drawing an item, the MeasureItem
event is raised. You can create an event handler for the MeasureItem
event to specify the size for the item that you are going to draw in
your event handler for the DrawItem event.
I try to draw an image from a image list in a ComboBox when the item is selected.
I am able to draw the image, but when the onSelctedIndexChanged event finish, I lost my image.
My ComboBox already have the DrawMode.OwnerDrawFixed.
I have a ListImage control named ImageList with 10 pictures.
For my short example I just need to draw in my ComboBox the image at position 1 of my ImageList, it's the reason why I get this.ImageList.Draw(g, 0, 0, **1**);
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
if (this.SelectedIndex > -1)
{
var g = this.CreateGraphics();
this.ImageList.Draw(g, 0, 0, 1);
}
}
Probably I am not subscribing to the right event. Any suggestion?
See the picture below with a breakpoint in SelectedIndexChanged after the image is drawn. It works, but I lose my image after the event.
Change your ComboBox DrawMode to OwnerDrawVariable.
Use the DrawItem event to draw the images from your source (an ImageList, in this case) inside the ComboBox item Bounds.
If the ComboBox DropDownStyle is set to DropDownList, the image will be shown in the selection box; if it's set to DropDown, only the text will be drawn.
Here, the Focus rectangle is only drawn when the mouse point hovers the ListControl's items, while it's not used when an item is selected, which is determined by:
(e.State.HasFlag(DrawItemState.Focus) && !e.State.HasFlag(DrawItemState.ComboBoxEdit)).
private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0) return;
var cbo = sender as ComboBox;
Color foreColor = e.ForeColor;
if (e.State.HasFlag(DrawItemState.Selected) && !(e.State.HasFlag(DrawItemState.ComboBoxEdit))) {
e.DrawBackground();
e.DrawFocusRectangle(); // <= could be removed for a cleaner rendering
}
else {
using (var brush = new SolidBrush(cbo.BackColor)) {
var rect = e.Bounds;
rect.Inflate(1, 1);
e.Graphics.FillRectangle(brush, rect);
}
foreColor = cbo.ForeColor;
}
TextRenderer.DrawText(e.Graphics, cbo.GetItemText(cbo.Items[e.Index]), e.Font,
new Point(e.Bounds.Height + 10, e.Bounds.Y), foreColor);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawImage(imageList1.Images[e.Index],
new Rectangle(e.Bounds.Location,
new Size(e.Bounds.Height - 2, e.Bounds.Height - 2)));
}
The Magic Numbers here (10, -2) are just offsets:
e.Bounds.Height + 10 => 10 pixels to the right of the image.
e.Bounds.Height -2 => 2 pixels less than the item.Bounds.Height.
I am using MenuItem.DrawItem to change MenuItem's appearance based on the fact whether user either hovers or select it. Unfortunatelly the hitbox seems too small. Here's my drawing code.
private void DrawCustomMenuItem(object sender,
DrawItemEventArgs e)
{
MenuItem customItem = (MenuItem) sender;
System.Drawing.Brush aBrush = System.Drawing.Brushes.DarkMagenta;
Font aFont = new Font("Arial", 10,
FontStyle.Italic, GraphicsUnit.Point);
SizeF stringSize = e.Graphics.MeasureString(
customItem.Text, aFont);
Pen p = new Pen(Color.Aqua, 2);
if ((e.State & DrawItemState.HotLight) == DrawItemState.HotLight
|| (e.State & DrawItemState.Selected) == DrawItemState.Selected)
p = new Pen(Color.Black, 2);
e.Graphics.DrawString(customItem.Text, aFont,
aBrush, e.Bounds.X, e.Bounds.Y);
e.Graphics.DrawEllipse(p,
new Rectangle(e.Bounds.X, e.Bounds.Y,
(int) stringSize.Width,
(int) stringSize.Height));
}
It only changes color when mouse is nearby left edge of the item. It isn't surprising I guess. I have no code that changes hitbox's dimensions and it is using the default one and I am drawing outside of it. I really have no idea how to approach it. I thought I should use MenuItem.Size to achieve that. But this property doesn't exist.
EDIT:
Ok, I got it. It's all about MeasureItem event. But another problem came up. The MeasureItemEventArgs.Graphics.MeasureString() seems to return too big width for the text. Here are my current functions:
private void Mitem2OnMeasureItem(object sender, MeasureItemEventArgs e)
{
MenuItem item = sender as MenuItem;
if (item == null) return;
var size = e.Graphics.MeasureString(item.Text, f);
e.ItemHeight = (int) size.Height;
e.ItemWidth = (int) size.Width;
}
private void Mitem2OnDrawItem(object sender, DrawItemEventArgs e)
{
MenuItem item = sender as MenuItem;
if (item == null) return;
SizeF size = e.Graphics.MeasureString(item.Text, f);
e.Graphics.FillRectangle(
(e.State & DrawItemState.HotLight) == DrawItemState.HotLight ? Brushes.CornflowerBlue : Brushes.Aqua,
e.Bounds);
e.Graphics.DrawString(item.Text, f, Brushes.Black, e.Bounds.X, e.Bounds.Y);
e.DrawFocusRectangle();
}
This is how it looks like
To get a better measurement use the overload of MeasureString that takes StringFormat class:
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
var size = e.Graphics.MeasureString(
item.Text,
aFont,
new Point(1,1),
StringFormat.GenericTypographic
);
Notice the use of the TextRenderingHint with a value of AntiAlias that is set on the Graphics object.
You'll still have a slightly bigger bounding box but in my test the box is 5 pixels less wide with this code instead of your code.
I'm wondering if there's a way to add padding between my line items. It's a form intended to be used on a tablet, and space between each one would make it easier to select different items.
Anyone know how I can do this?
There is an ItemHeight property.
You have to change DrawMode property to OwnerDrawFixed to use custom ItemHeight.
When you use DrawMode.OwnerDrawFixed you have to paint/draw items "manually".
Here is an example: Combobox appearance
Code from link above (written/provided by max):
public class ComboBoxEx : ComboBox
{
public ComboBoxEx()
{
base.DropDownStyle = ComboBoxStyle.DropDownList;
base.DrawMode = DrawMode.OwnerDrawFixed;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
e.DrawBackground();
if(e.State == DrawItemState.Focus)
e.DrawFocusRectangle();
var index = e.Index;
if(index < 0 || index >= Items.Count) return;
var item = Items[index];
string text = (item == null)?"(null)":item.ToString();
using(var brush = new SolidBrush(e.ForeColor))
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
e.Graphics.DrawString(text, e.Font, brush, e.Bounds);
}
}
}
How can I set the background color of a specific item in a System.Windows.Forms.ListBox?
I would like to be able to set multiple ones if possible.
Thanks for the answer by Grad van Horck. It guided me in the correct direction.
To support text (not just background color), here is my fully working code:
//global brushes with ordinary/selected colors
private SolidBrush reportsForegroundBrushSelected = new SolidBrush(Color.White);
private SolidBrush reportsForegroundBrush = new SolidBrush(Color.Black);
private SolidBrush reportsBackgroundBrushSelected = new SolidBrush(Color.FromKnownColor(KnownColor.Highlight));
private SolidBrush reportsBackgroundBrush1 = new SolidBrush(Color.White);
private SolidBrush reportsBackgroundBrush2 = new SolidBrush(Color.Gray);
//custom method to draw the items, don't forget to set DrawMode of the ListBox to OwnerDrawFixed
private void lbReports_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
bool selected = ((e.State & DrawItemState.Selected) == DrawItemState.Selected);
int index = e.Index;
if (index >= 0 && index < lbReports.Items.Count)
{
string text = lbReports.Items[index].ToString();
Graphics g = e.Graphics;
//background:
SolidBrush backgroundBrush;
if (selected)
backgroundBrush = reportsBackgroundBrushSelected;
else if ((index % 2) == 0)
backgroundBrush = reportsBackgroundBrush1;
else
backgroundBrush = reportsBackgroundBrush2;
g.FillRectangle(backgroundBrush, e.Bounds);
//text:
SolidBrush foregroundBrush = (selected) ? reportsForegroundBrushSelected : reportsForegroundBrush;
g.DrawString(text, e.Font, foregroundBrush, lbReports.GetItemRectangle(index).Location);
}
e.DrawFocusRectangle();
}
The above adds to the given code and will show the proper text plus highlight the selected item.
Probably the only way to accomplish that is to draw the items yourself.
Set the DrawMode to OwnerDrawFixed and code something like this on the DrawItem event:
private void listBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Silver), e.Bounds);
// Print text
e.DrawFocusRectangle();
}
The second option would be using a ListView, although they have an other way of implementations (not really data bound, but more flexible in way of columns).
// Set the background to a predefined colour
MyListBox.BackColor = Color.Red;
// OR: Set parts of a color.
MyListBox.BackColor.R = 255;
MyListBox.BackColor.G = 0;
MyListBox.BackColor.B = 0;
If what you mean by setting multiple background colors is setting a different background color for each item, this isn't possible with a ListBox, but it is with a ListView, with something like:
// Set the background of the first item in the list
MyListView.Items[0].BackColor = Color.Red;
public MainForm()
{
InitializeComponent();
this.listbox1.DrawItem += new DrawItemEventHandler(this.listbox1_DrawItem);
}
private void listbox1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
e.DrawBackground();
Brush myBrush = Brushes.Black;
var item = listbox1.Items[e.Index];
if(e.Index % 2 == 0)
{
e.Graphics.FillRectangle(new SolidBrush(Color.Gold), e.Bounds);
}
e.Graphics.DrawString(((ListBox)sender).Items[e.Index].ToString(),
e.Font, myBrush,e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
}
}