I have a timer event that does several things. One item I am trying to get it to do is to programmatically remove the CheckListBox items that are checked once the timer hits the completed action I am performing.
This is the code for the timer and what I have tried to do.
private void timer1_Tick(object sender, EventArgs e)
{
string s;
if (DbFirmwareUpdateComplete.WaitOne(1))
{
DbFirmwareUpdateComplete.Reset();
mnuLoadKeyFile.Enabled = true;
}
if (DbUpdateComplete.WaitOne(1))
{
DbUpdateComplete.Reset();
mnuLoadKeyFile.Enabled = true;
btnLoad.Enabled = true;
}
if (CacheComplete.WaitOne(1))
{
CacheComplete.Reset();
btnLoad.Enabled = true;
}
if (UpdateRunning)
{
bool UpdateDone = true;
int StillActive = 0;
// loop through all active jobs to check if all have completed
foreach (clsCnaPair cna in ActiveJobs)
{
if (cna.Job.JobComplete == false)
{
UpdateDone = false;
StillActive++;
}
else
{
if (cna.Job.UpdateSuccess)
{
// Update color of CLB.Items.Selected if success.
int count = CLB.Items.Count;
for (int index = count; index > 0; index--)
{
if(CLB.CheckedItems.Contains(CLB.Items[index-1]))
{
CLB.Items.RemoveAt(index - 1);
}
}
}
else
{
// Update color of CLB.Items.Selected if failed.
}
}
}
if (UpdateDone)
{
UpdateRunning = false;
log("All Update jobs have finished.");
}
if (ckTop.Checked == true)
{
ckTop.Checked = false;
}
else
{
ckTop.Checked = false;
}
When I run the program and it hits this piece;
if (cna.Job.UpdateSuccess)
{
// Update color of CLB.Items.Selected if success.
int count = CLB.Items.Count;
for (int index = count; index > 0; index--)
{
if(CLB.CheckedItems.Contains(CLB.Items[index-1]))
{
CLB.Items.RemoveAt(index - 1);
}
}
}
I get an error:
System.ArgumentOutOfRangeException: InvalidArgument=Value of '-1' is not valid for 'index'.
Parameter name: index
The Error occurs after this piece of code;
private void CLB_SelectedIndexChanged(object sender, EventArgs e)
{
// One of the CNA IPs was selected. sender is the CheckedListBox.
// Here we want to display its fingerprint in the text box, or if the push is running, the status.
// get the CnaPair class represented by this IP:
clsCnaPair CnaPair = (clsCnaPair)CLB.Items[CLB.SelectedIndex];
// Display the corresponding fingerprint string in the editBox:
if (CnaPair.Job != null) txtStatus.Text = CnaPair.Job.GetStatus();
else txtStatus.Text = CnaPair.GetInfo();
}
Or more specifically at the line:
clsCnaPair CnaPar = (clsCnaPair)CLB.Items[CLB.SelectedIndex];
What am I missing? Searching google, shows the way I am doing the remove is consistent with the examples found there.
Thanks,
It's dangerous to modify the contents of the ChecklistBox inside a loop when the loop conditions depend on the contents of the ChecklistBox. Once you call RemoveAt(), the CheckedItems list and the CLB.Items.Count has changed and you will have a problem. In this case, the loop fired the SelectedIndexChanged() event with an invalid Index (-1).
Better to do this in a do-while loop:
bool done;
do
{
done = true;
for (int index = CLB.Items.Count; index > 0; index--)
{
if(CLB.CheckedItems.Contains(CLB.Items[index-1]))
{
CLB.Items.RemoveAt(index - 1);
done = false;
break;
}
}
}while(!done);
This way, every time an item is removed, you break out and start the loop all over again.
After some experimentation, I commented out the CLB_SelecteIndexChanged code and it now completes with the original code.
That leaves one issue. What is the work around with the CLB_SelectedIndexChanged code left in. I will work on that one more and see f I can figure it out with what you guys have provided.
Thanks to both m.rogalski and mcNets.
Related
This code works fine except multiple dialog box prompt when there are multiple empty textbox but I only want it to prompt once.
For example, if I enter 1,1,(null),(null),d,g, dialog box will prompt twice since there is two empty textboxes but I only need it to prompt once.
How can I solve this problem?
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
}
}
}
You can simply introduce a flag:
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
bool hasEmpty = false;
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
hasEmpty = true;
}
}
if (hasEmpty) {
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
}
}
Why not break out of the loop so that it stops checking?
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break; // <--
}
}
}
return will also work in this situation.
Test if BeforeSave is running twice:
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
MessageBox.Show("Test"); // <--
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break; // <--
}
}
}
As yo can see I added a new "Test" message at the top of the method (outside the loop), if you see duplicate "Test" messages when using the code, it means that BeforeSave is running twice.
In that case you need to look at why it is running twice, and then fix that. If that is not fixable, then there could be some syncronization solution... such as:
private int canSave;
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
if (Interlocked.CompareExchange(ref canSave, 0, 1) != 1)
{
// Any cancelation logic that's appropiate here
return;
}
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break; // <--
}
}
}
Then you see canSave 1 to allow the code to run 0 to disallow it. The Interlocked operation will ensure that the code in BeforeSave will not run again until you set canSave to 1 somewhere in the code (it automatically sets it to 0 when it gets executed - no chance for multiple threads to mess it up).
Although I'm giving you a solution to control double execution of BeforeSave, if it is running twice than expected shows that there is some problem somewhere else, and you should be trying to fix that (unless it is third party code).
Either use a flag, or use Linq.
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e) {
bool flag = false;
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
flag = true;
break;
}
}
if (flag)
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
}
I don't know enough of the used objects to offer a linq solution
Following way should work for you even if your BeforeSave method is getting called multiple times.
private bool _isMessageBoxShown;
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
if(!_isMessageBoxShown)
{
_isMessageBoxShown = true;
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
break;
}
}
}
}
Just make sure that when you want your messagebox to be shown next time, you will need to set _isMessageBoxShown = false;.
I found out another way to solve it.
public void BeforeSave(BCE.AutoCount.Invoicing.Sales.SalesOrder.SalesOrderBeforeSaveEventArgs e)
{
int tt = 0;
for (int i = 0; i < e.MasterRecord.DetailCount; i++)
{
if (tt == 0)
{
if (String.IsNullOrEmpty(e.MasterRecord.GetDetailRecord(i).YourPONo.ToString()))
{
MessageBox.Show("You left Your PO No empty. Please check it carefully.");
tt = 1;
}
}
}
}
I'm trying to convert my foreach method to multi-threading.
I have a datagridview and the method gets the value from cell[0] (which contains a url) and sends it to another method which works with httpwebrequest.
public void UrlCheck()
{
foreach (DataGridViewRow row in dataUrlList.Rows)
{
string url= row.Cells[0].Value.ToString();
try
{
string get = getHtml(url);
//work with get string removed
if()
{
row.Cells[1].Value = "page info here";
}
else
{
row.Cells[1].Value = "error info here";
}
}
catch
{
}
}
MessageBox.Show("Done.");
}
The above code is working without any problem but sequentially.
Then with this i've tried to convert this code to that to be multithreaded :
Button :
private void button9_Click(object sender, EventArgs e)
{
int threadcount = Convert.ToInt32(numThreadSearch.Value);
ThreadForSearch = new Thread[threadcount];
checkingForSearch = dataUrlList.Rows.Count;
isRunningForSearch = true;
beenCheckedForSearch = 1;
for (int i = 0; i <= threadcount - 1; i++)
{
ThreadForSearch[i] = new Thread(new ParameterizedThreadStart(MultiThreadMet));
ThreadForSearch[i].Start(i);
}
}
and the multi-threaded method is here :
public void MultiThreadMet (object IndexForSearch)
{
int index = (int)IndexForSearch;
DataGridViewRow row = dataUrlList.Rows[index];
while (isRunningForSearch)
{
try
{
if (beenCheckedForSearch >= checkingForSearch)
{
isRunningForSearch = false;
}
if (index >= dataUrlList.Rows.Count)
{
ThreadForSearch[index].Abort();
}
//For just test i'm trying to add "test" in every cell[1] in datagridview
dataUrlList.Invoke(new Action(() => row.Cells[1].Value = "test"));
beenCheckedForSearch++;
}
catch
{
}
}
ThreadForSearch[1].Abort();
}
It gets the thread count to run from a numericUpDown control and if I choose the value 10 from the numericUpDown it puts "test" text in the first 10 cells of datagridview and then stops, if i choose 4(or 2,5,7), then it puts the "test" text in the first 4(2,5,7) cells and stops.
It doesn't continue to the next rows after the thread is finished. So I'm trying to fire 5 threads (I always choose it from numericUpDown) and when a thread finish its work, it must go to next row.. How can i solve this problem ?
Also these variables have been declared:
Thread[] ThreadForSearch;
int beenCheckedForSearch;
int checkingForSearch;
private bool isRunningForSearch;
from output screen i'm getting
Exception thrown: 'System.Threading.ThreadAbortException' in mscorlib.dll
Thanks.
The index counter was not incremented in the thread method.
There is still an overlapping problem in the program that I will let you fix. Assuming there are 50 urls, the problem is that thread 1 will update cells 0 to 49, thread 2 will update cells 1 to 49, and so on.
public void MultiThreadMet (object IndexForSearch)
{
int index = (int)IndexForSearch;
while (isRunningForSearch)
{
try
{
if (index >= dataUrlList.Rows.Count)
{
return;
}
DataGridViewRow row = dataUrlList.Rows[index];
index++;
//For just test i'm trying to add "test" in every cell[1] in datagridview
dataUrlList.Invoke(new Action(() => row.Cells[1].Value = "test"));
}
catch
{
}
}
}
Problem solved.
The original "private void buttonSave_Click" was changed to:
private void buttonSave_Click(object sender, EventArgs e)
{
if (MusicCollection.FormMain.PublicVars.AlbumList.Count != 100)
{
MusicCollection.FormMain.PublicVars.AlbumList.Add(new Album(NameTextBox.Text));
MessageBox.Show("New Album added: " + NameTextBox.Text);
formMain.ListAlbums(formMain.AlbumsListBox.Items);
this.Close();
}
else
{
MessageBox.Show("No room for new album.");
this.Close();
}
}
Original Post:
I'm new to using C#, so appologies for any seemly obvious mistakes or terrible coding.
I'm trying to create a new Album object (that gets its Name from NameTextBox.Text on Form FormAlbumAC) and add it to List AlbumList when the user clicks the save button on FormAlbumAC. Then I want to list all of AlbumList in a ListBox on Form FormMain.
When I run the program and click the save button, I'm getting the error "ArgumentOutOfRangeException was unhandled, Index was out of range" at the line:
if (MusicCollection.FormMain.PublicVars.AlbumList[i] == null)
// line 8 on my excerpt from Form FormAblumAC
I'm not sure what I'm doing wrong. Any help would be much appreciated, thank you.
Form FormMain:
public const int MAX_ALBUMS = 100;
public int totalAlbums = 0;
public FormMain()
{
InitializeComponent();
}
public static class PublicVars
{
public static List<Album> AlbumList { get; set; }
static PublicVars()
{
AlbumList = new List<Album>(MAX_ALBUMS);
}
}
public ListBox AlbumListBox
{
get
{
return AlbumListBox;
}
}
public void ListAlbums(IList list)
{
list.Clear();
foreach (var album in PublicVars.AlbumList)
{
if (album == null)
continue;
list.Add(album.Name);
}
}
Form FormAlbumAC:
private FormMain formMain;
private void buttonSave_Click(object sender, EventArgs e)
{
int index = -1;
for (int i = 0; i < MusicCollection.FormMain.MAX_ALBUMS; ++i)
{
if (MusicCollection.FormMain.PublicVars.AlbumList[i] == null)
{
index = i;
break;
}
}
if (index != -1)
{
MusicCollection.FormMain.PublicVars.AlbumList[index] = new Album(NameTextBox.Text);
++formMain.totalAlbums;
MessageBox.Show("New Album added: " + NameTextBox.Text);
formMain.ListAlbums(formMain.AlbumsListBox.Items);
this.Close();
}
else
{
MessageBox.Show("No room for new album.");
this.Close();
}
}
Your problem (from your comments) is that your for loop's condition is incorrect. Your for loop is this:
for (int i = 0; i < MusicCollection.FormMain.MAX_ALBUMS; ++i)
There is one problem and one potential problem here. First, when this code is actually run, it's really running:
for (int i = 0; i < 100; ++i)
because MusicCollection.FormMain.MAX_ALBUMS is declared as 100. This causes an error when the length of MusicCollection.FormMain.PublicVars.AlbumList is less than 100, because you're trying to grab an index that doesn't exist.
Instead, you need to iterate from i=0 to the length of ....PublicVars.AlbumList-1, or, preferably, for(int i = 0; i < ....PublicVars.AlbumList.Count; i++).
The second potential problem is that you are potentially skipping index 0. Arrays start at index zero and continue to index length-1. As such, you probably want i++, not ++i. Depends on your implementation, though.
Below is my current code:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public int[] trialArray = new int[10];
public int trialCounter = -1;
private void button1_Click(object sender, EventArgs e)
{
bool button1Click = true;
if (button1Click == true)
{
ITIpanel.Visible = true;
for (int i = 0; i < trialArray.Length; i++) { trialArray[i] = -1; } // Set default value through array
int counter = 0;
Random rnd = new Random();
while (counter < 10 / 2)
{ // Red trials, fill half array
int index = rnd.Next(0, 10 - 1);
if (trialArray[index] == -1) { trialArray[index] = 1; ++counter; } //if unchanged value, change it
}
while (counter < 10)
{
int index = rnd.Next(0, 10);
if (trialArray[index] == -1) { trialArray[index] = 2; ++counter; }
}
}
}
private void ITIpanel_Paint(object sender, PaintEventArgs e)
{
if (ITIpanel.Visible == true)
{
trialCounter += 1;
timer1.Enabled = true;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
ITIpanel.Visible = false;
timer1.Enabled = false;
if (trialArray[trialCounter] == 1) { redstimPanel.Visible = true; }
else { bluestimPanel.Visible = true;}
if (trialCounter == 9) { Application.Exit(); }
}
public int counter = 0;
public event EventHandler Clicked5TimesEvent;
private void OnClicked5TimesEvent()
{ if (Clicked5TimesEvent != null) { Clicked5TimesEvent(this, EventArgs.Empty); } }
private void bluestimPanel_MouseDown(object sender, EventArgs e)
{
//FR requirement
counter++; if (counter % 5 == 0) { redstimPanel.Visible = false; ITIpanel.Visible = true; }
}
private void redstimPanel_MouseDown(object sender, EventArgs e)
{
//FR requirement
counter++; if (counter % 5 == 0) { redstimPanel.Visible = false; ITIpanel.Visible = true; }
}
}
}
As you can see, I am attempting to make a global array with 10 items. On the button click the 10 items are supposed to be altered such that half contain the value 1 and the other half contain the value 2.
Then, on the timer tick, depending on the value in the trialCounter, which determines the part of the array to be accessed, it should display either the redstimPanel or the bluestimPanel.
Therefore, if the 'trialCounter' is equal to 8, and 8 in the TrialArray is equal 1, the 'redstimPanel' should become Visible. Alternatively, if 8 in the 'TrialArray' is equal to 2, the 'bluestimPanel' should become Visible.
This, however, is not working as I would like it to. Thus, there are clearly some issues with my code. Do you all have any suggestions?
You never reset counter, or have the second loop (the one setting the 2s) be the full array.
There is also an error with the random number, rnd.Next(a,b) a - lower bound (inclusive), b - upper bound (exclusive). So it should be rnd.Next(0,10); so you have a chance of populating the last array position.
while (counter < 10 / 2) { // Red trials, fill half array
int index = rnd.Next(0, 10);
if (trialArray[index] == -1) { trialArray[index] = 1; ++counter; } //if unchanged value, change it
}
//Counter on the first loop here is already 5 (it exited the previous loop)
//So allow it to get to 10, and populate the FULL array.
while (counter < 10) {
int index = rnd.Next(0, 10);
if (trialArray[index] == -1) { trialArray[index] = 2; ++counter; }
}
Allow me to give you some tips and some explanations regarding your code:
First of all, you probably wanted that local button1Click variable to know later on whether the button has been clicked or not. For that to work, you should place it outside that function, otherwise it's never going to be used, and will be true with every button click, something like this:
bool button1Click = false;
private void button1_Click(object sender, EventArgs e)
{
if (!button1Click)
{
When you have a condition, you want the code to decide, whether an expression is true or false you may omit the part "== true" because it doesn't add anything new.
You have two whiles. Your idea was to run the counter until 5, with the first piece of code, and then from 5 to 10 the second piece of code. Now let me try to explain what is actually going on. The counter will go on until 5 filling 1s at random indices. Then at 5, the expression in the while will become false and it breaks out from the loop. Since the second while has the very same expression, it simply avoids it and goes on. One of the many solutions would be to have an if in the loop like this:
while (counter < 10)
{
if (counter<5)
{
// fill red
}
else
{
// fill blue
}
}
The way you fill up the values in your array. Have you thought about what's going to happen when the same index will be generated several times? It means it'll overwrite the previous value while certain index will remain -1.
I think it's obvious what I'm trying to do, but if you don't understand, please ask.
if (listBox1.SelectedIndex == 1 && 2)
{
label1.Text = "Sometext";
}
SelectedIndices is what you want if you have enabled multi-select. You can also check the size of the SelectedItems property.
The documentation for ListBox.SelectedIndex states:
For a standard ListBox, you can use this property to determine the index of the item that is selected in the ListBox. If the SelectionMode property of the ListBox is set to either SelectionMode.MultiSimple or SelectionMode.MultiExtended (which indicates a multiple-selection ListBox) and multiple items are selected in the list, this property can return the index to any selected item.
Try this
if( listBox1.SelectedItems.Count > 1 )
{
// multiple items are selected
}
if (listBox1.SelectedIndices.Count > 1) // I'd use to group all of your multi-selection cases
{
if (listBox1.SelectedIndices.Contains(1) && listBox1.SelectedIndices.Contains(2))
{
label1.Text = "Sometext";
}
}
Keep in mind that the control is 0 based so if you're trying to select the first two options, you'll want to check for 0 (item 1) and 1 (item 2).
edit: modified to handle the requirement listed in comments. Note, there's probably a better way and there may even be a method for this built in (never used the multi-selection list box). But I built a function to handle so you don't have to do it for every scenario.
The function that does the work:
private bool CasesFunction(ListBox lbItem, List<int> validIndices)
{
for (int index = 0; index < lbItem.Items.Count; index++)
{
if (lbItem.SelectedIndices.Contains(index) && !validIndices.Contains(index))
return false;
}
return true;
}
And how I used it:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndices.Count > 1)
{
List<int> MyCase = new List<int> { 0, 1 };
if (CasesFunction(listBox1, MyCase))
{
label1.Text = "Sometext";
return;
}
else
label1.Text = "";
MyCase = new List<int> { 1, 2 }; // can do other checks
if (CasesFunction(listBox1, MyCase))
{
label1.Text = "Sometext 2";
return;
}
else
label1.Text = "";
}
else
label1.Text = listBox1.SelectedIndex.ToString();
}