OwnerDrawVariable Combobox DropDown Blank Space - c#

I have developed the following custom combo box to increase the height of the items. Once that is done, a blank space appears at the end of the drop down menu when there is a scrollbar.
How can I correct the issue?
class MyComboBoxXX : ComboBox
{
public MyComboBoxXX():base()
{
this.DrawMode = DrawMode.OwnerDrawVariable;
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.MaxDropDownItems = 5;
this.IntegralHeight = false;
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
e.ItemHeight = 40;
this.DropDownHeight = 40 * 5;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
e.DrawBackground();
var index = e.Index;
if (index < 0 || index >= Items.Count) return;
using (var brush = new SolidBrush(e.ForeColor))
{
Rectangle rec = new Rectangle(e.Bounds.Left, e.Bounds.Top + ((e.Bounds.Height - ItemHeight) / 2), e.Bounds.Width, ItemHeight);
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, new SolidBrush(this.ForeColor), rec);
}
e.DrawFocusRectangle();
}
}

If you look closely, it appears that the DropDown area has a 1 pixel border at the top, and also at the bottom. You can get rid of the space by adding 2 pixels to the DropDownHeight.
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
e.ItemHeight = 40;
this.DropDownHeight = (40 * 5) + 2; //add 2 pixels to include the border
}
Result:

I think you should reduce the DropDownHeight value to the appropriate number of items if it's less than the max number in your OnMeasureItem method override.

Need to store off the default ItemHeight since changing the DrawMode from Normal causes padding of 2 pixels to be added to the item height. Retrieving the Handle is required prior to the call to ItemHeight since it creates the handle. Without this, the value of the ItemHeight property is not correct.
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/ComboBox.cs

Related

Problem while scrolling merged Header Cells of a DataGridView

In my WinForm project I have a DataGridView control.
I achieved to merge the Header cells of 2 Columns. It is working fine but there is a problem when scrolling. The image below should explain it.
Can anyone help?
This is the code I use to merging the Column Header cells.
The indexes of the merged Columns are 20 and 21.
private void loadEvents()
{
this.dgv_db_door.Paint += new PaintEventHandler(dgv_db_door_Paint);
this.dgv_db_door.Scroll += new ScrollEventHandler(dgv_db_door_Scroll);
this.dgv_db_door.ColumnWidthChanged += DataGridView1_ColumnWidthChanged;
this.dgv_db_door.Resize += DataGridView1_Resize;
}
private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
string doorCloser = "DOOR CLOSER";
//Index numbers of merged columns ara 20 and 21;
int mergedColumn1 = 20;
int mergedColumn2 = 21;
Rectangle r1 = dgv_db_door.GetCellDisplayRectangle(mergedColumn1, -1, true);
int w2 = dgv_db_door.GetCellDisplayRectangle(mergedColumn2, -1, true).Width;
r1.X += 1;
r1.Y += 1;
r1.Width = r1.Width + w2 - 2;
r1.Height = r1.Height / 2 - 2;
e.Graphics.FillRectangle(new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.BackColor), r1);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(doorCloser, dgv_db_door.ColumnHeadersDefaultCellStyle.Font,
new SolidBrush(dgv_db_door.ColumnHeadersDefaultCellStyle.ForeColor), r1, format);
}
private void dgv_db_door_Scroll(object sender, ScrollEventArgs e)
{
if (e.OldValue > e.NewValue)
{
}
else
{
this.InvalidateHeader();
}
}
private void DataGridView1_Resize(object sender, EventArgs e)
{
this.InvalidateHeader();
}
private void DataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
this.InvalidateHeader();
}
private void InvalidateHeader()
{
Rectangle rtHeader = this.dgv_db_door.DisplayRectangle;
rtHeader.Height = this.dgv_db_door.ColumnHeadersHeight / 2;
this.dgv_db_door.Invalidate(rtHeader);
}
A few modifications to the drawing procedure:
The custom Header drawing Rectangle is sized calculating the Width of all the Columns in the specified Range.
The position of the drawing area is calculated using the left-most Column's position, the RowHeadersWidth and the current DataGridView.HorizontalScrollingOffset, which defines the horizontal scroll position of the client rectangle.
To draw the custom Header, a clipping region is created - using Graphics.SetClip() - to exclude the Row Header from the drawing area.
The custom Header is only rendered when visible on screen.
I'm using TextRenderer.DrawText instead of Graphics.DrawString(), since this is the method used by this control to render its content.
Hence, I'm calculating the height of the text bounds using TextRenderer.MeasureText()
TextFormatFlags.PreserveGraphicsClipping is used to instruct TextRenderer not to draw the text outside the clipping region of the Graphics object.
Note 1: I've added Double-Buffering to the DataGridView, to avoid any flickering when clicking the Header Cells. It may have an impact when the grid needs to render a high number of Rows.
Note 2: You can remove all those InvalidateHeader() calls, not needed here.
► This new behavior allows to reset the range of Columns to include in the custom Header and the Header's Text at any time (as shown in the visual example).
This is how it looks now:
using System.Reflection;
TextFormatFlags centerTopflags =
TextFormatFlags.HorizontalCenter | TextFormatFlags.Top | TextFormatFlags.PreserveGraphicsClipping;
string mergedHeaderText = string.Empty;
int[] mergedColumns = null;
public void SomeForm()
{
this.InitializeComponent();
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
dgvTest.GetType().GetProperty("DoubleBuffered", flags).SetValue(dgvTest, true);
mergedColumns = new int[] { 20, 21 };
mergedHeaderText = "DOOR CLOSER"
}
private void dgv_db_door_Paint(object sender, PaintEventArgs e)
{
var dgv = sender as DataGridView;
var headerStyle = dgv.ColumnHeadersDefaultCellStyle;
int colsWidth = -1;
int colsLeft = 1;
// Absolute Width of the merged Column range
for (int i = 0; i < mergedColumns.Length; i++) {
var col = dgv.Columns[mergedColumns[i]];
colsWidth += col.Visible ? col.Width : 0;
}
// Absolute Left position of the first Column to merge
if (mergedColumns[0] > 0) {
colsLeft += dgv.Columns.OfType<DataGridViewColumn>()
.Where(c => c.Visible).Take(mergedColumns[0]).Sum(c => c.Width);
}
// Merged Headers raw drawing Rectangle
var r = new Rectangle(
dgv.RowHeadersWidth + colsLeft - dgv.HorizontalScrollingOffset, 2,
colsWidth, dgv.ColumnHeadersHeight);
// Measure the Height of the text to render - no wrapping
r.Height = TextRenderer.MeasureText(e.Graphics, mergedHeaderText, headerStyle.Font, r.Size, centerTopflags).Height;
// Draw the merged Headers only if visible on screen
if (r.Right > dgv.RowHeadersWidth || r.X < dgv.DisplayRectangle.Right) {
// Clip the drawing Region to exclude the Row Header
var clipRect = new Rectangle(
dgv.RowHeadersWidth + 1, 0,
dgv.DisplayRectangle.Width - dgv.RowHeadersWidth, dgv.ColumnHeadersHeight);
e.Graphics.SetClip(clipRect);
using (var brush = new SolidBrush(headerStyle.BackColor)) e.Graphics.FillRectangle(brush, r);
TextRenderer.DrawText(e.Graphics, mergedHeaderText, headerStyle.Font, r, headerStyle.ForeColor, centerTopflags);
e.Graphics.ResetClip();
}
}

Custom ComboBox Column for DataGridView

We have a Custom ComboBox working on a form that displays some shapes instead of text. To do that all I had to do was to override the OnDrawItem function and it displays what we want. Here is a snippet for reference:
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
e.DrawBackground();
if (e.Index >= 0)
{
Brush brush = new SolidBrush(Color.LightGray);
int size = this.Height/2;
int origenX = e.Bounds.X + 1;
int origenY = e.Bounds.Y + 3;
System.Drawing.Drawing2D.GraphicsPath path =
new System.Drawing.Drawing2D.GraphicsPath();
switch (e.Index)
{
case 0:
e.Graphics.FillRectangle(brush, origenX, origenY, size, size);
Rectangle r = new Rectangle(origenX, origenY, size, size);
ControlPaint.DrawBorder(e.Graphics, r, Color.Black,
ButtonBorderStyle.Solid);
break;
case 1:
path.AddEllipse(origenX, origenY, size, size);
e.Graphics.FillPath(brush, path);
e.Graphics.DrawPath(Pens.Black, path);
break;
}
}
}
So, if you add that to a form and add a couple of items to your collection all you see is a square and a circle in the drop down.
Ok, so, what I want to do now is add this same combo box to a DataGridView. I know that this control has a DataGridViewComboBoxColumn. I was trying to extend the control, however, I don't see this OnDrawItem function to override. I guess there exists something similar?
Any help would be appreciated.
Thanks!
You need to interperet the DataGridViewComboBox as your custom Combobox.
private void dgTest_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dgTest.CurrentCell.ColumnIndex == 0) // Which column ever is your DataGridComboBoxColumn
{
// This line will enable you to use the DataDridViewCOmboBox like your
// Custom ComboBox.
CustomComboBox combo = e.Control as CUstomComboBox;
}
}

Resize textbox and form size according to Text Length

How can I automatically increase/decrease TextBox and Windows Form size according to text Length?
You can try overriding the OnTextChanged event, then changing the Width depending on the size of the text.
protected override OnTextChanged(EventArgs e)
{
using (Graphics g = CreateGraphics())
{
SizeF size = g.MeasureString(Text, Font);
Width = (int)Math.Ceiling(size.Width);
}
base.OnTextChanged(e);
}
Try this, it will also work...
Here I have taken 100 as minimum width of textbox. "txt" is TextBox.
const int width = 100;
private void textBox1_TextChanged(object sender, EventArgs e)
{
Font font = new Font(txt.Font.Name, txt.Font.Size);
Size s = TextRenderer.MeasureText(txt.Text, font);
if (s.Width > width)
{
txt.Width = s.Width;
}
}
Hope it helps.
Here is better solution.
Scenario is: I have a textbox that Filled on form (usercontrol). So, I want to change Form Height each time number of line in textBox change, but its height is not less than MinHeight (a constant)
private void ExtendFormHeight()
{
int heightChanged = txtText.PreferredSize.Height - txtText.ClientSize.Height;
if (Height + heightChanged > MinHeight)
{
Height += heightChanged;
}
else
{
Height = MinHeight;
}
}
Hope this help!
set width to Auto in properties

c# listbox get displayed range of indices

i have a listbox and i would want to display a label displaying:
scrolling through items XXX to XYY of ZZZ.
how do I do this because using SelectedIndex will not be useful, as i would like the label to update even when nothing is selected. (scrolling too, it does not select an item).
update:
for example I have 200 items in my listbox. at any one time i can only display only 10 items because of my listbox's height. so the label should read:
displaying items 1 to 10 of 200
or
displaying items 5 to 15 of 200
however i must take into account that there may not be any indices selected because i can simply scroll and not select anything.
You can get the top index value using listbox.TopIndex and the count using listbox.Items.Count but I can't see any way to get the bottom item withotu calculating it from the result of listbox.GetItemHeight() and listbox.ClientSize.Height:
int visibleCount = listBox1.ClientSize.Height / listBox1.ItemHeight;
this.Text = string.Format("{0:d} to {1:d} of {2:d}", listBox1.TopIndex + 1, listBox1.TopIndex + visibleCount, listBox1.Items.Count);
This can be done on a timer as I see no scroll event.
Just use the Scroll event of the ListBox. Oh wait, there isn't one. You can add one:
public class ListBoxEx : ListBox {
public event EventHandler Scrolling;
private const int WM_VSCROLL = 0x0115;
private void OnScrolling() {
if (Scrolling != null)
Scrolling(this, new EventArgs());
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == WM_VSCROLL)
OnScrolling();
}
}
Once you use this, it's just math (refactor as needed):
private void listBoxEx1_Resize(object sender, EventArgs e) {
DisplayRange();
}
private void listBoxEx1_Scrolling(object sender, EventArgs e) {
DisplayRange();
}
private void DisplayRange() {
int numFrom = listBoxEx1.TopIndex + 1;
int numTo = numFrom + (listBoxEx1.ClientSize.Height / listBoxEx1.ItemHeight) - 1;
this.Text = numFrom.ToString() + " to " + numTo.ToString();
}
If IntegralHeight=False then you might have to play with the range number to determine whether or not to include partial rows or not.
If using DrawMode=OwnerDrawVariable, then you need to loop through the visible rows with the MeasureItem event.
This is a Draw, you can do this
private int min = 1000;
private int max = 0;
private void comboBox3_DrawItem(object sender, DrawItemEventArgs e)
{
if (min >= e.Index) min = e.Index+1;
if (max <= e.Index) max = e.Index+1;
float size = 10;
System.Drawing.Font myFont;
FontFamily family = FontFamily.GenericSansSerif;
System.Drawing.Color animalColor = System.Drawing.Color.Black;
e.DrawBackground();
Rectangle rectangle = new Rectangle(2, e.Bounds.Top + 2,
e.Bounds.Height, e.Bounds.Height - 4);
e.Graphics.FillRectangle(new SolidBrush(animalColor), rectangle);
myFont = new Font(family, size, FontStyle.Bold);
e.Graphics.DrawString(comboBox3.Items[e.Index].ToString(), myFont, System.Drawing.Brushes.Black, new RectangleF(e.Bounds.X + rectangle.Width, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
e.DrawFocusRectangle();
label1.Text = String.Format("Values between {0} and {1}",min,max);
}
you have to find the right event to reset min and max values and the right values to redraw the combobox.
This code is not a solution, it's only an idea of how you can implement your requirement.
best regards.

Problem with the scrolling!

I have implemented a picturebox in my in my form.I even implemented the scrollbars to make the image fit in it.so now the problem is when i try to scroll the button down,it scrolls down and immediately when i leave the mouse the button scrolls up ..here is the code implemnted please give some suggestions..
public void DisplayScrollBars()
{
// If the image is wider than the PictureBox, show the HScrollBar.
if (pictureBox1.Width > pictureBox1.Image.Width - this.vScrollBar1.Width)
{
hScrollBar1.Visible = false;
}
else
{
hScrollBar1.Visible = true;
}
// If the image is taller than the PictureBox, show the VScrollBar.
if (pictureBox1.Height >
pictureBox1.Image.Height - this.hScrollBar1.Height)
{
vScrollBar1.Visible = false;
}
else
{
vScrollBar1.Visible = true;
}
}
private void HandleScroll(Object sender, ScrollEventArgs se)
{
/* Create a graphics object and draw a portion
of the image in the PictureBox. */
Graphics g = pictureBox1.CreateGraphics();
g.DrawImage(pictureBox1.Image,
new Rectangle(0, 0, pictureBox1.Right - vScrollBar1.Width,
pictureBox1.Bottom - hScrollBar1.Height),
new Rectangle(hScrollBar1.Value, vScrollBar1.Value,
pictureBox1.Right - vScrollBar1.Width,
pictureBox1.Bottom - hScrollBar1.Height),
GraphicsUnit.Pixel);
pictureBox1.Update();
}
public void SetScrollBarValues()
{
// Set the Maximum, Minimum, LargeChange and SmallChange properties.
this.vScrollBar1.Minimum = 0;
this.hScrollBar1.Minimum = 0;
// If the offset does not make the Maximum less than zero, set its value.
if ((this.pictureBox1.Image.Size.Width - pictureBox1.ClientSize.Width) > 0)
{
this.hScrollBar1.Maximum =
this.pictureBox1.Image.Size.Width - pictureBox1.ClientSize.Width;
}
// If the VScrollBar is visible, adjust the Maximum of the
// HSCrollBar to account for the width of the VScrollBar.
if (this.vScrollBar1.Visible)
{
this.hScrollBar1.Maximum += this.vScrollBar1.Width;
}
this.hScrollBar1.LargeChange = this.hScrollBar1.Maximum / 10;
this.hScrollBar1.SmallChange = this.hScrollBar1.Maximum / 20;
// Adjust the Maximum value to make the raw Maximum value
// attainable by user interaction.
this.hScrollBar1.Maximum += this.hScrollBar1.LargeChange;
// If the offset does not make the Maximum less than zero, set its value.
if ((this.pictureBox1.Image.Size.Height - pictureBox1.ClientSize.Height) > 0)
{
this.vScrollBar1.Maximum =
this.pictureBox1.Image.Size.Height - pictureBox1.ClientSize.Height;
}
// If the HScrollBar is visible, adjust the Maximum of the
// VSCrollBar to account for the width of the HScrollBar.
if (this.hScrollBar1.Visible)
{
this.vScrollBar1.Maximum += this.hScrollBar1.Height;
}
this.vScrollBar1.LargeChange = this.vScrollBar1.Maximum / 10;
this.vScrollBar1.SmallChange = this.vScrollBar1.Maximum / 20;
// Adjust the Maximum value to make the raw Maximum value
// attainable by user interaction.
this.vScrollBar1.Maximum += this.vScrollBar1.LargeChange;
}
private void pictureBox1_Resize(object sender, EventArgs e)
{
// If the PictureBox has an image, see if it needs
// scrollbars and refresh the image.
if (pictureBox1.Image != null)
{
this.DisplayScrollBars();
this.SetScrollBarValues();
this.Refresh();
}
}
As I comment above, the correct way of doing this is to put your pictureBox in a panel and set the panel.AutoScroll=true. Also you need to set pictureBox.SizeMode=AutoSize so it is sized equal to the size of the image that it contains. Check: PictureBoxSizeMode Enumeration

Categories

Resources