Deleting multiple checked items from an SQL bound checklist - c#

So right now, I have tried the following code, however it only removes one list item. It seems that after it deletes the first item, the list refreshes and the other checked item does not get deleted. How can I go around this so that all the checked items are deleted from the DB Table?
//NewFoodInputTextBox is an ASP.NET TextBox which takes input
//just fine and outputs status/error updates as well.
protected void DeleteFoodButton_Click(object sender, EventArgs e)
{
try
{
int delCount = 0;
int prevDelCount = 0;
string status = "";
foreach (ListItem Item in FoodChecklist.Items)
{
if (Item.Selected)
{
FoodList.Delete(); //only deletes 1 item
prevDelCount = delCount;
delCount += 1;
if (delCount > prevDelCount) //printing out the deleted items
{
status = status + " " + Item.ToString(); //returns all checked items normally
}
}
}
if (delCount == 0)
{
NewFoodInputTextBox.Text = "Nothing selected to delete";
}
else
{
NewFoodInputTextBox.Text = "Deleted the following: " + status;
}
}
catch
{
NewFoodInputTextBox.Text = "Unexpected behavior detected";
}
}

Your problem is that you are using the Selected items, and not the Checked ones. These are 2 different things (selected is the blue highlight, checked is the checkbox). Use the Checked property in your condition and everything should work fine.
Side note, you could also use the CheckedItems property of the ListView in your foreach to simplify your code.
foreach (ListItem Item in FoodChecklist.CheckedItems)

Related

Winforms ListView item selection bug

I'm making an application that uses a ListView control with MultiSelect = false. In some situations I need to prevent the user from changing the selected item. I thought it would be a simple task, but hours later I'm still trying to figure out what's going on.
So in order to have the option to "freeze" the ListView selection, I made a custom class CListView that inherits from ListView. If FreezeSelection is set to true, every time the users changes the selection, I'm trying to change it back:
public class CListView : ListView
{
public bool FreezeSelection { get; set; } = false;
bool _applyingSelectionUpdates = false;
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (FreezeSelection)
{
if (_applyingSelectionUpdates)
return;
// for simplicity consider that the selected index while the selection is frozen is always 2
int selectedIndex = 2;
_applyingSelectionUpdates = true;
try
{
SelectedIndices.Clear();
if (selectedIndex >= 0)
SelectedIndices.Add(selectedIndex);
}
finally { _applyingSelectionUpdates = false; }
return;
}
base.OnSelectedIndexChanged(e);
}
}
The problem is when I set FreezeSelection back to false, and the user tries to select a different item. First of all, even if MultiSelect is false, visually it appears as there are two items selected. But programatically, when the user changes the selection, it seems there is sometimes the correct item selected, sometimes no item selected.
This behaviour is clearly a bug and I suspect what is causing this bug. When the user clicks on an item, the event SelectedIndexChanged is fired twice. Once after the SelectedIndices collection is cleared and the second time after the clicked item is added to the collection of selected items. I think the bug is caused by changing the selected items between these two events, but I need to know more about this. If MultiSelect is true and the user tries to select items with Ctrl, I have no problems.
To reproduce this bug you can use the following TestForm:
public class TestForm : Form
{
CListView listView;
CheckBox checkBox;
public TestForm()
{
listView = new() { Dock = DockStyle.Fill, View = View.Details, FullRowSelect = true, MultiSelect = false };
listView.Columns.Add("col 1");
listView.SelectedIndexChanged += ListView_SelectedIndexChanged;
Controls.Add(listView);
checkBox = new() { Dock = DockStyle.Right, Text = "freeze selection" };
checkBox.CheckedChanged += CheckBox_CheckedChanged;
Controls.Add(checkBox);
listView.Items.Add("item 1");
listView.Items.Add("item 2");
listView.Items.Add("item 3");
listView.Items.Add("item 4");
}
private void CheckBox_CheckedChanged(object? sender, EventArgs e)
{
listView.FreezeSelection = checkBox.Checked;
}
DateTime lastSelChangeTime = DateTime.MinValue;
private void ListView_SelectedIndexChanged(object? sender, EventArgs e)
{
if ((DateTime.Now - lastSelChangeTime).TotalMilliseconds > 200)
Debug.WriteLine(""); // this is just to group together what happens on a single user interaction
var indices = listView.SelectedIndices.Cast<int>().ToArray();
Debug.WriteLine("CListView fired selection changed event! "
+ DateTime.Now.ToString("h:m:s:fff") + " "
+ "{ " + string.Join(", ", indices) + " }");
lastSelChangeTime = DateTime.Now;
}
}
If you run this form:
Select the third item (with index 2)
Check "freeze selection"
Click on the forth item
Uncheck "freeze selection"
Try changing the selected item now and observe the bug
The question is how to solve this bug or how to achieve my initial goal (prevent users from selecting a different item).
Update:
To clarify, what I refered to as "a bug" is not the fact that I get two events for one selection change (I'm fine with that), it's the inconsistent behaviour between the UI and ListView.SelectedIndices after I "unfreeze" the selected index. I will demonstrate the problem with the following picture (note that each screenshot is taken after I clicked where the cursor is positioned; also the output window shows the SelectedIndices every time I get an SelectedIndexChanged event):
I use .NET 6.0.
As others have mentioned, there is no bug here as shown in this sequence of selecting Item 1, then Selecting Item2 (which first changes the selection by deselecting Item 1.
If you don't want the User to be selecting things during some arbitrary task (like waiting for a modified document to be saved), why not just set ListView.Enabled to false while you perform the work? In the testcode referenced below, I made an all-in-one for when the checkbox changes that sets the SelectionIndices collection to '2' as in your post;
There are now no issues going back to a state where freeze selection is unchecked and selecting some new item.
public TestForm()
{
InitializeComponent();
listView.MultiSelect = false;
listView.Columns.Add("col 1");
for (int i = 1; i <= 4; i++) listView.Items.Add($"Item {i}");
listView.SelectedIndexChanged += (sender, e) =>
{
richTextBox.AppendLine(
$"{DateTime.Now} : [{string.Join(", ", listView.SelectedIndices.Cast<int>())}]" );
var sel =
listView
.SelectedItems
.Cast<ListViewItem>();
if (sel.Any())
{
foreach (var item in sel)
{
richTextBox.AppendLine(item);
richTextBox.AppendLine();
}
}
else richTextBox.AppendLine("No selections", Color.Salmon);
};
checkBox.CheckedChanged += (sender, e) =>
{
listView.Enabled = !checkBox.Checked;
if (checkBox.Checked) doWork();
};
void doWork()
{
listView.SelectedIndices.Clear();
listView.SelectedIndices.Add(2);
}
}
Uses this extension for RichTextBox
static class Extensions
{
public static void AppendLine(this RichTextBox richTextBox) =>
richTextBox.AppendText($"{Environment.NewLine}");
public static void AppendLine(this RichTextBox richTextBox, object text) =>
richTextBox.AppendText($"{text}{Environment.NewLine}");
public static void AppendLine(this RichTextBox richTextBox, object text, Color color)
{
var colorB4 = richTextBox.SelectionColor;
richTextBox.SelectionColor = color;
richTextBox.AppendText($"{text}{Environment.NewLine}");
richTextBox.SelectionColor = colorB4;
}
}

C# ListView search item without clear list

I have winform project on C# platform. I have listview and textbox as you see in pic below.
I want to reorder the list according to the text value entered by the user.
I researched before ask here, I generally saw solutions based on removing and re-adding all units to listview again. I don't want to do that because my listview has too many items with pictures so removing and re-adding items causes listview to work slowly.
What I want is that, when the user enters characters in the textbox, the items which starts with this characters, bring this items the top of the list something similar google search system.
I tried the codes below but this send the item at the end of the list even though i chose index 0.
Thanx.
private void txt_search_TextChanged(object sender, EventArgs e)
{
string text = txt_search.Text;
var item = listView1.FindItemWithText(text);
if (item != null)
{
int index = listView1.Items.IndexOf(item);
if (index > 0)
{
listView1.Items.RemoveAt(index);
listView1.Items.Insert(0, item);
}
}
}
ListView is sorted using the .Sort() function, not sure what the default behaviour is, but I think you need a custom comparer.
Here is an example implementation by (ab)using the ListViewItem.Tag.
Custom Comparer:
private class SearchCompare : Comparer<ListViewItem>
{
public override int Compare(ListViewItem x, ListViewItem y)
{
if (x?.Tag != null && y?.Tag != null)
{
return x.Tag.ToString().CompareTo(y.Tag.ToString());
}
return 0;
}
}
Initializing the ListView:
var items = new[]
{
"1 no",
"2 yes",
"3 no",
"4 yes"
};
foreach (var item in items)
{
listView1.Items.Add(item);
}
listView1.ListViewItemSorter = new SearchCompare(); // custom sorting
And ofcourse the text changed event handler:
private void textBox1_TextChanged(object sender, EventArgs e)
{
string text = textBox1.Text;
foreach (ListViewItem item in listView1.Items)
{
if (item.Text.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1)
{
item.Tag = "a"; // a is sorted before b
}
else
{
item.Tag = "b"; // b is sorted after a
}
}
listView1.Sort();
}
Typing "yes" in the search textbox will sort items 2 and 4 in front of items 1 and 3.

Datagridview not updating correctly

I'm new-ish to C# and I'm trying to input a list of objects into a datagridview. I constantly add items to this list each time I click a button and the datagridview should refresh by setting it's data source back to the list.
Here is the list:
List<Models.OrderItem> orderitemlist = new List<Models.OrderItem>();
And here is the code that adds to the list and refreshes the list:
private void btnAddToOrder_Click(object sender, EventArgs e)
{
int quantity = Convert.ToInt32(tbAddOrderQuantity.Text);
int stock = Convert.ToInt32(ItemDataGrid.CurrentRow.Cells[6].Value);
int newstock = stock - quantity;
if (newstock < 0)
MessageBox.Show("You do not have enough items in stock for this.");
else
{
ItemDataGrid.CurrentRow.Cells[6].Value = newstock;
int itemID = Convert.ToInt32(ItemDataGrid.CurrentRow.Cells[0].Value);
string itemname = Convert.ToString(ItemDataGrid.CurrentRow.Cells[1].Value);
int sellprice = Convert.ToInt32(ItemDataGrid.CurrentRow.Cells[5].Value);
Models.OrderItem item = new Models.OrderItem(itemID, itemname, sellprice, quantity);
orderitemlist.Add(item);
RefreshItemsOnOrderData();
RefreshPrice();
}
}
private void RefreshItemsOnOrderData()
{
ItemOnOrderDataGrid.DataSource = orderitemlist;
}
The list will update with the first item however when I try to add another item it seems to run the block of code however doesn't actually add it to the datagrid view. Is anyone able to help? Have I made a simple error I just can't see?
As mentioned,
Set the source to null, re-ref the list, then reset bindings
ItemOnOrderDataGrid.DataSource = null;
ItemOnOrderDataGrid.DataSource = orderitemlist;
ItemOnOrderDataGrid.ResetBindings();
You may want to try omitting the null. I can't recall if this works without the null.

Hide item from Combobox temporarily

I was wondering if there was a way to temporarily hide or remove an item from a combo box that is being populated from a database?
I have two combo boxes that are being populated by the same column (To and From Machine Number)
You can't really have Motor1 connected to Motor1, and short of having to repopulate an entire combo box with each selection, I was thinking there has to be a way to temporarily hide the same selection from the second combobox.
Please let me know if you need information on how the comboboxes are being populated(code etc.)
EDIT Here is the population code for the combo boxes:
void PopulateCreateView(CableID_CreateView CView)
{
// Creates a new Model, and gets data from the Db.
CModel = new CableID_Model();
CModel.CNId = 1;
Database_Facade.Operation_Switch(OPREAD);
// Populates the form with data for the Plant Area Codes, Supplier Info and Major Equipment.
foreach (PlantAreaCode_Model Model in PlantAreaCode_Controller.PList)
{
CView.cmbAreaCode.Items.Add(Model.AreaName);
CView.lblDummy.Text = Model.AreaName;
if (CView.lblDummy.Width > CView.cmbAreaCode.DropDownWidth)
{
// Sets the width +20 to allow for the scroll bar.
CView.cmbAreaCode.DropDownWidth = CView.lblDummy.Width + 20;
}
}
foreach (SupplierID_Model Model in SupplierID_Controller.SList)
{
if (Model.CondConfig != null) { CView.cmbXsec.Items.Add(Model.CondConfig); }
if (Model.Insulation != null)
{
CView.cmbInsulation.Items.Add(Model.Insulation);
CView.lblDummy.Text = Model.Insulation;
if (CView.lblDummy.Width > CView.cmbInsulation.DropDownWidth)
{
// Sets the width +20 to allow for the scroll bar.
CView.cmbInsulation.DropDownWidth = CView.lblDummy.Width + 20;
}
}
}
foreach (MajorEquipment_Model Model in MajorEquipment_Controller.MeList)
{
CView.cmbFromLoc.Items.Add(Model.EqipmentNumber);
CView.cmbToLoc.Items.Add(Model.EqipmentNumber);
}
}
Here is the code for the MySQL Query:
public void GetCableId(CableID_Model CModel)
{
DbConnect();
try
{
MajorEquipment_Controller.MeList = new List<MajorEquipment_Model>();
mySqlCommand = mySqlConnect.CreateCommand();
mySqlCommand.CommandText = "SELECT * FROM MajorEquipment;";
mySqlReader = mySqlCommand.ExecuteReader();
while (mySqlReader.Read())
{
MajorEquipment_Controller.MeList.Add(new MajorEquipment_Model
{
EqipmentNumber = Convert.ToString(mySqlReader["EquipmentNumber"])
});
}
mySqlReader.Close();
mySqlCommand.ExecuteNonQuery();
}
catch (MySqlException e) { MessageBox.Show(e.Message); }
finally
{
if (mySqlConnect != null)
{
mySqlConnect.Close();
}
}
}
Unfortunately there is no simple way to keep a ListBox / ComboBox item hidden.
What you can do is simplfy the logic you are using to Load item onto the List. Since both the ComboBox are list of machines that are bound to be dependent on each other
To keep a hidden item you have to create your custom controls that only renders the visible item from collection.
A pseudocode solution would be
On DropDownListChange
Get Selected Item
for all other drop down lists
restore items from backup
if selected value is not default
remove current item from list box

Adding items to List<> of object which already contain data, add duplicate item in the List

I am developing a Windows phone app for which I want to get a list of first 15 object from my web service plus an extra item to represent the "Load next 15 items button" and bind it to a listbox. On selection of the last element or index(buttton), I want to remove the last item(button) from the list and call the service again to get the next 15 items plus the button item which will again add up to the current list without clearing the list. And the same process goes on as we select the last item from the listbox.
My problem is I am able to get it correctly for the first round. In the second round, the next 15 element is added to the list twice. I don't know what is the real problem. I am not getting any error or exception also.
Below is what I have done so far :
private void ListBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int LastIndex=app.imgItem.Count - 1;
if (ListBox1.SelectedIndex == LastIndex)
{
app.imgItem.Remove(app.imgItem[app.imgItem.Count-1]);
proxy.SelectNextItemsCompleted += new EventHandler<ServiceReference1.SelectNextItemsCompletedEventArgs>(proxy_SelectNextItemsCompleted);
int id = app.imgItem.Last().Id;
proxy.SelectNextItemsAsync(categoryID, id);
}
}
void proxy_SelectNextItemsCompleted(object sender, ServiceReference1.SelectNextItemsCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("Cannot load data.");
return;
}
else
{
foreach (var item in e.Result)
{
ImgData idata = new ImgData();
idata.CategoryID = item.CategoryID;
idata.ImageID = item.ImageID;
idata.ImageTitle = item.ImageTitle;
idata.Thumbnail = item.Thumbnail;
app.imgItem.Add(idata);
}
}
ImageData btnData = new ImageData();
btnData.CategoryID = 0;
btnData.ImageID = 0;
btnData.ImageTitle = "";
btnData.Thumbnail = "Images/loadButton.jpg";
app.imgItem.Add(btnData);
ListBox1.ItemsSource = app.imgItem;
}
It looks like you're re-registering an event handler in the SelectionChanged event handler, and as a result, the SelectNextItemsCompleted handler is probably getting called twice the second time around:
proxy.SelectNextItemsCompleted += new EventHandler<ServiceReference1.SelectNextItemsCompletedEventArgs>(proxy_SelectNextItemsCompleted);
You should probably be registering that event handler somewhere else where the code only runs once.

Categories