I have an infinite loop caused by what I believe is an event being raised. However I don't understand why the event would be raised.
There is a listbox of items and another checkbox list that accompanies it. The checkboxlist shows child items from each item in the listbox.
private void checkedListBoxSignIn_ItemCheck(object sender, ItemCheckEventArgs e)
{
int userIndex = listBoxSignIn.SelectedIndex;
UpdateUserList();
listBoxSignIn.SelectedIndex = userIndex;
}
private void UpdateUserList()
{
listBoxSignIn.Items.Clear();
foreach (User u in _userList)
{
listBoxSignIn.Items.Add(u.Name);
}
}
private void listBoxSignIn_SelectedIndexChanged(object sender, EventArgs e)
{
int index = listBoxSignIn.SelectedIndex;
UpdateCheckListBox(index);
}
private void UpdateCheckListBox(int index)
{
checkedListBoxSignIn.Items.Clear();
foreach (Item i in _userList.Items)
{
checkedListBoxKartSignIn.Items.Add(i, i.status);
}
}
Once you tick a checkbox a loop starts:
-> checkedListBox_ItemCheck()
-> listbox_SelectedIndexChanged()
-> checkedListBox_ItemCheck()
...
It seems that adding an item to the checked listbox counts as a "Item Check" despite the "Item Check" occuring on construction.
Is this correct behaviour? If so, how do I avoid the loop?
I am using Winforms
When dealing with controls events that will load or modify other controls I usually had a flag to stop infinite loops like the one you might be having.
It is not an ideal solution but it was the simplest I could think at the time.
I haven't run the code below, just is just as illustration:
New variables:
private bool _updatingCheckList = false;
private bool _updatingList = false;
Then:
private void checkedListBoxSignIn_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (_updatingList) return; //if it is already doing it, don't do it again
int userIndex = listBoxSignIn.SelectedIndex;
try {
_updatingList = true;
UpdateUserList();
} finally {
_updatingList = false;
}
listBoxSignIn.SelectedIndex = userIndex;
}
private void UpdateUserList()
{
listBoxSignIn.Items.Clear();
foreach (User u in _userList)
{
listBoxSignIn.Items.Add(u.Name);
}
}
private void listBoxSignIn_SelectedIndexChanged(object sender, EventArgs e)
{
if (_updatingCheckList) return; //if it is already doing it, don't do it again
int index = listBoxSignIn.SelectedIndex;
try {
_updatingCheckList = true;
UpdateCheckListBox(index);
finally { _updatingCheckList = false; }
}
private void UpdateCheckListBox(int index)
{
checkedListBoxSignIn.Items.Clear();
foreach (Item i in _userList.Items)
{
checkedListBoxKartSignIn.Items.Add(i, i.status);
}
}
Another alternative is to remove the handlers before doing the operation and readding.
I hope this helps.
I have managed to fix the issue. The main problem here was that the listbox was being updated everytime anything was done to the checkedbox list. The fix was to ensure that some user interaction has actually taken place. The easiest way for me to do this was a simple if statement checking that both lists have been selected.
private void checkedListBoxSignIn_ItemCheck(object sender, ItemCheckEventArgs e)
{
if(listbox.SelectedIndex > -1 && checkedListBox.SelectedIndex > -1){
int userIndex = listBoxSignIn.SelectedIndex;
UpdateUserList();
listBoxSignIn.SelectedIndex = userIndex;
}
}
Related
Has anyone figured out an event that fires whenever a menu item is highlighted?
I would like to display a description of each menu command in the status bar as they are highlighted. I'd like this to happen whether they are highlighted using the mouse or the keyboard.
But after considerable effort, I don't see any event like this. I even tried overriding WndProc to detect raw menu messages but found none are sent. Apparently, WinForms doesn't use the standard Windows menus.
It seems like knowing when a menu item is clicks and when it is selected (highlighted without being clicked) should be the two most important menu events. I don't know why the latter wouldn't be supported.
Anyone been able to figure this out?
In addition to the mouse events, you can add the keyboard keys part by handling the KeyUp event of the owner menu to get the selected item and display a description in a status-bar label.
public YourForm()
{
InitializeComponent();
menuStrip1.ShowItemToolTips = false;
menuStrip1.KeyUp += OnToolStripKeyUp;
foreach (var item in GetAllToolStripItems(menuStrip1.Items))
{
item.AutoToolTip = false;
item.MouseEnter += OnToolStripItemMouseEnter;
item.MouseLeave += OnToolStripItemMouseLeave;
if (item.GetCurrentParent() is ToolStrip dm)
{
dm.ShowItemToolTips = false;
dm.KeyUp -= OnToolStripKeyUp;
dm.KeyUp += OnToolStripKeyUp;
}
}
}
private void OnToolStripItemMouseEnter(object sender, EventArgs e)
{
sbrLabel.Text = (sender as ToolStripItem).ToolTipText;
}
private void OnToolStripItemMouseLeave(object sender, EventArgs e)
{
sbrLabel.Text = "Ready";
}
private void OnToolStripKeyUp(object sender, KeyEventArgs e)
{
var s = sender as ToolStrip;
var selItem = s.Items.OfType<ToolStripMenuItem>().FirstOrDefault(x => x.Selected);
sbrLabel.Text = selItem?.ToolTipText;
}
private IEnumerable<ToolStripItem> GetAllToolStripItems(ToolStripItemCollection tsic)
{
foreach (var tsi in tsic.Cast<ToolStripItem>())
{
yield return tsi;
if (tsi is ToolStripDropDownItem tsddi && tsddi.HasDropDown)
foreach (var ddi in GetAllToolStripItems(tsddi.DropDownItems))
yield return ddi;
}
}
With #dr.null's help, I got this working. Here's my version of the code.
private void InitializeMenuStatus(ToolStrip toolStrip)
{
toolStrip.ShowItemToolTips = false;
toolStrip.KeyUp += ToolStrip_KeyUp;
foreach (ToolStripItem toolStripItem in toolStrip.Items)
{
toolStripItem.AutoToolTip = false;
toolStripItem.MouseEnter += ToolStripItem_MouseEnter;
toolStripItem.MouseLeave += ToolStripItem_MouseLeave;
if (toolStripItem is ToolStripDropDownItem dropDownItem)
InitializeMenuStatus(dropDownItem.DropDown);
}
}
private ToolStripItem? SelectedMenuItem = null;
private void SetSelectedMenuItem(ToolStripItem? item)
{
if (!ReferenceEquals(item, SelectedMenuItem))
{
SelectedMenuItem = item;
lblStatus.Text = item?.ToolTipText ?? string.Empty;
}
}
private void ToolStripItem_MouseEnter(object? sender, EventArgs e)
{
if (sender is ToolStripMenuItem menuItem && menuItem.Selected)
SetSelectedMenuItem(menuItem);
}
private void ToolStripItem_MouseLeave(object? sender, EventArgs e)
{
SetSelectedMenuItem(null);
}
private void ToolStrip_KeyUp(object? sender, KeyEventArgs e)
{
if (sender is ToolStripDropDownMenu dropDownMenu)
{
ToolStripMenuItem? menuItem = dropDownMenu.Items.OfType<ToolStripMenuItem>()
.Where(m => m.Selected)
.FirstOrDefault();
SetSelectedMenuItem(menuItem);
}
}
Why a Selected event was never added to menu items escapes me. I have suggested that it be added. If you agree, please go and show your support for that request.
If anyone's interested, I spent some time fine tuning this code and ended up making a free component, now published as a NuGet package. You can view the code on GitHub
I am trying to re-enable only the listbox that pertains to the item that has been removed from the output listbox. For example, If I select "Wagon" from Body Type listbox and "Advance" from Package listbox, the listbox named "lstOutPut" displays the following:
What I want to do is, if I were to remove "SUV", I would only want for Body Type listbox re-enabled and not the rest
Here is my code
private void lstBody_SelectedIndexChanged(object sender, EventArgs e)
{
if (lstBody.SelectedItem != null)
{
lstOutput.Items.Add(lstBody.SelectedItem);
lstBody.SelectionMode = SelectionMode.None;
lstBody.Enabled = false;
}
}
private void lstPackage_SelectedIndexChanged(object sender, EventArgs e)
{
if (lstPackage.SelectedItem != null)
{
lstOutput.Items.Add(lstPackage.SelectedItem);
lstPackage.SelectionMode = SelectionMode.None;
lstPackage.Enabled = false;
}
}
And this is what I have for the remove button
private void btnRemove_Click(object sender, EventArgs e)
{
//remove selected item only
while (lstOutput.SelectedItems.Count > 0)
{
lstOutput.Items.Remove(lstOutput.SelectedItems[0]);
}
lstBody.Enabled = true;
lstPackage.Enabled = true;
}
Any ideas?
Thanks.
Assuming there would not be duplicates values among the different input listboxes,you could do the following in the btnRemove_Click event
var inputListBoxes = new[] { lstPackage, lstBody };
while (lstOutput.SelectedItems.Count > 0)
{
inputListBoxes.First(x => x.Items.Contains(lstOutput.SelectedItems[0])).Enabled = true;
lstOutput.Items.Remove(lstOutput.SelectedItems[0]);
}
For each listbox, enable it if non of its items exist in lstOutput:
var listBoxes = new ListBox[] {body, package, wheel, accessories};
foreach(var lst in listBoxes)
lst.Enabled = !lst.Items.Cast<string>().Any(x => lstOutput.Items.Contains(x));
I am working on a Visual Studio extension and my current goal is to set up a menu item in the Tools menu. When clicked on this menu item will open a WinForms window containing a ListView, 3 textboxes, and a button. The idea is when you click on one of the rows in the ListView the data from that row will be populated in the textboxes so that you can update it. If you click the button a new row is added and the textboxes are cleared. However, I'm having an issue with getting the index of the row that I've selected.
private int _index;
private void newSourceBtn_Click(object sender, EventArgs e)
{
// Add new row to the ListView
ListViewItem row = new ListViewItem();
row.SubItems.Add("new");
row.SubItems.Add(String.Empty);
row.SubItems.Add(String.Empty);
remoteSourceListView.Items.Add(row);
int index = remoteSourceListView.Items.Count - 1;
remoteSourceListView.Items[index].Selected = true;
newSourceAdded = true;
sourceNameTextBox.Clear();
sourceUrlTextBox.Clear();
}
public void SourceName_TextChanged(object sender, EventArgs e)
{
remoteSourceListView.Items[IndexSelected].SubItems[1].Text = sourceNameTextBox.Text;
}
public void SourceURL_TextChanged(object sender, EventArgs e)
{
string url = sourceUrlTextBox.Text;
if ((url.StartsWith("http")) || (url.StartsWith("https")) || (url.StartsWith("git")))
{
sourceBranchTextBox.Enabled = true;
}
remoteSourceListView.Items[IndexSelected].SubItems[2].Text = url;
}
public void SourceBranch_TextChanged(object sender, EventArgs e)
{
}
public void SourcesListView_SelectedIndexChanged(object sender, EventArgs e)
{
ListView.SelectedListViewItemCollection selectedRows = remoteSourceListView.SelectedItems;
foreach (ListViewItem row in selectedRows)
{
sourceNameTextBox.Text = row.SubItems[1].Text;
sourceUrlTextBox.Text = row.SubItems[2].Text;
IndexSelected = row.Index;
if (row.SubItems[3].Text != "")
{
sourceBranchTextBox.Enabled = true;
sourceBranchTextBox.Text = row.SubItems[3].Text;
}
}
}
public int IndexSelected
{
get { return _index; }
set { _index = value; }
}
This code shows the button click event which adds the new row to the ListView, the text changed events for each of the textboxes which updates the row in the ListView (sorta), and the selected index changed event for the ListView which is where I'm getting the index of the row that was just selected. While debugging, I noticed that when I click on a row I'm getting the correct index in the selected index changed event; however, when I call IndexSelected from either of the text changed events it is always giving me a different index.
Any suggestions?
From the code posted I can't find any reason that explain the behavior documented.
A possible reason could be the insertion/deletion of new/existing ListViewItem in a position before the saved RowIndex.
However another approach is possible. Instead of keeping the RowIndex you could try to set a global property to the ListViewItem selected and reuse this instance when you need to set its subitems.
In this way you avoid problems if the number of ListViewItems change and some item is inserted/removed before the saved RowIndex. However a safeguard against a null value should be provided.
private ListViewItem CurrentItemSelected {get;set;}
......
public void SourcesListView_SelectedIndexChanged(object sender, EventArgs e)
{
ListView.SelectedListViewItemCollection selectedRows = remoteSourceListView.SelectedItems;
foreach (ListViewItem row in selectedRows)
{
sourceNameTextBox.Text = row.SubItems[1].Text;
sourceUrlTextBox.Text = row.SubItems[2].Text;
CurrentItemSelected = row;
if (row.SubItems[3].Text != "")
{
sourceBranchTextBox.Enabled = true;
sourceBranchTextBox.Text = row.SubItems[3].Text;
}
}
}
public void SourceName_TextChanged(object sender, EventArgs e)
{
if(CurrentItemSelected != null)
CurrentItemSelected.SubItems[1].Text = sourceNameTextBox.Text;
}
However, I am a bit perplexed by your code. Do you have the property MultiSelect set to true? Because if it is set to false then your code doesn't need to loop.
public void SourcesListView_SelectedIndexChanged(object sender, EventArgs e)
{
if(remoteSourceListView.SelectedItems.Count > 0)
{
// With MultiSelect = false; there is only one selected item.
CurrentItemSelected = remoteSourceListView.SelectedItems[0];
sourceNameTextBox.Text = CurrentItemSelected.SubItems[1].Text;
sourceUrlTextBox.Text = CurrentItemSelected.SubItems[2].Text;
if (CurrentItemSelected.SubItems[3].Text != "")
{
sourceBranchTextBox.Enabled = true;
sourceBranchTextBox.Text = CurrentItemSelected.SubItems[3].Text;
}
}
}
I'm trying to make it in my program, if you click on a text box it will select it all. And then if you click it again it deselects it all.
I've tried making it like this..
private void url_MouseDown(object sender, MouseEventArgs e)
{
url.ReadOnly = false;
url.SelectAll();
url.DeselectAll();
}
I know the url.DeselectAll(); is in the wrong spot.
Any help? Thanks in advance!
Clicking the textbox itself clears a selection, so you'd have to do something like this;
bool selected;
private void url_MouseDown(object sender, MouseEventArgs e)
{
url.ReadOnly = false;
if (!selected)
{
selected = true;
url.SelectAll();
}
else
{
selected = false;
url.DeselectAll();
}
}
Your current code first calls
url.SelectAll();
and then immediately calls
url.DeselectAll();
Instead, check the current state of the item you are trying to toggle. It's not clear to me from the question exactly what that is, so in pseudocode:
private bool isSelected = false;
private void url_MouseDown(object sender, MouseEventArgs e)
{
url.ReadOnly = false;
if (isSelected)
{
url.DeselectAll();
}
else
{
url.SelectAll();
}
isSelected = !isSelected;
}
Replace IsDeselected with something that checks whether the current state is deselected or not.
Your code always selects and then deselects again, so your text will always be deselected after mouse down.
Try this instead:
private void url_MouseDown(object sender, MouseEventArgs e)
{
url.ReadOnly = false;
if (url.SelectedText.Length < url.Text.Length) {
url.SelectAll();
} else {
url.DeselectAll();
}
}
I want to lock my wpf tab to change the index but I'm getting dispatcher error messages with my code below. Where am I doing wrong? I'm aware that once the content changes, it fires the same event but is there any other event to fire for this ?
private void MainTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!(e.Source is TabControl))
return;
if (Helper.GetProperty<bool>("IsTabLocked")) // my condition
{
MessageBox.Show("tab is locked");
e.Handled = true;
return;
}
The easiest solution that I can see is setting the required tab as the selected one when the execution comes to SelectionChanged event.
Try something like below.
int MyPreferedTabPageIndex = 1; // ?
private void MainTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Helper.GetProperty<bool>("IsTabLocked")) // my condition
{
MainTabControl.SelectedIndex = MyPreferedTabPageIndex ;
MessageBox.Show("tab is locked");
}
}
I could come up with a custom solution with the source code below, but I'm sure someone has thought about this and there is an event or an easier trick which I've never heard before.
static int TabControlIndex = 0;
private void MainTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!(e.Source is TabControl))
return;
if (TabControlIndex == MainTabControl.SelectedIndex)
return;
if (Helper.GetProperty<bool>("IsTabLocked") && TabControlIndex != MainTabControl.SelectedIndex)
{
MessageBox.Show("locked");
MainTabControl.SelectedIndex = TabControlIndex;
// = true;
return;
}