Timer MinValue, MaxValue, and Value in C# - c#

Ok I know this sounds odd, but to be honest, I do remember solving an example with timers in c# where I set the timer's minvalue, maxvalue, tick, interval etc... yet I don't remember/don't know how to do that it again, MVS 2019 won't show such parameters.
Any help would be really appreciated.
I am trying to start a row based on the value of the 7th index AFTER the datagridview (dg) is fully loaded from the database but, it is not working properly, either some rows are not loaded or it loaded but the O Complexity is VERY LOW (Program response is very BAD).
private void Dg_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
OleDbConnection con = GetConnection();
OleDbCommand cmd = new OleDbCommand("SELECT * FROM Items", con);
try
{
OleDbDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
string PartName = reader.GetValue(0).ToString();
string SN = reader.GetValue(1).ToString();
string ModNum = reader.GetValue(2).ToString();
string TS = reader.GetValue(3).ToString();
string Man = reader.GetValue(4).ToString();
string Brand = reader.GetValue(5).ToString();
string Price = reader.GetValue(6).ToString();
string Quan = reader.GetValue(7).ToString();
string IPP = reader.GetValue(8).ToString();
string HeadMat = reader.GetValue(9).ToString();
string Descibtion = reader.GetValue(10).ToString();
string MinQuan = reader.GetValue(11).ToString();
string MaxQuan = reader.GetValue(12).ToString();
string PricePI = reader.GetValue(13).ToString();
string ExpiryDate = reader.GetValue(14).ToString();
string USDRate = reader.GetValue(15).ToString();
}
reader.Close();
con.Close();
}
catch (OleDbException ex)
{
MessageBox.Show(ex.Message.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
if (GetConnection().State == ConnectionState.Closed)
{
timer1.Start();
Thread thread = new Thread(Blink);
thread.Start();
timer1.Stop();
}
}
Without this piece of code, the program runs just fine no problems what so ever
Here is the Blink method to handle the rows colour change and blinking.
private void Blink(object o)
{
if (dg.Rows.Count != 0)
{
while (dg.Rows.Count >= 0 && go)
{
foreach (DataGridViewRow row in dg.Rows)
{
int value = int.Parse(row.Cells[7].Value.ToString());
if (value == 0 || value == 1)
{
row.DefaultCellStyle.BackColor = Color.Red;
}
else if (value > 1 || value < 4)
{
row.DefaultCellStyle.BackColor = Color.Yellow;
}
else
row.DefaultCellStyle.BackColor = Color.Green;
}
go = true;
Thread.Sleep(100);
while (dg.Rows.Count != 0 && !go)
{
foreach (DataGridViewRow row in dg.Rows)
{
int value1 = int.Parse(row.Cells[7].Value.ToString());
if (value1 == 0 || value1 == 1)
{
row.DefaultCellStyle.BackColor = Color.White;
}
else if (value1 > 1 || value1 < 4)
{
row.DefaultCellStyle.BackColor = Color.White;
}
else
row.DefaultCellStyle.BackColor = Color.White;
}
go = false;
Thread.Sleep(1000);
}
}
}
}

I do not want to dissuade you from doing this, however, if you will indulge me for a moment as I am having a difficult time trying to understand “why” you would want the rows to “blink” for a short amount of time. This seems odd to me as explained below.
I am somewhat leery of the “blinking” rows. I am not sure how this
“blinking” of the rows for 5 seconds “HELPS” the user considering the
row color is changed to a particular color in the end. I personally
think the “blinking” is distracting/annoying and serves no purpose.
Granted, possibly a “few” rows “blinking” when the data is loaded to
indicate a problem with those rows. But even then, it is easy to
imagine the user looking away at that moment and miss the “blinking”
rows and what about the rows that are below the visible grid size?
“Blinking” those rows makes no sense. So, I am not understanding “what
purpose” this “blinking” rows serves? I would think that it would make
more sense if the “blinking” never stopped until the user corrects the
offending data.
With that said, I suggest a closer look at what you currently have code wise. This code approach may well work, however in my tests, I am confident that the UI is going to act sluggish and will be slow to respond. Example, if you scroll the grid in either direction, you will notice how the display is sluggish and jumpy and not smooth as expected. Also, if you edit a cell and press the “Enter” key, you will notice a lag… it will take a second or two to drop down to the next cell.
This is mainly coming from the fact that the Blink code is executed FROM the grid’s CellPainting event. The grid’s CellPainting event gets fired OFTEN. It will fire when none of the cells have changed. Example, if the user “scrolls” the grid. Because of this, the Blink code will run unnecessarily since no cells have changed. In other words, the Blink method may run and change the rows back color when the rows are already the proper color. This is causing the sluggishness. The code is running the Blink method many more times than is needed.
You may be saying… “well I run the Blink method under another thread.” … with…
Thread thread = new Thread(Blink);
thread.Start();
And you would be correct… HOWEVER, this is actually “creating” even more problems in relation to sluggishness of the UI. If you step back and look at this, you should ask yourself… “How many threads do I need?” … This code is literally “creating” hundreds of threads, uses them ONCE and never disposes of them.
Example, let’s say the data has three (3) columns with twenty (20) rows. While loading the data into the grid, the grid’s CellPainting event will fire at least sixty (60) times (once for each cell). (I am confident it will actually fire more than this), however, even at 60 times, this will mean the code is creating at least 60 “separate” threads that never get disposed. Unabated, a crash should not be surprising.
You may somewhat alleviate this, if somewhere, the code properly disposes of the threads, however, in the codes current state, this is not possible since the Thread is “created”, started, and is never disposed in the same if statement. The variable thread will go out of scope (without properly being disposed) as soon as the if statement is exited.
Granted, once out of scope, the GC MAY dispose of this, however, I wouldn’t count on it to be quick about it. The main point here, is that starting multiple Threads for this just seems unnecessary and is problematic. In addition, my understanding is that when using win forms, it is better to use a BackGroundWorker instead of a Thread.
I am simply alluding to the fact that… if you DO use a thread/background worker for this, then only ONE background worker is really needed. We could use the same background worker over and over. Since it takes milliseconds for the code to execute, the worker will be done quickly. I could be mistaken, but I just do not see the need for a background worker here. In some cases, it may be needed and a whole different approach would be required.
Blink code …
In reference to the Blink code… it may well work, however, the user will never see this “changing” of the rows color. The user will not see the color changes as the code does not “Refresh” the grid. I assume this is why the sleep statement is there. The grid will not “automatically” refresh until the code exits the Blink method. The user will only see the final product. The user will not see the row colors change and the rows will appear to remain white.
You may consider simply calling the grids Refresh method, then sleep execution for a few milliseconds to allow the user to “see” the row color change. Unfortunately, this is not going to work. Blink is being called from the grids CellPainting event. If we call the grids Refresh method from Blink… that is going to “re-fire” the grids CellPainting event. A re-re-entrant error is almost guaranteed. This same idea would apply when the Blink method sets a rows color. This may re-fire the grid’s cell painting event again. All the more reason to avoid using this event.
Given all this and emphasizing my first comment, is should be clear that using the grids CellPainting event for this is probably not the best approach for numerous reasons. Most grid “painting” events fire often and for what the code is doing, we end up running the code more times than needed.
In this case, if the current code did execute, it is going to continue the “blinking” of the rows even after 5 seconds. Every time the grids CellPainting event fires, the code will call the Blink method and rows will start blinking. Eventually, the grid will start flashing like a Christmas tree and the UI will start to stall.
The main thing here, is that when using the “GRIDS” painting events, they are firing many more times than we need and we CAN get around this, however, considering that you only want the rows to “blink” for 5 seconds after the data loads, then after 5 seconds, why have the grid worry about it? This is something that should run for 5 seconds, then stop. So, bringing in the grids events seems like overkill. It can be done, but why. After the data loads, start a 5 second timer, start blinking the rows… then, after five seconds, stop the rows from blinking… done.
Given this, my approach does not use any of the grid’s events (yet). Instead of using the grids CellPainting event to call the Blink code… Let us go with your initial suggestion of using Timers. We could use two (2) timers. One timer will have an interval of 5000 and will keep track of the 5 seconds we want the rows to “blink” after the data is loaded into the grid. The second timer would have an interval of say 500 or 1/2 of a second. With each tick of this timer, we would call the Blink method to “toggle/blink” the row colors.
A small trace of the current Blink code…
If we start at the first foreach loop through the grid rows, if the cell at column 7 is a zero (0) or a one (1), then the code will color that row red. On the else portion we have… if (value > 1 || value < 4) … which I am confident you mean && to check for the numbers 2 and 3. Otherwise, you need to explain how this would ever be false. Assuming we want to use &&, this will color the row yellow when cell 7 values are 2 or 3.
Then if the cells value is not 0, 1, 2 or 3, then set that row color to green.
Continuing, the code needlessly freezes the UI and “sleeps” for 100 milliseconds. Then the code again starts a foreach loop through the rows in the grid. Here, I do not follow the logic, if ALL the rows are being set to white, then why check the value of cell 7? Checking for the value in cell 7 is unnecessary since the code is setting the rows to the same color (white).
It would appear, that a simpler approach would be to either “color” ALL the rows based on the cells value in column 7… OR … color ALL the rows white. This should simplify the code and we can use the timer to tell the code “which” way to color the rows… i.e., color the rows based on column 7 or color the rows white.
To help, we can create a global bool variable and call it ColorOn. We can use this variable to help us “toggle” the color state of the rows. If ColorOn is true and we run the Blink code it will color the rows based on the cell value at column 7. If ColorOn is false, then Blink will color the rows white.
Since we may be looping through the grids rows several times to color the rows, it may help if we create a method called SetRowColor that takes a DataGridViewRow and a Boolean value to indicate if we should make the row color “white” or color the row based on column 7. This method will come in handy when the code loops through the grid rows and changes the row color.
The SetRowColor method is below. First the code checks for the “new” row… we will ignore the grids “new” row if it exists. Next, a check is made on the bool white parameter. If it is true, then color the row white. If it is false, then color the row based on the value in column 7.
If we want to color the row based on the cell value in column (7) “TargetColumn.” A TryParse is used to safely validate the cells value as a number and place that number in the out variable value. Next a switch statement is used on the value variable to determine which color to set the row. 0 or 1, red; 2 or 3 yellow and any other number green.
private void SetRowColor(DataGridViewRow row, bool white) {
if (row.IsNewRow) {
return;
}
if (white) {
row.DefaultCellStyle.BackColor = Color.White;
}
else {
if (int.TryParse(row.Cells["TargetColumn"].Value.ToString(), out int value)) {
switch (value) {
case 0:
case 1:
row.DefaultCellStyle.BackColor = Color.Red;
break;
case 2:
case 3:
row.DefaultCellStyle.BackColor = Color.Yellow;
break;
default:
row.DefaultCellStyle.BackColor = Color.LightGreen;
break;
}
}
}
}
We can use the SetRowColor method in the new Blink method and it may look like below. Following the code, a check is made on the ColorOn variable...
If true, then loop through all the rows and set each row color based on the cells value in column 7.
If false, then loop through all the rows and set each row color to white.
We will call this Blink method in the MilliSecondTimer_Tick event.
private void Blink() {
if (ColorOn) {
foreach (DataGridViewRow row in dg.Rows) {
SetRowColor(row, false);
}
}
else {
foreach (DataGridViewRow row in dg.Rows) {
SetRowColor(row, true);
}
}
}
This looks cleaner and it will take milliseconds to execute.
Now that we have the Blink method set up, next we need a Timer to call it. This timer’s interval needs to be set such that it defines how “often” we want the rows color to toggle. Example, if we set the interval to 1 second, then the rows will change color every second. With each tick, we call Blink, then toggle the ColorOn variable to change the color next time around. This MilliSecondTimer_Tick event may look like…
private void MilliSecondTimer_Tick(object sender, EventArgs e) {
Blink();
ColorOn = !ColorOn;
}
If we run this, the timers tick event will fire at every interval and toggle the row colors but it does not stop after five seconds. It is possible to “check” the amount of time that has elapsed since the timer was first started, and if it is greater than 5 seconds, then stop the timer. This is doable; however, I am going the lazy route and use a second timer with an interval of 5 seconds.
Start this five second timer at the same time as the “blink” timer. Then, after 5 seconds has elapsed, its tick event will fire where we can turn off the “blink” timer and also stop its own timer as it is no longer needed. Since we want the rows to “remain” in a colored state after the “blinking” we need to set the ColorOn to true and call the Blink method one last time. Simply stopping the timers may leave the grid in a state such that ALL the rows are colored white. This FiveSecondTimer_Tick event may look something like…
private void FiveSecondTimer_Tick(object sender, EventArgs e) {
MilliSecondTimer.Stop();
FiveSecondTimer.Stop();
ColorOn = true;
Blink();
}
A helper method is used to set up and start the global Timers. We set the interval, subscribe (wire up) to the timer’s tick event and start the timers. We will call this method right after the data is loaded. To note, if you decrease the MilliSecondTimer interval the rows will “blink” faster and obviously, a larger number will “blink” slower. The five second timer’s interval is set to 5 seconds. Increasing its interval will allow the rows to continue “blinking” longer than 5 seconds.
private void StartTimers() {
FiveSecondTimer.Interval = 5000;
MilliSecondTimer.Interval = 500;
FiveSecondTimer.Tick += new EventHandler(FiveSecondTimer_Tick);
MilliSecondTimer.Tick += new EventHandler(MillSecondTimer_Tick);
MilliSecondTimer.Start();
FiveSecondTimer.Start();
}
A method to get some test data GetDT is created. A DataTable is used as a data source to the grid. It has three (3) columns, such that the “target” column we want to check the values with is in column 2. Its name is ”TargetColumn” and this name is used in the SetRowColor method instead of the column index number. Then the table is filled with thirty (30) rows, such that the TargetColumn values will come from a random number generator with random numbers from 0 to 6.
private DataTable GetDT() {
DataTable dt = new DataTable();
dt.Columns.Add("Col0", typeof(string));
dt.Columns.Add("TargetColumn", typeof(int));
dt.Columns.Add("Col2", typeof(string));
Random rand = new Random();
for (int i = 0; i < 30; i++) {
dt.Rows.Add("COR" + i, rand.Next(0, 7), "C2R" + i);
}
return dt;
}
Putting all this together…
To put all this together and test, the code below should demonstrate what is described above. Create a new win forms project, drop a DataGridView onto the form as shown above and re-name the grid to dg using the OPs grid name… dg. Running the code should “blink” the rows in the grid for 5 seconds and after the 5 seconds will leave the rows in a colored state.
DataTable GridDT;
bool ColorOn = true;
System.Windows.Forms.Timer FiveSecondTimer = new System.Windows.Forms.Timer();
System.Windows.Forms.Timer MilliSecondTimer = new System.Windows.Forms.Timer();
private void Form1_Load(object sender, EventArgs e) {
GridDT = GetDT();
dg.DataSource = GridDT;
StartTimers();
}
What happens if the user “changes” the value in one of the target column’s cells?
Now that we have the coloring and the blinking of the rows done, you may ask yourself, ”What happens if the user (or code) changes one of the cells values that is in the “target” column?” The color will not change and we may have to run the Blink code again.
This is where one of the grids events will come in handy. The grids, CellValueChanged event will fire when the user changes a cells value and tries to “leave” the cell. If we wire up (subscribe) to this event, we could run the Blink code again. But this seems like overkill. In other words, only ONE (1) cell has changed, it seems unnecessary to loop through ALL the rows. And we already know which row it is.
In this case, we only want to change the row color when a “TargetColumn” cell changes value. If the other column value’s change, we can ignore those. We are only concerned with the “TargetColumn” cells changing. Below is the identical code as the Blink code however it does not loop through all the grid rows… it only re-colors the row that changed. Using the SetRowColor method makes things a little easier.
private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (dg.Columns[e.ColumnIndex].Name == "TargetColumn") {
if (dg.Rows[e.RowIndex].Cells["TargetColumn"].Value != null) {
SetRowColor(dg.Rows[e.RowIndex], false);
}
}
}
This should complete the example. Please forgive my long-winded rant. I hope it makes sense and helps.

Related

Winform numericUpDown have different delay for up and down value change

I have just started studying .Net Winform with C# (.Net 5). I did a simple winform with a NumericUpDown and a ProgressBar, basically, the ProgressBar will update its value when the NumericUpDown's value changes via the ValueChanged event.
Here is my code:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
progressBar2.Value = (int)numericUpDown1.Value;
}
I notice that the ProgressBar update took about half a second to detect the NumericUpDown ValueChanged when the value is increasing (from 0 to 1, 1 to 2, 2 to 3, etc.) no matter whether I was holding the Up key to increase it continuously or press one at a time.
However, when the value is decreasing, the progress bar update instantly, even when I hold down the Down button to decrease it continuously. Which is pretty weird.
Even when I enter the value directly, it still does the same thing: enter 80 when it is at 20 took a bit to fill up while enter 20 when it is 80 update instantly
The most obvious way to see it is holding the Up button to continuously increasing the value and then press the Down key once. The Progress Bar fill up slowly and then suddenly jump to the correct value when the Down key is pressed
My question is: Is this the default behavior? Does Visual Studio had different algorithm to detect increase or decrease value change? Or am I missing something?
I am pretty sure what you are seeing is the default behavior of the ProgressBar Control. When the progress bar increases, there is a small “delay” to show each step. I may be mistaken; however, I could not find any particular property in the ProgressBar control that would remove or shorten this “delay.”
Fortunately, I did find a fairly simple solution that appears to work. In this solution the idea is to “increase” the current value from the NUD by one (1). Then set the progress bars value to that value, then immediately change the progress bars value back to the true value.
The only issue is if the NUDS value is at the maximum of the progress bar. If we try and increase the NUDS value by 1, then we would go out of bounds on the progress bar. So, in that case we simply need to increase the progress bars maximum value by 1, set the value, then immediately set it back to the original maximum value.
See if the code below works as you are wanting. Please let me know as I will remove it if it does not work as you are wanting.
private void numericUpDown1_ValueChanged(object sender, EventArgs e) {
//progressBar1.Value = (int)numericUpDown1.Value;
if ((int)numericUpDown1.Value == progressBar1.Maximum) {
progressBar1.Maximum = progressBar1.Maximum + 1;
progressBar1.Value = (int)numericUpDown1.Value + 1;
progressBar1.Value = (int)numericUpDown1.Value;
progressBar1.Maximum = progressBar1.Maximum - 1;
}
else {
progressBar1.Value = (int)numericUpDown1.Value + 1;
progressBar1.Value = (int)numericUpDown1.Value;
}
}

BackgroundWorker only reporting progress when finished

I have a very simple app that loads an excel file with several thousand rows into a DataGridView component. This table is then scanned for duplicates and various other issues by pressing the Scan button. I chose to have this intensive task run in a BackgroundWorker so the UI remains responsive and so I can report its progress. The code for my Scan button simply calls the RunWorkerAsync() method on the background worker component, which then does this :
Scanner scanner = new Scanner(this, dgvScancodes, dgvErroredScancodes, BgwScanner);
This calls another class to do the actual work, passing the scanner as a parameter. The Scanner class then does this :
foreach (DataGridViewRow row in table.Rows)
{
//Long computations on each row
worker.ReportProgress(row.Index / table.RowCount * 100);
}
The scan executes perfectly fine and produces the expected output but my ProgressBar never updates with the following code on the BackgroundWorker's ProgressChanged event :
private void BgwScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbProgress.Value = e.ProgressPercentage;
}
Thinking this could be an issue with the way I calculate the percentage, I replaced my call to ReportProgress and just sent a value of 50 to see what would happen. The progress bar does update, but only when the entire scan is completed! No exception is raised and it's behaving like the UI thread is too busy doing other things (which, aside from looping over every row in a table, it is not to the best of my knowledge). Any idea why I'm seeing this behavior?
******EDIT******
I found my culprit. I forgot that, during the scan, the table's rows can be updated with a tooltip and a background color. I commented these 2 lines and sure enough the progress bar works perfectly fine now. This proves my theory that indeed the UI thread is being overwhelmed. Is there potentially a way around this? Some sort of higher priority for the progress bar updates?
For integer division the value rounds towards zero to the next nearest integer.
In your case it will always round towards exactly zero before being multiplied by 100, since row.index is presumably between 0 and table.RowCount - 1.
Being totally verbose:
row.Index / table.RowCount * 100
Could become:
(int)(((double)row.Index / (double)table.RowCount) * 100)
I addressed this issue by doing the processing on the underlying DataSource for my tables until the very last minute when it needs to be displayed. This way there are no UI updates until the table is displayed. I still need to iterate over the table itself to set the background color and tooltip for certain cells at the end (since you obviously can't do this on the DataSource itself) but that takes a relatively short time compared to the processing that is being done on the DataSource beforehand. My ProgressBar now works properly.
******EDIT******
You can actually use the DataGridView's CellFormatting event to color cells, no need to iterate over the rows and this has the bonus effect of keeping the tooltip and the background color when re-ordering the table :
private void dgvErroredScancodes_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
DataGridViewCell scancodeCell = dgvErroredScancodes.Rows[e.RowIndex].Cells[1];
if (e.Value.Equals((int) ErrorType.DUPLICATE))
{
scancodeCell.Style.BackColor = ErrorType.DUPLICATE.BackgroundColor();
scancodeCell.ToolTipText = ErrorType.DUPLICATE.ErrorMessage();
}
...
}

Stopping line jump when refreshing a C# datagrid

We display our data on datagrids, bound to a dataset, which is in turn fed from a Progress database on the server. During processing, we need to make a change to the data-set and refresh it's value from the server. So far, all well and good and no problems.
The problem is that when we come back with the new data, we want the selection in the datagrid to remain on the same row it was on before. We've managed this with the following code:
int iPostingPos = dgridPostings.CurrentRow.Index;
// process data on server
dataContTranMatch.RunBoProcedure(dataContTranMatch.BoProcedure,
transactionMatchingDataSet);
// Reload Data
LoadData();
if (iPostingPos > ttPOSTingsRowBindingSource.Count)
{
iPostingPos = ttPOSTingsRowBindingSource.Count;
}
if (ttPOSTingsRowBindingSource.Count > 0)
{
ttPOSTingsRowBindingSource.Position = iPostingPos;
dgridPostings.Rows[iPostingPos].Selected = true;
}
This works, but we get the selected line jumping about on the screen, which is really annoying the users.
For example, if you select row 7, then run this code, you have row 7 selected, selection then jumps to row 0, then jumps back to row 7. This isn't acceptable.
In an attempt to fix this, we've tried enclosing the above code in the following additional lines:
chTableLayoutPanel1.SuspendLayout();
*DO CODE*
chTableLayoutPanel1.ResumeLayout();
But this didn't help.
So far, the most acceptable solution that we've been able to reach is to change the colour on the selection so that you can't see it, letting it leap about and then putting the colours back as they should be. This makes the flicker more acceptable.
dgridPostings.RowsDefaultCellStyle.SelectionBackColor =
SystemColors.Window;
dgridPostings.RowsDefaultCellStyle.SelectionForeColor =
SystemColors.ControlText;
DO CODE
dgridPostings.RowsDefaultCellStyle.SelectionBackColor =
SystemColors.Highlight;
dgridPostings.RowsDefaultCellStyle.SelectionForeColor =
SystemColors.HighlightText;
We beleive that the issue is caused by the binding source being temporarily empty as the dataset is refreshed, we then re-navigate one it's got data in it again.
Can anyone offer any ideas on how we can prevent this unpleasent flicker from occuring?
Many thanks
Colin
It may be a bit heavy handed but one option would be to suspend painting of the control. A user asked how to achieve this here: How Do I Suspend Painting For a Control and Its' Children. I've used the selected answer there to achieve something similar.

c# DatagridView Control (VirtualMode) and Threading

I have a weird issue with the DataGridView control in virtual mode and threading. I use a Cache class to stored 2 pages of data. When I scroll down and I have to load a new page in cache, there is a little delay before data is display (it's ok). But, instead of having
the next row displayed(199th,200th,201th), the control skip many rows (CellValueNeeded). By example, I cached 1 to 200 rows, when I scrolling at 199, I'm loading a page to 200 à 299. However, in the datagrid it's display the row 320.. instead of 300. (I kept the mouse button pressed 2-3 secondes, before releasing it). I notice I have the problem only when I load the cache in a particular thread. Does anyone have a idea how I can fix this?
private void datagridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = m_rfiCache.RetrieveElement(e.RowIndex, e.ColumnIndex);
}
....
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
e : " + lowerPageBoundary);
DataTable dt = new DataTable();
if (m_useThreading) //THIS DOESN'T WORK WELL
{
Thread MulThread = new Thread(delegate()
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
});
MulThread.Start();
MulThread.Join();
}
else //OK.
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
}
return dt;
}
P.S. : This is only a snippet, I used an external API to extract data from ODB. This API used thread to load data.
Does anyone have a idea or I can fix this?
You can load the data in a background thread, but you can't add it to your main DataTable in the background thread itself.
One potential approach would be to load the data as you're doing, but into a separate DataTable. You could then, on the UI thread, call DataSet.Merge to merge this data in with your bound data correctly (which will be reasonable fast).
Finally, I found the solution. When the tread is processing and the scroll down button is pressed, my event for scrolling aren't sent to the datagrid unless my thread is finished. So I received a bunch of calls for drawing cells and the index was wrong. To avoid this, I removed the vs scrollbar from the datagridview and added a VSscrollbar control. When I'm loading data in my thread, I set to false the enable property on my scrollbar. When the thread is completed, I enable the scrollbar. Is not the BEST solution, but at least, there is now row skipped.
Note : Virtual Mode works very well if you don't load your data in a separate thread.

Set selected WPF DataGridCell to focused and editable upon event

Woot, first Stack Overflow post! I've been asked to work on a desktop application to improve an inventory process for my company. I dabbled with WPF in school and I figured I'd start there. After researching some, I learned about MVVM, put a design together, and forged ahead. Finally, I'm stuck and looking for some help and also a sanity check to see if I'm on the right path.
I have single-column DataGrid bound to an observable collection. Users of the application use a scan gun to enter values in. One potential value that I catch in my "Cell" model object is a "MoveNextColumn" value. This raises a custom event in my model that is handled in the View Model. The handler is supposed to simulate blank entries for all remaining rows in that column, set focus on the last row, and wait for input before moving on. So here is what I have so far:
private void dummyCell_MoveToNextColumn(object sender, RoutedEventArgs e) {
e.Handled = true;
// Cell is the model object containing the parsing rules and raising events
var lSender = sender as Cell;
var gridItems = ViewGridReference.Items;
var lastItem = gridItems[gridItems.Count - 1];
if (lSender == lastItem) {
// We are at the bottom of the column
// Move the program on to the next column
CurrentColumn++;
OnPropertyChanged("ItemPositions");
} else {
// Simulate "empty position" input for this cell and all cells down the column
// Cells are validating themselves as the simulation progresses
foreach (Cell item in ViewGridReference.Items) {
item.ActualItemCode = string.Empty;
}
// ViewGridReference is a reference to my DataGrid set from the view
ViewGridReference.Focus();
ViewGridReference.SelectedIndex = gridItems.Count - 1;
ViewGridReference.CurrentCell = new DataGridCellInfo(lastItem, ViewGridReference.Columns[0]);
((DataGridCell)ViewGridReference.SelectedItem).Focus();
}
}
All of this seems to be working as expected: all rows receive blank input and are validated (I use color properties in the cell to which the view binds to signify the validity of the entry).
Unfortunately, though the focus is on the last row as desired, it is not editable and the user cannot submit another "MoveNextColumn" value which would move the program on. The goal here is to minimize any keyboard interaction. Everything should be done with scan guns and barcodes.
Any ideas on how to make the selected cell editable after this code executes?
Any "hey, your design sucks" feedback would be cool too. This is new to me and I'm open to constructive criticism.
I have made some progress with this. The entire grid was left at an uneditable state in the code above. This now leaves focus on the last cell in my column and allows me to submit input with the scan gun.
This seems to work, but I'd still appreciate some feedback on whether there is a better way.
private void dummyCell_MoveToNextColumn(object sender, RoutedEventArgs e) {
e.Handled = true;
// Cell is the model object containing the parsing rules and raising events
var lSender = sender as Cell;
var gridItems = ViewGridReference.Items;
var lastItem = gridItems[gridItems.Count - 1];
if (lSender == lastItem) {
// We are at the bottom of the column
// Move the program on to the next column
CurrentColumn++;
OnPropertyChanged("ItemPositions");
} else {
// Simulate "empty position" input for this cell and all cells down the column
// Cells are validating themselves as the simulation progresses
foreach (Cell item in ViewGridReference.Items) {
item.ActualItemCode = string.Empty;
}
ViewGridReference.SelectedIndex = gridItems.Count - 1;
ViewGridReference.CurrentCell = new DataGridCellInfo(lastItem, ViewGridReference.Columns[0]);
(ViewGridReference.ItemsSource as ListCollectionView).EditItem(ViewGridReference.SelectedItem);
((DataGridCell)ViewGridReference.SelectedItem).Focus();
}
}
Updated 12/2/2010
Hey, there is an important update to this. The first thing to note is that text entry is being done with a scan gun in my scenario, so 'Enter' keys are sent down with each pull of the trigger. It shoots down each character followed by the Enter key all at once.
WPF sees this enter and wants to set the focus to the DataGridCell directly beneath the cell in which the Enter key input was received. The code above sets the focus to the last cell, but then the Enter key event still fires and is handled by DataGrid after this code is run. The effect is that the focus is reset back to the subsequent cell, not the last cell like I want.
So I need to either figure out how to eat the Enter key for just that scan, or I need to break how WPF handles Enter keys. The last line up there actually throws an exception. We are trying to use a Model class (Class.cs) as a DataGridCell, and there is nothing to handle that cast. Because of that, the Focus() method tries to operate on a null object and we get a NullReferenceException. This was really confusing me because Visual Studio 2010 would sometimes break to alert me about this, but sometimes it wouldn't. However, if I run the executable outside of Visual Studio, it works just fine. That's because unhandled, non-fatal exceptions are ignored and the Enter key behavior fails to operate as normal.
So it works, but in a pretty gross way. I either need to figure out how to do one-time handling of the Enter key and override the default WPF handler, or just leave it like it is and grimace.

Categories