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);
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);
I am trying to add different controls to cells in the same column. The drop down does not show and there is no visible setter:
private void AddBooleanDropDown(DataGridView grid, int row, KeyValuePair<string, string> kvp)
{
DataGridViewComboBoxCell dropDownCell = new DataGridViewComboBoxCell();
dropDownCell.DataSource = new string[] { "True", "False" };
grid.Rows[row].Cells["Value"] = dropDownCell;
}
Not sure if this will be helpful to you, but maybe an alternative method?
I wanted to be able to update an excel spreadsheet that I read into a DataGridView and give the user a few options. I used a ContextMenuStrip that would display on a MouseClick event.
It displays a small menu when you right click on a cell:
If it's not what you're looking for at all, sorry; just perhaps an alternate solution:
////////////////////////////////////////////////////////////////////////
//Change Priority Strip
////////////////////////////////////////////////////////////////////////
ContextMenuStrip changePriority = new ContextMenuStrip();
ToolStripMenuItem highPriority = new ToolStripMenuItem("High Priority");
changePriority.Items.Add(highPriority);
highPriority.Click += new EventHandler(changePriorityHighEvent);
ToolStripMenuItem normalPriority = new ToolStripMenuItem("Normal Priority");
changePriority.Items.Add(normalPriority);
normalPriority.Click += new EventHandler(changePriorityNormalEvent);
ToolStripMenuItem lowPriority = new ToolStripMenuItem("Low Priority");
changePriority.Items.Add(lowPriority);
lowPriority.Click += new EventHandler(changePriorityLowEvent);
////////////////////////////////////////////////////////////////////////
private void gridView_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right) //On Right Click
{
DataGridView.HitTestInfo hit = gridView.HitTest(e.X, e.Y); //Get the clicked cell
if (e.RowIndex < 0) //If it's a header, ignore
return;
gridView.CurrentCell = gridView[e.ColumnIndex, e.RowIndex]; //Select the cell for future info
if (gridView.CurrentCell.ColumnIndex == 6) //If this is the priority column
{
changePriority.Show(Cursor.Position.X, Cursor.Position.Y); //Show the strip
}
}
}
private void changePriorityHighEvent(Object sender, EventArgs e) {
//make changes
}
private void changePriorityNormalEvent(Object sender, EventArgs e) {
//make changes
}
private void changePriorityLowEvent(Object sender, EventArgs e) {
//make changes
}
Here is very good MSDN Example.
The DataGridView control provides several column types, enabling your users to enter and edit values in a variety of ways. If these column types do not meet your data-entry needs, however, you can create your own column types with cells that host controls of your choosing. To do this, you must define classes that derive from DataGridViewColumn and DataGridViewCell. You must also define a class that derives from Control and implements the IDataGridViewEditingControl interface.
Not sure if you can change a specific cell in a grid unless it's the same type.
You could try adding a new column of combo boxes all with that data source
var newCol = new DataGridViewComboBoxColumn()
{
DataSource = new string[] { "True", "False" }
};
grid.Columns.Add(newCol);
also you might want to check that the int your passing in isn't greater than the number of rows.
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 subscribed to a SelectionChangedEvent on a ComboBox in a DataGrid with the following code:
public static DataGridTemplateColumn CreateComboboxColumn(string colName, Binding textBinding, SelectionChangedEventHandler selChangedHandler = null)
{
var cboColumn = new DataGridTemplateColumn {Header = colName};
...
if (selChangedHandler != null)
cboFactory.AddHandler(Selector.SelectionChangedEvent, selChangedHandler);
...
return cboColumn;
}
The handler I actually register contains:
private void ComboBoxSelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(#"selectHandler");
var cboBox = sender as ComboBox;
if (cboBox == null)
return;
if (cboBox.IsDropDownOpen) // a selection in combobox was made
{
CommitEdit();
}
else // trigger the combobox to show its list
cboBox.IsDropDownOpen = true;
}
... and is located in my custom DataGrid class.
If I select an item in the ComboBox, e.AddedItems and cboBox.SelectedItem contains the selected value, but nothing is changed on CommitEdit().
What I want is to force a commit to directly update the DataGrid's ItemsSource, when the user selects an item in the drop-down-list. Normally this is raised if the control looses focus...
The link in the solution found in this thread is not available any more and I don't know how to use this code.
I created a tricky, but working, solution for my problem. Here's the modified handler from above:
private void ComboBoxSelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine(#"selectHandler");
var cboBox = sender as ComboBox;
if (cboBox == null)
return;
if (cboBox.IsDropDownOpen) // a selection in combobox was made
{
cboBox.Text = cboBox.SelectedValue as string;
cboBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
}
else // user wants to open the combobox
cboBox.IsDropDownOpen = true;
}
Because my ComboBoxColumn is a custom DataGridTemplateColumn I force it to show its list, when the user first selects the cell.
To change the bound items value I manually overwrite the displayed text with recently selected item and force the UI to select another item (in this case the control to the right) to make an implicit call to CellEditEnding event, which (in my case) commits the whole row:
private bool _isManualEditCommit = false;
private void _CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
// commit a manual edit
// this if-clause prevents double execution of the EditEnding event
if (!_isManualEditCommit)
{
Console.WriteLine(#"_CellEditEnding() manualeditcommit");
_isManualEditCommit = true;
CommitEdit(DataGridEditingUnit.Row, true);
_isManualEditCommit = false;
checkRow(e.Row);
}
}
Maybe I could help somebody with this "dirty" solution ;-)
Can I change the appearance of a Winforms ComboBox so that a Combobox with DropDownStyle = DropDownList looks more like one that is DropDownStyle = DropDown. The functional difference between them is that the former doesn't allow for user entered values, the problem is that it's default color scheme looks grayed out and doesn't match with textboxes on the same dialog.
you can get DropDown appearance from DropDownList style by changing DrawMode property to DrawMode.OwnerDrawFixed and handling item painting by yourself (thankfully, that's easy). Sample class, implementing this idea:
public class ComboBoxEx : ComboBox
{
public ComboBoxEx()
{
base.DropDownStyle = ComboBoxStyle.DropDownList;
base.DrawMode = DrawMode.OwnerDrawFixed;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
e.DrawBackground();
if(e.State == DrawItemState.Focus)
e.DrawFocusRectangle();
var index = e.Index;
if(index < 0 || index >= Items.Count) return;
var item = Items[index];
string text = (item == null)?"(null)":item.ToString();
using(var brush = new SolidBrush(e.ForeColor))
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
e.Graphics.DrawString(text, e.Font, brush, e.Bounds);
}
}
}
You could try to change the FlatStyle property and see if you get something more to your liking. If you really want it to look like it does with DropDownStyle set to DropDown, you could set the DropDownStyle to DropDown and eat the KeyPress event:
private void comboBox1_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
}
Still, I would probably not do this as the appearance of the ComboBox is a visual cue to the user indicating whether they should be able to type in the text area or not.