I'm developing an application for kind of touch screen device. In order be user friendly, I need to change size of combobox.
I've checked many thing including DrawItemEventHandler and MeasureItemEventHandler, but it didn't work as I want.
Basically I would like to change height of combobox without touching font size. When I change font size of combobox, it looks like left side of the image.
How can I set my combobox which will look like right side of the image?
By the way, don't know if it's effect solution, I am not using array string. I'm binding data like.
combobox.DisplayMember = "Name";
combobox.ValueMember = "ID";
combobox.DataSource = new BindingSource { DataSource = datalist };
Thanks in advance.
With TaW solution, I managed to set items as I want. The only thing I couldn't set text in middle when combobox items not droped down. How can I set this text position to the centre?
You can set the ItemHeight property and then draw the items yourself in the DrawItem event.
Not terribly hard, search for 'ownerdraw' & 'combobox'. There is one example on Code Project
Here is a minimal version, pulled from the above link:
private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0) return;
Font f = comboBox1.Font;
int yOffset = 10;
if ((e.State & DrawItemState.Focus) == 0)
{
e.Graphics.FillRectangle(Brushes.White, e.Bounds);
e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(), f, Brushes.Black,
new Point(e.Bounds.X, e.Bounds.Y + yOffset));
}
else
{
e.Graphics.FillRectangle(Brushes.Blue, e.Bounds);
e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(), f, Brushes.White,
new Point(e.Bounds.X, e.Bounds.Y + yOffset));
}
}
You also have to set the DropDownStyle to DropDownList to get the highlighting to work and you need to set the DrawMode to OwnerDrawFixed. (Or to OwnerDrawVariable, if you want to have different heights for some itmes..)
Related
I am developing an Office plugin and would like to produce something like this:
Right now I am using ListView, but the ListViewItem, even in Tile mode, is not customizable to be what I want. It only allows up to 2 lines of text max.
Anyone can help pointing to the right Windows Forms control that I can use? Should I extend the ListView or ListViewItem? Any existing solution?
Thanks,
I recommend using the ListBox instead. I will reproduce an example for you.
Put a ListBox into your form. Then you can write a code similar to this one on the Form's OnLoad event:
private void Form1_Load(object sender, EventArgs e)
{
// This will change the ListBox behaviour, so you can customize the drawing of each item on the list.
// The fixed mode makes every item on the list to have a fixed size. If you want each item having
// a different size, you can use DrawMode.OwnerDrawVariable.
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
// Here we define the height of each item on your list.
listBox1.ItemHeight = 40;
// Here i will just make an example data source, to emulate the control you are trying to reproduce.
var dataSet = new List<Tuple<string, string>>();
dataSet.Add(new Tuple<string, string>("5:30 PM - 6:00 PM", "11 avaliable rooms"));
dataSet.Add(new Tuple<string, string>("6:00 PM - 6:30 PM", "12 available rooms"));
listBox1.DataSource = dataSet;
}
Now, edit your ListBox DrawItem event, and write a code similar to this one:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
// This variable will hold the color of the bottom text - the one saying the count of
// the avaliable rooms in your example.
Brush roomsBrush;
// Here we override the DrawItemEventArgs to change the color of the selected
// item background to one of our preference.
// I changed to SystemColors.Control, to be more like the list you are trying to reproduce.
// Also, as I see in your example, the font of the room text part is black colored when selected, and gray
// colored when not selected. So, we are going to reproduce it as well, by setting the correct color
// on our variable defined above.
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e = new DrawItemEventArgs(e.Graphics, e.Font, e.Bounds,
e.Index, e.State ^ DrawItemState.Selected, e.ForeColor, SystemColors.Control);
roomsBrush = Brushes.Black;
}
else
{
roomsBrush = Brushes.Gray;
}
// Looking more at your example, i noticed a gray line at the bottom of each item.
// Lets reproduce that, too.
var linePen = new Pen(SystemBrushes.Control);
var lineStartPoint = new Point(e.Bounds.Left, e.Bounds.Height + e.Bounds.Top);
var lineEndPoint = new Point(e.Bounds.Width, e.Bounds.Height + e.Bounds.Top);
e.Graphics.DrawLine(linePen, lineStartPoint, lineEndPoint);
// Command the event to draw the appropriate background of the item.
e.DrawBackground();
// Here you get the data item associated with the current item being drawed.
var dataItem = listBox1.Items[e.Index] as Tuple<string, string>;
// Here we will format the font of the part corresponding to the Time text of your list item.
// You can change to wathever you want - i defined it as a bold font.
var timeFont = new Font("Microsoft Sans Serif", 8.25f, FontStyle.Bold);
// Here you draw the time text on the top of the list item, using the format you defined.
e.Graphics.DrawString(dataItem.Item1, timeFont, Brushes.Black, e.Bounds.Left + 3, e.Bounds.Top + 5);
// Now we draw the avaliable rooms part. First we define our font.
var roomsFont = new Font("Microsoft Sans Serif", 8.25f, FontStyle.Regular);
// And, finally, we draw that text.
e.Graphics.DrawString(dataItem.Item2, roomsFont, roomsBrush, e.Bounds.Left + 3, e.Bounds.Top + 18);
}
And, when running, we have something like that. It is very similar to your example, isn't?
If you want to make more changes, you just need to play with the drawings on the DrawItem event. Hope it helps!
My question is basically, does implementing DrawItem for my ComboBox in WinForms, change my Text property, why and I can I stop it?
Because my OwnerDraw event works perfectly except the Text property "also" gets set to the same logic as all the items in Items[] (ie implemented in DrawItem event below)
For context, I show URL's in the list, but some are so long I basically chop them and put the text "..." at the end - to make it more readable. I have DataSource set so that it renders one property of my class "DisplayUrl" but uses another "Url" for the actual value. (MyUrl below)
At the end of some code, I explicitly set cmbUrl.Text = "THE FULL TEXT"
But somehow the DrawItem event is also effecting the "Text" property because even after running this code, once the DrawItem event is finished my Text property is set to the same as Item[0]. ie With the text chopped off - as in "THE FULL T..."
void cmbUrl_DrawItem(object sender, DrawItemEventArgs e)
{
var text = ((MyUrl)((ComboBox)sender).Items[e.Index]).DisplayUrl;
var brush = text.Contains("bla) ? Brushes.DarkGreen : Brushes.Black;
// Fill in the background
e.Graphics.FillRectangle(new SolidBrush(e.BackColor), e.Bounds);
if (e.Index < 0) return;
// Work out where every thing goes
int nX = e.Bounds.Left;
int nY = e.Bounds.Top;
const int nMarg = 2;
int nH = e.Bounds.Height - (2 * nMarg);
// Draw the Colour Gymph
var penFore = new Pen(e.ForeColor);
var rectGymph = new Rectangle(nX + nMarg, nY + nMarg, nH, nH);
e.Graphics.FillRectangle(brush, rectGymph);
e.Graphics.DrawRectangle(penFore, rectGymph);
var fullWidth = nX + nH + (2 * nMarg);
e.Graphics.DrawString(text, e.Font, brush, fullWidth, e.Bounds.Top);
}
I think you want to show your the full Text in your combobox and just want to show the short text in Items drop-down list, so the solution may be this:
private void cmbUrl_DropDown(object sender, EventArgs e){
cmbUrl.DisplayMember = "DisplayUrl";
}
private void cmbUrl_DropDownClosed(object sender, EventArgs e){
cmbUrl.DisplayMember = "Url";
}
I have a ListView where I wish to tweak the drawing of items (for example highlighting certain strings in list view itmes), however I don't want to radically alter the way that items are displayed.
I have set the OwnerDraw to true and can get my head around how to draw my highlighting effect, however whenever I try to defer to the default implementation to draw the rest of the list view item things go wrong and I'm left with a whole load of graphical problems indicating that actually I've completely gone wrong.
Is there somewhere that I can see what the "Default" handlers for the DrawItem and DrawSubItem events do so that I can better my understanding and more easily tweak my code?
For reference here is a snippet showing what I'm currently doing:
public MyListView()
{
this.OwnerDraw = true;
this.DoubleBuffered = true;
this.DrawColumnHeader += new DrawListViewColumnHeaderEventHandler(MyListView_DrawColumnHeader);
this.DrawItem += new DrawListViewItemEventHandler(MyListView_DrawItem);
this.DrawSubItem += new DrawListViewSubItemEventHandler(MyListView_DrawSubItem);
}
private void MyListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
// Not interested in changing the way columns are drawn - this works fine
e.DrawDefault = true;
}
private void MyListView_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawBackground();
e.DrawFocusRectangle();
}
private void MyListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
string searchTerm = "Term";
int index = e.SubItem.Text.IndexOf(searchTerm);
if (index >= 0)
{
string sBefore = e.SubItem.Text.Substring(0, index);
Size bounds = new Size(e.Bounds.Width, e.Bounds.Height);
Size s1 = TextRenderer.MeasureText(e.Graphics, sBefore, this.Font, bounds);
Size s2 = TextRenderer.MeasureText(e.Graphics, searchTerm, this.Font, bounds);
Rectangle rect = new Rectangle(e.Bounds.X + s1.Width, e.Bounds.Y, s2.Width, e.Bounds.Height);
e.Graphics.FillRectangle(new SolidBrush(Color.Yellow), rect);
}
e.DrawText();
}
I haven't got the time now to write up a complete answer so instead I'll put down some quick notes and come back to it later.
As LarsTech said, owner drawing a ListView control is a pain - the .Net ListView class is a wrapper around the underlying Win32 List View Control and the ability to "Owner draw" is provided by the NM_CUSTOMDRAW notification code. As such there is no "default .Net implementation" - the default is to use the underlying Win32 control.
To make life even more difficult there are a number of extra considerations to make:
As LarsTech pointed out, the first subitem in fact represents the parent item itself, and so if you handle rendering in both DrawItem and DrawSubItem you may well be drawing the contents of the first cell twice.
There is a bug in the underlying list view control (documented on the note on this page) that means that a DrawItem event will occur without corresponding DrawSubItem events, meaning that if you draw a background in the DrawItem event and then draw the text in the DrawSubItem event your item text will disappear when you mouse over.
Some of the rendering also appears to not be double-buffered by default
I also noticed that the ItemState property is not always correct, for example just after resizing a column. Consequently I've found its best not to rely on it.
You also need to make sure that your text doesn't split over multiple lines, else you will see the top few pixels of the lower line being rendered at the bottom of the cell.
Also special consideration needs to be given when rendering the first cell to take account of extra padding that the native list view uses.
Because the DrawItem event occurs first, anything you draw in the DrawItem handler (e.g. the selection effect) may well be overlayed by things you do in the DrawSubItem handler (e.g. having certain cells with a different background color).
All in all handling owner drawing is a fairly involved affair - I found it best to handle all drawing inside the DrawSubItem event, its also best to perform your own double-buffering by using the BufferedGraphics class.
I also found looking at the source code for ObjectListView very handy.
Finally, all of this is just to handle the details mode of the list view (the only mode I am using), if you want the other modes to work too then I believe that there are extra things to take account of.
When I get a chance I'll try and post my working example code.
I don't know if this will completely help you, but I'll add a few notes:
One thing to keep in mind is that DrawSubItem will draw the first item, too, and that's probably where you are getting the double-rendered look from.
Some things to try (not factored for speed):
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e) {
e.DrawBackground();
if ((e.State & ListViewItemStates.Selected) == ListViewItemStates.Selected) {
Rectangle r = new Rectangle(e.Bounds.Left + 4, e.Bounds.Top, TextRenderer.MeasureText(e.Item.Text, e.Item.Font).Width, e.Bounds.Height);
e.Graphics.FillRectangle(SystemBrushes.Highlight, r);
e.Item.ForeColor = SystemColors.HighlightText;
} else {
e.Item.ForeColor = SystemColors.WindowText;
}
e.DrawText();
e.DrawFocusRectangle();
}
For your DrawSubItem routine, make sure you aren't drawing in the first column and I added the DrawBackground() routine. I added some clipping to the highlight rectangle so it wouldn't paint outside the column parameters.
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) {
if (e.ColumnIndex > 0) {
e.DrawBackground();
string searchTerm = "Term";
int index = e.SubItem.Text.IndexOf(searchTerm);
if (index >= 0) {
string sBefore = e.SubItem.Text.Substring(0, index);
Size bounds = new Size(e.Bounds.Width, e.Bounds.Height);
Size s1 = TextRenderer.MeasureText(e.Graphics, sBefore, this.Font, bounds);
Size s2 = TextRenderer.MeasureText(e.Graphics, searchTerm, this.Font, bounds);
Rectangle rect = new Rectangle(e.Bounds.X + s1.Width, e.Bounds.Y, s2.Width, e.Bounds.Height);
e.Graphics.SetClip(e.Bounds);
e.Graphics.FillRectangle(new SolidBrush(Color.Yellow), rect);
e.Graphics.ResetClip();
}
e.DrawText();
}
}
In general, owner drawing a ListView control is welcoming in a world of hurt. You aren't drawing in Visual Styles anymore, you would have to do that yourself, too. Ugh.
Item selected back color changed. In by default blue in windows. This code will help for u in any colors:
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawBackground();
if (e.Item.Selected)
{
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(250, 194, 87)), e.Bounds);
}
e.Graphics.DrawString(e.Item.Text, new Font("Arial", 10), new SolidBrush(Color.Black), e.Bounds);
}
private void Form1_Load(object sender, EventArgs e)
{
for (int ix = 0; ix < listView1.Items.Count; ++ix)
{
var item = listView1.Items[ix];
item.BackColor = (ix % 2 == 0) ? Color.Gray : Color.LightGray;
}
}
private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
}
}
ComponentOwl recently released a freeware component called Better ListView Express.
It looks and behaves exactly like the ListView, but has much more powerful owner drawing capabilities - you can draw accurately over all elements and even turn off some drawing (e.g. selection to make you on).
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.DrawBackground();
if (e.Item.Selected)
{
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(250, 194, 87)), e.Bounds);
}
e.Graphics.DrawString(e.Item.Text, new Font("Arial", 10), new SolidBrush(Color.Black), e.Bounds);
}
I have a ComboBox on a form, and its default height is 21. How do I change it?
ComboBox auto-sizes to fit the font. Turning that off is not an option. If you want it bigger then give it a bigger font.
Set the DrawMode to OwnerDrawVariable. However customization of the ComboBox leads to other issues. See this link for a tutorial on how to do this completely:
http://www.csharphelp.com/2006/09/listbox-control-in-c/
OwnerDrawVariable sample code here:
https://msdn.microsoft.com/en-us/library/system.windows.forms.combobox.drawitem%28v=vs.110%29.aspx
Once that's done, you need to set the ItemHeight property of the combobox to set the effective height of the combobox.
Just as another option, if you'd like to increase the height of the ComboBox without increasing the font size or having to worry about drawing everything yourself, you can use a simple Win32 API call to increase the height like this:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Win32ComboBoxHeightExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
private const Int32 CB_SETITEMHEIGHT = 0x153;
private void SetComboBoxHeight(IntPtr comboBoxHandle, Int32 comboBoxDesiredHeight)
{
SendMessage(comboBoxHandle, CB_SETITEMHEIGHT, -1, comboBoxDesiredHeight);
}
private void button1_Click(object sender, EventArgs e)
{
SetComboBoxHeight(comboBox1.Handle, 150);
comboBox1.Refresh();
}
}
}
Result:
To do this, you need to set the DrawMode to OwnerDrawVariable or OwnerDrawFixed and manually draw your items. This can be done with a pretty simple class.
This example will allow you to use the ItemHeight property of the ComboBox regardless of font size. I threw in an bonus property TextAlign which will also allow you to center the items.
One thing worth mentioning is, you must set DropDownStyle to DropDownList for the selected item to respect our customizations.
// The standard combo box height is determined by the font. This means, if you want a large text box, you must use a large font.
// In our class, ItemHeight will now determine the height of the combobox with no respect to the combobox font.
// TextAlign can be used to align the text in the ComboBox
class UKComboBox : ComboBox
{
private StringAlignment _textAlign = StringAlignment.Center;
[Description("String Alignment")]
[Category("CustomFonts")]
[DefaultValue(typeof(StringAlignment))]
public StringAlignment TextAlign
{
get { return _textAlign; }
set
{
_textAlign = value;
}
}
private int _textYOffset = 0;
[Description("When using a non-centered TextAlign, you may want to use TextYOffset to manually center the Item text.")]
[Category("CustomFonts")]
[DefaultValue(typeof(int))]
public int TextYOffset
{
get { return _textYOffset; }
set
{
_textYOffset = value;
}
}
public UKComboBox()
{
// Set OwnerDrawVariable to indicate we will manually draw all elements.
this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
// DropDownList style required for selected item to respect our DrawItem customizations.
this.DropDownStyle = ComboBoxStyle.DropDownList;
// Hook into our DrawItem & MeasureItem events
this.DrawItem +=
new DrawItemEventHandler(ComboBox_DrawItem);
this.MeasureItem +=
new MeasureItemEventHandler(ComboBox_MeasureItem);
}
// Allow Combo Box to center aligned and manually draw our items
private void ComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
// Draw the background
e.DrawBackground();
// Draw the items
if (e.Index >= 0)
{
// Set the string format to our desired format (Center, Near, Far)
StringFormat sf = new StringFormat();
sf.LineAlignment = _textAlign;
sf.Alignment = _textAlign;
// Set the brush the same as our ForeColour
Brush brush = new SolidBrush(this.ForeColor);
// If this item is selected, draw the highlight
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
brush = SystemBrushes.HighlightText;
// Draw our string including our offset.
e.Graphics.DrawString(this.Items[e.Index].ToString(), this.Font, brush,
new RectangleF(e.Bounds.X, e.Bounds.Y + _textYOffset, e.Bounds.Width, e.Bounds.Height), sf);
}
}
// If you set the Draw property to DrawMode.OwnerDrawVariable,
// you must handle the MeasureItem event. This event handler
// will set the height and width of each item before it is drawn.
private void ComboBox_MeasureItem(object sender,System.Windows.Forms.MeasureItemEventArgs e)
{
// Custom heights per item index can be done here.
}
}
Now we have full control over our font and height of the ComboBox separately. We no longer need to make a large font to size our ComboBox.
If you are wanting to adjust to the number of items in the ComboBox you can change the value of the DropDownHeight as follows, given a List of items. I'm using 24 here as a "per item amount"; this is by no means fixed.
comboBox1.DropDownHeight = SomeList.Count * 24;
ComboBox has a property 'DropDownHeight' which can be changed either via the properties window of the combobox or programmatically. i.e.
public partial class EventTestForm : Form
{
public EventTestForm()
{
InitializeComponent();
cmbOwners.DropDownHeight = 100;
}
In code, a.Height should work. In the designer, go into the properties and look in Size->Height.
Alternatively, you can change the Font Size and the combo box will grow bigger to accomodate it, but I don't think that's what you want.
C# In a nut shell can I display images in a list box? I have a list of users and I want to display a green tick next to some of the names, is this possible?
Thanks
The following code displays how to do custom drawing in a listbox.
using System.Windows.Forms;
using System.Drawing;
namespace Toolset.Controls
{
public class CustomDrawListBox : ListBox
{
public CustomDrawListBox()
{
this.DrawMode = DrawMode.OwnerDrawVariable; // We're using custom drawing.
this.ItemHeight = 40; // Set the item height to 40.
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
// Make sure we're not trying to draw something that isn't there.
if (e.Index >= this.Items.Count || e.Index <= -1)
return;
// Get the item object.
object item = this.Items[e.Index];
if (item == null)
return;
// Draw the background color depending on
// if the item is selected or not.
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
// The item is selected.
// We want a blue background color.
e.Graphics.FillRectangle(new SolidBrush(Color.Blue), e.Bounds);
}
else
{
// The item is NOT selected.
// We want a white background color.
e.Graphics.FillRectangle(new SolidBrush(Color.White), e.Bounds);
}
// Draw the item.
string text = item.ToString();
SizeF stringSize = e.Graphics.MeasureString(text, this.Font);
e.Graphics.DrawString(text, this.Font, new SolidBrush(Color.White),
new PointF(5, e.Bounds.Y + (e.Bounds.Height - stringSize.Height) / 2));
}
}
}
System.Windows.Forms.ListView will do the trick very easily. You might have to work a little harder than a ListBox if you want the list in 'details' view though.
In WPF it's quite simple, but if you're using winforms, you can't do it with the System.Windows.Forms.ListBox control. You can do it with the ListView control, or third party controls.