Custom draw items in ComboBoxCell - c#

I am trying to draw items in a ComboBoxCell in a DataGridView using the DrawItem Event. Following is my code.
Updated Code:
private void dgv_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
int index = dgv.CurrentCell.ColumnIndex;
if (index == FormatColumnIndex)
{
var combobox = e.Control as ComboBox;
if (combobox == null)
return;
combobox.DrawMode = DrawMode.OwnerDrawFixed;
combobox.DrawItem -= combobox_DrawItem;
combobox.DrawItem += new DrawItemEventHandler(combobox_DrawItem);
}
}
void combobox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
{
return;
}
int index = dgv.CurrentCell.RowIndex;
if (index == e.Index)
{
DataGridViewComboBoxCell cmbcell = (DataGridViewComboBoxCell)dgv.CurrentRow.Cells["ProductFormat"];
string productID = dgv.Rows[cmbcell.RowIndex].Cells["ProductID"].Value.ToString();
string item = cmbcell.Items[e.Index].ToString();
if (item != null)
{
Font font = new System.Drawing.Font(FontFamily.GenericSansSerif, 8);
Brush backgroundColor;
Brush textColor;
if (e.State == DrawItemState.Selected)
{
backgroundColor = SystemBrushes.Highlight;
textColor = SystemBrushes.HighlightText;
}
else
{
backgroundColor = SystemBrushes.Window;
textColor = SystemBrushes.WindowText;
}
if (item == "Preferred" || item == "Other")
{
font = new Font(font, FontStyle.Bold);
backgroundColor = SystemBrushes.Window;
textColor = SystemBrushes.WindowText;
}
if (item != "Select" && item != "Preferred" && item != "Other")
e.Graphics.DrawString(item, font, textColor, new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
else
e.Graphics.DrawString(item, font, textColor, e.Bounds);
}
}
}
}
The items are displayed properly, but the dropdown seems out of place and looks awkward.
Also when I hover over the dropdown items, they seem to be painted over again which makes them look darker and blurred. How can I fix this? Thanks.

Your drawing routine looks like it is confusing the RowIndex of the grid with the e.Index of the ComboBox items collection. Different things.
Try removing this:
// int index = dgv.CurrentCell.RowIndex;
// if (index == e.Index) {
As far as the blurriness is concerned, add the following line to fix that:
void combobox_DrawItem(object sender, DrawItemEventArgs e) {
e.DrawBackground();

I had the same problem and fixed it by setting the TextRenderingHint property of e.Graphics to SingleBitPerPixelGridFit before calling DrawString:
e.DrawBackground()
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit
e.Graphics.DrawString(lstr_h, e.Font, lbrush_fore, e.Bounds)
e.DrawFocusRectangle()

Related

Highlight ListBox items based on value from class

Is it possible to loop through items in a ListBox and highlight or indicate item unavailability somehow by checking a class for a value?
Basically, got a Game class and within stored info whether Game is Available so I need to check this class when looping through the ListBox Items and somehow indicate on the ListBox if GameAvailable = false.
Got to this point and not sure how to carry on:
private void HighlightUnavailable()
{
foreach(string item in listbox_consoles.Items)
{
foreach (Products.Game game in GameService.AllGames())
{
if (item == game.GameName.ToString())
{
if (game.GameAvailable)
{
}
}
}
}
}
Yes that's possible in such a way as:
Bind the ListBox to the GameService.AllGames() which returns I believe a list or an array of the Game objects.
Set the ListBox.DrawMode to DrawMode.OwnerDrawFixed and handle the ListBox.DrawItem event to draw the items according to their GameAvailable properties.
Assuming the controls names are Form1 and listBox1, add in Form1 constructor:
public Form1()
{
InitializeComponent();
//...
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.DrawItem += (s, e) => OnListBoxDrawItem(s, e);
listBox1.DataSource = GameService.AllGames();
}
Say you want to display the available games with green and the rest with red foreground colors.
private void OnListBoxDrawItem(object sender, DrawItemEventArgs e)
{
//Comment if you don't need to show the selected item(s)...
e.DrawBackground();
if (e.Index == -1) return;
var game = listBox1.Items[e.Index] as Game;
var foreColor = game.GameAvailable ? Color.Green : Color.Red;
//Pass the listBox1.BackColor instead of the e.BackColor
//if you don't need to show the selection...
TextRenderer.DrawText(e.Graphics, game.GameName, e.Font,
e.Bounds, foreColor, e.BackColor,
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
... or with different background colors:
private void OnListBoxDrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index == -1) return;
var game = listBox1.Items[e.Index] as Game;
var backColor = e.State.HasFlag(DrawItemState.Selected)
? e.BackColor
: game.GameAvailable
? Color.LightGreen
: listBox1.BackColor;
//Or this if you don't need to show the selection ...
//var backColor = game.GameAvailable
// ? Color.LightGreen
// : listBox1.BackColor;
using (var br = new SolidBrush(backColor))
e.Graphics.FillRectangle(br, e.Bounds);
TextRenderer.DrawText(e.Graphics, game.GameName, e.Font,
e.Bounds, Color.Black, backColor,
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
... or with a couple of yes and no images from your resources:
Bitmap YesImage, NoImage;
public Form1()
{
InitializeComponent();
//...
YesImage = Properties.Resources.YesImage;
NoImage = Properties.Resources.NoImage;
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.DrawItem += (s, e) => OnListBoxDrawItem(s, e);
listBox1.DataSource = GameService.AllGames();
this.FormClosed += (s, e) => { YesImage.Dispose(); NoImage.Dispose(); };
}
private void OnListBoxDrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index == -1) return;
var game = listBox1.Items[e.Index] as Game;
var backColor = e.State.HasFlag(DrawItemState.Selected)
? e.BackColor
: listBox1.BackColor;
var bmp = game.GameAvailable ? YesImage : NoImage;
var rectImage = new Rectangle(
3, e.Bounds.Y + ((e.Bounds.Height - bmp.Height) / 2),
bmp.Width, bmp.Height
);
var rectTxt = new Rectangle(
rectImage.Right + 3, e.Bounds.Y,
e.Bounds.Right - rectImage.Right - 3,
e.Bounds.Height
);
using (var br = new SolidBrush(backColor))
e.Graphics.FillRectangle(br, e.Bounds);
e.Graphics.DrawImage(bmp, rectImage);
TextRenderer.DrawText(e.Graphics, game.GameName, e.Font,
rectTxt, Color.Black, backColor,
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}

Set color for the first character in a cell of DataGridView

I am new to Winform C#. I have a question: Are there any ways to set color for the first character in a cell of DataGridView?
Thank you!
Handling the CellPainting event is the right way to do it. Here is a code snippet applying your requirement to specific cell excluding the grid column headers.
private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.Value != null && !string.IsNullOrEmpty(e.Value.ToString()) && e.RowIndex != -1)
{
// Current cell pending to be painted.
var currentCell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
// Cell that needs to be painted. In this case the first cell of the first row.
var cellToBePainted = dataGridView1.Rows[0].Cells[0];
if (currentCell == cellToBePainted)
{
using (Brush customColor = new SolidBrush(Color.Red))
using (Brush cellDefaultBrush = new SolidBrush(e.CellStyle.ForeColor))
{
string fullText = e.Value.ToString();
string firstChar = fullText[0].ToString();
string restOfTheText = fullText.Substring(1);
e.PaintBackground(e.CellBounds, true);
Rectangle cellRect = new Rectangle(e.CellBounds.Location, e.CellBounds.Size);
Size entireTextSize = TextRenderer.MeasureText(fullText, e.CellStyle.Font);
Size firstCharSize = TextRenderer.MeasureText(fullText[0].ToString(), e.CellStyle.Font);
e.Graphics.DrawString(fullText[0].ToString(), e.CellStyle.Font, customColor, cellRect);
if (!string.IsNullOrEmpty(restOfTheText))
{
Size restOfTheTextSize = TextRenderer.MeasureText(restOfTheText, e.CellStyle.Font);
cellRect.X += (entireTextSize.Width - restOfTheTextSize.Width);
cellRect.Width = e.CellBounds.Width;
e.Graphics.DrawString(restOfTheText, e.CellStyle.Font, cellDefaultBrush, cellRect);
}
e.Handled = true;
}
}
}
}

Change Item Height of ComboBox

How to set combobox item height? My combobox.size=new size(320,40) and I had set combobox.itemheight=18 but it didn't work. I want my itemheight or text height to be 18, and fixed size for the combobox which is 320x40. I used also drawmode property but nothing is happening.
Try changing Font Size of your combo box
Well, in order to prevent combobox resizing to its default height, you can declare it being manually drawing:
myComboBox.DrawMode = DrawMode.OwnerDrawFixed; // or DrawMode.OwnerDrawVariable;
myComboBox.Height = 18; // <- what ever you want
Then you have to implement DrawItem event:
private void myComboBox_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();
}
Edit: to have the combobox stretched, but not its dropdown list
myComboBox.DrawMode = DrawMode.OwnerDrawVariable;
myComboBox.Height = 18; // Combobox itself is 18 pixels in height
...
private void myComboBox_MeasureItem(object sender, MeasureItemEventArgs e) {
e.ItemHeight = 17; // while item is 17 pixels high only
}

Changing the Color of ComboBox highlighting

I am trying to work around changing the color of highlighting in a ComboBox dropdown on a C# Windows Forms application.
I have searched the whole web for an answer, and the best option i found so far was to draw a rectangle of the desired color when the item that is selected is being drawn.
Class Search
{
Public Search()
{
}
private void addFilter()
{
ComboBox field = new ComboBox();
field.Items.AddRange(new string[] { "Item1", "item2" });
field.Text = "Item1";
field.DropDownStyle = ComboBoxStyle.DropDownList;
field.FlatStyle = FlatStyle.Flat;
field.BackColor = Color.FromArgb(235, 235, 235);
field.DrawMode = DrawMode.OwnerDrawFixed;
field.DrawItem += field_DrawItem;
}
private void field_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index >= 0)
{
ComboBox combo = sender as ComboBox;
if (e.Index == combo.SelectedIndex)
e.Graphics.FillRectangle(new SolidBrush(Color.Gray),
e.Bounds
);
else
e.Graphics.FillRectangle(new SolidBrush(combo.BackColor),
e.Bounds
);
e.Graphics.DrawString(combo.Items[e.Index].ToString(), e.Font,
new SolidBrush(combo.ForeColor),
new Point(e.Bounds.X, e.Bounds.Y)
);
}
}
}
The problem with this code, is that once another Item in the dropdown is selected, the other Item I draw a rectangle is still with the color i want to highlight.
Then i tried to save the last Item drawn and redraw it:
Class Search
{
private DrawItemEventArgs lastDrawn;
Public Search()
{
lastDrawn = null;
}
private void addFilter()
{
ComboBox field = new ComboBox();
field.Items.AddRange(new string[] { "Item1", "item2" });
field.Text = "Item1";
field.DropDownStyle = ComboBoxStyle.DropDownList;
field.FlatStyle = FlatStyle.Flat;
field.BackColor = Color.FromArgb(235, 235, 235);
field.DrawMode = DrawMode.OwnerDrawFixed;
field.DrawItem += field_DrawItem;
}
private void field_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index >= 0)
{
ComboBox combo = sender as ComboBox;
if (e.Index == combo.SelectedIndex)
{
e.Graphics.FillRectangle(new SolidBrush(Color.Gray), e.Bounds);
if(lastDrawn != null)
lastDrawn.Graphics.FillRectangle(new SolidBrush(combo.BackColor),
lastDrawn.Bounds
);
lastDrawn = e;
}
else
e.Graphics.FillRectangle(new SolidBrush(combo.BackColor),
e.Bounds
);
e.Graphics.DrawString(combo.Items[e.Index].ToString(), e.Font,
new SolidBrush(combo.ForeColor),
new Point(e.Bounds.X, e.Bounds.Y)
);
}
}
}
This line returns an error because of lastDrawn.Bounds (incompatible type)
lastDrawn.Graphics.FillRectangle(new SolidBrush(combo.BackColor),
lastDrawn.Bounds
);
I am feeling that changing the highlight color of the dropdown is impossible.
Thanks in advance!
In case you are using the ComboBox in more than one place in your project, it will not make sense to repeat the same code for DrawItem event over and over again. Just add this class to your project and you will have a new ComboBox control that has the HightlightColor property which will makes it easier to use the control all over the project:
class AdvancedComboBox : ComboBox
{
new public System.Windows.Forms.DrawMode DrawMode { get; set; }
public Color HighlightColor { get; set; }
public AdvancedComboBox()
{
base.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
this.HighlightColor = Color.Gray;
this.DrawItem += new DrawItemEventHandler(AdvancedComboBox_DrawItem);
}
void AdvancedComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
return;
ComboBox combo = sender as ComboBox;
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
e.Graphics.FillRectangle(new SolidBrush(HighlightColor),
e.Bounds);
else
e.Graphics.FillRectangle(new SolidBrush(combo.BackColor),
e.Bounds);
e.Graphics.DrawString(combo.Items[e.Index].ToString(), e.Font,
new SolidBrush(combo.ForeColor),
new Point(e.Bounds.X, e.Bounds.Y));
e.DrawFocusRectangle();
}
}

DataGridView selected cell style

How can I change the "selection style" on a DataGridView (winforms)?
You can easily change the forecolor and backcolor of selcted cells by assigning values to the SelectedBackColor and SelectedForeColor of the Grid's DefaultCellStyle.
If you need to do any further styling you you need to handle the SelectionChanged event
Edit: (Other code sample had errors, adjusting for multiple selected cells [as in fullrowselect])
using System.Drawing.Font;
private void dataGridView_SelectionChanged(object sender, EventArgs e)
{
foreach(DataGridViewCell cell in ((DataGridView)sender).SelectedCells)
{
cell.Style = new DataGridViewCellStyle()
{
BackColor = Color.White,
Font = new Font("Tahoma", 8F),
ForeColor = SystemColors.WindowText,
SelectionBackColor = Color.Red,
SelectionForeColor = SystemColors.HighlightText
};
}
}
Use the SelectedCells property of the GridView and the Style property of the DataGridViewCell.
Handle the SelectionChanged event on your DataGridView and add code that looks something like this:
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
foreach (DataGridViewRow row in this.dataGridView1.Rows)
{
foreach (DataGridViewCell c in row.Cells)
{
c.Style = this.dataGridView1.DefaultCellStyle;
}
}
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Red;
style.Font = new Font("Courier New", 14.4f, FontStyle.Bold);
foreach (DataGridViewCell cell in this.dataGridView1.SelectedCells)
{
cell.Style = style;
}
}
You can try the solution provided in this topic. I've tested and approved it.
Hope that helped.
With this you can even draw a colored border to the selected cells.
private void dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex >= 0 && e.ColumnIndex >= 0)
{
if (dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected == true)
{
e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);
using (Pen p = new Pen(Color.Red, 1))
{
Rectangle rect = e.CellBounds;
rect.Width -= 2;
rect.Height -= 2;
e.Graphics.DrawRectangle(p, rect);
}
e.Handled = true;
}
}
}

Categories