So I'm new in programming with Xamarin (actually even with C# tbh)
What I'm trying to acheave is a Task which should only work when a Switch (called S1) is Toggled.
My idea:
public async Task GetCon(){
for (; ; )
{
if (S1.IsToggled == true)
{
AI1.IsRunning = true;
bool CStat = await CrossConnectivity.Current.IsRemoteReachable("https://www.google.ch");
if (CStat == true)
{
StatLbl.Text = "Online";
}
else if (CStat == false)
{
StatLbl.Text = "Offline";
break;
}
off:;
await Task.Delay(3000);
}
All works fine but if I turn the Switch back off and make google unreachable, the StatLbl text doesn't change to offline.
Any idea?
As you're new to C#, I'm surprised no-one else has picked up on this, but here goes.
Instead of infinitely running a Task and waiting for the switch to be toggled, use the Toggled event to trigger when the toggle status changes. How does it work? When you toggle the switch, your program will automatically call that method. For example:
public MyConstructor()
{
S1.Toggled += S1_Toggled;
}
void S1_Toggled(object sender, ToggledEventArgs e)
{
System.Diagnostics.Debug.WriteLine(String.Format("Switch is now {0}", e.Value));
}
You can find more information (and some example) for the Switch at the Xamarin Forms Docs.
As Jason pointed out, you should be modifying UI properties from the UI thread. Properties like colour, visibility, text etc (anything that changes on the display) should be done in Device.BeginInvokeOnMainThread like so:
Device.BeginInvokeOnMainThread(() =>
{
StatLbl.Text = "Offline";
});
you need to execute UI changes on the UI thread
Device.BeginInvokeOnMainThread(() => {
StatLbl.Text = "Offline";
});
Most of the code executes only when the switch is on, you have this line that prevents from changing the label name when the switch is on:
if (S1.IsToggled == true)
Related
I'm newer to the concept of threading and I would like to use Task that is a component of Thread in my application because the save task takes time for executing.
This is my code:
private void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
Task.Run(() =>
{
var itemsS = Gridview.Items;
Dispatcher.Invoke(() =>
{
foreach (ItemsModel item in itemsS)
{
PleaseWaittxt.Visibility = Visibility.Visible;
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
PleaseWaittxt.Visibility = Visibility.Hidden;
});
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem is that when I save all items, I got duplicate data in the database. Otherwise, the count of ItemsS is fixed to 300, but after the saving, I got 600,
Did Task.Run() repeat the save task to the database ?
NB: I'm working on UI project ( WPF Desktop app )
I'm thinking you'd need something along the lines of this.
I quickly whipped it up but i hope its enough to attempt a fix yourself.
private async void SaveItem(object sender, RoutedEventArgs e)
{
try {
var itemsS = GridviewServices.Items.ToList(); // to list makes shallow copy
await Task.Run(() => {
foreach (ItemsModel item in itemsS)
{
bool testAdd = new Controller().AddItem(item);
}
});
// Dont update ui in task.run, because only the ui thread may access UI items
// Do so here - after the await. (or use dispatcher.invoke).
GridviewServices.Items.Clear();
GridviewServices.Items = itemsS;
} catch { ... } // Handle exceptions, log them or something. Dont throw in async void!
}
I'm also thinking this would work:
private async void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
var itemsS = GridviewServices.Items;
await Task.Run(() =>
{
foreach (ItemsModel item in itemsS)
{
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Visible;})
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Hidden;})
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem you're running in to, is because the Task you're executing isn't running in parallel, but synchronously to the rest of your application.
When you're running CPU-intensive tasks in the background of your UI-application, you'll want to either work with actual threads or async/await - which is what you attempted with your code.
What you'll want to do is something similar to this:
private async void SaveItem(object sender, RoutedEventArgs e) => await Task.Run(
/*optionally make this async too*/() => {
// Execute your CPU-intensive task here
Dispatcher.Invoke(() => {
// Handle your UI updates here
});
});
This is just a general overview, I don't know your exact use-case, but this should get you started in the right direction.
One thing to be weary of when using Lambdas and such, is closures.
If your application tends to use a lot of memory, you might want to re-think the structure of your calltree and minimize closures in your running application.
I am currently making a windows form application on Visual studio in C#. I have a couple of text boxes where I want the user to input some stuff and then this information is checked whether it exists, if not, an error is thrown and a text box saying "Invalid File" is meant to appear, in red.
However, currently, when I enable it's visibility, it simply shows up as a blank box, with no colour and no formatting.
Here is the code I was using:
catch
{
textBox9.Visible = true;
System.Threading.Thread.Sleep(3000);
textBox9.Visible = false;
}
The only thing it happens is the txtbox be visible, so the only code executed is the code inside catch...
Try to set all the properties in the catch, something like that:
For sure all gonna be executed now.
catch
{
textBox9.Text = "Invalid File";
textBox9.BackColor = Color.Red;
textBox9.Visible = true;
Thread.Sleep(3000);
textBox9.Visible = false;
}
Edit:
I saw the comment, and that's right Thread will block all the code for 3 seconds.
So I've other option, something like that:
catch
{
textBox9.Text = "Invalid File";
textBox9.BackColor = Color.Red;
textBox9.Visible = true;
int seconds = 3;
if (seconds < 1) return;
DateTime _desired = DateTime.Now.AddSeconds(seconds);
while (DateTime.Now < _desired)
{
System.Windows.Forms.Application.DoEvents();
}
textBox9.Visible = false;
}
If i understand correctly you are trying to make the textbox work over 3 seconds and then go away, if that is so the code you need would look like this
Task.Run(async () =>
this.Invoke(new Action(delegate (){
textBox9.Visible = true;
await Task.Delay(3000)
textBox9.Visible = false;
}));
EDIT: This code is needed because you dont wanna hang the whole thread just wait 3 seconds and then make it go away, the way you are doing it, you are freezing the whole application if you are not using threads
EDIT2: It isnt showing anything because you are freezing the thread before it draws on your screen and then you are setting the textbox hidden. So nothing will show
private void DisplayError()
{
Task.Run(async () => (
this.Invoke(new Action(async delegate () {
textBox9.Visible = true;
await Task.Delay(3000);
textBox9.Visible = false;
}))));
}
Thanks to nalnpir for the basis of this. This works for me.
Can anyone help me understand why my call to dialogservice executes after the CanNavigateAway function has returned its value? (My goal is to warn the user they are about to navigate away from a view without saving their changes. If they click OK, the navigation is allowed. I'm using MVVM Light.
When I step through the code, it does reach the dialog service, but then proceeds to the end of CanNavigateAway before creating the dialog. The CanNavigateAway method is called by OnNavigatingFrom.
public bool CanNavigateAway()
{
if (!changesSaved && Model.IsModified && !continueNavigation)
{
dialogService.ShowMessage("Are you sure you want to continue?",
"Confirmation",
buttonConfirmText: "Continue", buttonCancelText: "Discard",
afterHideCallback: (confirmed) =>
{
if (confirmed)
{
// User has pressed the "confirm" button.
// ...
continueNavigation = true;
}
else
{
// User has pressed the "cancel" button
// (or has discared the dialog box).
// ...
continueNavigation = false;
}
});
return continueNavigation;
}
}
Here is the OnNavigatingFrom method from the MVVM Light Bindable Page class:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (!navigableViewModel.CanNavigateAway())
{
e.Cancel = true;
}
}
}
I tried this a different way to get the dialog service out of the mix, but showConfirmationDialogAsync still does not seem to execute in time:
public bool CanNavigateAway()
{
continueNavigation = false;
if (!changesSaved && Model.IsModified && !continueNavigation)
{
showConfirmationDialogAsync();
return continueNavigation;
}
private async void showConfirmationDialogAsync()
{
continueNavigation = false;
ContentDialog noSaveConfirmation = new ContentDialog
{
Title = "Warning",
Content = "You have unsaved changes. Are you sure you want to leave this page without saving?",
PrimaryButtonText = "Leave without saving",
SecondaryButtonText = "Stay and finish"
};
ContentDialogResult result = await noSaveConfirmation.ShowAsync();
if (result == ContentDialogResult.Primary)
{
continueNavigation = true;
}
else if (result == ContentDialogResult.Secondary)
{
continueNavigation = false;
}
}
None of the solutions will work if you require a response from the user. The problem is that when the code is inside the navigation event handler, it is running on the UI thread and the user prompt runs asynchronously, so that the UI is free to present the dialog to the user. This however means that the event handler finishes before the user has a chance to respond.
However, you can use a workaround solution. Add a flag bool field like forceNavigation. Then inside the OnNavigatingFrom display the dialog to the user and set Cancel to true right away and display the user the confirmation dialog. If the user says yes, then set forceNavigaiton to true and trigger the navigation manually again. Now it will skip the confirmation part and navigate right away.
protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
//if navigation is forced, skip all logic
if ( !forceNavigation )
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
e.Cancel = true;
//display the dialog to the user, if he says yes, set
//forceNavigation = true; and repeat the navigation (e.g. GoBack, ... )
}
}
}
I'm having issues with the new SystemMediaTransportControls that replace MediaControl.
Currently, I have my app set up with:-
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsPlayEnabled = true;
systemControls.IsStopEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.ButtonPressed += SystemControls_ButtonPressed;
And
async void SystemControls_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
System.Diagnostics.Debug.WriteLine(args.Button);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
if (mediaElement1.CurrentState != MediaElementState.Playing)
{
restartSource();
}
else
{
completeClosure();
}
break;
case SystemMediaTransportControlsButton.Pause:
case SystemMediaTransportControlsButton.Stop:
completeClosure();
break;
default:
break;
}
});
}
And:
private async void completeClosure()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement1.Stop();
mediaElement1.Source = null;
timer.Stop();
});
}
private async void restartSource()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement1.Source = new Uri(holdThisSource, UriKind.Absolute);
mediaElement1.Play();
timer.Start();
});
}
When a user presses the Pause Button, args.Button shows up as "Play", hence the need for the checking for MediaElement's state. However, when I attempt to resume to media, it successfully resumes in restartSource() and updates the app accordingly but the icon on the Volume Control does not change from the Play sign, although hardware buttons still work.
Along with this, pressing the hardware Stop button NEVER works, and fails to even show up in Debug.WriteLine.
This is an online streaming app where the source does not allow resuming and thus I have to close the stream this way.
I'd love some help on this.
Since you did not update the systemControls.PlaybackStatus, the control button on transport control will not auto change to correct status.
You should always update the systemControls.PlaybackStatus property when the playback state has changed.
May this could solve your problems.
I think I'm missing something obvious here, but how do I update the GUI when using a task and retrieving the value? (I'm trying to use await/async instead of BackgroundWorker)
On my control the user has clicked a button that will do something that takes time. I want to alert the parent form so it can show some progress:
private void ButtonClicked()
{
var task = Task<bool>.Factory.StartNew(() =>
{
WorkStarted(this, new EventArgs());
Thread.Sleep(5000);
WorkComplete(this, null);
return true;
});
if (task.Result) MessageBox.Show("Success!");//this line causes app to block
}
In my parent form I'm listening to WorkStarted and WorkComplete to update the status bar:
myControl.WorkStarting += (o, args) =>
{
Invoke((MethodInvoker) delegate
{
toolStripProgressBar1.Visible = true;
toolStripStatusLabel1.Text = "Busy";
});
};
Correct me if I'm wrong, but the app is hanging because "Invoke" is waiting for the GUI thread to become available which it won't until my "ButtonClicked()" call is complete. So we have a deadlock.
What's the correct way to approach this?
You're blocking the UI thread Task.Result blocks until the task is completed.
Try this.
private async void ButtonClicked()
{
var task = Task<bool>.Factory.StartNew(() =>
{
WorkStarted(this, new EventArgs());
Thread.Sleep(5000);
WorkComplete(this, null);
return true;
});
await task;//Wait Asynchronously
if (task.Result) MessageBox.Show("Success!");//this line causes app to block
}
You can use Task.Run to execute code on a background thread. The Task-based Asynchronous Pattern specifies a pattern for progress updates, which looks like this:
private async void ButtonClicked()
{
var progress = new Progress<int>(update =>
{
// Apply "update" to the UI
});
var result = await Task.Run(() => DoWork(progress));
if (result) MessageBox.Show("Success!");
}
private static bool DoWork(IProgress<int> progress)
{
for (int i = 0; i != 5; ++i)
{
if (progress != null)
progress.Report(i);
Thread.Sleep(1000);
}
return true;
}
If you are targeting .NET 4.0, then you can use Microsoft.Bcl.Async; in that case, you would have to use TaskEx.Run instead of Task.Run. I explain on my blog why you shouldn't use Task.Factory.StartNew.