I've built a form which can add/delete records to an SQL database. When I add a record, I want the form to reload and the listbox with all the records to now include the newly added record.
I googled this and saw that a new thread was recommended to refresh the form, but the instructions weren't clear enough for a newbie like me.
Any help would be appreciated.
Thanks in advance.
Edit: this is a desktop app using c# and not asp.
Some of the controls are populated by a wizard that I ran and for others I coded the datasource myself.
namespace LomWindows
{
public partial class Form1 : Form
{
SqlConnection myConnection;
public Form1()
{
InitializeComponent();
myConnection = new SqlConnection(global::LomWindows.Properties.Settings.Default.esConnectionString);
tConnStr.Text = global::LomWindows.Properties.Settings.Default.esConnectionString;
try
{
myConnection.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private void button1_Click(object sender, EventArgs e)
{
string sqlComm = "INSERT INTO ES_TOOL ....";
try
{
myCommand.ExecuteNonQuery();
}
catch (Exception exce)
{
Console.WriteLine(exce.ToString());
}
try
{
myConnection.Close();
}
catch (Exception exc)
{
Console.WriteLine(exc.ToString());
}
InitializeComponent();
MessageBox.Show("Tool Added");
this.Invalidate();
}
}
}
You might rebind the control again after add/edit/delete this will reload the controls with the updated data.
WinForms
There is a great article over at MSDN covering the following: Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads.
When you want to Re-draw your window you can call the method Invalidate. However when you Re-bind a control; setting a new datasource, it will update the content in it.
Here are some great videos to watch for WinForms: "How Do I Videos" from WindowsClient.net.
WPF
Here is a matching article to the one in WinForms covering: Build More Responsive Apps With The Dispatcher.
Herer are some great videos to watch for WPF: "How Do I Videos", aslo from WindowsClient.net.
ASP.NET
If you want to create more responsive webbapps you might want to consider having a look at jQuery and Ajax. You can then request a new part of your web-site and replace the old one.
However if you are just doing a postback and you want to add items to your listbox, you can just call DataBind on the ListBox and it should re-bind the items in the data-source.
The underlying question isn't really about Winforms repaints, rather it's how to freshen the datasource to which the Winform controls are bound, or from which the controls are being manually populated in unbound mode, after the database has been changed by your client application.
Unless you create the data model, your datasource object has no way of knowing that the data have been changed when a DML statement is executed by a command somewhere. It all has to be "tied together". ADO.NET uses a 'disconnected recordset' model. The ADO.NET datalayer objects will raise events related to data i/o and data errors, and you must must attach listeners/eventhandlers to them; these eventhandlers must, in turn, invoke your presentation-layer code.
At present you're just scratching the surface with the command object. Best thing to do would be to read one of the books that show you how to wire up eventhandlers to the ADO.NET event model.
EDIT: Here's a link to get you started: http://msdn.microsoft.com/en-us/library/w9y9a401.aspx
If it's ASP.NET you can refresh the form by redirecting it to itself by using the following code:
Response.Redirect(Request.RawRul);
If it's a Windows Application, you need to rebind the listbox control by setting its DataSource property again.
If you're already calling Invalidate on the ListBox, you might want to add a debug statement to the Paint method to make sure it's really repainting. If it is, then you should look into where Paint gets the state needed to draw the items. Do you query the database inside Paint? (I hope not.) Or is the form keeping its state in memory somewhere? In that case, you should make sure that you're keeping the in-memory state consistent with the database.
Have you thought of the performance impact of re-loading all the rows whenever one row is added/deleted/updated? IMHO, reloading the entire data will just annoy the users and also increase the bandwidth usage. Instead, create POCO objects for each line of data. Whenever there is a (successful) CRUD operation (which you perform on the BackgroundWorker component), just add it to the DataSource and call DataBind.
For more information on how to use BackgroundWorker, see this.
Write Select query in Form_Load and call after where ever you need to refresh your form like,
form_load(Object sender,Event_args e)
{
select Query to display on Loading form
}
add_click(Object sender,Event_args e)
{
form_load(sender,e);
}
Related
EDIT: Solved using this: http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/
In my project (.net/windows forms) I'm filling a DataGridView with a large DataTable. Filling can take up to 20 seconds, so I'd like an animated loading window. This animation freezes if the thread is busy, so I'll have to use a new thread either for the window or for filling the DataGridView.
I've tried using the BackgroundWorker to show the form, but it'll be a blank white window in the correct shape.
I've also tried using the BackgroundWorker to fill the DataGridView, but it'll throw an error saying the DataGridView is being accessed by a different thread than the one it has been created for. Since the DataGridView is being created in the designer class, I can't just create it in the new thread - plus that solution doesn't sound very elegant.
What's the best way for me to show a working animated form while filling the DataGridView?
Edit: The answer didn't solve the issue for me, so I've tried to break the code down to something that can be presented here. I didn't do it before because it didn't really seem relevant enough to work through some 1k lines of code. Might be something missing or some remnants from previous experiments in the code presented here. Please ignore bad naming of function, that's a legacy thing I'll fix once I got it working. Parts of the code are ancient.
I've made it run without errors, but the frmLoading still isn't animated (it is if I keep it alive while the working thread isn't busy, though).
namespace a
{
public partial class frmMain : DockContent, IPlugin
{
//...
private delegate void SafeCallDelegate(DataTable dt);
private Thread thread1 = null;
private frmLoading frmLoading = new frmLoading();
public frmMain()
{
//...
}
//...
private void FillDataGrid(DataTable dt)
{
if(this.InvokeRequired)
{
var d = new SafeCallDelegate(FillDataGrid);
Invoke(d, new object[] { dt });
}
else
{
//...
DataGridFiller(dt);
}
}
private void DataGridFiller(DataTable dt)
{
BindingSource dataSource = new BindingSource(dt, null);
//...
dgvData.DataSource = dataSource;
//...
frmLoading.Hide();
}
private void btnGetData_Click(object sender, EventArgs e)
{
DataTable dt = [...];
// Wenn Daten vorhanden sind, dann anzeigen
if (dt != null)
{
//...
frmLoading.Show();
thread1 = new Thread(() => FillDataGrid(dt));
thread1.Start();
}
}
}
}
The second approach is the correct one: use a BackgroundWorker to do any work that will freeze the UI if done in the main thread. About the exception you're getting, that's because the call you're making isn't thread-safe (Because the control was created on a different thread that the one whos calling its methods). Plase take a look at this link to MSDN to understand how to make this kind of call between threads, using backgroundWorker's event-driven model.
From the link:
There are two ways to safely call a Windows Forms control from a
thread that didn't create that control. You can use the
System.Windows.Forms.Control.Invoke method to call a delegate created
in the main thread, which in turn calls the control. Or, you can
implement a System.ComponentModel.BackgroundWorker, which uses an
event-driven model to separate work done in the background thread from
reporting on the results.
Take a look at the second example which demonstrates this technique using the backgroundWorker
EDIT
From your comments I get that the real problem here is size. The problem is that the thread that owns the DataGridView control, is the thread that is displaying it, so no matter how, if you load all the data at once it will freeze for the time it takes this thread to draw all that data on the screen. Fortunately you're not the first that had this problem, and microsoft got you covered this time. This is, I think, a great example of what the XY problem is, the real problem is that you want to load a huge dataset (X) and the answer on how to do that is here, but instead you asked how to display a loading icon while filling your datagrid whithout the UI freezing (Y), which was your attempted solution.
This was from a technical perspective but, from an UI/UX perspective I would like you to think about the actuall use of the form. Do you really need all that data loaded every time you visit this section? If the answer is no, maybe you could think about implementing some pagination (example here). Also 200 colums means horizontal scroll even for an ultrawide monitor. I can't really figure out the user case for you to need all that information on a list/table view at once. I think that maybe implemeting a Master-Detail kind of interface could be more useful.
EDIT 2.0
I think that you can try to create a whole new Form window in another thread, place it over your datagrid and paint the loading animation there. Meanwhile in the main window you can draw the grid without its painting making the loading animation freeze (the other thread is taking care of the drawing). You may want to hold a reference to the thread and kill it, or maybe better try to hold a reference to the From and close it more gracefully.
I've never done this but, I think I recall from some legacy application doing something like that, and it was ugly.
Use an asynchronous task that displays the pop-up loading window until the DataGridView is filled up.
Here's a link to a write-up Microsoft made for async programming: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
I am using a data-bound datagridview (sbDataGridView) control in an Windows Form based C# desktop application.
Loading data from the database is working fine and all the values are displayed correctly.
But when simulating a change of the data in the database by manually adding some rows, this change is not automatically reflected by the datagridview control. Is there a way to make the control aware of the change and update itself automatically?
I've already added some time which performs the following tasks, but none of them worked...
private void RefreshTimer_Tick(object sender, EventArgs e)
{
// tBindingSource.ResetBindings(false);
// sBindingSource.ResetBindings(false);
// sbBindingSource.ResetBindings(false);
sbDataGridView.DataSource = null;
sbDataGridView.DataSource = sbBindingSource;
//MessageBox.Show("Tick");
//sbBindingSource.ResetBindings(false);
//tBindingSource.ResetBindings(false);
//sBindingSource.ResetBindings(false);
//sbDataGridView.Refresh();
//sbDataGridView.Invalidate();
//sDataSet.Reset();
//tTableAdapter.Fill(sDataSet.t);
//sTableAdapter.Fill(sDataSet.s);
//sbTableAdapter.Fill(sDataSet.sb);
//sbDataGridView.Invalidate();
}
On the Form_Load method I have a line RefreshTimer.Start().
If uncommenting the MessageBox line, the MessageBox is shown every second. The timer is currently set to elapse every second.
(I've tried all and some of the above in various orders...)
Any ideas what's wrong here?
I already tried the "dirty" solution from C# DataGridView not updated when datasource is changed, but that did not work either.
Best regards,
Tom
I have a web form where I register a new employee. There're 3 parts in the form: Personal info, Address info, Special Status. But there's only one button for the whole form. When I submit the form all the information is updated to the database. So three Update statements are executed against the database. The methods are UpdatePersonalInfo, UpdateAddressInfo and UpdateSpStatus. Is there a way to check if there's been a change in any field in the certain part and run update method only if it's true. So something like this:
if (There's been any change to the personal data of the employee)
{
UpdatePersonalInfo;
}
if (There's been any change to the address information of the employee)
{
UpdateAddressInfo;
}
Sure I know, I can save all the previous values in a session object in PageLoad and then compare them one by one before running the method. But I thought maybe there's a magic way of doing this more easily.
Not sure that this is a better solution than any of the alternatives you already mentioned, but you could create a default handler to attach to the TextChanged, SelectedIndexChanged, etc events of your controls to keep track of which ones have changed.
List ChangedControls = new List(Of, String);
private void ChangedValue(object sender, System.EventArgs e) {
WebControl cntrl = (WebControl) sender;
ChangedControls.Add(cntrl.ID);
}
Then on your button click scour the ChangedControls list for the relevant controls.
I have a form that is loading quite an amount of data from SQL server. Below is the code that will provide a good hint:
private void BranchCenter_Load(object sender, EventArgs e) {
//Combo boxes:
LoadCities();
LoadCoordinators();
LoadComputerSystems();
//Actual entity
LoadBranch();
}
private void LoadCities() {
//LINQ2SQL to load data. ~5000 records.
}
private void LoadCoordinators() {
//LINQ2SQL to load data. ~50 records.
}
private void LoadComputerSystems() {
//LINQ2SQL to load data. ~550 records.
}
private void LoadBranch() {
LoadBranchInit();
LoadBranchDetails();
LoadBranchTimings();
LoadBranchServices();
LoadBranchLocumsHistory();
LoadBranchJobs();
LoadBranchNotes();
}
private void LoadBranchInit() {
//LINQ2SQL to load the Branch object based upon the Branch ID
}
private void LoadBranchDetails() {
//LINQ2SQL to load basic branch stuff. 38 fields. Mixed editors.
}
private void LoadBranchTimings() {
//LINQ2SQL to load timings info into 80 date-time controls
}
private void LoadBranchServices() {
//LINQ2SQL to load services offered at branch info into 20 check-boxes controls
}
private void LoadBranchLocumsHistory() {
//LINQ2SQL to load branch history info into grid control. Always increasing # of rows :(
}
private void LoadBranchJobs() {
//LINQ2SQL to load branch jobs info into grid control. Always increasing # of rows :(
}
private void LoadBranchNotes() {
//LINQ2SQL to load branch notes info into grid control
}
The UI is a form with a tab controls. each detail from above goes to a tab page. I need to load and show the form to user as fast as possible. once the form is shown, i need to launch a series of background workers to get the data for each page.
I have been trying to mess with the background worker but unable to understand it's usage. I end up getting message of "different thread attempted to access control on your main thread... or something like that..."
The ideal situation would be to have a progress bar loading the data on every tab and the tab becoming inter-actable once the respective background worker finishes.
Any strategy or advice? Thanks for the read.
If each type of data is displayed on just one page, I would move the code so that each corresponding type of data is loaded the first time each tab page is loaded. That way the data loading work will be distributed over a longer time, and perhaps avoided altogether if the user does not navigate to all tabs during a session.
When loading a page, you could use a BackgroundWorker or any other asynchronous mechanism. In the tab page you can have two panel controls. One that contains the UI for the page, and that has Visible = false when the form is loaded, and one containing a label with a text like "Loading, please wait...". When the data is loaded for the page, and the UI is updated, toggle visibility on the two panels to show the UI. That way the form will still be responsive while the data is loading, and since you load data for only one page at a time, load times should be fairly short.
the ideal solution is that you do not really load data in the form load event.
Let the form load properly and entirely so the UI render completes.
After that you could display a progress bar wherever you want and you could execute the actual data loading, either synchronous or asynchronous.
if you don't need everything loaded at the same time I would also consider loading data only the first time a tab is accessed, so if the user never clicks on the last tab, for example, you didn't load anything for it.
Create a BackgroundWorker for each of the tabs. In the Load event of the form, disable all tabs and call the RunWorkerAsync() method for each of the background workers.
In each DoWork event handler, load the data required for the associated tab page from the database into a data table and set the Result property of the DoWorkEventArgs to the data table.
Note: In the DoWork event handler you should NOT update any UI control since it is operating in a different thread. You should only retrieve the data from the database and set the Result property.
In the RunWorkerCompleted event handlers, you can access the data table, that was retrieved in the DoWork event handler, by getting the Result property of the RunWorkerCompletedEventArgs. Then you can set the properties of the UI controls accordingly and then enable the tab page associated with the current background worker.
The problem here is that the DoWork method of the BackgroundWorker cannot touch the GUI.
It has to use the BackgroundWorker.ReportProgress( int progress, object state) to invoke a method on the GUI thread that is free to update the GUI.
The important point here is that the GUI controls should only be updated from the GUI thread itself, else random exceptions will occur here and there in your program.
You can't mess around with the UI from any other thread apart from the UI thread; that's the source of your errors. If you want more information on how to properly update a UI element from a different thread, you'll need to use Invoke. This question on Stack Overflow might help you out.
Yes, BackgroundWorker is great for this.
I think that you do it wrong because you want to change your form in the DoWork event.
However, you should only collect results of this in e.Results of that event
Then in RunWorkerCompleted you use its e.Results to change the form.
The TabControl already does lazy-loading.
Do yourself a favor and place the contents of each tab on a UserControl.
To address just the problem you are encountering with the background worker thread, it sounds like you may be trying to access UI components from the thread itself. To do this from a non-UI thread you will need to make use of Control.Invoke rather than trying to access the components directly.
Okay, I face a new scenario. I shifted to Visual Studio 2010 and ran into Task.Factory.StartNew(() => myMethod());
This is what I have so far:
private void LoadLocum() {
var TaskInit = Task.Factory.StartNew(() => LoadLocumInit());
TaskInit.Wait();
var TaskDetails = Task.Factory.StartNew(() => LoadLocumDetails());
TaskDetails.Wait();
var TaskQualifications = Task.Factory.StartNew(() => LoadLocumQualifications());
//Enable Qualification TabPage automatically whenever TaskQualifications is complete
Parallel.Invoke(
() => LoadLocumComputerSystems(),
() => LoadLocumOtherInfo(),
() => LoadLocumEmergencyContacts(),
() => LoadLocumDistanceRates(),
() => LoadLocumDocuments(),
() => LoadLocumNotes(),
() => LoadLocumBannedStatus()
);
}
The first two steps (tasks) are critical. Now, the Tab Pages of the Tab are disabled. I need to enable them based upon their related task completion. I can find an event to subscribe to that indicates a certain task is completed or not.
Okay. Massive response in just about no time. Love this place. Jumping from link-2-link, I came face to face with Reactive Extensions for .NET (Rx). Can this be a good alternate as well? Can't find a tutorial on how to use Rx in my scenario.
Hy,
In my project asp.net (c#), I am using threading to process some messages. I have a display page that contain a listbox with all the actions that are taken in processing the message. And a class where I am processing the message.
Default.aspx:
protected void Page_Load(object sender, EventArgs e)
{
//starts the thread and call the method from Process.cs
}
public void SetListItem(string text)
{
myListBox.Items.Add(text);
myListBox.DataBind();
}
Process.cs
public class Process
{
public Process()
{
Default defaultPage;
defaultPage.SetListItem("==> Received message!");
}
}
But the listbox do not show nothing.
Does anyone has any ideea where I'm wrong?
Thanks.
Jeff
Remove the databind
myListBox.DataBind();
You don't need it since you are adding an item to the list collection. If you were setting the DataSource property, you would then have to use it. This is all depending on where in the life-cycle your update is firing as well. It could be that your update is being replaced by code somewhere else in the process as well.
In truth, I would take this out of being a separate thread. Since you need it to be updated before the page submits information to the browser, you either need to keep it in the same thread to make sure it completes, or you're going to need to have some sort of a check at the end of your page executing process to make sure it has finished. It is possible your thread is completing after the page has finished processing.
An asp.net page, such as Default generates html which is returned to the requesting browser. When this html has been generated and sent to the browser, there is no way you can add items on the server side to whatever is displayed in the browser.
So spawning threads that do some work is not necesarily a good idea for your current scenario.
Klaus is right. Once the page has been drawn it's just sitting there.
So, to do what you want to do, running Process in a seperate thread:
Write your output message in Process to a data store, even if that's just appending to a text file. Then, set your page to automatically refresh every X time, and refill your listbox on load with the content of that datastore.
Later on, when everything's working, you could use Ajax to make just the listbox refresh, instead of the whole page.