How to change a control property during main thread execution? - c#

I know there are several question related to my issues, I've studied them all but it seems that I still can't get it.
Something like this or like this.
I have a method that downloads some files through FTP (it takes somewhere around 5 seconds). When I click the button to download the files I also want to change a control property so that a "loading" kind of thing is visible.
For this I have a CircleProgressBar with "animated" property set by default to false. When I call the previous method I want first to change that property to true and after the download is complete to set it back to false as it was.
I tried many solutions but in vain:
void UpdateMessage(bool value)
{
Action action = () => DownloadLC_Normal_CircleProgressBar.animated = value;
Invoke(action);
}
private void DownloadLC_Normal_Button_Click(object sender, EventArgs e)
{
// try 1
//UpdateMessage(true);
// try 2
//DownloadLC_Normal_CircleProgressBar.Invoke((MethodInvoker)(() =>
//{
// DownloadLC_Normal_CircleProgressBar.animated = true;
//}));
// try 3
if (DownloadLC_Normal_CircleProgressBar.InvokeRequired)
{
DownloadLC_Normal_CircleProgressBar.BeginInvoke((MethodInvoker)delegate () { DownloadLC_Normal_CircleProgressBar.animated = true; });
}
else
{
DownloadLC_Normal_CircleProgressBar.animated = false;
}
// DOWNLOAD FILES THROUGH FTP BY CALLING A METHOD FROM A .cs FILE
// FROM THE PROJECT
//UpdateMessage(false);
//DownloadLC_Normal_CircleProgressBar.animated = false;
}
The CircleProgressBar never animates. What am I missing? What am I doing wrong, please? :(
EDIT:
My missing part of code:
ftp ftpClient = new ftp("ftp://" + "192.168.1.200" + "/", "anonymous", "anonymous");
NetworkCredential credentials = new NetworkCredential("anonymous", "anonymous");
string url = "ftp://" + "192.168.1.200" + "/Documents and Settings/";
ftpClient.DownloadFtpDirectory(url, credentials, newDirectoryDownloadLocation);

I'm assuming you're using framework 4.5/higher or 4.0 with Microsoft.Bcl.Async installed ok.
Try it:
private async void DownloadLC_Normal_Button_Click(object sender, EventArgs e)
{
try
{
DownloadLC_Normal_Button.Enabled = false;
DownloadLC_Normal_CircleProgressBar.animated = true;
ftp ftpClient = new ftp("ftp://" + "192.168.1.200" + "/", "anonymous", "anonymous");
NetworkCredential credentials = new NetworkCredential("anonymous", "anonymous");
string url = "ftp://" + "192.168.1.200" + "/Documents and Settings/";
//the code you post + change this line from:
//ftpClient.DownloadFtpDirectory(url, credentials, newDirectoryDownloadLocation);
//to: It makes the call be async
await Task.Run(() => ftpClient.DownloadFtpDirectory(url, credentials, newDirectoryDownloadLocation));
}
finally
{
DownloadLC_Normal_CircleProgressBar.animated = false;
DownloadLC_Normal_Button.Enabled = true;
}
}

One of the easiest options is to use async/await:
async void DownloadLC_Normal_Button_Click(object sender, EventArgs e)
{
DownloadLC_Normal_CircleProgressBar.animated = true;
DownloadLC_Normal_Button.Enabled = false; // prevent further clicks
await Task.Run(() =>
{
... // long running code, use `Invoke` to update UI controls
});
DownloadLC_Normal_CircleProgressBar.animated = false;
DownloadLC_Normal_Button.Enabled = true;
}

Related

How to run the UI Grafik and reader function without letting the UI get stopped until function ends? [duplicate]

I have a button that after I click it send a lot of data in a remote database with a loop, but during this operation whole wpf UI is freezing. My goal is to make the loader work while it is processing everything with the database.
My button code:
private void btn_Start_Click(object sender, RoutedEventArgs e)
{
pb_loader.IsIndeterminate = true; //<- it has to start to make animation
IEmailService emailService = new EmailService();
IUserQueryService emailQueryService = new UserQueryService();
var idIniziale = int.Parse(txtIdIniziale.Text);
var idFinale = int.Parse(txtIdFinale.Text);
var n = idFinale - idIniziale;
string mail = "";
for(int i=0; i<=n; i++)
{
mail = txtMail.Text + idIniziale + "#mail.local";
var exist = emailQueryService.CheckUserExist(mail); //<- db operation method
if (exist == false)
{
var lastUniqueId = emailQueryService.GetLastUniqueId();//<- db operation method
lastUniqueId = lastUniqueId + 1;
var idUtente = emailService.SalvaUtente(mail, lastUniqueId); //<- db operation method
emailService.AssegnaReferente(idUtente, txtMail.Text);//<- db operation method
emailService.AssegnaRuoli(idUtente); //<- db operation method
}
idIniziale++;
}
pb_loader.IsIndeterminate = false; //<- it has to end animation of loading
}
One straighforward approach for running a background operation in an event handler is to declare the event handler async and run and await a Task:
private async void btn_Start_Click(object sender, RoutedEventArgs e)
{
// prevent click while operation in progress
btn_Start.IsEnabled = false;
pb_loader.IsIndeterminate = true;
// access UI elements before running the Task
var mail = txtMail.Text + idIniziale + "#mail.local";
...
await Task.Run(() =>
{
// perform background operation
// use local variables "mail" etc. here
});
pb_loader.IsIndeterminate = false;
btn_Start.IsEnabled = true;
}

Call function from method in one classes in another class

Hello so i got very easy function to download files from FTP. Looks like this:
//Download files from FTP, return true of false if succed
public static bool DownloadFileFromFTP( string ip, string RemoteFilePath, string LocalFilePath, string username, string password)
{
try
{
FtpClient client = new FtpClient(ip);
client.Credentials = new NetworkCredential(username, password);
client.Connect();
ProgressBar progressBar;
progressBar = new ProgressBar();
Progress<double> progress = new Progress<double>(x => {
if (x > 0)
{
progressBar.Report((double)x / 100);
}
});
bool succes = client.DownloadFile(LocalFilePath, RemoteFilePath, FtpLocalExists.Overwrite, FluentFTP.FtpVerify.Retry, progress);
if(succes == true)
{
succes = true;
}
else
{
succes = false;
}
client.Disconnect();
progressBar.Dispose();
return succes;
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
return false;
}
}
And this method is in one class and i call it in another class like this:
Functions_General.DownloadFileFromFTP("192.168.240.86", "Ultra_Script/path", #"C:\Windows\Temp\Adobe_Reader.exe", "username", "password");
Everything works as expected. But the function itself have progressbar inside it and its disposing after finished downloading with this:
progressBar.Dispose();
But there comes the problem i need to dispose it in class where im calling the method is there any option how can i achieve that?
I need to have 3 synchronous progress bars and dispose them after all of 3 downloads are complete.
Thanks,

Await of MapLocationFinder.FindLocationsAtAsync never ends

With the await of MapLocationFinder my program still runs, even after trying to close it with Application.Current.Shutdown();. I'm a beginer.
I already tried to use CancellationToken or run this as Task. But I don't know if I had done this in the right way. I tried different thinks for some hours but nothing worked for me.
private async Task GetLocation()
{
var accesStatus = await Geolocator.RequestAccessAsync();
switch (accesStatus)
{
case GeolocationAccessStatus.Allowed:
// locate user
var locator = new Windows.Devices.Geolocation.Geolocator();
var location = await locator.GetGeopositionAsync();
var position = location.Coordinate.Point.Position;
// get city name
Geopoint geopoint = new Geopoint(new BasicGeoposition
{
Latitude = position.Latitude,
Longitude = position.Longitude
});
Here the problem starts
MapLocationFinderResult result = await MapLocationFinder.FindLocationsAtAsync(geopoint, MapLocationDesiredAccuracy.Low);
if (result.Status == MapLocationFinderStatus.Success)
{
locationBlock.Text = "City: " + result.Locations[0].Address.Town;
}
problem ended, the rest is just for the context
// calculate time
int[] sun = SunDate.CalculateSunriseSunset(51.434406, 6.762329);
var sunrise = new DateTime(1, 1, 1, sun[0] / 60, sun[0] - (sun[0] / 60) * 60, 0);
var sunset = new DateTime(1, 1, 1, sun[1] / 60, sun[1] - (sun[1] / 60) * 60, 0);
//fit UI
lightStartBox.Text = sunrise.Hour.ToString();
darkStartBox.Text = sunset.Hour.ToString();
// apply settings
lightStartBox.IsEnabled = false;
darkStartBox.IsEnabled = false;
break;
case GeolocationAccessStatus.Denied:
locationCheckBox.IsChecked = false;
locationBlock.Text = "The App needs permission to location";
await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-location"));
break;
case GeolocationAccessStatus.Unspecified:
locationCheckBox.IsChecked = false;
locationBlock.Text = "The App needs permission to location";
await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-location"));
break;
}
return;
}
If I close the program, it should also end the await task. Better: It should end the operation after he got the info.
If I close the program, it should also end the await task. Better: It should end the operation after he got the info.
I have run your code, but I could not reproduce the issue, I could get MapLocationFinderResult with low delay. I found you used MapLocationDesiredAccuracy.Low parameter. And it will leverage the maps disk cache to get accurate info up to the city level. maps disk cache may cause this issue. You could try to use MapLocationDesiredAccuracy.High parameter.
As you see, FindLocationsAtAsync is IAsyncOperation method. So, you could cancel it manually or set timeout cancel token.
For example
private IAsyncOperation<string> GetAsyncOperation()
{
return AsyncInfo.Run<string>(
(token) => // CancellationToken token
Task.Run<string>(
() =>
{
token.WaitHandle.WaitOne(3000);
token.ThrowIfCancellationRequested();
return "hello";
},
token));
}
private IAsyncOperation<string> operation;
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
operation = GetAsyncOperation();
var res = await operation;
}
catch (Exception)
{
System.Diagnostics.Debug.WriteLine("method end");
}
}
private void Cancel_Button_Click(object sender, RoutedEventArgs e)
{
operation?.Cancel();
}
Set Timeout
private async void Button_Click(object sender, RoutedEventArgs e)
{
var source = new CancellationTokenSource(4000);
var res = await GetAsyncOperation().AsTask(source.Token);
}
Looks like this is a known bug
To work-around it I ended up setting a static flag on my App class so that when the app was shutting down it would force kill the process.
// Before call to MapLocationFinder.FindLocationsAsync()
App.RequiresProcessKill = true;
and then in my shutdown process (ie in the OnClosed method of your main window) I forced closed the app if neccessary:
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (App.RequiresProcessKill)
{
var self = Process.GetCurrentProcess();
self.Kill();
}
}

C# UI for AWS S3 not updating

I am creating a download application in C# that downloads files from Amazon AWS S3 storage. I am able to download the file without issue, but I am trying to create a progress event.
To create the event I am using the following code within the download function:
Application.DoEvents();
response2.WriteObjectProgressEvent += displayProgress;
Application.DoEvents();
The event handler I created is as follows:
private void displayProgress(object sender, WriteObjectProgressArgs args)
{
// Counter for Event runs
label7.BeginInvoke(new Action(() => label7.Text = (Convert.ToInt32(label7.Text)+1).ToString()));
Application.DoEvents();
// transferred bytes
label4.BeginInvoke(new Action(() => label4.Text = args.TransferredBytes.ToString()));
Application.DoEvents();
// progress bar
progressBar1.BeginInvoke(new Action(() => progressBar1.Value = args.PercentDone));
Application.DoEvents();
}
The issue is that it only updates when a file it downloaded, but the event runs more often. When I download the last file (12MB); lable7 (event counter) jumps from 3 to 121, so I know it is running, but just not updating.
I have also tried just a 'standard' Invoke, but I had the same result.
Additional Code of the function:
AmazonS3Config S3Config = new AmazonS3Config
{
ServiceURL = "https://s3.amazonaws.com"
};
var s3Client = new AmazonS3Client(stuff, stuff, S3Config);
ListBucketsResponse response = s3Client.ListBuckets();
GetObjectRequest request = new GetObjectRequest();
request.BucketName = "dr-test";
request.Key = locationoffile[currentdownload];
GetObjectResponse response2 = s3Client.GetObject(request);
response2.WriteObjectProgressEvent += displayProgress;
string pathlocation = Path.GetDirectoryName(Directory.GetCurrentDirectory()) + "\\" + Instrument[currentdownload] + "\\" + NewFileName[currentdownload];
response2.WriteResponseStreamToFile(pathlocation);
You're not using the asynchronous call for GetObject or WriteResponseStreamToFile, so the UI thread (that you're calling it from) is going to be blocked, which means that it can't update the progress (regardless of those DoEvents calls, which are generally considered evil and you should avoid).
Without actually being able to try it myself, here's what I think you need to do.
private async void Button_Click(object sender, EventArgs e)
{
foreach(...download....in files to download){
AmazonS3Config S3Config = new AmazonS3Config
{
ServiceURL = "https://s3.amazonaws.com"
};
var s3Client = new AmazonS3Client(stuff, stuff, S3Config);
ListBucketsResponse response = s3Client.ListBuckets();
GetObjectRequest request = new GetObjectRequest();
request.BucketName = "dr-test";
request.Key = locationoffile[currentdownload];
GetObjectResponse response2 = await s3Client.GetObjectAsync(request, null);
response2.WriteObjectProgressEvent += displayProgress;
string pathlocation = Path.GetDirectoryName(Directory.GetCurrentDirectory()) + "\\" + Instrument[currentdownload] + "\\" + NewFileName[currentdownload];
await response2.WriteResponseStreamToFileAsync(pathlocation, null);
}
}
The two nulls that I added are for cancellation tokens, I can't tell from the AWS docs if it's allowed to pass a null token, if not please create one and pass it in.

c# .net stop async method from running

I am working on signalR. I have a server (ASP .NET) and a client (C# WinForms).
In my client app I have a async method:
public async void conAsync()
{
HubConn = new HubConnection(serverURL);
HubPrx = HubConn.CreateHubProxy("myHUB");
try
{
await HubConn.Start();
richTextBox1.AppendText("connected");
button1.BackColor = Color.Green;
groupBox1.Enabled = true;
groupBox2.Enabled = true;
}
catch(Exception err)
{
// deactive comps
groupBox1.Enabled = false;
groupBox2.Enabled = false;
richTextBox1.AppendText(err.toString());
}
}
The Start Button executes the above method.
I want provide a Stop Button which stops the connection to the server. I read about CancellationToken but I got confused about how to use it in my case.
Actually my button is a CheckBox which acts like a ToggleButton.
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
checkBox1.ForeColor = Color.Green;
conAsync();
}
else
{
//stop conAsync() here
}
}
HubConn.close() solved the problem and it stops the conAsync() ..... i think the cancellation token is not the solution here (i was wrong) but it may be alternative one for another case.

Categories