I created the custom combobox on .net framework 1.1, i can custom draw dropdown items, but i can't set or draw the combobox text on Middle Left , combobox text always render top left , but i need text should be render on middle left.
[ToolboxBitmap(typeof(ComboBox))]
public class MenComboBox :ComboBox
{
private Image _image = Image.FromFile("Expand.png");
public MenComboBox()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
this.BackColor = Color.White;
this.ItemHeight = 18;
this.Font = new Font("Arial",12f,FontStyle.Regular);
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (!DesignMode)
{
if (e.Index > -1)
{
int textHeight = (int)e.Graphics.MeasureString(this.Items[e.Index].ToString(), e.Font).Height;
Point textPos = new Point(e.Bounds.X + 4, e.Bounds.Y + ((this.ItemHeight - textHeight) / 2));
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e.Graphics.FillRectangle(Brushes.Blue, e.Bounds);
e.Graphics.DrawString(this.Items[e.Index].ToString(),e.Font,Brushes.White,textPos);
}
else
{
e.Graphics.FillRectangle(Brushes.White, e.Bounds);
e.Graphics.DrawString(this.Items[e.Index].ToString(),e.Font,Brushes.Black,textPos);
}
}
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x000F)
{
using (Graphics g = this.CreateGraphics())
{
g.FillRectangle(new SolidBrush(BackColor), this.ClientRectangle);
g.DrawRectangle(Pens.Blue, new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1));
Rectangle rect = new Rectangle(this.Width - 15, 3, 12, this.Height - 6);
g.FillRectangle(new SolidBrush(BackColor), rect);
g.DrawImage(this._image, this.Width - 16, (this.Height - 8) / 2);
g.Dispose();
}
}
}
}
In an owner draw ComboBox the text of the Edit part of the control will always be shown at top left, regardless of the height of the ItemHeight.
To position the Edit part vertically in middle, you can find the Edit element using GetComboBoxInfo and then using SetWindowPos set a new position for it to stand vertically in middle of the ComboBox.
You need to reposition it when the control size changes. Also you need to fill the background of ComboBox with a Color.
Here is the code that I used:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyComboBox : ComboBox
{
public MyComboBox()
{
SetStyle(ControlStyles.ResizeRedraw, true);
DrawMode = DrawMode.OwnerDrawFixed;
ItemHeight = 40;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public int Width { get { return Right - Left; } }
public int Height { get { return Bottom - Top; } }
}
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, int uFlags);
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO
{
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public int stateButton;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SetupEdit();
Invalidate();
}
private int buttonWidth = SystemInformation.HorizontalScrollBarArrowWidth;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0xF)
{
using (var g = this.CreateGraphics())
{
var r = new Rectangle(2, 2,
ClientRectangle.Width - buttonWidth - 2,
ClientRectangle.Height - 4);
g.FillRectangle(Brushes.White, r);
}
}
base.WndProc(ref m);
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
SetupEdit();
}
private void SetupEdit()
{
var info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
GetComboBoxInfo(this.Handle, ref info);
SetWindowPos(info.hwndEdit, IntPtr.Zero, 3,
(this.Height - Font.Height) / 2,
ClientRectangle.Width - buttonWidth - 3,
ClientRectangle.Height - Font.Height - 4,
SWP_NOZORDER);
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
e.DrawBackground();
var txt = "";
if (e.Index >= 0)
txt = GetItemText(Items[e.Index]);
TextRenderer.DrawText(e.Graphics, txt, Font, e.Bounds,
ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
}
ok, below code doesn't answer the actual question about the Text portion; Hans got it right, as usual.
I keep the answer because I think it does a few things better than OP code..
if (!DesignMode)
{
if (e.Index > -1)
{
using (StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
{
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e.Graphics.FillRectangle(SystemBrushes.MenuHighlight, e.Bounds);
e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(),
e.Font,SystemBrushes.HighlightText, e.Bounds, fmt);
}
else
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(),
e.Font, SystemBrushes.MenuText,e.Bounds, fmt);
}
}
}
}
Instead of calculating a centered position I use the DrawString overload that takes a target rectangle and add a StringFormat to center in both directions. StringFormat was available since .Net 1.1 and indeed is IDisposable, so we should dipose of each we create, best in a using clause..
Note that for drawing controls the use of TextRenderer is encouraged but only came with .Net 2.0.
Also note that I substituted the Brushes for SystemBrushes..
Also: My ComboBox doesn't place the text in its Text portion top-left but middle-left. Maybe the old .Net1.1 control is the culprit?
Related
I've extended WinForms TreeView in C# in order to draw my own text (I'm drawing a shadowed font with black under white) and everything looks okay apart from when I expand a node with a large number of children (by large I'm only talking 20+). I've tried to capture what happens but it's so quick I've been unsuccessful so I'll try to explain it.
The TreeView has around 15 1st level nodes in it and when I open one of the 20+ children nodes, I get a glitch where it displays the node under the one I've clicked multiple times (looks like the same amount of times as the children nodes that subsequently appear) for an instant and then displays all the child nodes. It's very frustrating, mostly because I can't find anything online similar to this.
I've set the TreeView to use double buffering but although it covers lots of flicking, it doesn't cover this glitch.
Here's my TreeView class:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Liq.CommonControls.Tree
{
public partial class LiqTreeView : TreeView
{
private Brush backBrush, highlightBrush, blackBrush, textBrush;
private Font drawFont;
private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45;
private const int TVS_EX_DOUBLEBUFFER = 0x0004;
private const int WM_ERASEBKGND = 0x0014;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
public LiqTreeView()
{
InitializeComponent();
this.DrawNode += LiqTreeView_DrawNode;
BackColor = Color.FromArgb(255, 105, 105, 105);
backBrush = new SolidBrush(this.BackColor);
highlightBrush = new SolidBrush(Color.LightGray);
blackBrush = new SolidBrush(Color.Black);
textBrush = new SolidBrush(Color.WhiteSmoke);
drawFont = new Font("Arial", 14, FontStyle.Bold, GraphicsUnit.Pixel);
}
protected override void OnHandleCreated(EventArgs e)
{
SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);
base.OnHandleCreated(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_ERASEBKGND)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
private void LiqTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (e.Bounds.Height < 1 || e.Bounds.Width < 1) return;
e.DrawDefault = false;
if (!e.Node.IsVisible)
return;
if (e.Node.IsSelected)
e.Graphics.FillRectangle(highlightBrush, e.Bounds);
Rectangle r = e.Bounds;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Near;
sf.Trimming = StringTrimming.Character;
r.Y -= 2;
r.X += e.Node.Level * 10;
TextRenderer.DrawText(e.Graphics, e.Node.Text, drawFont, (Rectangle)r, Color.Black, TextFormatFlags.Left|TextFormatFlags.EndEllipsis);
r.Y -= 1;
r.X += 1;
TextRenderer.DrawText(e.Graphics, e.Node.Text, drawFont, (Rectangle)r, Color.WhiteSmoke, TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
}
}
}
I have a custom TextBox in which I draw some place holder text when it's empty.
It works pretty well, but it flickers when the mouse enters and leaves the TextBox. It seems related to the border becoming blue when the mouse hovers the control (I'm on Windows 8.1).
Any idea how I could fix this ?
I've tried various SetStyles flags without success.
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center
};
private Font mPlaceHolderFont;
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0F)
{
if (string.IsNullOrEmpty(Text) && !Focused)
{
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var rect = new RectangleF(2, 2, Width - 4, Height - 4);
g.FillRectangle(Brushes.White, rect);
g.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
}
}
}
}
I had other problems with overriding OnPaint. Here is the best solution I came up with :
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
private Font mPlaceHolderFont;
private Brush mForegroundBrush;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var bounds = new Rectangle(-2, -2, Width, Height);
var rect = new RectangleF(1, 0, Width - 2, Height - 2);
e.Graphics.FillRectangle(Brushes.White, rect);
if (string.IsNullOrEmpty(Text) && !Focused)
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
if (mForegroundBrush == null)
mForegroundBrush = new SolidBrush(ForeColor);
e.Graphics.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
else
{
var flags = TextFormatFlags.Default | TextFormatFlags.TextBoxControl;
if (!Multiline)
flags |= TextFormatFlags.SingleLine | TextFormatFlags.NoPadding;
TextBoxRenderer.DrawTextBox(e.Graphics, bounds, Text, Font, flags, TextBoxState.Selected);
}
}
}
Is there a special reason for using WM_PAINT instead of OnPaint? In WM_PAINT you obtain a drawing context from the handle, which is always a direct access to the control. In OnPaint you already have a Graphics in the event args, which can be either a buffer or a direct context, depending on the styles.
You mentioned that you have tried a few styles with no success. Firstly I would say try these and move your paint logic into OnPaint:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
If it does not work (a focused control may behave strangely in Windows) and you must stick to WM_PAINT, then create a buffer manually. Your original code draws a white rectangle first, then some text, which causes flickering. You can avoid this by using a buffer:
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
// creating a buffered context
using (BufferedGraphicsContext context = new BufferedGraphicsContext())
{
// creating a buffer for the original Graphics
using (BufferedGraphics bg = context.Allocate(e.Graphics, ClientRectangle))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var gBuf = bg.Graphics;
var rect = ClientRectangle;
rect.Inflate(-1, -1);
gBuf.FillRectangle(Brushes.White, rect);
gBuf.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
// copying the buffer onto the original Graphics
bg.Render(e.Graphics);
}
}
}
I have created a custom Control called Ellipse. I'm able to resize, move, and paint this Ellipse. Now I'm trying to add undo/redo functionality for the resizing. The user can resize the control at the bottom right corner. At the moment the control prints hello as long as the cursor is positioned at the bottom right corner of the Control. But what I want is that when the user starts resizing (so leftmouse button is down and cursor is at the bottom right corner) hello is printed (only once). How to do this or is there a another (better) way to do it?
Ellipse.cs
class Ellipse : Control
{
private Point mDown { get; set; }
public Ellipse()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Draw a black ellipse in the rectangle represented by the control.
e.Graphics.FillEllipse(Brushes.Black, 0, 0, Width, Height);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
mDown = e.Location;
}
protected override void OnMouseMove(MouseEventArgs e)
{
// Call MyBase.OnMouseMove to activate the delegate.
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left)
{
Location = new Point(e.X + Left - mDown.X, e.Y + Top - mDown.Y);
}
}
/* Allow resizing at the bottom right corner */
protected override void WndProc(ref Message m)
{
const int wmNcHitTest = 0x84;
const int htBottomLeft = 16;
const int htBottomRight = 17;
if (m.Msg == wmNcHitTest)
{
Console.WriteLine("Hello");
int x = (int)(m.LParam.ToInt64() & 0xFFFF);
int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
Point pt = PointToClient(new Point(x, y));
Size clientSize = ClientSize;
if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
return;
}
}
base.WndProc(ref m);
}
I would try adding a couple more messages to check for the mouse going down in the non-client area and then another one for when the sizing was finished to complete the transaction:
private bool userResizing = false;
protected override void WndProc(ref Message m) {
const int wmNcHitTest = 0x84;
const int htBottomLeft = 16;
const int htBottomRight = 17;
const int WM_EXITSIZEMOVE = 0x232;
const int WM_NCLBUTTONDWN = 0xA1;
if (m.Msg == WM_NCLBUTTONDWN) {
if (!userResizing) {
userResizing = true;
Console.WriteLine("Start Resizing");
}
} else if (m.Msg == WM_EXITSIZEMOVE) {
if (userResizing) {
userResizing = false;
Console.WriteLine("Finish Resizing");
}
} else if (m.Msg == wmNcHitTest) {
int x = (int)(m.LParam.ToInt64() & 0xFFFF);
int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
Point pt = PointToClient(new Point(x, y));
Size clientSize = ClientSize;
if (pt.X >= clientSize.Width - 16 &&
pt.Y >= clientSize.Height - 16 &&
clientSize.Height >= 16) {
m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
return;
}
}
base.WndProc(ref m);
}
I am working on creating a simple notebook application. I have been asked to make the input area look like a sheet of notebook paper, with the text sitting on light blue lines. I am trying to make this work, but it seems to be failing miserably.
So far, I have created a transparent RichTextBox that sits on top of a panel. The Text Box is:
using System;
using System.Windows.Forms;
public class TransparentTextBox : RichTextBox
{
public TransparentTextBox()
{
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x20; // Turn on WS_EX_TRANSPARENT
return parms;
}
}
}
The paint code for the panel:
private void paper_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.DrawLine(new Pen(Brushes.LightPink, 2), 20, 0, 20, paper.Height);
int h = TextRenderer.MeasureText("Testj", txtBody.Font).Height;
for (int x = 2 + h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
}
The lines are static, and they will grow to fit any font size/family that is chosen. The problem is when the text box is scrolled. The lines won't move with the text. I have tried to link the handle of the scroll bar to the lines, but they don't seem to be linking properly.
The code to get the current scroll position:
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLINFO
{
public int cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
public enum ScrollBarDirection
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2,
SB_BOTH = 3
}
public enum ScrollInfoMask
{
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
}
...
public partial class Form1 : Form
{
[DllImport("User32.dll", EntryPoint = "GetScrollInfo")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetScrollInfo([In]IntPtr hwnd, [In]int fnBar, [In, Out]ref SCROLLINFO lpsi);
...
private void txtBody_VScroll(object sender, EventArgs e)
{
inf.cbSize = Marshal.SizeOf(inf);
inf.fMask = (int)ScrollInfoMask.SIF_ALL;
GetScrollInfo(txtBody.Handle, 1, ref inf);
Console.WriteLine(inf.nTrackPos + ":" + inf.nPos + ":" + TextRenderer.MeasureText("Testj", txtBody.Font).Height);
paper.Invalidate();
}
Then the paint above was modified to use this:
for (int x = inf.nPos % h; x < paper.Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, paper.Width, x);
}
I also tried to use nTrackPos, but neither seemed to follow the text like I want it to. I'm not too familiar with C#, so I wanted to know what I am missing/could do better. I am using Visual Studio 2008, with Visual C# 2008. .Net framework 3.5 SP1
So, here is what I came up with after some intensive googling. I decided to follow more into Gusman's comment on my question and look into drawing on the textbox again. After some playing, I realized I was improperly calculating the position of the start line. So, I reconfigured my custom RichTextBox to look like:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Journal
{
class CustomRichTextBox : RichTextBox
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
private const int WM_PAINT = 0x00F;
private const int EM_GETSCROLLPOS = 0x4DD;
public int lineOffset = 0;
[DllImport("user32.dll")]
public static extern int SendMessage(
IntPtr hWnd,
int Msg,
IntPtr wParam,
ref Point lParam
);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
using (Graphics g = base.CreateGraphics())
{
Point p = new Point();
//get the position of the scrollbar to calculate the offset
SendMessage(this.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ref p);
//draw the pink line on the side
g.DrawLine(new Pen(Brushes.LightPink, 2), 0, 0, 0, this.Height);
//determine how tall the text will be per line
int h = TextRenderer.MeasureText("Testj", this.Font).Height;
//calculate where the lines need to start
lineOffset = h - (p.Y % h);
//draw lines until there is no more box
for (int x = lineOffset; x < Height; x += h)
{
g.DrawLine(new Pen(Brushes.LightSkyBlue, 2), 0, x, Width, x);
}
//force the panel under us to draw itself.
Parent.Invalidate();
}
}
}
public CustomRichTextBox()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
}
I then set this box inside of a panel to get the padding I want. The panel is forced to redraw itself with the text box.
I am trying to figure out the best way to populate address fields from a select list (vague but read on)..
The layout:
When I select the Address dropdown, I would like to see a nice list of full addresses, ie, with street name, country, postcode, etc. but as Im sure you are aware, combo's are one liners only.
Ideal scenario:
The result:
Has anyone a method for doing this?
Here is the full solution and as you can see, its perfectly what I wanted.
The ComboBoxEx is a class derived from a ComboBox which I have copied last. The reason for it is to set the height of the Items container (DropDownHeight). Without it, the container is calculated on the size of the first item x no. of items, and since the first item is zero height, the container would be zero height. So it needed a new class.
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeComboBox();
}
private ComboxBoxEx cbox1 = new ComboxBoxEx();
private DataTable items = new DataTable();
private void InitializeComboBox()
{
items.Columns.AddRange(new DataColumn[] { new DataColumn("id"), new DataColumn("name"), new DataColumn("address") });
items.Rows.Add(new object[] { 0, "[Please choose an address]", "" });
items.Rows.Add(new object[] { 1, "Country", "Country" });
items.Rows.Add(new object[] { 2, "House name", "House name\nStreet name\nTown name\nPostcode\nCountry" });
items.Rows.Add(new object[] { 3, "House name", "House name\nStreet name\nTown name\nPostcode\nCountry" });
cbox1.Location = new Point(39, 20);
cbox1.Size = new System.Drawing.Size(198, 21);
cbox1.DrawMode = DrawMode.OwnerDrawVariable;
cbox1.DrawItem += new DrawItemEventHandler(comboBox2_DrawItem);
cbox1.MeasureItem += new MeasureItemEventHandler(comboBox2_MeasureItem);
cbox1.SelectedIndexChanged += new EventHandler(comboBox2_SelectedIndexChanged);
//cbox1.DropDownWidth = 250;
//cbox1.DropDownHeight = 300;
//cbox1.MaxDropDownItems = 6;
this.Controls.Add(cbox1);
cbox1.ValueMember = "id";
cbox1.DisplayMember = "name";
cbox1.DataSource = new BindingSource(items, null);
//cbox1.SelectedIndex = -1;
}
private void comboBox2_MeasureItem(object sender, MeasureItemEventArgs e)
{
ComboxBoxEx cbox = (ComboxBoxEx)sender;
DataRowView item = (DataRowView)cbox.Items[e.Index];
string txt = item["address"].ToString();
int height = Convert.ToInt32(e.Graphics.MeasureString(txt, cbox.Font).Height);
e.ItemHeight = height + 4;
e.ItemWidth = cbox.DropDownWidth;
cbox.ItemHeights.Add(e.ItemHeight);
}
private void comboBox2_DrawItem(object sender, DrawItemEventArgs e)
{
ComboxBoxEx cbox = (ComboxBoxEx)sender;
DataRowView item = (DataRowView)cbox.Items[e.Index];
string txt = item["address"].ToString();
e.DrawBackground();
e.Graphics.DrawString(txt, cbox.Font, System.Drawing.Brushes.Black, new RectangleF(e.Bounds.X + 2, e.Bounds.Y + 2, e.Bounds.Width, e.Bounds.Height));
e.Graphics.DrawLine(new Pen(Color.LightGray), e.Bounds.X, e.Bounds.Top + e.Bounds.Height - 1, e.Bounds.Width, e.Bounds.Top + e.Bounds.Height - 1);
e.DrawFocusRectangle();
}
private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
{
ComboxBoxEx cbox = (ComboxBoxEx)sender;
if (cbox.SelectedItem == null) return;
DataRowView item = (DataRowView)cbox.SelectedItem;
//label1.Text = item["id"].ToString();
}
}
}
The ComboBoxEx Class
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace WindowsFormsApplication1
{
public partial class ComboxBoxEx : ComboBox
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
public const int SWP_NOZORDER = 0x0004;
public const int SWP_NOACTIVATE = 0x0010;
public const int SWP_FRAMECHANGED = 0x0020;
public const int SWP_NOOWNERZORDER = 0x0200;
public const int WM_CTLCOLORLISTBOX = 0x0134;
private int _hwndDropDown = 0;
internal List<int> ItemHeights = new List<int>();
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
if (_hwndDropDown == 0)
{
_hwndDropDown = m.LParam.ToInt32();
RECT r;
GetWindowRect((IntPtr)_hwndDropDown, out r);
int newHeight = 0;
int n = (Items.Count > MaxDropDownItems) ? MaxDropDownItems : Items.Count;
for (int i = 0; i < n; i++)
{
newHeight += ItemHeights[i];
}
newHeight += 5; //to stop scrollbars showing
SetWindowPos((IntPtr)_hwndDropDown, IntPtr.Zero,
r.Left,
r.Top,
DropDownWidth,
newHeight,
SWP_FRAMECHANGED |
SWP_NOACTIVATE |
SWP_NOZORDER |
SWP_NOOWNERZORDER);
}
}
base.WndProc(ref m);
}
protected override void OnDropDownClosed(EventArgs e)
{
_hwndDropDown = 0;
base.OnDropDownClosed(e);
}
}
}