Form controls not rendering - c#

I'm having troubles rendering my custom non blocking 'MessageBox' like form. So I have made my Alert form:
public partial class Alert : Form
{
public Alert(string title, string text)
{
InitializeComponent();
this.Text = title;
labelText.Text = text;
}
private void buttonOk_Click(object sender, System.EventArgs e)
{
Close();
}
}
and looks like this in designer:
But when I try to open this Alert form within my main form like this:
var alert = new Alert("Test", "Hello, this is test.");
alert.Show();
Then my Alert form renders like this:
Where is my button and label?
//EDIT
alert.ShowDialog();
renders the form as intended
The method I'm using this form in:
private void buttonTestConnection_Click(object sender, EventArgs e)
{
var f = new Alert("Test", "Hello, this is test");
f.Show();
Thread.Sleep(2000);
var result = TestSqlConnection(); // This may that a while
if (result)
{
f.Close();
MessageBox.Show("Test", "Test successful", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
f.Close();
MessageBox.Show("Test", "Connection failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

The UI thread renders your UI. You are blocking the UI thread by calling Thread.Sleep. Thread.Sleep has it's place in programming, but not in UI programming.
My suggestion would be to move to asynchronous programming. For example:
private async void buttonTestConnection_Click(object sender, EventArgs e)
{
var f = new Alert("Test", "Hello, this is test");
f.Show();
await Task.Delay(2000);
var result = await Task.Run(() => TestSqlConnection());
if (result)
{
f.Close();
MessageBox.Show("Test", "Test successful",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
f.Close();
MessageBox.Show("Test", "Connection failed",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Notice the async keyword added to your method signature and calls to await.
I won't go in to how this works as there are many, many tutorials online. Here is a great place to start. The guy is a resident genius around here when it comes to this pattern.
Typically SQL connections can be converted to asynchronous, so that Task.Run should be temporary until you figure out the pattern.

Another quick example:
private async void buttonTestConnection_Click(object sender, EventArgs e)
{
buttonTestConnection.Enabled = false;
using (var f = new Alert("Test", "Hello, this is test"))
{
f.Show();
bool result = await Task.Run(() => {
Thread.Sleep(2000);
return TestSqlConnection();
});
MessageBox.Show("Test", "Test " + (result ? "successful" : "failed"),
MessageBoxButtons.OK,
(result ? MessageBoxIcon.Information : MessageBoxIcon.Error));
}
buttonTestConnection.Enabled = true;
}

Related

Windows form not responding: How can I resolve this issue?

I create a windows service and a setup project.
During the installation of the windows service, I add a windows form which allow the user to upload a file in the project folder but when I click on the button to upload the file my windows form is always on state not responding
ProjectInstaller of my windows service
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
Form1 validationForm = new Form1();
validationForm.ShowDialog();
}
Windows form
public Form1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, EventArgs e)
{
try
{
OpenFileDialog fileDialog = new OpenFileDialog();
//fileDialog.Filter = "Dat files |*.dat";
fileDialog.Multiselect = false;
if (fileDialog.ShowDialog() == DialogResult.OK)
{
var path = fileDialog.FileName;
Process.Start(path);
}
}
catch (Exception)
{
MessageBox.Show("An error occured", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
I think Process.Start(path); block UI thread.
Try to use Task.Run(() => Process.Start(a)); instead.
Your UI is locked up due to a long running process which is why you see "Not responding"
Mark your Click Async:
private async void button1_Click_1(object sender, EventArgs e)
and
await Task.Run(() =>
{
//Insert the long running stuff here
Process.Start(path);
});
Try this.
private void button1_Click(object sender, EventArgs e)
{
var task = new Thread(() => GetFile());
task.SetApartmentState(ApartmentState.STA);
task.Start();
task.Join();
}
private static void GetFile()
{
try
{
OpenFileDialog fileDialog = new OpenFileDialog();
//fileDialog.Filter = "Dat files |*.dat";
fileDialog.Multiselect = false;
if (fileDialog.ShowDialog() == DialogResult.OK)
{
var path = fileDialog.FileName;
Process.Start(path);
}
}
catch (Exception)
{
MessageBox.Show("An error occured", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Can't access winforms label after await?

I have a long running method which I made async. I made my button click handler async as well, but when I try to access my label in my button click after the long method is done, it tells me it can't can't access it from another thread. Here is the code:
private void Migrate()
{
for (int i = 2; i <= excelData.GetUpperBound(0); i++)
{
var poco = new ExpandoObject() as IDictionary<string, object>;
foreach (var column in distributionColumnExcelHeaderMappings)
{
if (column.ColumnIndex > 0)
{
var value = excelData[i,column.ColumnIndex]?.ToString();
poco.Add(column.DistributionColumnName.Replace(" ", ""), value);
}
}
pocos.Add(poco);
}
migrationRepository.BulkInsert(insertToTable, "Id", pocos);
}
private async void btnMigrate_Click(object sender, EventArgs e)
{
Task task = new Task(()=> Migrate());
task.Start();
lblStatus.Text = "Migrating data....";
await task;
lblStatus.Text = "Migration Complete";
}
When the button is clicked, I see the status Migrating data..... When that is complete, it throws an error on lblStatus.Text = "Migration Complete". I thought after await, it goes back to the UI thread?
I cleared out most of the code and it still throws the same error. This is a VSTO excel add-in. Could that be part of the problem?
private void Migrate()
{
}
private async void btnMigrate(object sender, EventArgs e)
{
Task.Run(()=>Migrate());
lblStatus.Text = "Done"; //still get error here
}
Try and update your code to the following:
Instead of creating your task and then starting it manually, update it to just await on Task.Run:
private async void btnMigrate_Click(object sender, EventArgs e)
{
lblStatus.Text = "Migrating data....";
await Task.Run(()=> Migrate());
lblStatus.Text = "Migration Complete";
}
Edit:
You can use a helper method that will check to see if the label needs to be invoked before updating.
private async void btnMigrate_Click(object sender, EventArgs e)
{
SetLabelText(lblStatus, "Migrating data....");
await Task.Run(()=> Migrate());
SetLabelText(lblStatus, "Migration complete.");
}
private void SetLabelText(Label label, string text)
{
if (label.InvokeRequired)
{
label.BeginInvoke((MethodInvoker) delegate() {label.Text = text;});
}
else
{
label.Text = text;
}
}

C# Stop Task under Form_Show()

Hi I have read other tutorials but could not figure it out. I am running a task and when task is completed I want to hide the current form and load another form but It hangs and nothing is displayed. This is my code please guide me -
public Loading()
{
InitializeComponent();
Shown += Loading_Shown;
}
private void Loading_Shown(object sender, EventArgs e)
{
label2.Text = "Step 1...";
Task.Run(() =>
{
if (Directory.Exists(contentPath))
{
filePresent = false;
}
if (filesPresent == false)
{
BeginInvoke(
(MethodInvoker)delegate
{
label2.Text = "Downloading Files...";
}
);
Directory.CreateDirectory(contentPath);
Home form = new Home();
form.Visible = true;
}
else
{
Home form = new Home();
form.Visible = true;
}
});
}
The other form loads half and screen hangs. Please guide me how to continue with this. Thanks
You don't create the second form "when [the] task is completed", but inside that task. So you create the second form on a different thread than the first one. This is a bad idea.
One solution is to make Loading_Shown an async method and await the task. Then, when the task really has completed and control flow returned to the original ui thread, you can create the second form:
private async void Loading_Shown(object sender, EventArgs e)
{
label2.Text = "Step 1...";
await Task.Run(() =>
{
// different thread
filePresent = Directory.Exists(contentPath);
if (!filePresent) Directory.CreateDirectory(contentPath);
});
// back on UI thread
if (!filesPresent)
{
label2.Text = "Downloading Files..."; });
Home form = new Home();
form.Visible = true;
}
else{
Home form = new Home();
form.Visible = true;
}
}

How to trigger an event upon progress bar completion

I have created a Windows Forms program that breaks down a file and sends its components to a server. These files are large, so I created a progressBar so users don't think it froze while the transactions are happening. What I would like to do is have some mechanism that will actively trigger only when all threads are complete without blocking the UI thread (again, so the plebs wont think its frozen). The best I could come up with is a kind of passive "wait until true" but I feel like there has to be a better way to do this. I have experimented with trying to create an event or a callback but honestly I've just ended up more confused than when I started. Here is an example of how I am doing this now:
public partial class Program : Form
{
private readonly OpenFileDialog _ofd = new OpenFileDialog();
public delegate void BarDelegate();
private string _path;
private void button1_Click(object sender, EventArgs e)
{
if (_ofd.ShowDialog() != DialogResult.OK) return;
textBox1.Text = _ofd.SafeFileName;
_path = _ofd.FileName;
}
private void button2_Click(object sender, EventArgs e)
{
var allLinesFromFile = File.ReadAllLines(_path);
progressBar1.Minimum = 0;
progressBar1.Maximum = allLinesFromFile.Length;
Task.Factory.StartNew(() => Parallel.ForEach(allLinesFromFile, DoSomething));
while (progressBar1.Value < progressBar1.Maximum) //there has to be a better way to do this...
{
MessageBox.Show("Please wait.", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
//some processes here which should only take place after all threads are complete.
var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK) Environment.Exit(0);
}
private void DoSomething(string record)
{
//some string manipulation and server transactions here
BeginInvoke(new BarDelegate(() => progressBar1.Increment(1)));
}
}
Try using Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms") for this. Then your code becomes:
private void button2_Click(object sender, EventArgs e)
{
var allLinesFromFile = File.ReadAllLines(_path);
progressBar1.Minimum = 0;
progressBar1.Maximum = allLinesFromFile.Length;
IDisposable subscription =
allLinesFromFile
.ToObservable()
.SelectMany(f => Observable.Start(() => DoSomething(f)))
.ObserveOn(this)
.Do(x => progressBar1.Value += 1)
.Subscribe(x => { }, () =>
{
var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK)
{
Application.Exit();
}
});
}
private void DoSomething(string record)
{
System.Threading.Thread.Sleep(5);
}
If you need to stop this early then just call subscription.Dispose(). I've tested this and it works fine for me.
You should be using the BackGroundWorker class, see: How to use a BackgroundWorker?
And use BackGroundWorker.RunWorkerComplete for when the thread has finished
Background worker:
**Backgroundworker (System.ComponentModel)**
BackgroundWorker loader = new BackgroundWorker();
loader.DoWork += load_Specials_BW_Thread;
loader.WorkerReportsProgress = true;
loader.ProgressChanged += load_Special_Feeds_Progress_Changed;
private void load_Specials_BW_Thread(object sender, DoWorkEventArgs e)
{
int pctComplete = (int)Math.Floor(ptComplete * 100);//recs done / total recs
(sender as BackgroundWorker).ReportProgress(pctComplete);
}
Good luck!

Forms opens two times

In the code below, I open a form with frmContact.ShowDialog(); and then when I close the form by clicking on the OK button in the form it closes, but then it opens again because I have the frmContact.ShowDialog() in the if statement. Could this be done in some oterh way?
// Button add new customer
private void btnAdd_Click(object sender, EventArgs e)
{
ContactForm frmContact = new ContactForm();
frmContact.ShowDialog(); // Show the contact form window
if (frmContact.ShowDialog() == DialogResult.OK)
{
MessageBox.Show("OK", "Test", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Simply remove the first call:
ContactForm frmContact = new ContactForm();
if (frmContact.ShowDialog() == DialogResult.OK)
{
MessageBox.Show("OK", "Test", ...);
}
Another option (especially useful if the code that shows the form is not next to the code that checks the return value) is to use Form.DialogResult:
ContactForm frmContact = new ContactForm();
frmContact.ShowDialog();
if (frmContact.DialogResult == DialogResult.OK)
{
MessageBox.Show("OK", "Test", ...);
}
Just get rid of the first ShowDialog.
Just leave the second if, like this:
private void btnAdd_Click(object sender, EventArgs e)
{
ContactForm frmContact = new ContactForm();
if (frmContact.ShowDialog() == DialogResult.OK) //just one call
{
MessageBox.Show("OK", "Test", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
private void btnAdd_Click(object sender, EventArgs e)
{
ContactForm frmContact = new ContactForm();
frmContact.ShowDialog();
}

Categories