I'm trying to do a drag and drop of multiple items between ListBox in windows forms. The problem I'm having is if I select multiple items holding the Shift key and try to drag and drop it without release the the key I get an error. Actually the SelectedIndices and SelectedItems just show 1 item, the one I clicked first, even though multiple items are highlighted in the ListBox.
I'm using SelectionMode = MultiExtended
void ZListBox_MouseMove(object sender, MouseEventArgs e)
{
if (isDraggingPoint.HasValue && e.Button == MouseButtons.Left && SelectedIndex >= 0)
{
var pointToClient = PointToClient(MousePosition);
if (isDraggingPoint.Value.Y != pointToClient.Y)
{
lastIndexItemOver = -1;
isDraggingPoint = null;
var dropResult = DoDragDrop(SelectedItems, DragDropEffects.Copy);
}
}
}
It seems that if I don't release the left mouse button before I do "DoDragDrop", the items aren't selected and also if I try to get the SelectedIndices from the other ListBox, the Count is the number of "selected items", but when I try to navigate the list, I get a IndexOutOfRangeException.
Is there any work around for it?
Sample code to reproduce the issue:
(To reproduce:
1- Select an item
2- Hold shift and click in another item, than without release shift and mouse button, drag this item (if you have a breakpoint inside the 'if', you'll see just 1 item on SelectedItems))
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Load += Form1_Load;
}
private void Form1_Load(object sender, EventArgs e)
{
var someList = new List<ListItemsTest>();
someList.Add(new ListItemsTest() { ID = 1, Name = "Name 1" });
someList.Add(new ListItemsTest() { ID = 2, Name = "Name 2" });
someList.Add(new ListItemsTest() { ID = 3, Name = "Name 3" });
someList.Add(new ListItemsTest() { ID = 4, Name = "Name 4" });
someList.Add(new ListItemsTest() { ID = 5, Name = "Name 5" });
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "ID";
listBox1.DataSource = someList;
listBox1.SelectionMode = SelectionMode.MultiExtended;
listBox1.MouseMove += ListBox1_MouseMove;
listBox1.AllowDrop = true;
}
void ListBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && listBox1.SelectedIndex >= 0)
{
var dropResult = DoDragDrop(listBox1.SelectedItems, DragDropEffects.Copy);
}
}
public class ListItemsTest
{
public int ID { get; set; }
public string Name { get; set; }
}
}
Another example, in case you need to know what Items were selected in a ListBox, using the SHIFT key to create an extended selection, even if you don't need to initiate a Draq&Drop operation:
Using the data sample you provided in the question, a List.
In the example, a List<int> (lbSelectedIndexes) is used to keep track of what items are currently selected in the ListBox. This List is filled only when the selection is performed using the SHIFT key, or after a Drag&Drop operation is initiated. This can be useful to determine the type of selection.
In all other cases the List<int> is empty and the SelectedItems and SelectedIndices collections can be used to determine the items currently selected.
The SystemInformation.DragSize value is also used to determine if the drag operation should be initiated when the Mouse Pointer is moved while the Left Button is pressed.
When the Drag&Drop operation is started, a new DataObject is filled with ListBox Items corresponding to the current selection, no matter how the selection was performed.
The DragDropEffects is set to DragDropEffects.Copy.
Point lbMouseDownPosition = Point.Empty;
List<int> lbSelectedIndexes = new List<int>();
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
var lb = sender as ListBox;
lbMouseDownPosition = e.Location;
lbSelectedIndexes = new List<int>();
int idx = lb.IndexFromPoint(e.Location);
if (ModifierKeys == Keys.Shift && idx != lb.SelectedIndex) {
lbSelectedIndexes.AddRange(Enumerable.Range(
Math.Min(idx, lb.SelectedIndex),
Math.Abs((idx - lb.SelectedIndex)) + 1).ToArray());
}
}
private void listBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left &&
((Math.Abs(e.X - lbMouseDownPosition.X) > SystemInformation.DragSize.Width) ||
(Math.Abs(e.Y - lbMouseDownPosition.Y) > SystemInformation.DragSize.Height)))
{
var lb = sender as ListBox;
DataObject obj = new DataObject();
if (lbSelectedIndexes.Count == 0) {
lbSelectedIndexes = lb.SelectedIndices.OfType<int>().ToList();
}
List<object> selection = lb.Items.OfType<object>().Where((item, idx) =>
lbSelectedIndexes.IndexOf(idx) >= 0).ToList();
obj.SetData(typeof(IList<ListItemsTest>), selection);
lb.DoDragDrop(obj, DragDropEffects.Copy);
}
}
To test the results, drop another ListBox (listBox2, here) on the Form, set its AlloDrop property to true and subscribe to the DragEnter and DragDrop events.
When the Mouse Pointer enters the second ListBox client area, the DragDropEffects.Copy effect is triggered if the e.Data.GetDataPresent() method detects that the dragged object contains a List<ListItemsTest>.
If the Data format is accepted, the Data Object is transformed back to a List<ListItemsTest> - using the IDataObject.GetData() method - and set as the DataSource of listBox2.
private void listBox2_DragDrop(object sender, DragEventArgs e)
{
ListBox lb = sender as ListBox;
if (e.Data != null && e.Data.GetDataPresent(typeof(IList<ListItemsTest>))) {
lb.DisplayMember = "Name";
lb.ValueMember = "ID";
lb.DataSource = e.Data.GetData(typeof(IList<ListItemsTest>));
}
}
private void listBox2_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(IList<ListItemsTest>))) {
e.Effect = DragDropEffects.Copy;
}
}
I see the issue now. The funny thing is, pressing the Ctrl key to select items in the list works normal, but the Shift key doesn't. My solution would be to recreate your own SelectedItems collection:
void listBox1_MouseMove(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left && listBox1.SelectedItems.Count > 0) {
int mouseIndex = listBox1.IndexFromPoint(e.Location);
if (mouseIndex > -1) {
ListBox.SelectedObjectCollection x = new ListBox.SelectedObjectCollection(listBox1);
if (Control.ModifierKeys == Keys.Shift) {
int i1 = Math.Min(listBox1.SelectedIndex, mouseIndex);
int i2 = Math.Max(listBox1.SelectedIndex, mouseIndex);
for (int i = i1; i <= i2; ++i) {
x.Add(listBox1.Items[i]);
}
} else {
x = listBox1.SelectedItems;
}
var dropResult = DoDragDrop(x, DragDropEffects.Move);
}
}
}
Just to let you know I found another solution. If I set Capture = false in the MouseDown event, Items will work as expected and we don't need to do the manual selection.
Eg:
void ZListBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Capture = false;
}
}
Hope it helps!
I have a ListBox that I wanted to be drag and drop sortable, and also have extended multiselect. I was getting the same issue as the OP, where the selected items was bombing out when using shift+click to select the items. If I add in a check to make sure no modifier keys are pressed on mouse down before initiating the DoDragDrop, it solves the problem.
(this is a VB.net code snippet, but you get the idea)
Private Sub lbClasses_MouseDown(sender As Object, e As MouseEventArgs) Handles lbClasses.MouseDown
If e.Button = MouseButtons.Left AndAlso Control.ModifierKeys = 0 Then
If Not IsNothing(lbClasses.SelectedItem) Then
lbClasses.DoDragDrop(lbClasses.SelectedItem, DragDropEffects.Move)
End If
End If
End Sub
Related
I have a datagridview in a .NET winform app. I would like to rightclick on a row and have a menu pop up. Then i would like to select things such as copy, validate, etc
How do i make A) a menu pop up B) find which row was right clicked. I know i could use selectedIndex but i should be able to right click without changing what is selected? right now i could use selected index but if there is a way to get the data without changing what is selected then that would be useful.
You can use the CellMouseEnter and CellMouseLeave to track the row number that the mouse is currently hovering over.
Then use a ContextMenu object to display you popup menu, customised for the current row.
Here's a quick and dirty example of what I mean...
private void dataGridView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
ContextMenu m = new ContextMenu();
m.MenuItems.Add(new MenuItem("Cut"));
m.MenuItems.Add(new MenuItem("Copy"));
m.MenuItems.Add(new MenuItem("Paste"));
int currentMouseOverRow = dataGridView1.HitTest(e.X,e.Y).RowIndex;
if (currentMouseOverRow >= 0)
{
m.MenuItems.Add(new MenuItem(string.Format("Do something to row {0}", currentMouseOverRow.ToString())));
}
m.Show(dataGridView1, new Point(e.X, e.Y));
}
}
While this question is old, the answers aren't proper. Context menus have their own events on DataGridView. There is an event for row context menu and cell context menu.
The reason for which these answers aren't proper is they do not account for different operation schemes. Accessibility options, remote connections, or Metro/Mono/Web/WPF porting might not work and keyboard shortcuts will down right fail (Shift+F10 or Context Menu key).
Cell selection on right mouse click has to be handled manually. Showing the context menu does not need to be handled as this is handled by the UI.
This completely mimics the approach used by Microsoft Excel. If a cell is part of a selected range, the cell selection doesn't change and neither does CurrentCell. If it isn't, the old range is cleared and the cell is selected and becomes CurrentCell.
If you are unclear on this, CurrentCell is where the keyboard has focus when you press the arrow keys. Selected is whether it is part of SelectedCells. The context menu will show on right click as handled by the UI.
private void dgvAccount_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex != -1 && e.RowIndex != -1 && e.Button == System.Windows.Forms.MouseButtons.Right)
{
DataGridViewCell c = (sender as DataGridView)[e.ColumnIndex, e.RowIndex];
if (!c.Selected)
{
c.DataGridView.ClearSelection();
c.DataGridView.CurrentCell = c;
c.Selected = true;
}
}
}
Keyboard shortcuts do not show the context menu by default, so we have to add them in.
private void dgvAccount_KeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == Keys.F10 && e.Shift) || e.KeyCode == Keys.Apps)
{
e.SuppressKeyPress = true;
DataGridViewCell currentCell = (sender as DataGridView).CurrentCell;
if (currentCell != null)
{
ContextMenuStrip cms = currentCell.ContextMenuStrip;
if (cms != null)
{
Rectangle r = currentCell.DataGridView.GetCellDisplayRectangle(currentCell.ColumnIndex, currentCell.RowIndex, false);
Point p = new Point(r.X + r.Width, r.Y + r.Height);
cms.Show(currentCell.DataGridView, p);
}
}
}
}
I've reworked this code to work statically, so you can copy and paste them into any event.
The key is to use CellContextMenuStripNeeded since this will give you the context menu.
Here's an example using CellContextMenuStripNeeded where you can specify which context menu to show if you want to have different ones per row.
In this context MultiSelect is True and SelectionMode is FullRowSelect. This is just for the example and not a limitation.
private void dgvAccount_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
{
DataGridView dgv = (DataGridView)sender;
if (e.RowIndex == -1 || e.ColumnIndex == -1)
return;
bool isPayment = true;
bool isCharge = true;
foreach (DataGridViewRow row in dgv.SelectedRows)
{
if ((string)row.Cells["P/C"].Value == "C")
isPayment = false;
else if ((string)row.Cells["P/C"].Value == "P")
isCharge = false;
}
if (isPayment)
e.ContextMenuStrip = cmsAccountPayment;
else if (isCharge)
e.ContextMenuStrip = cmsAccountCharge;
}
private void cmsAccountPayment_Opening(object sender, CancelEventArgs e)
{
int itemCount = dgvAccount.SelectedRows.Count;
string voidPaymentText = "&Void Payment"; // to be localized
if (itemCount > 1)
voidPaymentText = "&Void Payments"; // to be localized
if (tsmiVoidPayment.Text != voidPaymentText) // avoid possible flicker
tsmiVoidPayment.Text = voidPaymentText;
}
private void cmsAccountCharge_Opening(object sender, CancelEventArgs e)
{
int itemCount = dgvAccount.SelectedRows.Count;
string deleteChargeText = "&Delete Charge"; //to be localized
if (itemCount > 1)
deleteChargeText = "&Delete Charge"; //to be localized
if (tsmiDeleteCharge.Text != deleteChargeText) // avoid possible flicker
tsmiDeleteCharge.Text = deleteChargeText;
}
private void tsmiVoidPayment_Click(object sender, EventArgs e)
{
int paymentCount = dgvAccount.SelectedRows.Count;
if (paymentCount == 0)
return;
bool voidPayments = false;
string confirmText = "Are you sure you would like to void this payment?"; // to be localized
if (paymentCount > 1)
confirmText = "Are you sure you would like to void these payments?"; // to be localized
voidPayments = (MessageBox.Show(
confirmText,
"Confirm", // to be localized
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2
) == DialogResult.Yes);
if (voidPayments)
{
// SQLTransaction Start
foreach (DataGridViewRow row in dgvAccount.SelectedRows)
{
//do Work
}
}
}
private void tsmiDeleteCharge_Click(object sender, EventArgs e)
{
int chargeCount = dgvAccount.SelectedRows.Count;
if (chargeCount == 0)
return;
bool deleteCharges = false;
string confirmText = "Are you sure you would like to delete this charge?"; // to be localized
if (chargeCount > 1)
confirmText = "Are you sure you would like to delete these charges?"; // to be localized
deleteCharges = (MessageBox.Show(
confirmText,
"Confirm", // to be localized
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2
) == DialogResult.Yes);
if (deleteCharges)
{
// SQLTransaction Start
foreach (DataGridViewRow row in dgvAccount.SelectedRows)
{
//do Work
}
}
}
Put a context menu on your form, name it, set captions etc. using the built-in editor
Link it to your grid using the grid property ContextMenuStrip
For your grid, create an event to handle CellContextMenuStripNeeded
The Event Args e has useful properties e.ColumnIndex, e.RowIndex.
I believe that e.RowIndex is what you are asking for.
Suggestion: when user causes your event CellContextMenuStripNeeded to fire, use e.RowIndex to get data from your grid, such as the ID. Store the ID as the menu event's tag item.
Now, when user actually clicks your menu item, use the Sender property to fetch the tag. Use the tag, containing your ID, to perform the action you need.
Use the CellMouseDown event on the DataGridView. From the event handler arguments you can determine which cell was clicked. Using the PointToClient() method on the DataGridView you can determine the relative position of the pointer to the DataGridView, so you can pop up the menu in the correct location.
(The DataGridViewCellMouseEvent parameter just gives you the X and Y relative to the cell you clicked, which isn't as easy to use to pop up the context menu.)
This is the code I used to get the mouse position, then adjust for the position of the DataGridView:
var relativeMousePosition = DataGridView1.PointToClient(Cursor.Position);
this.ContextMenuStrip1.Show(DataGridView1, relativeMousePosition);
The entire event handler looks like this:
private void DataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
// Ignore if a column or row header is clicked
if (e.RowIndex != -1 && e.ColumnIndex != -1)
{
if (e.Button == MouseButtons.Right)
{
DataGridViewCell clickedCell = (sender as DataGridView).Rows[e.RowIndex].Cells[e.ColumnIndex];
// Here you can do whatever you want with the cell
this.DataGridView1.CurrentCell = clickedCell; // Select the clicked cell, for instance
// Get mouse position relative to the vehicles grid
var relativeMousePosition = DataGridView1.PointToClient(Cursor.Position);
// Show the context menu
this.ContextMenuStrip1.Show(DataGridView1, relativeMousePosition);
}
}
}
Follow the steps:
Create a context menu like:
User needs to right click on the row to get this menu. We need to handle the _MouseClick event and _CellMouseDown event.
selectedBiodataid is the variable that contains the selected row information.
Here is the code:
private void dgrdResults_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
contextMenuStrip1.Show(Cursor.Position.X, Cursor.Position.Y);
}
}
private void dgrdResults_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
//handle the row selection on right click
if (e.Button == MouseButtons.Right)
{
try
{
dgrdResults.CurrentCell = dgrdResults.Rows[e.RowIndex].Cells[e.ColumnIndex];
// Can leave these here - doesn't hurt
dgrdResults.Rows[e.RowIndex].Selected = true;
dgrdResults.Focus();
selectedBiodataId = Convert.ToInt32(dgrdResults.Rows[e.RowIndex].Cells[1].Value);
}
catch (Exception)
{
}
}
}
and the output would be:
Simply drag a ContextMenu or ContextMenuStrip component into your form and visually design it, then assign it to the ContextMenu or ContextMenuStrip property of your desired control.
For the position for the context menu, y found the problem that I needed a it to be relative to the DataGridView, and the event I needed to use gives the poistion relative to the cell clicked. I haven't found a better solution so I implemented this function in the commons class, so I call it from wherever I need.
It's quite tested and works well. I Hope you find it useful.
/// <summary>
/// When DataGridView_CellMouseClick ocurs, it gives the position relative to the cell clicked, but for context menus you need the position relative to the DataGridView
/// </summary>
/// <param name="dgv">DataGridView that produces the event</param>
/// <param name="e">Event arguments produced</param>
/// <returns>The Location of the click, relative to the DataGridView</returns>
public static Point PositionRelativeToDataGridViewFromDataGridViewCellMouseEventArgs(DataGridView dgv, DataGridViewCellMouseEventArgs e)
{
int x = e.X;
int y = e.Y;
if (dgv.RowHeadersVisible)
x += dgv.RowHeadersWidth;
if (dgv.ColumnHeadersVisible)
y += dgv.ColumnHeadersHeight;
for (int j = 0; j < e.ColumnIndex; j++)
if (dgv.Columns[j].Visible)
x += dgv.Columns[j].Width;
for (int i = 0; i < e.RowIndex; i++)
if (dgv.Rows[i].Visible)
y += dgv.Rows[i].Height;
return new Point(x, y);
}
There is simple answer to this topic and that is to use CellMouseDown
After designing your ContextMenu
// somewhere in your code
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add(new MenuItem("Option1"));
cm.MenuItems.Add(new MenuItem("Option2"));
Assign it to the DataGridView
myDataView.ContextMenu = cm;
Get Data from the CLICKED cell without changing the Selected one
private void myDataView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
string myData = myDataView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
// Now ADD the captured DATA to the ContextMenu
cm.MenuItems.Add(new MenuItem("myData"));
// OR if you are doing it by the designer and YOU ALREADY have an Item
// you can simply change the name of it in the designer page to your
// desired one and then just change the Text of it
MenuItem_BTN.Text = "$Properties of {myData}";
}
}
I want to allow user to drag any item from MenuStrip to a ListBox.
I did it between to ListBoxes, but can not do it with MenuStrip.
Thanks a lot for your help.
I use WinForms, C#
For the destination ListBox I modified its property
this.listBox2.AllowDrop = true;
and created the following two events:
private void listBox2_DragOver(
object sender, System.Windows.Forms.DragEventArgs e)
{
e.Effect=DragDropEffects.All;
}
private void listBox2_DragDrop(
object sender, System.Windows.Forms.DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.StringFormat))
{
string str= (string)e.Data.GetData(
DataFormats.StringFormat);
listBox2.Items.Add(str);
}
}
What I need is what should be done to the source MenuStrip to allow drag items from it the ListBox, in over words how to make MenuStrip draggable.
Thanks to all for their help.
I found the solution:
The missing event is that I should add event to ToolStripMenuItem_MouseDown, I prefer to use right click instead of left click to avoid the conflict between ToolStripMenuItem_Click and the drag event, this the code:
AllowDrop = true;
private void tsmi_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
DoDragDrop(sender, System.Windows.Forms.DragDropEffects.Copy);
}
Add also this code to the ListView:
private void lvAllowDropListView_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
System.Windows.Forms.ToolStripMenuItem button = e.Data.GetData(typeof(System.Windows.Forms.ToolStripMenuItem))
as System.Windows.Forms.ToolStripMenuItem;
if (button != null)
{
try
{
SmallImageList = sysIcons.SmallIconsImageList;
LargeImageList = sysIcons.LargeIconsImageList;
System.Windows.Forms.ToolStripMenuItem item = e.Data.GetData(typeof(System.Windows.Forms.ToolStripMenuItem))
as System.Windows.Forms.ToolStripMenuItem;
if (item != null)
{
AddToolStripMenuItem(item.Text, item.Name);
}
}
catch { }
}
}
private void AddToolStripMenuItem(string name, string tag)
{
System.Windows.Forms.ListViewItem item = new System.Windows.Forms.ListViewItem(name);
int Index = -1;
for (int i = 0; i < Items.Count;i++ )
if(Items[i].Tag.ToString() == tag)
{
Index = i;
break;
}
if (Index == -1)
{
item.Tag = tag;
Items.Add(item);
}
}
Drag Menu Strip Item is the same like ListBox item.
Check your code...
I have a form on which are "listBox1" and "button1". I have two function. The second function adds checkboxes to listbox1 and the first function displays message box. But I don´t know how to write the first function.
Here I want to check which checkbox is checked and write a message:
private void button1_click(object sender, EventArgs e)
{
MessageBox.Show("radiobutton: " + rb[i].Text);
}
Here I create checkboxes: //it´s working
internal void loadSurveys()
{
WebClient client2 = new WebClient();
var json = client2.DownloadString("http://www.test.net/api/surveys/?api_key=123");
JObject data = JObject.Parse(json);
var example = JsonConvert.DeserializeObject<Example>(json);
int y = 5;
int i = 0;
RadioButton[] rb = new RadioButton[example.surveys.Length];
String chkBox_name = "";
String chkBox_text = "";
foreach (var survey in data["surveys"].Children())
{
rb[i] = new RadioButton();
rb[i].Location = new Point(5, y);
rb[i].Name = chkBox_name + survey["id"];
rb[i].Text = chkBox_text + survey["title"];
rb[i].AutoSize = true;
this.listBox1.Controls.Add(rb[i]);
y += 20;
i++;
}
}
You can go through listBox1.Controls and pick checked RadioButton
private void button1_click(object sender, EventArgs e)
{
var rb = this.listBox1.Controls.OfType<RadioButton>().SingleOrDefault(n => n.Checked);
if (rb != null)
MessageBox.Show("radiobutton: " + rb.Text);
}
since this is RadioButton there shouldn't be more then one checked
The first step, is to make the radiobutton array a variable on form level:
RadioButton[] rb
which is assigned inside loadSurveys
rb = new RadioButton[example.surveys.Length];
Then the array is accessible inside your button click
var rb = rb.FirstOrDefault(r=>r.Checked);
if(rb==null)
MessageBox.Show("No radiobutton was selected");
else
MessageBox.Show("radiobutton: " + rb[i].Text);
edit Just noticed you add the radiobuttons to a listbox. Is the listbox1 variable an actual listbox? The above will still work, but if the goal is to display a listbox of radiobuttons, you can custom paint the listbox and else use a normal panel instead of a listbox.
Either way, you can also do a firstordefault on the controls on the controls of the listbox1 variable (with OfType), but if you'd use a listbox, and fill its items, you could simply use SelectedIndexChanged
edit 2
Since I already had it, wanted to show a way to make your listbox a radiobutton box. You can make any existing listbox a radiobutton box with the following class:
public class RadioButtonBoxPainter:IDisposable
{
public readonly ListBox ListBox;
public RadioButtonBoxPainter(ListBox ListBox)
{
this.ListBox = ListBox;
ListBox.DrawMode = DrawMode.OwnerDrawFixed;
ListBox.DrawItem += ListBox_DrawItem;
}
void ListBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index == -1) return;
Rectangle r = e.Bounds;
r.Width=r.Height;
bool selected= (e.State & DrawItemState.Selected) > 0;
e.DrawBackground();
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ControlPaint.DrawRadioButton(e.Graphics, r, selected ? ButtonState.Checked : ButtonState.Normal);
r.X = r.Right + 2;
r.Width = e.Bounds.Width - r.X;
string txt;
if (ListBox.Site != null && ListBox.Site.DesignMode && e.Index >= ListBox.Items.Count)
txt = ListBox.Name;
else
txt = ListBox.GetItemText(ListBox.Items[e.Index]);
using (var b = new SolidBrush(e.ForeColor))
e.Graphics.DrawString(txt, e.Font, b, r);
if (selected)
{
r = e.Bounds;
r.Width--; r.Height--;
e.Graphics.DrawRectangle(Pens.DarkBlue, r);
}
}
public void Dispose()
{
ListBox.DrawItem -= ListBox_DrawItem;
}
}
Example of a standard implementation:
public class RadioButtonBox:ListBox
{
public readonly RadioButtonBoxPainter Painter;
public RadioButtonBox()
{
Painter = new RadioButtonBoxPainter(this);
}
[DefaultValue(DrawMode.OwnerDrawFixed)]
public override DrawMode DrawMode
{
get{return base.DrawMode;}
set{base.DrawMode = value;}
}
}
The RadioButtonBox is a control I actually use a lot. Personally I find it a lot quicker in implementing then a load of separate radiobuttons.
In case you want to use it, and want an example how to implement it in your current code, leave a comment and I'll add one.
I'm currently working on a listview in winform c#
and everytime I click on an empty space on the listview,
the selected item is lost.
The listview control has a HideSelection property that defaults to True. Make it False and you're good to go... in some cases this is enough.
You have to inherit from the ListView class and do some low-level message processing
class ListViewThatKeepsSelection : ListView
{
protected override void WndProc(ref Message m)
{
// Suppress mouse messages that are OUTSIDE of the items area
if (m.Msg >= 0x201 && m.Msg <= 0x209)
{
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
var hit = this.HitTest(pos);
switch (hit.Location)
{
case ListViewHitTestLocations.AboveClientArea:
case ListViewHitTestLocations.BelowClientArea:
case ListViewHitTestLocations.LeftOfClientArea:
case ListViewHitTestLocations.RightOfClientArea:
case ListViewHitTestLocations.None:
return;
}
}
base.WndProc(ref m);
}
}
I thought there was a property that prevented this from happening, but now I can't find it.
You could try this:
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = sender as ListView;
if (listView.SelectedItems.Count == 0)
foreach (object item in e.RemovedItems)
listView.SelectedItems.Add(item);
}
This is much harder to do in WinForms than in WPF. WinForms has a SelectedIndexChanged event which doesn't tell you anything about what was already selected, plus it is fired every time a row is selected or deselected.
So if a row is selected and you select a different row, you receive two SelectedIndexChanged events:
one after the selected row is deselected
another when the new row is selected.
The problem is that, during event #1, the ListView has nothing selected and you don't know if event #2 is coming that will select the second row.
The best you can do is wait until your application is idle (a few milliseconds after the selection has changed), and if the listview still has nothing selected, put back the last selected row.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
ListView lv = (ListView)sender;
if (lv.SelectedIndices.Count == 0)
{
if (!this.appIdleEventScheduled)
{
this.appIdleEventScheduled = true;
this.listViewToMunge = lv;
Application.Idle += new EventHandler(Application_Idle);
}
}
else
this.lastSelectedIndex = lv.SelectedIndices[0];
}
void Application_Idle(object sender, EventArgs e)
{
Application.Idle -= new EventHandler(Application_Idle);
this.appIdleEventScheduled = false;
if (listViewToMunge.SelectedIndices.Count == 0)
listViewToMunge.SelectedIndices.Add(this.lastSelectedIndex);
}
private bool appIdleEventScheduled = false;
private int lastSelectedIndex = -1;
private ListView listViewToMunge;
I accomplished it like this:
private void lvReads_MouseUp(object sender, MouseEventArgs e)
{
if (lvReads.SelectedItems.Count == 0)
if (lvReads.Items.Count > 0)
lvReads.Items.Find(currentName, false)[0].Selected = true;
}
and
private void lvReads_SelectedIndexChanged(object sender, EventArgs e)
{
if (lvReads.SelectedItems.Count == 1)
{
selectedIndex = lvReads.SelectedIndices[0];
if (currentName != lvReads.Items[selectedIndex].Name)
{
//load item
}
currentName = lvReads.Items[selectedIndex].Name;
}
}
I know that question asked 10 years ago. But I face the same problem and found a simple and elegant solution just now and sincerely want to share it.
There is a code (in VB.NET but its no big problem to write the same in C#):
Public Class SettingsBox ' Form that contains ListView (lvScreen)
Private nScreenTracer As Integer
Private nSelectedScreen As Integer
Private Sub lvScreen_ItemSelectionChanged(sender As Object,
e As ListViewItemSelectionChangedEventArgs) Handles lvScreen.ItemSelectionChanged
If e.IsSelected Then nScreenTracer = e.Item.Index
End Sub
Private Sub lvScreen_MouseDown(sender As Object,
e As MouseEventArgs) Handles lvScreen.MouseDown
nScreenTracer = -1
End Sub
Private Sub lvScreen_MouseUp(sender As Object,
e As MouseEventArgs) Handles lvScreen.MouseUp
If nScreenTracer = -1 Then
lvScreen.SelectedIndices.Add(nSelectedScreen)
Else
nSelectedScreen = nScreenTracer
End If
End Sub
End Class
This solution works for a single item selection but also can be simply redesigned with List(Of Integer) for multi-select
Here are simple, but a bit hack way, based on LVN_ITEMCHANGED event realization (Sorry - plain C, not C#, but i hope it's understandable):
case LVN_ITEMCHANGED:
{
NMLISTVIEW* lPoint = (LPNMLISTVIEW)lParam;
if (lPoint->uNewState & LVIS_SELECTED && lPoint->iItem != -1) {
// Select other item...
eItem = (int)lPoint->lParam;
SendMessage(GetParent(hDlg), WM_APP + 2, 0, 1);
}
else {
if (!lPoint->uNewState && ListView_GetItemState(((NMHDR*)lParam)->hwndFrom, lPoint->iItem, LVIS_FOCUSED)) {
// Click on empty space
ListView_SetItemState(((NMHDR*)lParam)->hwndFrom, lPoint->iItem, LVIS_SELECTED, LVIS_SELECTED);
}
else
// Click on other item
ListView_SetItemState(((NMHDR*)lParam)->hwndFrom, lPoint->iItem, 0, LVIS_SELECTED);
}
} break;
How do you edit items and subitems in a listview? Let's say I have a listview with 3 columns,and subitems,
Car Brand | Car Name | Car Year
Ford | Mustang | 2000
Dodge | Charger | 2007
How would I Add items like that to listview and how would I edit let's say the Car Name on which ever row by index[] if I needed to edit at runtime at firing of an event?
If you're looking for "in-place" editing of a ListView's contents (specifically the subitems of a ListView in details view mode), you'll need to implement this yourself, or use a third-party control.
By default, the best you can achieve with a "standard" ListView is to set it's LabelEdit property to true to allow the user to edit the text of the first column of the ListView (assuming you want to allow a free-format text edit).
Some examples (including full source-code) of customized ListView's that allow "in-place" editing of sub-items are:
C# Editable ListView
In-place editing of ListView subitems
I use a hidden textbox to edit all the listview items/subitems. The only problem is that the textbox needs to disappear as soon as any event takes place outside the textbox and the listview doesn't trigger the scroll event so if you scroll the listview the textbox will still be visible.
To bypass this problem I created the Scroll event with this overrided listview.
Here is my code, I constantly reuse it so it might be help for someone:
ListViewItem.ListViewSubItem SelectedLSI;
private void listView2_MouseUp(object sender, MouseEventArgs e)
{
ListViewHitTestInfo i = listView2.HitTest(e.X, e.Y);
SelectedLSI = i.SubItem;
if (SelectedLSI == null)
return;
int border = 0;
switch (listView2.BorderStyle)
{
case BorderStyle.FixedSingle:
border = 1;
break;
case BorderStyle.Fixed3D:
border = 2;
break;
}
int CellWidth = SelectedLSI.Bounds.Width;
int CellHeight = SelectedLSI.Bounds.Height;
int CellLeft = border + listView2.Left + i.SubItem.Bounds.Left;
int CellTop =listView2.Top + i.SubItem.Bounds.Top;
// First Column
if (i.SubItem == i.Item.SubItems[0])
CellWidth = listView2.Columns[0].Width;
TxtEdit.Location = new Point(CellLeft, CellTop);
TxtEdit.Size = new Size(CellWidth, CellHeight);
TxtEdit.Visible = true;
TxtEdit.BringToFront();
TxtEdit.Text = i.SubItem.Text;
TxtEdit.Select();
TxtEdit.SelectAll();
}
private void listView2_MouseDown(object sender, MouseEventArgs e)
{
HideTextEditor();
}
private void listView2_Scroll(object sender, EventArgs e)
{
HideTextEditor();
}
private void TxtEdit_Leave(object sender, EventArgs e)
{
HideTextEditor();
}
private void TxtEdit_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return)
HideTextEditor();
}
private void HideTextEditor()
{
TxtEdit.Visible = false;
if (SelectedLSI != null)
SelectedLSI.Text = TxtEdit.Text;
SelectedLSI = null;
TxtEdit.Text = "";
}
Click the items in the list view.
Add a button that will edit the selected items.
Add the code
try
{
LSTDEDUCTION.SelectedItems[0].SubItems[1].Text = txtcarName.Text;
LSTDEDUCTION.SelectedItems[0].SubItems[0].Text = txtcarBrand.Text;
LSTDEDUCTION.SelectedItems[0].SubItems[2].Text = txtCarName.Text;
}
catch{}
Sorry, don't have enough rep, or would have commented on CraigTP's answer.
I found the solution from the 1st link - C# Editable ListView, quite easy to use. The general idea is to:
identify the SubItem that was selected and overlay a TextBox with the SubItem's text over the SubItem
give this TextBox focus
change SubItem's text to that of TextBox's when TextBox loses focus
What a workaround for a seemingly simple operation :-|
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
li = listView1.GetItemAt(e.X, e.Y);
X = e.X;
Y = e.Y;
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
int nStart = X;
int spos = 0;
int epos = listView1.Columns[1].Width;
for (int i = 0; i < listView1.Columns.Count; i++)
{
if (nStart > spos && nStart < epos)
{
subItemSelected = i;
break;
}
spos = epos;
epos += listView1.Columns[i].Width;
}
li.SubItems[subItemSelected].Text = "9";
}