Disable multiple controls based upon Combo Box value - c#

I have tried a number of iterations to get this to work, and although I am close, I cannot seem to solve this.
private void cbxMealsNum_SelectedIndexChanged(object sender, EventArgs e)
{
int count = 0;
int cbxCurr = cbxMealsNum.SelectedIndex+1;
foreach (Control control in foodMeals.Controls.OfType<ComboBox>().Where(x => x.Name.Contains("MealsNo" + cbxMealsNum.Text)))
{
TextBox textBox = control as TextBox;
if (count < cbxCurr)
{
control.Enabled = true;
}
else
{
control.Enabled = false;
}
count++;
}
}
The above code allows me to Enable the correct combo box but none of the combo boxes before it. As such, if I select '6' in cbxMealsNum, meal 6 is enabled, but not the 5 preceding it.
Thus, I am asking how I would change the index for cbxMealsNum to, say, 4, and have only cbxMealsNo1 through to 4 enabled. If I then change cbxMealsNum to 3, cbxMealsNo4 should be disabled. If I change cbxMealsNum to 5, cbxMealsNo4 should be enabled once more, as should cbxMealsNo5.
I have tried a number of iterations of this code, including the following answer here as seen in the above sample, but to no avail. I am new to C# but I have been looking for every possible solution I can. I do not know if my search terms are malformed.
Please note, I have used the Where method as I intend to add textboxes and other controls contain the same naming convention (thus; cbxMealsNo1, txtMealsNo1, lblMealsNo1, and so on)

I am assuming you want all the combos enabled up to the selected number in the “number of meals” combo box. If this is the case then the code below may help.
First, it may be easier to put all the combo boxes into a collection since you will be needing them each time the “number of meals” combo box changes. It seems unnecessary to “re-collect” them each time. In the example below I created a simple array of six (6) combo boxes. We can then use that array to loop through all the combo boxes and enable the proper combo boxes.
ComboBox[] combos = new ComboBox[6];
public Form1() {
InitializeComponent();
combos[0] = cbxMealsNo1;
combos[1] = cbxMealsNo2;
combos[2] = cbxMealsNo3;
combos[3] = cbxMealsNo4;
combos[4] = cbxMealsNo5;
combos[5] = cbxMealsNo6;
}
Then in the “number of meals” selection changed event, a simple loop to enable all the combo boxes that are less than the selected number of meals. Something like….
private void cbxMealsNum_SelectedIndexChanged(object sender, EventArgs e) {
int cbxCurr = cbxMealsNum.SelectedIndex;
for (int i = 0; i < combos.Length; i++) {
if (i <= cbxCurr) {
combos[i].Enabled = true;
}
else {
combos[i].Enabled = false;
}
}
}

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;
}
}

How to deal with a change of Radio Button selection after navigating back?

I have a set of RadioButtons in a repeater. Once a RadioButton is checked, the related repetition is determined in a loop through its items and a parameter is saved to a Data Base. This triggers the loading of the next question with a new set of RadioButtons is. So far so good.
If the user navigates back to the previous question then he could post a new selection and mess up everything. To deal with this unwanted operation, I wrote this code for the RadioButton OnChecked event:
All repeaters Radio Buttons are first set back to unchecked and then the latest selection restored with ((RadioButton)sender).Checked = true.
Afterwards I determine the related parameter and overwrite the already existing record for that question with the new value.
protected void RB_Checked(object sender, EventArgs e)
{
for (int i = 0; i <= RPT_DisplayInquiry.Items.Count - 1; i++)
{
RadioButton rb = (RadioButton)RPT_DisplayInquiry.Items[i].FindControl("RB_Choice");
rb.Checked = false;
}
((RadioButton)sender).Checked = true;
for (int i = 0; i <= RPT_DisplayInquiry.Items.Count - 1; i++)
{
RadioButton rb = (RadioButton)RPT_DisplayInquiry.Items[i].FindControl("RB_Choice");
if (rb.Checked == true)
{
try
{
string Choice = Convert.ToString(i + 1);
Label lbl = (Label)RPT_DisplayInquiry.Items[i].FindControl("Lbl_Structure");
SaveSelection(lbl.Text, Choice, 0);
}
catch (Exception ex)
{
throw ex;
}
}
}
}
For some reason this doesn't work. If I navigate back again after the new selection is saved, I see that the reset had not worked and that 2 RadioButtons are selected. Therefore the saved value is not necessarily correct as it is just related to the first found checked RadioButton in the repeater.
If I eliminate the call of the Save Routine from that methode and call it afterwards from a separate click event then the whole thing works.
Does the reset of the RadioButton not work as they are never rendered?
What is going on?

Multiple checkbox selections to display in textbox

I'm new to this, please bare with...
I have 4 checkboxes, each displaying a $ amount depending on the selection.
I have it working so that if 1 checkbox is selected, textbox shows $1.00 or $20.00.
But, I'm having trouble with if 2 checkboxes are selected it would be $21.00 or 3 checkboxes or all 4.
I have this to show one checkbox selection.
if (checkBox247Access.Checked)
{
textBoxExtras.Text = "$1.00";
}
something like this should work, but as in comment above you might want to use particular container to iterate through i.e. iterate through groupbox instead of entire form just incase you have checkboxes in other container/groupboxes
foreach (Control ctrl in form1.Controls)
{
if (ctrl is CheckBox)
{
//do the logic of if checkbox is checked
}
}
One thing you can do, is handle the CheckedChanged event for all 4 checkboxes. You can even use the same eventhandler for all 4.
Then, in the handler, add up alle the selected extra options, and add them to a variable. After figuring out the total amount, update the textbox.
Here's an example:
private void checkBox_CheckedChanged(object sender, EventArgs e)
{
int amount = 0;
if (checkBox247Access.Checked)
{
amount += 20;
}
if (checkBoxFreeCoffee.Checked)
{
amount += 1;
}
if (checkBoxExtraThing.Checked)
{
amount += 3;
}
if (checkBoxBonusThing.Checked)
{
amount += 11;
}
textBoxExtras.Text = "$" + amount.ToString("n2");
}

Textboxes in an Array, Connect 4

I'm trying to create a Connect 4 game using textboxes and buttons.
If you click on the button, the textbox background color is filled in and the text will either be filled with x or y.
private void button2_Click(object sender, EventArgs e)
{
Output_textBox.AppendText("You have inserted in Column 2");
Output_textBox.AppendText(Environment.NewLine);
TextBox[] boxes = { textBox21, textBox22, textBox23, textBox24, textBox25, textBox26 };
for (int i = 0; i < 6; i++)
{
if (boxes[i].BackColor != SystemColors.HotTrack)
{
boxes[i].BackColor = SystemColors.HotTrack;
boxes[i].Text = "Y";
}
}
}
(edited on 11/6/2013 2:25pm)
The Code above doesn't seem to work even though it seems to make perfect sense to me that it should.
What i want is for the textbox to turn one by one. So if i click on the button once. textbox11 changes first. Then if textbox11 is filled, textbox12 is filled next when i click on the button again etc.
The coloring needs to be from bottom to top in a column
About me: I'm new to coding. Sorry for the trouble
Thank you in advanced

ComboBox SelectionChangeCommitted event doesn't work with AutoComplete

Here is a short program that reproduces the problem I just encountered. This was compiled under MS Windows 7 with .NET 4.0, just in case that makes a difference.
using System;
using System.Drawing;
using System.Windows.Forms;
// Compile with "csc /target:exe /out:comboboxbug.exe /r:System.dll /r:System.Drawing.dll /r:System.Windows.Forms.dll comboboxbug.cs"
// in a Visual Studio command prompt.
static class Program
{
[STAThread]
static void Main()
{
//Create a label.
Label oLabel = new Label();
oLabel.Location = new Point (10, 10);
oLabel.Size = new Size (100, 15);
oLabel.Text = "Combo box bug:";
// Create a combo-box.
ComboBox oComboBox = new ComboBox();
oComboBox.Location = new Point (10, 50);
oComboBox.Size = new Size (150, 21);
oComboBox.Items.AddRange (new object[]
{ "A", "A B", "A C", "A B C", "A C B", "A B C D", "A C B D" });
oComboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
oComboBox.AutoCompleteSource = AutoCompleteSource.ListItems;
oComboBox.SelectionChangeCommitted
+= new EventHandler (comboBox_SelectionChangeCommitted);
// Create a form.
Form oForm = new Form();
oForm.Size = new Size (200, 150);
oForm.Controls.Add (oLabel);
oForm.Controls.Add (oComboBox);
// Run this form.
Application.Run (oForm);
}
static void comboBox_SelectionChangeCommitted (object sender,
EventArgs e)
{
MessageBox.Show ("SelectionChangeCommitted");
}
}
Click in the text portion of the combo-box and type "A". You will get a list of autocomplete suggestions. Click one of the selections with your mouse. The SelectionChangeCommitted event doesn't happen!
Select a menu-item without using autocomplete. You'll get a message-box showing that the SelectionChangeCommitted event happened!
Given that the selection was changed by the user in both cases, shouldn't SelectionChangeCommitted be called in both cases?
Using the SelectedIndexChanged event is not an option, because for the application behind this canned example, I only want it to happen when the user makes a selection, not when it's set programmatically.
EDIT 2020-Oct-28: I found another case where SelectionChangeCommitted doesn't get called! Auto-complete doesn't even need to be set for the problem to happen! Click to open the combo-box, press a key that matches the beginning of one of the combo-box items, and then press Tab to leave. The combo-box item gets selected, but SelectionChangeCommitted is not called! My revised answer is below.
Using the SelectedIndexChanged event is not an option, because for the application behind this canned example, I only want it to happen when the user makes a selection, not when it's set programmatically.
You could also accomplish this by writing a wrapper method for changing the selection that temporarily disables your event.
Unfortunately I do not know off hand a solution to the issue that SelectionChangeCommitted not being started for the more general case (such as where you don't control the ComboBox or how it is accessed).
EDIT:
I made a streamer of all the events that ComboBox calls, and it doesn't appear that any other event will do what you are looking for. The only solution I can think of would involve hooking into the events that the AutoComplete triggers. The difficulty is knowing what those events are, since they don't appear to trigger off the ComboBox from what my minor testing shows.
FYI, here was the best solution I ever came up with. Obviously, this is a Leave event-handler on a ComboBox subclass. The SelectionChangeCommitted event doesn't happen on the mouse-click, but at least it happens during the normal flow of GUI interaction.
private void this_Leave (object sender, EventArgs e)
{
// If this is an autocomplete combo-box, select the
// item that was found by autocomplete.
// This seems like something that ComboBox should be
// doing automatically...I wonder why it doesn't?
if (this.AutoCompleteMode != AutoCompleteMode.None)
{
// Determine which combo-box item matches the text.
// Since IndexOf() is case-sensitive, do our own
// search.
int iIndex = -1;
string strText = this.Text;
ComboBox.ObjectCollection lstItems = this.Items;
int iCount = lstItems.Count;
for (int i = 0; i < iCount; ++i)
{
string strItem = lstItems[i].ToString();
if (string.Compare (strText, strItem, true) == 0)
{
iIndex = i;
break;
}
}
// If there's a match, and this isn't already the
// selected item, make it the selected item.
//
// Force a selection-change-committed event, since
// the autocomplete was driven by the user.
if (iIndex >= 0
&& this.SelectedIndex != iIndex)
{
this.SelectedIndex = iIndex;
OnSelectionChangeCommitted (EventArgs.Empty);
}
}
}
If someone got this problem, I suggest a solution that works fine to me...
Think with me, to accept the suggest of Combo-box, generally the user needs to key down with an Enter key.
You can write into KeyDown event of Combo-box property, something like this:
private void cboProperty_SelectionChangeCommitted(object sender, EventArgs e)
{
//Call here the event of SelectionChangeCommitted
cboProperty_SelectionChangeCommitted(sender,null);
}
It will raise the SelectionChangeCommitted on the right time.
This workaround worked fine for me and hope for you too. When use Autocomplete by typing data in your combo box to get an item through keyboard or mouse selection you need _KeyDown event. From inside invoke _SelectionChangeCommitted method which contains code for other operations. See code below:
private void YourComboBox_KeyDown(object sender, KeyEventArgs e)
{
//Works also when user select and click on autocomplete list.
if (e.KeyCode == Keys.Enter && YourComboBox.SelectedItem != null)
YourComboBox_SelectionChangeCommitted(sender, e);
}
For the non-auto-complete case mentioned above (i.e. my 2020-Oct-28 edit), this Leave event-handler on a ComboBox subclass incorporates the new case as well as the old one, as long as your SelectionChangeCommitted event-handler is idempotent. Compared to my previous answer, it removes the test for auto-complete, and always calls OnSelectionChangeCommitted().
private void this_Leave (object sender, EventArgs e)
{
// Determine which combo-box item matches the text.
// Since IndexOf() is case-sensitive, do our own
// search.
int iIndex = -1;
string strText = this.Text;
ComboBox.ObjectCollection lstItems = this.Items;
int iCount = lstItems.Count;
for (int i = 0; i < iCount; ++i)
{
string strItem = lstItems[i].ToString();
if (string.Compare (strText, strItem, true) == 0)
{
iIndex = i;
break;
}
}
// If there's a match, and this isn't already the
// selected item, make it the selected item.
if (iIndex >= 0
&& this.SelectedIndex != iIndex)
this.SelectedIndex = iIndex;
// Force a selection-change-committed event, since
// the autocomplete was driven by the user.
OnSelectionChangeCommitted (EventArgs.Empty);
}

Categories