Send information to another form c# - c#

I have an application that uploads files to a server, but when I press upload it freezes until it is done, so I was thinking to make another form pop up that says uploading and does all of the uploading on that form nested of freezing that main form. But to do this I need to be able to send the selected information to that other form.
I have tried using a BackgroundWorker but that doesn't work, the form still freezes.

The reason why its freezing is because you are doing uploading on the same thread as the GUI or main thread.
You could create a worker thread to handle the working of the uploading so the GUI doesn't lock while processing the upload.
Example:
private void uploadButton_Click(object sender, EventArgs e)
{
object[] params = new object[] { "your file what ever type this is a generic example"};
Thread uploadThread = new Thread(new ParameterizedThreadStart(processUpload));
uploadThread.IsBackground = true;
uploadThread.Start(params);
}
private void processUpload(object params){
// do upload logic here
object[] _params = (object[])params;
string s = _params[0].ToString();
}
Passing information from one form to another is straight forward, but this form will also result in a lock while processing. If that's what you want to do then just create a constructor to take a param for whatever you'd like to pass. Then call it accordingly.
private string something = null;
public MySecondForm(string Something){
this.something = Something;
MessageBox.Show(this.something);
}
// Call this in the parent form
MySecondForm mySecondForm = new MySecondForm("hello world");
mySecondForm.Show();

If you are using WebClient class, you can use UploadFileAsync method. Also you can pass some information from one form to another as follows.
Form2
Add a simple constructor to Form2.
public Form2(string path) { // ... }
Form1
Form2 frm2 = new Form2("Path");

Take a look at this example on Code Project for some implementation tips.

Related

Implement loader in windows form like web application

I'm trying displaying Loader while any long running process being executing in windows forms. I have implemented code for that, but loader being displayed but not in CenterParent location, it will be displayed on Center of the screen.
Code:
CPLoader is form that I want to display while any process executing.
public class CommonLoader
{
CPLoader cploader = new CPLoader();
readonly Form form = null;
public CommonLoader(Form frm)
{
form = frm;
}
public void ShowLoader()
{
try
{
if (form.InvokeRequired)
{
try
{
cploader = new CPLoader();
cploader.ShowDialog();
}
catch
{
Console.WriteLine("Cp loader exception");
}
}
else
{
Thread th = new Thread(ShowLoader);
th.IsBackground = false;
th.Start();
}
}
catch
{
Console.WriteLine("Cp loader exception");
}
}
/// <summary>
/// this method will used for hide loader while process stop
/// </summary>
public void HideLoader()
{
try
{
if (cploader != null)
{
Thread.Sleep(200);
cploader.Invoke(new Action(cploader.Close));
}
}
catch
{
Console.WriteLine("Cp loader exception");
}
}
}
I have also try cploader.ShowDialog() with frm.BeginInvoke(new MethodInvoker(delegate(){cploader.ShowDialog(form); })).
If I use BeginInvoke() then I'm unable to close the loader.
Splash screens, progress screens etc appeared in Visual Basic or Delphi desktop applications long before web applications. They are just modeless forms/windows displayed on top of their application. They don't need threads either - back then applications were mostly single threaded.
Background threads can't modify the UI anyway, which means that the entire ShowLoader method does nothing more than try to call :
cploader = new CPLoader();
cploader.ShowDialog();
All of this can be replaced with
public void ShowLoader()
{
cploader.ShowDialog();
}
public void HideLoader()
{
cploader.Hide();
//or Close if we don't intend to reuse the loader
}
Specifying the parent
Calling ShowDialog without any parameters creates a window whose parent is the desktop. That's why the window appears centered on the screen, not the application.
To specify an owner/parent, just pass it as the owner parameter to ShowDialog or Show.
The following code can be used to display a dialog box centered on the current form :
var myDialog=new MyDialogForm();
myDialog.ShowDialog(this);
This means that ShowLoader probably has to accept the owner as a parameter :
public void ShowLoader(Form frm)
{
cploader.ShowDialog(frm);
}
Modeless windows
ShowDialog() is used to display a modal form - a form that retains the focus until it's closed, just like a dialog box. That's why the method is called ShowDialog() instead of ShowModal().
A loader needs to be modeless, so Show should be used instead :
public void ShowLoader(Form frm)
{
cploader.Show(frm);
}
Another difference is that ShowDialog returns a result with the user's choice (OK, Cancel etc) while Show returns nothing.
Modal Loader with notification
If you want to create a modal loader with ShowDialog but still perform some work in the background, you need a way to notify that loader from the background thread. You can do that using the Progress class.
The loader can expose IProgress<T> as a property. The T parameter can be a simple string or integer showing progress, or a complex entity with progress, a string message and a status indicator. For laziness' sake, let's use string and close the dialog if the value is empty :
public IProgress<string> Progress{get;private set;}
public CPLoader()
{
this.Progress=new Progress<string>(UpdateUI);
}
private void UpdateUI(string msg)
{
if(String.IsNullOrWhitespace(msg))
{
this.DialogResult=DialogResult.Cancel;
this.Close();
}
else
{
this.SomeLabel.Text=msg;
}
}
The code that works in the background needs access to that IProgress<string> property. Let's say the code that needs to work in the background is :
void Work(IProgress<string> progress)
{
for(int i=0;i<1000000;i++)
{
//Do something CPU intensive
//Report every 1000 items
if(i%1000==0)
{
progress.Report($"{i} out of 1000000");
}
}
//This tells the loader to close.
progress.Report("");
}
This code can run in the background and use the loader this way :
var loader=new CPLoader();
var task=Task.Run(()=>DoWork(loader.Progress));
loader.ShowDialog();
await task;
The loader is initialized first, giving us access to the IProgress<T> instance. The job gets started in the background after that with Task.Run. When it finishes, it sends an empty progress string and the loader's UpdateUI method closed the dialog in response
The code that needs to perform work while loading can access that IProgress<string> interface and use it to signal pro

"Invoke or BeginInvoke cannot be called on a control until" on a Label

I've read several questions/answers related to this problem but couldn't find a solution applicable to problem.
I have a form (MainForm) and a button (Upload) on it. When I click on the button (after selecting a file from a ComboBox to be uploaded to the server), it opens another form (UploadBackupForm) and uploads a file to the server. The upload process is controlled in UploadBackupForm and the form looks like this:
This works as long as upload is done once, I mean, the UploadBackupForm is called one time. The second time I click on the Upload button, UploadBackupForm opens and (after uploading some data) it throws an error saying:
System.InvalidOperationException: 'Invoke or BeginInvoke cannot be called on a control until the window handle has been created.'
at this specific line(s):
DurationLabel.Invoke((MethodInvoker)delegate
{
DurationLabel.Text = Count2Duration(count);
});
This has puzzled me because it works when it's done once, and doesn't work at the second time. I have basic knowdledge in C#, so I don't know what's causing this and how to solve it.
MainForm:
private void Upload2ServerButton_OnClick(object sender, EventArgs e)
{
Form UBF = new UploadBackupForm();
UBF.ShowDialog();
}
UploadBackupForm:
public partial class UploadBackupForm : Form
{
public UploadBackupForm()
{
InitializeComponent();
}
public static System.Timers.Timer timer = new System.Timers.Timer();
public static int count = 0;
private void UploadBackup_Load(object sender, EventArgs e)
{
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(delegate {
count++;
// didn't do any good (this.IsHandleCreated or DurationLabel.IsHandleCreated)
// if (!this.IsHandleCreated)
// {
// this.CreateControl();
// }
DurationLabel.Invoke((MethodInvoker)delegate
{
DurationLabel.Text = Count2Duration(count);
});
});
// upload the archive to the server
new Thread((ThreadStart)delegate
{
FTP.Item[] items = FTP.ListDirectoryDetails(DataIO.FTP.Server, DataIO.FTP.Username, DataIO.FTP.Password, DataIO.FTP.UploadDir);
// here, I upload the file to the server and update the progress bar and the uploaded / total labels
Because the timer variable is static it remains even after the form is closed. It contains a reference to a delegate which holds a reference to the form so the previous instances are kept alive through the lifetime of your application. Also, the single timer posts callbacks to all previous instances along with the current one.
As correctly noted in the comments by Evk, make the timer and count non-static so they are dedicated to each instance of the form.

Cross-class communication

I'm running a main WinForm with most UI elements, and then an Add form is called when the Add button is picked. It should receive input, and send it back to the WinForm once accepted.
This code runs when you click the "Add" button on the main class:
public void addButton_Click(object sender, EventArgs e)
{
AddView newadd = new AddView();
newadd.Show();
}
This code (also in the main class) should run based upon a button in the AddView:
public void AddDashObject(string dashName, string dashIdentifier, int dashFunction, string dashFunctionInfo, int dashVerbosity)
{
DashObject tmp = new DashObject("","",0,"",0);
tmp.DashName = dashName;
tmp.DashIdentifier = dashIdentifier;
tmp.DashFunction = dashFunction;
tmp.DashFunctionInfo = dashFunctionInfo;
tmp.DashVerbosity = dashVerbosity;
dashloaded.Add(tmp);
ReloadDashObjects();
}
I'm not really sure how to communicate between the forms - I can use a type created in the main class, and also methods, but I'm pretty sure I'm creating a separate instance. How can I communicate with the existing one?
MainView mnfrm = new MainView();
MainView.DashObject tmp = new MainView.DashObject("","",0,"",1); // Defaults
private void button1_Click(object sender, EventArgs e)
{
mnfrm.dashloaded.Add(tmp); // Add the default DashObject to MainView's currently loaded DashObjects
mnfrm.ReloadDashObjects(); // Reload the list
}
Not sure how to proceed on this - any advice?
Since the Add dialog is expected to be modal, the decoupled way to do this is to only close the dialog with an OK status. I.e. the form is just an input control that doesn't actually "do" anything.
Then the main form can:
1) check how the dialog was closed right after ShowDialog(),
2) call some GetResult() function to get the values from the add form before disposing it.
3) call the business logic that actually creates and reloads the DashObjects.

Close MessageBox when incoming new message

i created simple application to send and receive message using C# and GsmComm Library. if there is a new incoming message my application will show messageBox that new message arrived. my problem is when i have many new message, messageBox will show so many messageBox notication. How can I just show the last single message box using code?
this mycode:
private void comm_MessageReceived(object sender, MessageReceivedEventArgs e)
{
var obj = e.IndicationObject;
if (obj is MemoryLocation)
{
var loc = (MemoryLocation)obj;
var msg = string.Format("New message received in storage \"{0}\", index {1}.",
loc.Storage, loc.Index);
MessageBox.Show(msg);
return;
}
}
i confuse to fix this, i tried to another way using form to show new incoming notif form1.showDialog(); but same problem first form show cannot be closed when new form opened. this my reference: https://stackoverflow.com/a/13445167/3319555
I really thanks if anyone can help me..thanks
If you're using your second solution of displaying a form with form.ShowDialog() you can store the forms in a list. Then, when a new form needs to be displayed, you can iterate through the list and close each open form with form.Close(). Assuming that your comm_MessageReceieved method is run on another thread, which I assume is driven via an IO completion port, then something like this perhaps?
List<MyForm> formList = new List<MyForm>();
readonly object formListLock = new object();
private void comm_MessageReceived(object sender, MessageReceivedEventArgs e)
{
/// you need to lock the List for thread safe access
lock (formListLock)
{
/// iterate over a copy of the list to avoid mutating the list under iteration
foreach (MyForm form in formList.ToList())
{
form.ThreadSafeClose();
}
}
string msg = "message";
using (MyForm form = new MyForm(msg))
{
lock (formListLock) { formList.Add(form); }
form.ShowDialog();
lock (formListLock) { formList.Remove(form); }
}
}
This was just off the top of my head but might be another possible direction you could take.
You will have to make a thread safe call to form.Close() so that it is run on the form's UI thread. Read about invoke here. There's a lot of information on SO about this topic. This could be as simple as adding something like the following method to your form class:
public void ThreadSafeClose()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(Close)); /// or BeginInvoke...
}
else
{
Close();
}
}
Read more about Lists here: https://msdn.microsoft.com/en-us/library/6sh2ey19%28v=vs.110%29.aspx
Read more about the lock statement here: https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
Read more about thread synchronisation here: https://msdn.microsoft.com/en-us/library/ms173179.aspx
There are also numerous thread-safe collections that could possibly suit your needs, e.g. ConcurrentBag.

Providing multiple instances of a form yet processing events one at a time

I need to be able to let multiple instances of the same form be open as my application can be used in different places at once. On the other hand I need to be able to process the operations during the "OK" event one at a time to ensure data is stored safely and not overwritten by another form instance by accident.
I show my form using the .Show() method as I am using a few delegates in it:
private void newToolStripMenuItem_Click(object sender, EventArgs e)
{
bookingForm = new BookingForm(AddMemberBooking, AddUserBooking, CloseBooking);
bookingForm.Show();
}
I have tried to use the mutex to allow only one event of the OK button being pressed happen at a time, i have combined this with a Thread to meet the criteria i need.
When i click on the "OK" button I am given the following error:
Cross-thread operation not valid: Control 'comboBoxDay' accessed from a thread other than the thread it was created on.
This is the code for my booking form class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace Collection
{
//Allows the class to be serialized
[Serializable()]
public delegate void AddMemberBookingMethod(int date, int time, int mNo);
public delegate void AddUserBookingMethod(int date, int time, string fName, string lName, string pCode);
public delegate void CloseBookingFormMethod();
public partial class BookingForm : Form
{
public CloseBookingFormMethod CloseBookingForm;
public AddMemberBookingMethod AddMemberBooking;
public AddUserBookingMethod AddUserBooking;
private Mutex bookingMut = new Mutex();
private Thread thread;
public bool IsUser;
public BookingForm(AddMemberBookingMethod ambm, AddUserBookingMethod aubm, CloseBookingFormMethod cbfm)
{
InitializeComponent();
AddMemberBooking = ambm;
AddUserBooking = aubm;
CloseBookingForm = cbfm;
checkBoxMember.Checked = true;
//Control.CheckForIllegalCrossThreadCalls = false;
}
private void checkBoxUser_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxUser.Checked)
{
IsUser = true;
checkBoxMember.CheckState = CheckState.Unchecked;
textBoxMNo.Enabled = false;
textBoxFName.Enabled = true;
textBoxLName.Enabled = true;
textBoxPCode.Enabled = true;
}
else
{
IsUser = false;
checkBoxMember.CheckState = CheckState.Checked;
textBoxMNo.Enabled = true;
textBoxFName.Enabled = false;
textBoxLName.Enabled = false;
textBoxPCode.Enabled = false;
}
}
private void checkBoxMember_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxMember.Checked)
{
IsUser = false;
checkBoxUser.CheckState = CheckState.Unchecked;
textBoxFName.Enabled = false;
textBoxLName.Enabled = false;
textBoxPCode.Enabled = false;
}
else
{
IsUser = true;
checkBoxUser.CheckState = CheckState.Checked;
textBoxMNo.Enabled = false;
textBoxFName.Enabled = true;
textBoxLName.Enabled = true;
textBoxPCode.Enabled = true;
}
}
private void buttonOK_Click(object sender, EventArgs e)
{
this.thread = new Thread(new ThreadStart(MakeBooking));
this.thread.Name = "bookingThread";
this.thread.Start();
}
private void MakeBooking()
{
this.bookingMut.WaitOne();
int date = this.comboBoxDay.SelectedIndex;
int time = this.comboBoxTime.SelectedIndex;
if (IsUser)
{
string fName = textBoxFName.Text;
string lName = textBoxLName.Text;
string pCode = textBoxPCode.Text;
AddUserBooking(date, time, fName, lName, pCode);
}
else
{
int mNo = int.Parse(textBoxMNo.Text);
AddMemberBooking(date, time, mNo);
}
this.bookingMut.ReleaseMutex();
CloseBookingForm();
}
private void buttonClose_Click(object sender, EventArgs e)
{
CloseBookingForm();
}
}
}
I realise I may not be doing this in the most efficient way but time is a bit of a factor.
I've researched the error and have heard of using delegates and .Invoke() but I'm still not entirely sure how to fix it.
EDIT:
I've found this code snippet when searching for a fix to my problem. I don't understand where/how I would use it.
if(this.InvokeRequired)
{
this.Invoke(new MyEventHandler(this.CreateAForm()));
return;
}
EDIT2:
Seems the guy finally saw sense, by creating the from with the new word it apparently passes the criteria. I wish I'd have known this before trying to reinvent the wheel.
You are getting this exception because your thread is accessing controls. That's not legal, control properties must only ever be accessed from the UI thread. You're okay on the TextBox.Text property, that one happens to be cached. But not ComboBox.SelectedIndex. And closing the form from another thread is going to bomb too.
Your mutex has nothing to do with it, but keep it if you want to prevent threads from overlapping. Using a delegate's Invoke method isn't going to solve it, that just starts a thread as well. You'll need to collect the info that the thread is going to need in a little helper class and pass that as the argument to the Thread.Start() method.
Closing the form is a bit tricky too, the user might well have already closed it while the thread was running. That's going to cause an ObjectDisposed exception. A quick fix is to set the form's Enabled property to false so the user can't close it. You'll need to use the form's Invoke() method to ensure the closing is done on the UI thread.
Last but not least, if these threads don't take a lot of time (a second or so), consider not using threads at all and display a wait cursor instead.
One simple way to do this is to use the overload of the Thread.Start method that accepts an object: Thread.Start Method (Object). In this object you will store all the data/state necessary in order to make the update.
All the code that references the form and its controls needs to be moved into the OK click event method or refactored out to a method that just returns a data object. Then pass this object into the thread start method.
Some pseudo code:
on_click_event()
{
object data=getFormData();
thread.start(data);
}
There are better ways to do this but this is a quick fix for your code.
I think you could simply disable the OK buttons on other open forms to give users a visual cue. Then you shouldn't even have the issue. Provide a callback delegate to something in the application controller which knows which forms are open. Each form can provide a public method to disable the OK button. Disable to OK button on all the other forms.
I'm not really following your code too well. I would think the mutex could be outside of the form code in the first place (i.e. in the delegates that do the actual work), and if it is within a single application, you could just use the lock (object) method to ensure only one thread is executing a given bit of code.
I'd also like to add that a mutex is not going to stop multiple users on different machiens being able to click OK at the same time. I'm not sure if that's what you meant in your question by a form being run in different places.
I think that AddUserBooking and the other delegate should be responsible for ensuring that they are threadsafe and this should not be part of the UI. If they aren't threadsafe, why aren't they? It's relatively easy to make database commit functions each have their own connection to the database during their operations and thread-safety should not be an issue.

Categories

Resources