I'm currently working on some software and the customer is dead-set on a specific UI change that has been giving me some trouble:
I would like items in a ListView to not have any indentation. To illustrate I changed the backcolor of the ListView and the items.
Now, the little piece to the left of each item should also be white. Any ideas?
You can set the OwnerDraw property of ListView to true and using DrawItem, DrawColumnHeader and DrawSubItem draw the list view yourself.
For example:
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
e.Graphics.DrawString(e.Item.Text, e.Item.Font, Brushes.Black,
new Rectangle(e.Bounds.Left-2, e.Bounds.Top, e.Bounds.Width, e.Bounds.Height));
}
private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
if(e.ColumnIndex>0)
e.DrawDefault = true;
}
Note:
The above rendering is just an example and shows you how you can draw item text in desired location for example using using Graphics.DrawString.
Also it shows you how to set e.DrawDefault = true; to perform default rendering.
The first subitem of each ListViewItem object represents the parent item itself
To avoid issues with graphics flickering when owner drawing, override the ListView control and set the DoubleBuffered property to true.
To learn more, read documents of OwnerDraw property of ListView control.
Related
I am trying to get the selected ListViewItem index, so that when the user clicks on a particular row, such as row 2, I want the clicked cell's text to be set to a TextBox's text.
I also want to highlight only this cell, ideally using the regular selection ListView uses, or do I need to create a class that inherits from ListView to do this?
Something like this:
You can draw yourself the ListViewItem.ListViewSubItem selected, owner-drawing the Control (set ListView.OwnerDraw = true), then handle the ListView.DrawSubItem event.
The ListView.DrawColumnHeader event can use default values.
▶ I'm using TextRenderer since this is the default renderer. If you use Graphics.DrawText, you'll notice the difference.
TextFormatFlags flags = TextFormatFlags.LeftAndRightPadding |
TextFormatFlags.VerticalCenter;
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
var lv = sender as ListView;
var subItem = lv.HitTest(lv.PointToClient(MousePosition)).SubItem;
if (subItem != null && e.SubItem == subItem) {
using (var brush = new SolidBrush(SystemColors.Highlight)) {
e.Graphics.FillRectangle(brush, e.SubItem.Bounds);
}
TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font,
e.Bounds, SystemColors.HighlightText, flags);
}
else {
e.DrawDefault = true;
}
}
private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
=> e.DrawDefault = true;
// Invalidate on a mouse interaction, otherwise the ListView doesn't redraw the SubItem
private void listView1_MouseUp(object sender, MouseEventArgs e)
=> (sender as ListView).Invalidate();
Or, you can change the Colors of a SubItem when a mouse interaction is notified (here, using the MouseDown event) and save the previous state (just the Colors here). It's better to save the state because each SubItem can have it's own settings, so you cannot just revert back to the Parent ListViewItem or the ListView values.
As mentioned, set UseItemStyleForSubItems = false in each parent ListViewItem, otherwise the Colors settings are ignored.
Also, FullRowSelect must be set to false, otherwise it's pointless :)
Here, the state is saved in a nullable named Tuple Field, (ListViewSubItem, Color[]).
A class object is probably better, this is just shorter.
private (ListViewItem.ListViewSubItem Item, Color[] colors)? previousItem = null;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
var lv = sender as ListView;
var subItem = lv.HitTest(e.Location).SubItem;
if (previousItem.HasValue) {
// If an Item's Colors have been changed, restore the state
// It removes the selection if you click in an empty area
previousItem.Value.Item.BackColor = previousItem.Value.colors[0];
previousItem.Value.Item.ForeColor = previousItem.Value.colors[1];
lv.Invalidate(previousItem.Value.Item.Bounds);
}
if (subItem != null) {
// Save the SubItem's colors state
previousItem = (subItem, new[] { subItem.BackColor, subItem.ForeColor });
// Set new Colors. Here, using the default highlight colors
subItem.BackColor = SystemColors.Highlight;
subItem.ForeColor = SystemColors.HighlightText;
lv.Invalidate(subItem.Bounds);
}
}
This is how this thing works:
▶ About the Item / SubItem index, as it's mentioned in the question:
When you retrieve the ListViewItem / SubItem clicked with ListView.HitTest
var hitTest = lv.HitTest(e.Location);
then the ListViewItem index is of course:
var itemIndex = hitTest.Item.Index;
and the SubItem.Index is:
var subItemIndex = hitTest.Item.SubItems.IndexOf(hitTest.SubItem);
Is there a toolbox object that is like a listbox but each individual item can have a different font colour? I'm trying to make a mod manager of sorts and want to grey out the text for those mods that are uninstalled.
Try this:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
e.Graphics.DrawString(Convert.ToString(listBox1.Items[e.Index]), new Font("Aerial", 8), Brushes.Gray, new Point(0, 0));
e.DrawFocusRectangle();
}
For this to work, change the DrawMode of the Listbox to OwnerDrawFixed.
Also, if you want to select an item in a combobox, try something like this:
comboBox1.SelectedIndex = 0;
Hi guys I am trying to rebrush individual items in a listbox, I went with listbox draw item event, somehow it works but when I add items to my listbox, only string "b" is added and never the + "hi" part, I could write whatever I want or add whatever I want, only string b will be printed.
Not using draw mode and sticking with normal listbox obviously fixes the issue, so I guess it must be related to draw item event, but I cant figue out what exactly does this, I appreciate any help
listBox3.DrawMode = DrawMode.OwnerDrawFixed;
private async void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string b in liIDs)
{
listBox3.Invoke((MethodInvoker)delegate
{
listBox3.Items.Add(b + "hi");
listBox3.Update();
});
}
private void listBox3_DrawItem(object sender, DrawItemEventArgs e)
{
try
{
e.DrawBackground();
Brush myBrush = Brushes.White;
myBrush = Brushes.Red;
e.Graphics.DrawString(((ListBox)sender).Items[e.Index].ToString(),
e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
}
catch
{
}
}
I could only reproduce using items with values width larger than the list box width.
If thats the case you need to set the HorizontalScrollbar propertie to True and HorizontalExtent value
I fill a listbox listBoxHome from a dictionary dictionaryHome :
dictionaryHome.Add(item.Id, item.Name);
listBoxHome.DataSource = new BindingSource(dictionaryHome, null);
listBoxHome.DisplayMember = "Value";
listBoxHome.ValueMember = "Key";
I also use the following code to be able only first 5 items to be selectable
private void listBoxHome_SelectedIndexChanged(object sender, EventArgs e)
{
InvertMySelection(listBoxHome, listBoxAway);
//make only first5 selectable
for (int i = 5; i < listBoxHome.Items.Count; i++)
{
if (listBoxHome.GetSelected(i) == true)
listBoxHome.ClearSelected();
}
}
I want to apply a different color to the first 5 items and different color the other items.
Or maybe a transparent panel that shows difference from the first 5 items and the other items. Also I want to draw a line inside the listbox as shown in the image. Any suggestion?
EDIT:
Adding Luc Morin's code the result shown in the following picture
Is there a way to show only the text and not the id(as was before)?The id is used in the back.
First you need to set the DrawMode to OwnerDrawFixed in the Windows Forms Designer property grid.
Then, add an event handler to the ListBox.DrawItem event, something along those lines:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
// Draw the background of the ListBox control for each item.
e.DrawBackground();
// Define the default color of the brush as black.
Brush myBrush = Brushes.Black;
// Determine the color of the brush to draw each item based
// on the index of the item to draw.
if (e.Index < 3)
{
myBrush = Brushes.Red;
}
// Draw the current item text based on the current Font
// and the custom brush settings.
e.Graphics.DrawString(((KeyValuePair<int, string>)listBox1.Items[e.Index]).Value,
e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
// If the ListBox has focus, draw a focus rectangle around the selected item.
e.DrawFocusRectangle();
}
Adapt to your specific needs.
Code adapted from MSDN sample at: http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.drawitem(v=vs.110).aspx
Cheers
EDIT: In order to prevent selection of items, handle the ListBox.SelectedIhanged, something like this:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if(listBox1.SelectedIndex >=3)
{
listBox1.SelectedIndex = -1;
listBox1.Invalidate();
}
}
EDIT 2: When binding to a dictionary, the ListBox.Items collection actually contains KeyValuePair objects instead of just strings. I updated the code to account for this. My example assumes the Key is an int and the Value is a string.
I have a program which uses a barcode scanner as input device so that means I need to keep the focus on a text box.
The program has a listview control and I select one of the items programatically when a certain barcode is scanned. I set the background color of the row by:
listviewitem.BackColor = Color.LightSteelBlue;
Things I have tried:
listview.HideSelection set to false
call listview.Focus() after setting the color
listviewitem.Focused set to true
call listview.Invalidate
call listview.Update()
call listview.Refresh()
different combinations of the above
I've also did combinations above stuff in a timer so that they are called on a different thread but still no success.
Any ideas?
More info:
The key here is the control focus. The listview control does not have the focus when I select one of the items.
I select one item by doing:
listView1.Items[index].Selected = true;
the Focus is always in the textbox.
the computer does not have keyboard or mouse, only a barcode reader.
I have this code to keep the focus on the textbox:
private void txtBarcode_Leave(object sender, EventArgs e)
{
this.txtBarcode.Focus();
}
You need to have a textbox add that code to simulate my problem.
What you describe works exactly as expected, assuming that you've set the HideSelection property of the ListView control to False. Here's a screenshot for demonstration purposes. I created a blank project, added a ListView control and a TextBox control to a form, added some sample items to the ListView, set its view to "Details" (although this works in any view), and set HideSelection to false. I handled the TextBox.Leave event just as you showed in the question, and added some simple logic to select the corresponding ListViewItem whenever its name was entered into the TextBox. Notice that "Test Item Six" is selected in the ListView:
Now, as I suspected initially, you're going to mess things up if you go monkeying around with setting the BackColor property yourself. I'm not sure why you would ever want to do this, as the control already uses the default selection colors to indicate selected items by default. If you want to use different colors, you should change your Windows theme, rather than trying to write code to do it.
In fact, if I add the line item.BackColor = Color.LightSteelBlue in addition to my existing code to select the ListViewItem corresponding to the name typed into the TextBox, I get exactly the same thing as shown above. The background color of the item doesn't change until you set focus to the control. That's the expected behavior, as selected items look different when they have the focus than they do when their parent control is unfocused. Selected items on focused controls are painted with the system highlight color; selected items on unfocused controls are painted with the system 3D color. Otherwise, it would be impossible to tell whether or not the ListView control had the focus. Moreover, any custom BackColor property is completely ignored by the operating system when the ListView control has the focus. The background gets painted in the default system highlight color.
Explicitly setting the focus to the ListView control, of course, causes the custom background color to be applied to the ListViewItem, and things render with a color that very much contrasts with the color scheme that I've selected on my computer (remember, not everyone uses the defaults). The problem, though, becomes immediately obvious: you can't set the focus to the ListView control because of the code you've written in the TextBox.Leave event handler method!
I can tell you right now that setting the focus in a focus-changing event is the wrong thing to do. It's a hard rule in Windows you're not allowed to do things like that, and the documentation even warns you explicitly not to do it. Presumably, your answer will be something along the lines of "I have to", but that's no excuse. If everything were working as expected, you wouldn't be asking this question in the first place.
So, what now? Your application's design is broken. I suggest fixing it. Don't try and monkey with setting the BackColor property yourself to indicate that an item is selected. It conflicts with the default way that Windows highlights selected items. Also, don't try and set the focus in a focus-changing event. Windows explicitly forbids this, and the documentation is clear that you're not supposed to do this. If the target computer doesn't have a mouse or keyboard, it's unclear how the user is going to set focus to anything else in the first place, unless you write code to do it, which you shouldn't be doing.
But I have surprisingly little faith that you'll want to fix your application. People who ignore warnings in the documentation tend to be the same people who don't listen to well-meaning advice on Q&A sites. So I'll throw you a bone and tell you how to get the effect you desire anyway. The key lies in not setting the ListViewItem's Selected property, which avoids the conflict between your custom BackColor and the system default highlight color. It also frees you from having to explicitly set the focus to the ListView control and back again (which, as we established above, isn't actually happening, given your Leave event handler method). Doing that produces the following result:
And here's the code—it's not very pretty, but this is just a proof of concept, not a sample of best practice:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
listView1.View = View.Details;
listView1.HideSelection = false;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
foreach (ListViewItem item in listView1.Items)
{
if (item.Text == textBox1.Text)
{
item.BackColor = Color.LightSteelBlue;
return;
}
}
}
private void textBox1_Leave(object sender, EventArgs e)
{
this.textBox1.Focus();
}
}
A standard ListView does not let you set the background color of a selected row. The background (and foreground) colors of a selected row are always controlled by the theme of the OS.
You have to owner draw your ListView to get around this OR you can use ObjectListView. ObjectListView is an open source wrapper around .NET WinForms ListView, which makes it much easier to use, as well as easily allowing things that are very difficult in a normal ListView -- like changed the colors of selected rows.
this.objectListView1.UseCustomSelectionColors = true;
this.objectListView1.HighlightBackgroundColor = Color.Lime;
this.objectListView1.UnfocusedHighlightBackgroundColor = Color.Lime;
This shows the ObjectListView when it does not have focus.
Here's a solution for a ListView that does not allow multiple selections and
does not have images (e.g. checkboxes).
Set event handlers for the ListView (in this example it's named listView1):
DrawItem
Leave (invoked when the ListView's focus is lost)
Declare a global int variable (i.e. a member of the Form that contains the ListView,
in this example it's named gListView1LostFocusItem) and assign it the value -1
int gListView1LostFocusItem = -1;
Implement the event handlers as follows:
private void listView1_Leave(object sender, EventArgs e)
{
// Set the global int variable (gListView1LostFocusItem) to
// the index of the selected item that just lost focus
gListView1LostFocusItem = listView1.FocusedItem.Index;
}
private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
// If this item is the selected item
if (e.Item.Selected)
{
// If the selected item just lost the focus
if (gListView1LostFocusItem == e.Item.Index)
{
// Set the colors to whatever you want (I would suggest
// something less intense than the colors used for the
// selected item when it has focus)
e.Item.ForeColor = Color.Black;
e.Item.BackColor = Color.LightBlue;
// Indicate that this action does not need to be performed
// again (until the next time the selected item loses focus)
gListView1LostFocusItem = -1;
}
else if (listView1.Focused) // If the selected item has focus
{
// Set the colors to the normal colors for a selected item
e.Item.ForeColor = SystemColors.HighlightText;
e.Item.BackColor = SystemColors.Highlight;
}
}
else
{
// Set the normal colors for items that are not selected
e.Item.ForeColor = listView1.ForeColor;
e.Item.BackColor = listView1.BackColor;
}
e.DrawBackground();
e.DrawText();
}
Note: This solution will result in some flicker. A fix for this involves subclassing the ListView control so you
can change the protected property DoubleBuffered to true.
public class ListViewEx : ListView
{
public ListViewEx() : base()
{
this.DoubleBuffered = true;
}
}
On SelectedIndexChanged:
private void lBxDostepneOpcje_SelectedIndexChanged(object sender, EventArgs e)
{
ListViewItem item = lBxDostepneOpcje.FocusedItem as ListViewItem;
ListView.SelectedIndexCollection lista = lBxDostepneOpcje.SelectedIndices;
foreach (Int32 i in lista)
{
lBxDostepneOpcje.Items[i].BackColor = Color.White;
}
if (item != null)
{
item.Selected = false;
if (item.Index == 0)
{
}
else
{
lBxDostepneOpcje.Items[item.Index-1].BackColor = Color.White;
}
if (lBxDostepneOpcje.Items[item.Index].Focused == true)
{
lBxDostepneOpcje.Items[item.Index].BackColor = Color.LightGreen;
if (item.Index < lBxDostepneOpcje.Items.Count-1)
{
lBxDostepneOpcje.Items[item.Index + 1].BackColor = Color.White;
}
}
else if (lBxDostepneOpcje.Items[item.Index].Focused == false)
{
lBxDostepneOpcje.Items[item.Index].BackColor = Color.Blue;
}
}
}
You cant set focus on listview control in this situation. txtBarcode_Leave method will prevent this. But if you are desire to be able select listview items by clicking on them, just add code below to MouseClick event handler of listview:
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
ListView list = sender as ListView;
for (int i = 0; i < list.Items.Count; i++)
{
if (list.Items[i].Bounds.Contains(e.Location) == true)
{
list.Items[i].BackColor = Color.Blue; // highlighted item
}
else
{
list.Items[i].BackColor = SystemColors.Window; // normal item
}
}
}
This change color of selected item. but only in state listview not have focus.
Make sure HideSelection is !TRUE! and simple use this code:
private void ListView_SelectedIndexChanged(object sender, EventArgs e){
foreach(ListViewItem it in ListView.Items)
{
if (it.Selected && it.BackColor != SystemColors.Highlight)
{
it.BackColor = SystemColors.Highlight;
it.ForeColor = SystemColors.HighlightText;
}
if (!it.Selected && it.BackColor != SystemColors.Window)
{
it.BackColor = SystemColors.Window;
it.ForeColor = SystemColors.WindowText;
}
}
}
Just do like this:
Set property UnfocusedHighlighForegroundColor = "Blue"
Set property UnfocusedHighlighBackgroundColor = "White"
Set property UserCustomSelectionColors = true
Good luck :)