ListView Checkbox Behavior - c#

I have a Winform application I'm modifying for a friend. It has a listview and wants me to add a checkbox to each row and make them mutually exclusive. So in my testing of how to make it work I found a strange behavior and as hopping someone could tell me what I'm missing.
If I display the List view with no checkbox checked. When I click right on the checkbox I cannot get it to check, but the row dose get selected. If I click on the item (the name in this case) in the column it does get checked and selected.
No matter where I click on a row, any checkboxes in rows not selected will be uncheck. Here is my little test program. I’m using .NET 4
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
TestListView.Items.Add("Bob");
TestListView.Items.Add("Ann");
TestListView.Items.Add("Frank");
}
void TestListView_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) {
ListViewItem currentItem = TestListView.GetItemAt(e.X, e.Y);
if (currentItem != null) {
foreach (ListViewItem item in TestListView.Items) {
if (item.Text == currentItem.Text) {
item.Checked = true;
item.Selected = !currentItem.Selected;
}
else
item.Checked = false;
}
}
}
}

Seems that WinForms is checking the checkbox when you click on the checkbox directly, and then your code immediately undoes the checking, so you never see it.
Perhaps instead of MouseClick you should use the ItemCheck or ItemChecked event. The first is fired before the Checked property changes, the second after.

I'm currently having a very similar issue as well, however in response to Timwi it's not the code doing the unchecking. I've been stepping through it very slowly and as the code fires when clicking on a checkbox, it states that it has checked it. But when the form resumes, it is unchecked again. After reading Timwi post, he lead me onto the answer. It's not the code doing the unchecked, but the winforms event firing afterwards that unchecks the box. This fixes it:
My code is:
private bool allowCheck = false;
private bool preventOverflow = true;
private void lstvwRaiseLimitStore_MouseClick(object sender, MouseEventArgs e)
{
preventOverflow = false;
ListViewItem item = lstvwRaiseLimitStore.HitTest(e.X, e.Y).Item;
if (item.Checked)
{
allowCheck = true;
item.Checked = false;
}
else
{
allowCheck = true;
item.Checked = true;
}
}
private void lstvwRaiseLimitStore_ItemChecked(object sender, ItemCheckedEventArgs e)
{
if (!preventOverflow)
{
if (!allowCheck)
{
preventOverflow = true;
e.Item.Checked = !e.Item.Checked;
}
else
allowCheck = false;
}
}
So what it is doing, first I have to set a flag to prevent overflow, otherwise when the form is built or when you uncheck windows checking the box, it keeps looping the code and will eventually stack overflow. Next flag is the actual allow checking of the checkbox via your code and not via another method.
Clicking on them item, it locates where the click was and then sets the flag to allow a check to be done. The code then checks the box and the item checked section kicks off, becuase it was done by our code, it does nothing but reset the allowcheck flag.
If you clicked on a line, it does nothing else, however if it was a checkbox, at the end of our code, the Windows function kicks off and tries to check the box, becuase the allowcheck flag is false, the code first sets a flag to say I'm already preventing a check and then resets the check back to it's intial status. Becuase this is done, the itemchecked event kicks off again, but the code has set the flag to prevent it from doing anything. Then the code finishes and it has prevented windows from checking the check box and only allowed our code.
Hope it helps!

Related

Disabling a checkbox when another checkbox is clicked

I am trying to disable the thin and crispy checkbox when traditional checkbox is clicked. I have these in a group due to me enabling the whole group when the numericUpDown value is set to 1. When I click traditional checkbox, it doesn't disable the thin and crispy checkbox. I am using windows form application
Code
private void NudQuantity1_ValueChanged(object sender, EventArgs e)
{
if (NudQuantity1.Value == 0)
{
gbCheesePizza.Enabled = false;
}
else
{
gbCheesePizza.Enabled = true;
}
if (CBXTraditional1.Checked == true)
{
CBXthinandcrispy1.Enabled = false;
}
}
When I run this code outside of a groupbox, it works perfectly.
I don't think this block should be inside the event handler
if (CBXTraditional1.Checked == true)
{
CBXthinandcrispy1.Enabled = false;
}
It means that, provided you've got no other event handling for the checkboxes, this code will only be executed when you change the value of NudQuantity1 so it won't execute anything when you click the checkboxes afterwards.
Try use radio buttons as Steve mentioned. They do this for you.

Winforms, ListView - stages of selection change

I'd like to implement a scenario that requires me to know if the user selects an item or deselects everything by clicking on an empty space. Basically, I need to keep the first item (which always exists) selected. It should be possible to select a different item, but as soon as the user clicks on empty space and when normally selection will be removed it should jump back to the first (default) item.
I tried to employ the selection changed events of the listview control, but those are triggered in a sequence which is not very helpful: first the currently selected item loses the selection. Then there's a separate event to indicate that a new item is selected.
The question is: when the event ItemSelectionChanged is triggered for the first time to say that a previously selected item is not selected any more, is it possible to know if that item was de-selected because another item is being selected or because nothing is selected?
Try this to see if it can solve your problem. We remove the event callback to prevent the event to be called again, apply our logic, and re-assign the callback.
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
listView1.ItemSelectionChanged -= listView1_ItemSelectionChanged;
if (!e.IsSelected)
{
listView1.Items[0].Selected = true;
listView1.Items[0].Focused = true;
}
else
{
listView1.Items[0].Selected = false;
listView1.Items[e.ItemIndex].Selected = true;
}
listView1.ItemSelectionChanged += listView1_ItemSelectionChanged;
}
Thanks for the answers.
I solved it altogether differently: upon a selection change event I enable a short timer which, when ticked, checks whether or not there's a selected item in the ListView, and if not, it selects the first one. And it disables itself in any case. Seems that it works for me, so I am stopping there.
This will remember previous selection and keep it. (I needed it for my project and got some help here).
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
int temp = 0;
listView1.ItemSelectionChanged -= listView1_ItemSelectionChanged;
if (!e.IsSelected)
{
temp = e.ItemIndex;
listView1.Items[temp].Selected = true;
listView1.Items[temp].Focused = true;
}
else
{
for (int y = listView1.Items.Count - 1; y >= 0; y--)
{
listView1.Items[y].Selected = false;
}
listView1.Items[e.ItemIndex].Selected = true;
}
listView1.ItemSelectionChanged += listView1_ItemSelectionChanged;
}

Setting myButton.Enabled = true causes radiobutton's CheckedChanged handler to fire

I have absolutely no programmatic links or properties set such that my CheckedChanged fires as a result of anything except checking the radio button.
However, when I click a different, unrelated button, the button's click handler fires (this is expected). In this click handler, the button disables itself (it re-enables on a different button's click), which then triggers myRadioButton_CheckedChanged handle for an unrelated radiobutton fires.
The call stack that I'm seeing is essentially
myRadioButton_CheckedChanged (...)
myButton_Click(...)
Main(...)
The line in myButton_Click that is triggering the myRadioButton_CheckedChanged is apparently
myButton.Enabled = false;
The related code is:
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
// L-R
if (radioButton1.Checked == true)
{
orientation_left = 3;
pictureBox2.Invalidate();
Debug.Print("left {0}", orientation_left);
}
}
private void select1_Click(object sender, EventArgs e)
{
Debug.Print("select1click");
if (select1Down == false)
{
// ... stuff
select1.Enabled = false; // Causing the CheckedChanged to fire
select2.Enabled = false;
select1Down = true;
}
}
Ok, got it.
Check the TabOrder on your Button and RadioButton.
Seems that when you disable the Button, the focus is shifted to the next control, which is probably your RadioButton, causing it to become checked.
On my test From, all I had to do was to make sure that the RadioButton's TabOrder was not right after the Button.
Cheers
EDIT:
This seems to be a known problem as I just found this MSDN thread: http://social.msdn.microsoft.com/Forums/windows/en-US/77fbec3b-1f63-42e1-a200-19b261b63794/the-radiobutton-clicked-event-is-fired-without-the-radio-button-beeing-clicked-?forum=winforms
Okay, it's kinda hacky but it works without changing anything to the tab order:
private void select1_Click(object sender, EventArgs e)
{
if (!select1Down)
{
// ... stuff
SendKeys.SendWait("{Tab}");
select1.Enabled = false;
select2.Enabled = false;
select1Down = true;
}
}

listView ItemSelectionChanged 'Is Selected' not working as expected?

I'm having a problem with a ListView ItemSelectionChanged event being called twice. From searching I've found it's because the event is being called when an item is selected and again when an item is deselected. I only want it to fire when an item is selected, not when it's deselected. The solution seems to have an if(e.IsSelected) at the beginning of the event so it's only called when an item is selected, but that's not working for me in this case. Here's the basic structure of my code:
private void SelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
DialogResult GetDialogResult =
MessageBox.Show("Keep this item selected?",
"Keep Selected",
MessageBoxButtons.YesNo);
if (GetDialogResult == DialogResult.No)
listView1.SelectedItems[0].Selected = false;
}
}
MultiSelect is disabled so the selected item index will always be 0. My problem is I want the selected item to be deselected if the DialogResult is No, but when you click no, the SelectionChanged event is firing again, and e.IsSelected is apparently still true, so the Dialog Box pops up a second time. I imagine it has something to do with the fact that the first event hasn't completely finished executing when the item is being deselected, but I'm not sure how to solve that and make it so the Dialog Box only shows up once. Any suggestions?
Edit: Something else I've tried now is instead of Deselecting the item in the listBox, clearing all items in the listbox and recreating them. If the items are all cleared, the dialog box doesn't come up a second time. However if the items are immediately recreated, the same item becomes selected again and the dialog box still comes up the 2nd time.
If the behaviour you want is:
User clicks an item
Dialog pops up
If they click 'no' on the popup, selection doesn't happen
If they click 'yes', it does
then one approach is to unhook your event handler before you set the Selected property to false, then rehook afterwards, as follows:
if (GetDialogResult == DialogResult.No)
{
listView1.ItemSelectionChanged -= SelectionChanged;
e.Item.Selected = false;
listView1.ItemSelectionChanged += SelectionChanged;
}
This will stop the event handler from triggering again until your operation has completed.
ItemSelectionChanged (like several other events on other controls) gets fired when you change the value from the program as well. So when you run this line of code, that counts as a "selection changed"...
listView1.SelectedItems[0].Selected = false;
One way to handle this is by setting a flag:
private bool _programChangingSelection;
private void SelectionChanged(object sender, EventArgs e)
{
if (_programChangingSelection)
{
_programChangingSelection = false;
return;
}
if (e.IsSelected)
{
DialogResult GetDialogResult =
MessageBox.Show("Keep this item selected?",
"Keep Selected",
MessageBoxButtons.YesNo);
if (GetDialogResult == DialogResult.No)
{
_programChangingSelection = true;
listView1.SelectedItems[0].Selected = false;
}
}
}
Although, personally I think the add/remove handler method is probably a bit more elegant.
I've found a solution that works. Calling a function to deselect the item asynchronously in a separate thread seems to have solved the issue for me. Without doing this, the second SelectionChanged event is called and has to finish executing before the first one can finished executing, which apparently causes the problem I was seeing. Here is the code I came up with that has worked:
delegate void DeselectDelegate();
public void DeselectItem()
{
if (this.listView1.InvokeRequired)
{
DeselectDelegate del = new DeselectDelegate(DeselectItem);
this.Invoke(del);
}
else
{
listView1.SelectedItems[0].Selected = false;
}
}
private void SelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
{
DialogResult GetDialogResult =
MessageBox.Show("Keep this item selected?",
"Keep Selected",
MessageBoxButtons.YesNo);
if (GetDialogResult == DialogResult.No)
{
Thread thread = new Thread(DeselectItem);
thread.Start();
}
}
}

ListView ItemChanged puzzle with MessageBoxes - ItemSelectionChanged called twice

Here is the problem: I have a Windows Forms application that I'm developing, and in one segment I'm using a ListView control.
What I'm trying can be simply stated as: on event ListViewItemSelectionChange show a MessageBox for user to confirm the change, if not confirmed change to let's say the first item. This change to the first item would again fire ListViewItemSelecionChange, so I unregister and re-register the event handler method, so everything should be good, right?
What actually happens is that the handler method is called twice (actually ListView should fire two events on Selection change, one for deselect, other for newly selected item, but I have an e.IsSelected statement at the beginning to catch only selected items, so actually you could say that there are four events fired).
The problem is, if I generated the first event with mouse click on ListView item, and I've unsubscribed before programatically changing to the first item, what generates the second event firing? Is it some focus change because of the MessageBox call? Is there any way to prevent the second event to fire?
I have a simple example solution here, it can't be more simlified (25 SLOC), so if you can, please take a look. Note that commenting the line "if (ShowMessageBox())" stops the second event from firing, is this some focus change problem?
http://www.filedropper.com/listviewtestwithmsgbox
Edit: the relevant code:
private void listViewWithSelection1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
// listview actually generates two ItemSelectionChanged events,
// one for deselect of a item, and another event for a newly selected item (which we want here).
if (e.IsSelected)
{
if (ShowMessageBox())
Button1_Click(null, EventArgs.Empty);
label1.Text += "item selected ";
}
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void Button1_Click(object sender, EventArgs e)
{
// change ti first ListView item
listView1.ItemSelectionChanged -= listViewWithSelection1_ItemSelectionChanged;
listView1.Items[0].Selected = true;
listView1.ItemSelectionChanged += listViewWithSelection1_ItemSelectionChanged;
}
Hmm, can you describe how the selection is being changed to begin with? If it's by the user clicking to select an item, perhaps catch the Click or DoubleClick event rather than the ItemSelectionChanged event? I have this snippet I'm using on a program currently. If the user double clicks the list box (listView, in your case), do something with the selected item.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private bool ShowMessageBox()
{
return MessageBox.Show("Change to first item instead?", "test", MessageBoxButtons.YesNo) == DialogResult.Yes;
}
private void listView1_Click(object sender, EventArgs e)
{
if (ShowMessageBox())
listView1.TopItem.Selected = true;
label1.Text += "item selected ";
}
}
Edited to include relevant code.
One way to do this is to have a flag which says should the on change code run.
In your ListViewItemSelecionChange code you check the value of the flag and run code accordingly.

Categories