How to deselect all MenuStripItems? - c#

I'm trying to make sort of a selector/last detector in my menustrip. Essentially I have a bunch of "sub items" under one menu strip item. And I want to go through all of them, uncheck them, and then check only the one that was clicked.
Essentially I want to uncheck all of the stuff that starts with de_ (and that last one, so all of them)
Edit: Got it to work, here is the code I ended up using
private void ItemClick(object sender, EventArgs e)
{
foreach (ToolStripMenuItem item in mapsToolStripMenuItem.DropDownItems)
{
item.Checked = false;
}
((ToolStripMenuItem)sender).Checked = true;
}
But I'm not sure what to replace that ? with in order to look into the right place.

Try using the parent menu's DropDownItems collection:
foreach (ToolStripMenuItem item in mapStripMenuItem.DropDownItems ) {
item.Checked = false;
}
From you image, it should be named Map-something, I'm guessing.

Related

How to sync selected items and checked items in a ListView?

I have a ListView with the property CheckBoxes set to true and with the following event handler:
private void listView1_ItemCheck(object sender, ItemCheckEventArgs e)
{
listView1.Items[e.Index].Selected = e.NewValue == CheckState.Checked ? true : false;
}
Using the keyboard:
I can move the selection left or right using the arrow keys, and (un)check an item by using the Space key. I can select multiple items using Shift + arrow keys.
The problem: when pressing the Space key to uncheck one of the checked items, the selection is automatically set to be a single item, the currently focused item.
Using the mouse:
Selection and checked elements seem to be synchronized, until I check two items and then left-click on one of their two check boxes, moment when the selection is cleared and checked items all become unchecked.
Example screenshots:
Initial state:
then after left-clicking on the first check box and then left clicking on the second check box:
then after left-clicking on any of the two check boxes, the initial state again:
Expected behavior: one of the two check boxes (the one that was not clicked in the last step) should stay checked and selected.
Note: I wish to be able to use other Views of the ListView as well, LabelEdit property, drag & drop, icons, multiselect, groups.
I thought of the possibility of knowing the coordinates of the checkbox rectangle, and using the MouseDown event, or even drawing my own checkboxes but maybe there is an easier or better way.
Update 1:
In File Explorer in Windows 10 File Explorer I can enable a function that makes this possible:
Here the first folder (hidden folder) is unchecked and not selected, and the second and third items are selected either through the checkbox or through Ctrl+click, or through both.
Another screenshot:
I find the separation of the check boxes and selection useless, I think it is more intuitive for the user if they are combined.
I am trying to use selection and also check boxes for selecting which groups/folders to show in my application.
Update 2: About the code posted in this related question:
There are some bugs inside that code, one is the following:
Steps to reproduce:
Modify the designer code to add 5 items in total to the ListView.
At start of program the first item is focused (neither selected nor checked).
Pressing the right arrow key moves the focus to the second item.
When I keep Shift pressed and press the right Arrow key, the behavior is wrong: the second and third items are selected and checked, and after pressing again the right arrow while Shift is pressed, only item3 and item4 get selected - more exactly item2 and item3 are checked, and item3 and item4 are selected. Releasing the Shift key and clicking in empty space makes only the item3 checked, and none are selected.
The answer of Aleksa Ristic has some bugs:
double click on label checks/unchecks and the selection state is the opposite;
rubberband selection does not check the checkboxes;
once an item is selected by clicking on label, checking it deselects.
Update 3:
The answer of Aleksa Ristic still has some bugs, I see these now:
I start the program then I either:
directly click on either the label or the checkbox;
move the focus using the arrow keys;
press Space or try a Shift selection;
and I always get System.StackOverflowException on the same line i.Selected = false; (near line 85).
When the user clicks out of everything and deselects, I would like to make the currently selected and checked items bold and when the user clicks out of everything, the selection and checked items are cleared.
When the user clicks on a label I would like the same behavior like when it clicks on the checkbox near that label.
The rubberband works really well now.
Since this question got advanced in comments, i decided to delete everything and post finished code.
Create empty Form
Add ListView component to it with name listView1
Paste this code in you .cs file
CODE:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Magacin
{
public partial class TestForm : Form
{
bool HandleSelectionChange = true;
bool HandleCheckChange = true;
bool TempStopDeslect = false;
bool dragging = false;
bool multiJob = false;
public TestForm()
{
InitializeComponent();
listView1.CheckBoxes = true;
this.listView1.ItemCheck += OnCheck;
this.listView1.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.listView1_ItemSelectionChanged);
this.listView1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.listView1_KeyDown);
this.listView1.KeyUp += new System.Windows.Forms.KeyEventHandler(this.listView1_KeyUp);
this.listView1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.listView1_MouseDown);
this.listView1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.listView1_MouseUp);
listView1.Items.Add("Item1");
listView1.Items.Add("Item2");
listView1.Items.Add("Item3");
}
private ListViewItem GetItemFromPoint(ListView listView, Point mousePosition)
{
Point localPoint = listView.PointToClient(mousePosition);
return listView.GetItemAt(localPoint.X, localPoint.Y);
}
private void OnCheck(object sender, ItemCheckEventArgs e)
{
if (!HandleCheckChange)
return;
ListViewItem item = GetItemFromPoint(listView1, Cursor.Position);
if (item == null)
return;
if (e.Index != item.Index)
{
TempStopDeslect = true;
e.NewValue = e.CurrentValue;
return;
}
HandleSelectionChange = (multiJob) ? false : true;
if (e.NewValue == CheckState.Checked)
{
listView1.Items[e.Index].Selected = true;
}
else
{
listView1.Items[e.Index].Selected = false;
}
HandleSelectionChange = true;
}
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if (!HandleSelectionChange)
return;
bool temp = e.IsSelected;
if (!TempStopDeslect)
{
if (!multiJob && !dragging)
{
foreach (ListViewItem i in listView1.Items)
{
i.Checked = false;
i.Selected = false;
}
}
}
else
TempStopDeslect = false;
HandleCheckChange = false;
e.Item.Selected = temp;
e.Item.Checked = e.IsSelected;
HandleCheckChange = true;
}
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
ListViewItem item = GetItemFromPoint(listView1, Cursor.Position);
if (item == null)
dragging = true;
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
private void listView1_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.Control) // Change this to whatever you want
multiJob = true;
}
private void listView1_KeyUp(object sender, KeyEventArgs e)
{
multiJob = false;
}
}
}
I think i got behavior you want. If not ask me down there and i will edit. In most cases it is moving/changing boolean values but it is okay if you are not getting used to it.

C# contextmenustrip doesn't show any indication if any item is selected

I have added a context menu strip 'View' on my listview with some menu items, like Large icons/ Small icons/ Tiles.
Now whenever I select any of the options the respective view changes, but the menu doesn't get any Mark/ indication like that happens in Windows file explorer, where it shows bullet/ dot against the selected menu item.
Can someone please show, how I can get the similar dot/ bullet for my context menu?
I have tried CheckOnClick property which gets me a tick mark, but is there any other way that I could get that dot there?
Thanks in advance!
I couldn't find a way to get bullets/ dots like Windows File Explored's View options, but I used below logic and used checked states to indicate the selections made.
private void toolStripViewOptions_Click(object sender, EventArgs e)
{
ToolStripMenuItem selectedOption = sender as ToolStripMenuItem;
SetIndicationForSelectedOption(selectedOption);
}
private void SetIndicationForSelectedOption(ToolStripMenuItem selectedMenuItem)
{
ToolStripItemCollection menuItems = (contextMenuStrip.Items[(Int32)toolStripView.Tag] as ToolStripMenuItem).DropDownItems;
// Set checked state for only the selected view option and disable same for others.
foreach (ToolStripMenuItem item in menuItems)
{
if (selectedMenuItem == item)
selectedMenuItem.Checked = true;
else
item.Checked = false;
}
}
This worked for my requirement.

How to add subitems to a specific row/index in a listView

I am trying to make a soundboard program that plays a user added sound when either the play button is pressed or an optionally set hotkey is pressed. I would like to display the hotkey next to the sound name in the list view. But I do not know how to add/edit a subitem with only the index of the selected row.
I have seen people do this:
ListViewItem lvi = new ListViewItem();
lvi.SubItems.Add("SubItem");
listView1.Items.Add(lvi);
but it is not helpful because I cannot create a different variable for each row(I think) because it is inputted by the user.
This is the result I am hoping to get:
and this is what I have got so far:
Edit: I have worked out how to add the Items:
private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
{
foreach (string fileName in openFileDialog1.FileNames)
{
var item = new ListViewItem(fileName);
item.SubItems.Add("Alt+Shift+Numpad1");
lvAudio.Items.Add(item);
listBoxAudio.Items.Add(fileName);
}
}
But I am still not sure how to add/edit a hotkey on the selected row later on while the program is running.
In order to access the items and subitems, treat them like collections and user their indexes to get to them:
if (lvAudio.SelectedItems.Count > 0)
{
var file = lvAudio.SelectedItems[0];
if(file.SubItems.Count > 1)
{
var shortcut = file.SubItems[1];
shortcut.Text = "New Shortcut";
}
}

Remove() function seems only removes a couple of controls at a time

I'm trying to remove multiple controls at a time (textboxes and checkboxes) in a windows form application. Basically, I have a row of textboxes and a corresponding checkbox. When the "delete rows" button is clicked, it should remove all rows that have been selected. But it only seems to remove two or three at a time (the same two or three in each row, but there doesn't seem to be any reason it selects those same two or three). I've attached a couple of screenshots showing what is happening.
Here I've selected a couple of rows:
After hitting delete once:
After hitting delete twice:
This just shows the names of each element. As you can see, the names are the same in each row:
Here is the relevant code:
//Gets a list of all ticked checkboxes
public List<string> checkForChecked()
{
var allCheckboxes = tabPage1.GetAllControlsOfType<CheckBox>();
int count = allCheckboxes.Count<CheckBox>();
List<string> checkedChecks = new List<string>();
foreach(Control c in tabPage1.Controls)
{
if(c is CheckBox && ((CheckBox)c).Checked)
{
checkedChecks.Add(c.Name.ToString());
}
}
return checkedChecks;
}
//The button click. Loop through elements and remove ones with the right name
private void button2_Click(object sender, System.EventArgs e)
{
List<string> toDelete = checkForChecked();
foreach (var val in toDelete)
{
foreach (Control item in tabPage1.Controls.OfType<Control>())
{
if (item.Name == val.ToString())
{
tabPage1.Controls.Remove(item);
}
}
}
}
I'm using a windows form app, no asp or other web technology.
It's a common error of removing items from a collection while enumerating it. For example:
foreach (Control c in Controls)
Controls.Remove(c);
will remove only half of the controls and leave every second control.
Some of the common solutions to the general problem are:
removing items in reverse order starting from the end of the collection
removing the first found item until no items found
enumerating a copy of the collection
In your case, both methods can be combined to something like this ( .Find returns Control[] ) :
foreach (var cb in tabPage1.Controls.OfType<CheckBox>())
if (cb.Checked)
foreach (var c in tabPage1.Controls.Find(cb.Name, false))
tabPage1.Controls.Remove(c)
You could try something more like:
private void button2_Click(object sender, EventArgs e)
{
List<CheckBox> CheckedBoxes = new List<CheckBox>();
foreach (CheckBox cb in tabPage1.Controls.OfType<CheckBox>())
{
if (cb.Checked)
{
CheckedBoxes.Add(cb);
}
}
foreach (CheckBox cb in CheckedBoxes)
{
string cbName = cb.Name;
cb.Dispose();
// ... probably more code in here to find the other controls
// ... in the "row" based on "cbName"
}
}
You are iterating the controls collection and removing a control, which effects the index of every item in the collection every time you call the Remove() method. This probably messes with your iterator in the foreach, thus the 'skipping' behavior. When removing from any controls collection it is best not to be iterating it at the same time. Moreover, when looping on a Controls collection where you are removing items, code your loops as iterators working backwards.
for (var i=Parent.Controls.Count-1; i >=0; i--)
{
if (someCondition) Parent.Controls.Remove(Parent.Controls[i]);
}
Here is a bit cleaner version of your code. The list returned from the CheckForChecked method is now a list of controls which is easier to use than a list of the controls names.
In the remove, we are iterating the list of things to delete, not the controls collection from which we are deleting.
//Gets a list of all ticked checkboxes
public List<Control> CheckForChecked()
{
var tabPage1 = new TabPage();
var results = new List<Control>(0);
results.AddRange(from Control c in tabPage1.Controls
where c is CheckBox && (c as CheckBox).Checked
select c);
return results;
}
//The button click. Loop through elements and remove ones with the right name
private void button2_Click(object sender, System.EventArgs e)
{
var toDelete = CheckForChecked();
var tabPage1 = new TabPage();
foreach (var val in toDelete.Where(val => tabPage1.Controls.Contains(val)))
{
tabPage1.Controls.Remove(val);
}
}
Hope this helps someone.

Search listview items using textbox

I am working on a simple application (phonebook) in C# and in my project I have got a listview filled with contacts. I have been trying to implement the possibility to automatically (instantly) search through a listview using a textbox. I have managed to make it work, but not in the desired way. You will realise the actual problem if I give you an example. Let's say that I have got a contact named Bill Gates and when I try searching for it - it gets found and that part is OK. But, the problem is when I try to search for another contact. In that case, I have to clear the textbox before I type another name, but it is possible to remove only letter by letter. When I start removing the whole name, after removing a first letter it acts like I have just entered the name - it selects the item (and focuses as well) - actually there is no time to remove the whole name before it finds a contact again. I have to remove a first letter, then switch back to the textbox, remove another letter etc. Is there any solution for searching to be automatic - as it is now, but on the other hand for removing (clearing the textbox) without selecting contacts to be possible.
Take a look at the code:
private void txt_Search_TextChanged(object sender, System.EventArgs e)
{
if (txt_Search.Text != "")
{
foreach (ListViewItem item in listView1.Items)
{
if (item.Text.ToLower().Contains(txt_Search.Text.ToLower()))
{
item.Selected = true;
}
else
{
listView1.Items.Remove(item);
}
}
if (listView1.SelectedItems.Count == 1)
{
listView1.Focus();
}
}
else
{
LoadContacts();
RefreshAll();
}
}
There are some things wrong in your code, firstly when modifying a collection in a loop through it, we should not use foreach although in some case it seems to work but not really, it will surely be strange in future and confuse you. We should use a for loop instead and loop in the reverse order. The second wrong thing is you set the Selected to true which may cause your textBox lose focus to the listView. The solution is we have to use some other way to indicate that the item is selected, such as by using BackColor instead:
private void txt_Search_TextChanged(object sender, System.EventArgs e)
{
if (txt_Search.Text != "") {
for(int i = listView1.Items.Count - 1; i >= 0; i--) {
var item = listView1.Items[i];
if (item.Text.ToLower().Contains(txt_Search.Text.ToLower())) {
item.BackColor = SystemColors.Highlight;
item.ForeColor = SystemColors.HighlightText;
}
else {
listView1.Items.Remove(item);
}
}
if (listView1.SelectedItems.Count == 1) {
listView1.Focus();
}
}
else
LoadContacts();
RefreshAll();
}
}
Also after user focusing the ListView, all the BackColor and ForeColor should be reset, we can handle the Enter event of ListView:
//Enter event handler for listView1
private void listView1_Enter(object sender, EventArgs e){
foreach(ListViewItem item in listView1.Items){
item.BackColor = SystemColors.Window;
item.ForeColor = SystemColors.WindowText;
}
}
EDIT
you better not use Text_Changed, rather try Key_Down method as follows
private void txt_Search_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter) //apply your search only when pressing ENTER key
{
// you do your search as it was before
// i personally don't have suggestions here
if (!txt_Search.AutoCompleteCustomSource.Contains(txt_Search.Text)) txt_Search.AutoCompleteCustomSource.Add(txt_Search.Text);
//the line above will save all your searched contacts and display it in a beautiful format
}
else if (txt_Search.Text == "")
{
LoadContacts();
RefreshAll();
}
}
Of course don't forget to set the properties of txt_Search
AutoCompleteMode = SuggestAppend and AutoCompleteSource = CustomSource
This kind of feels like a hack, but you could track the length of text that has been typed into the textbox, and only perform your searching and focus logic if the length of text is greater than the previous time the event was called. That way if someone deletes a letter, the searching and focusing won't occur. Something like:
// declare lastSearchLength as a int outside of your TextChanged delegate
if (!String.IsNullOrEmpty(txt_Search.Text) && txt_Search.Text.Length > lastSearchLength)
{
foreach (ListViewItem item in listView1.Items)
{
if (item.Text.ToLower().Contains(txt_Search.Text.ToLower()))
{
item.Selected = true;
}
else
{
listView1.Items.Remove(item);
}
}
if (listView1.SelectedItems.Count == 1)
{
listView1.Focus();
}
lastSearchLength = txt_Search.Text.Length;
}
else
{
LoadContacts();
RefreshAll();
}
}
You are doing a postback with every key press. When the page reloads, it will not retain focus where you would expect. I recommend implementing this in JavaScript on the client, or using a search button instead of TextChanged event.

Categories