WinForms ComboBox problem - c#

In a Windows Form Application, I have a ComboBox1 which gets initialized in InitializeComponent() function. I add the values into it in a different function.
snippet:
public form1()
{
InitializeComponent();
addDataToDropDowns();
}
The problem I have is that, the rows loaded into the ComboBox1 have many characters(/length) and are not to be seen completely width wise.
Is it possible to have a horizontal scrollbar built into the ComboBox1 so that I can see the hidden part of the row too...??
Any ideas/inputs will be appreciated!
Thanks,
Ivar

There is actually a DropDownWidth property that controls how wide the drop down area is. This way you can have a narrow control that doesn't take up too much space on the form, but a larger drop down area that could extend over as much of the screen as you want.

http://www.codeproject.com/KB/combobox/ComboBoxAutoWidth.aspx
That has code sample that shows how to capture the event and widen the box.
OR, you could have it as a seperate function you manually call, like this:
http://weblogs.asp.net/eporter/archive/2004/09/27/234773.aspx

Combining the links in Caladain's answer, here is the code. It works for both strings and data bound objects. The method cbSample_DropDown() is linked to the DropDown event of the ComboBox.
private void AdjustWidthComboBox(ComboBox comboBox)
{
int width = comboBox.DropDownWidth;
using (Graphics g = comboBox.CreateGraphics())
{
Font font = comboBox.Font;
int vertScrollBarWidth =
(comboBox.Items.Count > comboBox.MaxDropDownItems)
? SystemInformation.VerticalScrollBarWidth : 0;
foreach (object item in comboBox.Items)
{
string valueToMeasure = comboBox.GetItemText(item);
int newWidth = (int)g.MeasureString(valueToMeasure, font).Width + vertScrollBarWidth;
if (width < newWidth)
{
width = newWidth;
}
}
}
comboBox.DropDownWidth = width;
}
private void cbSample_DropDown(object sender, EventArgs e)
{
AdjustWidthComboBox(sender as ComboBox);
}

Related

How to Programmatically Scroll a Panel

I have a System.Windows.Forms.Panel with some content.
I am trying to programmatically scroll the panel (vertically) either up or down.
I have tried setting the AutoScrollPosition property to a new Point on the panel but that doesn't seem to do it.
I have the AutoScroll property set to true.
I even tried to set the VerticalScroll.Value twice as suggested here, but that doesn't seem to work either.
This is what I am currently doing:
//I have tried passing both positive and negative values.
panel.AutoScrollPosition = new Point(5, 10);
The X and Y values on AutoScrollPosition remain 0 and 0.
Any help or direction on this would be greatly appreciated it.
Thanks in advance,
Marwan
Here is a solution. I guess you can scroll your Panel by arbitrary position using Win32 however there is a simple trick to help you achieve your requirement here:
public void ScrollToBottom(Panel p){
using (Control c = new Control() { Parent = p, Dock = DockStyle.Bottom })
{
p.ScrollControlIntoView(c);
c.Parent = null;
}
}
//use the code
ScrollToBottom(yourPanel);
Or use extension method for convenience:
public static class PanelExtension {
public static void ScrollToBottom(this Panel p){
using (Control c = new Control() { Parent = p, Dock = DockStyle.Bottom })
{
p.ScrollControlIntoView(c);
c.Parent = null;
}
}
}
//Use the code
yourPanel.ScrollToBottom();
UPDATE
If you want to set the exact position, modifying the code above a little can help:
//This can help you control the scrollbar with scrolling up and down.
//The position is a little special.
//Position for scrolling up should be negative.
//Position for scrolling down should be positive
public static class PanelExtension {
public static void ScrollDown(this Panel p, int pos)
{
//pos passed in should be positive
using (Control c = new Control() { Parent = p, Height = 1, Top = p.ClientSize.Height + pos })
{
p.ScrollControlIntoView(c);
}
}
public static void ScrollUp(this Panel p, int pos)
{
//pos passed in should be negative
using (Control c = new Control() { Parent = p, Height = 1, Top = pos})
{
p.ScrollControlIntoView(c);
}
}
}
//use the code, suppose you have 2 buttons, up and down to control the scrollbar instead of clicking directly on the scrollbar arrows.
int i = 0;
private void buttonUp_Click(object sender, EventArgs e)
{
if (i >= 0) i = -1;
yourPanel.ScrollUp(i--);
}
private void buttonDown_Click(object sender, EventArgs e)
{
if (i < 0) i = 0;
yourPanel.ScrollDown(i++);
}
Another solution you may want to use is using Panel.VerticalScroll.Value. However I think you need more research to make it work as you expect. Because I can see once changing the Value, the scrollbar position and control position don't sync well. Notice that Panel.VerticalScroll.Value should be between Panel.VerticalScroll.Minimum and Panel.VerticalScroll.Maximum.
This surprisingly works! NOTE THE MINUS SIGN in the code. There is strange behavior in setting scroll position. If you set the position to exact value (50), it goes negative when you read it next time (-50). So you have to invert it before setting new scroll value.
Scroll down:
private void ButtonScrollDown_OnClick(object sender, EventArgs e)
{
Point current = yourScrollPanel.AutoScrollPosition;
Point scrolled = new Point(current.X, -current.Y + 10);
yourScrollPanel.AutoScrollPosition = scrolled;
}
Scroll up similarly, (-current.Y - 10)
If you have a class that derives from Panel, then call these two protected methods to scroll the panel:
// The bottom is off screen; scroll down. These coordinates must be negative or zero.
SetDisplayRectLocation(0, AutoScrollPosition.Y - item.BoundingRect.Bottom + ClientRectangle.Bottom);
AdjustFormScrollbars(true);
In my example, item.BoundingRect.Bottom is the Y coordinate of the bottom of a thumbnail, and I need to scroll the panel down so that the whole thumbnail is visible.
#King King's solution of creating a temporary Control just so that scrolling could be done seemed "heavy" to me. And #Hans Passant's suggestion of setting AutoScrollMinSize and AutoScrollPosition didn't work for me.
Leave AutoScroll to its default value of 'true'.
Try this:-
panel.ScrollControlIntoView(childcontrol);
This should work. childcontrol is the particular control that you want to show in your display area.
Setting the value of the HorizontalScroll property and then using the method ScrollControlIntoView works for me:
lpanel.HorizontalScroll.Value = 100;
lpanel.ScrollControlIntoView(lpanel);
Use #King King Answered Code and if you want to hide horizontal and vertical scroll bar, just apply the below code in the constructor or initialization.
yourPanel.AutoScroll = false;
yourPanel.HorizontalScroll.Maximum = 0;
yourPanel.HorizontalScroll.Visible = false;
yourPanel.VerticalScroll.Maximum = 0;
yourPanel.VerticalScroll.Visible = false;
yourPanel.AutoScroll = true;
I had an issue where I couldnt get my panel to scroll back to top . I tried many things to try and get the panel to scroll back to the top after populating it with many controls.
Nomatter what I did it always put the VScroll bar to the bottom.
After exhaustive testing I found it was because my controls had the TabStop property set to true (default on user controls) was causing the issue.
Setting TabStop to false fixed it.
Create an control that sits slightly outside the visible area (so -1 at the top and clientsize+1 ) and then call ScrollControlIntoView:
public static class PanelExtension {
public static void ScrollDown(this Panel p)
{
using (Control c = new Control() { Parent = p, Height = 1, Top = p.ClientSize.Height + 1 })
{
p.ScrollControlIntoView(c);
}
}
public static void ScrollUp(this Panel p )
{
using (Control c = new Control() { Parent = p, Height = 1, Top = -1})
{
p.ScrollControlIntoView(c);
}
}
}
//use the code, suppose you have 2 buttons, up and down to control the scrollbar instead of clicking directly on the scrollbar arrows.
private void buttonUp_Click(object sender, EventArgs e)
{
yourPanel.ScrollUp();
}
private void buttonDown_Click(object sender, EventArgs e)
{
yourPanel.ScrollDown();
}
with yourpanel.SetAutoScrollMargin(1, 1); you can set very fine scrolling steps and then take a timer to call the srolling when buttons are down

Creating a dynamic UI in winforms

If I want to create a winform with dynamic UI controls appearing, what's the best way to do that?
I have a form with a textbox, a button1 to the right of it, a listbox underneath, and a button2 underneath the listbox. Pressing button1 should generate another textbox underneath the first textbox and the listbox/button2 should be shifted down. If anyone's used Adobe Bridge before, the batch rename window is an example of what I'm talking about.
I was thinking of simply adding textboxN.Height to this.Size, then textboxN.Height to each of the controls (except the first textbox) Y position so they all get shifted down by textboxN.Height pixels. But I think there's a better way of doing this. Rather, is Winforms suitable for something like this?
You -could- just add the height of the TextBox to the form's size, but tbh it would be better to use a constant that dictates the size of the TextBoxes, and then add that.
For moving the listBox/button2, anchor them to the bottom of the form, and they'll automatically stay at the same distance from the bottom of the form.
As for dynamic generation, use a List (or a Stack, depending on what exactly you're doing with it).
partial class Form1 : Form
{
List<TextBox> textBoxes = new List<TextBox>(); // or stack
const int textBoxWidth = 200; // control variables for TextBox placement
const int textBoxHeight = 50;
const int textBoxMargin = 5;
void button1_Click(object sender, EventArgs e)
{
this.Height += textBoxHeight + textBoxMargin;
TextBox tb = new TextBox();
if (textBoxes.Count == 0)
{
tb.Top = textBoxMargin;
}
else
{
tb.Top = ((textBoxHeight + textBoxMargin) * textBoxes.Count) + textBoxMargin;
}
tb.Left = textBoxMargin;
tb.Height = textBoxHeight;
tb.Width = textBoxWidth;
textBoxes.Add(tb);
this.Controls.Add(tb);
}
}
That should work. The thing with the method here is pretty much all of the placement customisation can be done with the constant values.
Is it best to do it in WinForms? Well, there's certainly no real reason to not do it in WinForms, this functionality is easy enough to implement. I'm a WPF guy myself but this is still legit.
Edited for logic errors

Hide and show a cell of the TableLayoutPanel

My tablelayout panel has one column and three rows. (one docked to Fill panel in each cell.)
Now I would like to be able to hide/show the rows . I want only one row to be visible at any time ( based on a user selection of some radio buttons) and I want to to get resized so it fills all the area of the TableLayoutPanel.
How can I do that? Any thoughts?
If rows in your TableLayoutPanel is autosized then hiding content panel will hide cell where panel placed too.
I would suggest setting the other rows heights to 0 is the easiest way:
Row one:
this.tableLayoutPanel1.RowStyles[1].Height = 0;
Try this
TableLayoutPanel1.ColumnStyles[1].SizeType = SizeType.Absolute;
TableLayoutPanel1.ColumnStyles[1].Width = 0;
So why did you use a TableLayoutPanel?
Just put three Panels on your form, fill in everyone the content of each row and set the Dock property of all three panels to Fill. Set two panels Visible = false and one to true.
If you like to see another panel, just make it visible and hide the other two (based on your radio button settings).
My scenario is similar. I needed a TableLayoutPanel with 4 rows each of which needed to be visible according to a checkbox selection. So instead of only showing one row at a time, I can show 1 - 4.
After designing the layout with 1 column and 4 rows, the controls were added and Dock set to Fill for each one.
Then in a single CheckedChanged event handler for the checkboxes, I coded as shown below. It's kind of a brute force method, but, Hey...it works!
private void checkBox_CheckedChanged(object sender, EventArgs e)
{
this.SuspendLayout();
int seldCount = checkBox1.Checked ? 1 : 0;
seldCount += checkBox2.Checked ? 1 : 0;
seldCount += checkBox3.Checked ? 1 : 0;
seldCount += checkBox4.Checked ? 1 : 0;
float pcnt = 0;
if (seldCount == 1)
pcnt = 1;
if (seldCount == 2)
pcnt = 0.5f;
if (seldCount == 3)
pcnt = 0.33f;
if (seldCount == 4)
pcnt = 0.25f;
int newHeight = (int)(tableLayoutPanel1.Height * pcnt);
if (checkBox1.Checked)
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[0].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[0].Height = 0;
}
if (checkBox2.Checked)
{
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[1].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[1].Height = 0;
}
if (checkBox3.Checked)
{
tableLayoutPanel1.RowStyles[2].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[2].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[2].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[2].Height = 0;
}
if (checkBox4.Checked)
{
tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Percent;
tableLayoutPanel1.RowStyles[3].Height = newHeight;
}
else
{
tableLayoutPanel1.RowStyles[3].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[3].Height = 0;
}
this.ResumeLayout();
}
To hide row try this!!
tableLayoutPanel1.RowStyles[1].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[1].Height = 0;
I had similar task to do and my solution is following:
Add a TableLayoutPanel to your form (or any container).
Set TableLayoutPanel's columns and rows count to 1 and size to 100%.
Set Dock to Fill.
Set GrowStyle to fixedSize.
Set AutoSize to true.
Then programmatically add all of three forms/controls, one of which you have to show depending on radio button choice. Be sure that only one of them is visible. That could be done with initial FirstControl.Show(); and then on each RadioButton event hide the current one and show another. you may "remember" in local variable (say: "currentlyVisibleControl" the reference which is currently visible)
note: if you will .Show() more than one at time. then TableLayoutPanel wil fire the exception that it is full and can't add any more item.
P.S. In My own example I have TableLayoutPanel in MDI window and three forms which substitute each other on button clicks on them so I think copying my source code will complicate the "verbal" example.
P.P.S. From my experience Visual Studio does some weird things in design mode sometimes. I had to remove and re-add the TableLayoutPanel to set properties correctly and get the results both in designer and in runtime. So if either autosize or absolute/percent values are not depicted on designer screen it may be designers problem rather that yours. JUST DELETE IT AND RETRY.
I tried fooling around with the Height and SizeType properties, but it was giving me odd results. For example, the Labels on the target row were being hidden, but the TextBoxes were not.
Here is an extension class that I came up with using #arbiter's suggestion of hiding the children Controls of the row.
// these methods only works on rows that are set to AutoSize
public static class TableLayoutPanelExtensions
{
public static void HideRows(this TableLayoutPanel panel, params int[] rowNumbers)
{
foreach (Control c in panel.Controls)
{
if (rowNumbers.Contains(panel.GetRow(c)))
c.Visible = false;
}
}
public static void ShowRows(this TableLayoutPanel panel, params int[] rowNumbers)
{
foreach (Control c in panel.Controls)
{
if (rowNumbers.Contains(panel.GetRow(c)))
c.Visible = true;
}
}
}

Can I display links in a ListView's detail mode?

I'm displaying a set of search results in a ListView. The first column holds the search term, and the second shows the number of matches.
There are tens of thousands of rows, so the ListView is in virtual mode.
I'd like to change this so that the second column shows the matches as hyperlinks, in the same way as a LinkLabel shows links; when the user clicks on the link, I'd like to receive an event that will let me open up the match elsewhere in our application.
Is this possible, and if so, how?
EDIT: I don't think I've been sufficiently clear - I want multiple hyperlinks in a single column, just as it is possible to have multiple hyperlinks in a single LinkLabel.
You can easily fake it. Ensure that the list view items you add have UseItemStyleForSubItems = false so that you can set the sub-item's ForeColor to blue. Implement the MouseMove event so you can underline the "link" and change the cursor. For example:
ListViewItem.ListViewSubItem mSelected;
private void listView1_MouseMove(object sender, MouseEventArgs e) {
var info = listView1.HitTest(e.Location);
if (info.SubItem == mSelected) return;
if (mSelected != null) mSelected.Font = listView1.Font;
mSelected = null;
listView1.Cursor = Cursors.Default;
if (info.SubItem != null && info.Item.SubItems[1] == info.SubItem) {
info.SubItem.Font = new Font(info.SubItem.Font, FontStyle.Underline);
listView1.Cursor = Cursors.Hand;
mSelected = info.SubItem;
}
}
Note that this snippet checks if the 2nd column is hovered, tweak as needed.
Use ObjectListView -- an open source wrapper around a standard ListView. It supports links directly:
This recipe documents the (very simple) process and how you can customise it.
The other answers here are great, but if you don't want to have to hack some code together, look at the DataGridView control which has support for LinkLabel equivalent columns.
Using this control, you get all the functionality of the details view in a ListView, but with more customisation per row.
You can by inheriting the ListView control override the method OnDrawSubItem.
Here is a VERY simple example of how you might do:
public class MyListView : ListView
{
private Brush m_brush;
private Pen m_pen;
public MyListView()
{
this.OwnerDraw = true;
m_brush = new SolidBrush(Color.Blue);
m_pen = new Pen(m_brush)
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if (e.ColumnIndex != 1) {
e.DrawDefault = true;
return;
}
// Draw the item's background.
e.DrawBackground();
var textSize = e.Graphics.MeasureString(e.SubItem.Text, e.SubItem.Font);
var textY = e.Bounds.Y + ((e.Bounds.Height - textSize.Height) / 2);
int textX = e.SubItem.Bounds.Location.X;
var lineY = textY + textSize.Height;
// Do the drawing of the underlined text.
e.Graphics.DrawString(e.SubItem.Text, e.SubItem.Font, m_brush, textX, textY);
e.Graphics.DrawLine(m_pen, textX, lineY, textX + textSize.Width, lineY);
}
}
You can set HotTracking to true so that when the user hovers mouse over the item it appears as link.

Adding an Array of labels to a Panel

I'm trying to add an array of labels to a panel in my Form.
I chose a label because I could set colors for the text.
If there is a better way, please let me know.
The code below runs fine but will only display one label.
I set a breakpoint and looked at the array before adding and all the
elements are there.
However, only one label actually shows up on the Panel.
Here's the code.
int y = 0;
int index = 0;
Label[] labels = new Label[10];
//Add Spareboard Employees to Spare List
foreach (Employee employee in EmployeeList)
{
labels[index] = new Label();
labels[index].Text = employee.Name;
labels[index].ForeColor = Color.Red;
labels[index].Location = new Point(0, y);
y = y + 10;
++index;
}
// Add the Label control to the form.
SparePanel.Controls.AddRange(labels);
Thanks in advance
The default size of the label is too big and each label's bottom is covering up the top of the label below it. You should add something like this:
labels[index].Size = new Size(50, 12);
maybe
Label[] labels = new Label[10];
needs to be
Control[] labels = new Control[10];
As far as I know, you need to implement the IEnumerable Interface and IEnumerate.Compare() method too, in order to iterate a foreach loop over your Employee object.
public class Employee : IEnumerator
{
//Implement IEnumerate method here
}
I'm not that experienced though so don't take my word for it! I would put more detailed code but I don't have it to hand.
Another possibility (which you were also looking for) is to draw the strings directly on the UI without adding controls. Do it during the paint event of the panel.
private void SparePanel_Paint(object sender, PaintEventArgs e)
{
using (SolidBrush empBrush = new SolidBrush(Color.Red))
{
int y = 0;
foreach (Employee employee in EmployeeList)
{
e.Graphics.DrawString(employee.Name, ((Panel)sender).Font, empBrush, 0, y);
y += 10;
}
}
}

Categories