Activity Spinner in Xamarin doesn't instantly update - xamarin

So I have this problem...
I have an activity Indicator in Xamarin set up like this
<ActivityIndicator IsRunning="{Binding IsSearching}" />
In my page class I bind it up like this...
private BindableProperty IsSearchingProperty =
BindableProperty.Create("IsSearching", typeof(bool), typeof(MainPage), false);
public bool IsSearching
{
get { return (bool)GetValue(IsSearchingProperty); }
set
{
SetValue(IsSearchingProperty, value) ;
}
}
Everythig works but only one problem. The spinner won't initialize before the method from which it is called is not finished... For example...
private async void New_Survey_Button_OnClicked(object sender, EventArgs e)
{
IsSearching = true;
some method that takes a long time...
await Navigation.PushAsync(new SurveyNavigationPage());
}
In this case the IsSearching true value will get initialized after the compiler hits await. Truly stuck with this issue. Please help. Thank you.
UPDATE 1___________________________
So after some debugging here is what exactly is going on.
1) IsSearching is hit which takes the program to the setter.
2) After it hits the setter it continues to the method that takes a long time.
3) Only after it is done with the time consuming method and goes out of the New_Survey_Button_OnClicked it actually hits the getter and updates the UI.

Related

Xamarin forms switch events lead to infinte loop - Group switches

I am trying to develop an app using Xamarin.Forms. At a certain point I am trying to have multiple switches that are grouped. This is to say that when one switch is toggled, every other switch needs to be untoggled and, at the same time, there needs to be at least one switch always toggled. This is to say that tapping on a switch that is already toggled should not change anything.T
Now my problem is that Xamarin.forms Toggled event for switches can be fired from the UI, but is also fired programmatically. I thought I had found a way around this problem, but still by doing:
-If the switch was turned on, turn off all others and do application stuff.
-else if a switch was turned off, check if there are any others that are on. If not, turn the switch back on. If yes, do nothing.
A sample code for two switches could be:
private void OnFirstToggled(object sender, EventArgs args)
{
if(FirstSwitch.isToggled)
{
//Application stuff.
SecondSwitch.isToggled = false;
}
else if (!SecondSwitch.isToggled)
{
FirstSwitch.isToggled = true;
}
}
private void OnSecondToggled(object sender, EventArgs args)
{
if(SecondSwitch.isToggled)
{
//Application stuff.
FirstSwitch.isToggled = false;
}
else if (!FirstSwitch.isToggled)
{
SecondSwitch.isToggled = true;
}
}
This solution results in an infinite loop when an already toggled switch is tapped. In fact, the isToggled property of the switch alternates between true and false infinitely. However when debugging the other event never seems to be fired (or at least my debugger does not see it). This is why I don't understand where the isToggled property is changed after that first tap.
I know this is probably a very simple issue, but I cannot seem to find the solution somewhere online. Can anyone see the problem or recommend a better, common way to implement this?
I write a simple solution to you to always keep one Switch open from a Switch group.
Let's first add three switch for test, make sure these Switch will fire the same event of Toggled:
<StackLayout>
<!-- Place new controls here -->
<Switch Toggled="Switch_Toggled" x:Name="FirstSwitch"/>
<Switch Toggled="Switch_Toggled" x:Name="SecondSwitch"/>
<Switch Toggled="Switch_Toggled" x:Name="ThirdSwitch"/>
</StackLayout>
In the code behind, I add those Switches into a list, and loop them in Switch_Toggled event to open/close the Switches:
public partial class MainPage : ContentPage
{
List<Switch> switchList;// To store all your Switches
bool isLooping; //To make sure the Switch_Toggled metod not fired a second time during one toogle event
public MainPage()
{
InitializeComponent();
switchList = new List<Switch>();
switchList.Add(FirstSwitch);
switchList.Add(SecondSwitch);
switchList.Add(ThirdSwitch);
isLooping = false;
}
private void Switch_Toggled(object sender, ToggledEventArgs e)
{
//To make sure the Switch_Toggled metod not fired a second time during one toogle event
if (isLooping == true)
{
return;
}
isLooping = true;
Switch clickSwitch = sender as Switch;
clickSwitch.IsToggled = true;
foreach (var tempSwitch in switchList)
{
if (tempSwitch != clickSwitch)
{
if (tempSwitch.IsToggled == true)
{
tempSwitch.IsToggled = false;
}
}
}
isLooping = false;
}
}
You can try this solution and feel free to ask me any question if you don't understand.
Your problem are the two else blocks. Take in account that you're toggling it on anyway.

Can I change the UI from a method that gets data (not async) that has been called with await?

I have this code:
protected override async void OnAppearing()
{
base.OnAppearing();
Subscribe();
vm.IsBusy = true;
if (Change.firstTimeCardsTab == true)
{
SetButtons(Settings.cc.ShortText());
await Task.Run(() => GetOnAppearingData());
}
}
private void GetOnAppearingData()
{
App.DB.GetData();
AddDetailSection();
SetPageDetails();
Change.firstTimeCardsTab = false;
vm.IsBusy = false;
}
vm.IsBusy = true; shows an activity indicator on the page and vm.IsBusy = false; would I hoped turn it off.
However when I run the code I get a message saying:
UIKit Consistency error: you are calling a UIKit method that can only
be invoked from a UI thread.
Can someone give me advice on how I can set the activity indicator to show, then get the data and then set it to not show? Note that AddDetailSection(); adds some elements to the XAML. Could this be the problem?
UI can be manipulated only from the main thread. There is a method Device.BeginInvokeOnMainThread(Action) that can help to solve your issue, more can be found in the official documentation.
Just wrap all UI related actions by it:
Device.BeginInvokeOnMainThread(() => {
SetPageDetails();
// Any other actions
});

Windows Forms Event before the Closing event? [duplicate]

I have a form that spawns a BackgroundWorker, that should update form's own textbox (on main thread), hence Invoke((Action) (...)); call.
If in HandleClosingEvent I just do bgWorker.CancelAsync() then I get ObjectDisposedException on Invoke(...) call, understandably. But if I sit in HandleClosingEvent and wait for bgWorker to be done, than .Invoke(...) never returns, also understandably.
Any ideas how do I close this app without getting the exception, or the deadlock?
Following are 3 relevant methods of the simple Form1 class:
public Form1() {
InitializeComponent();
Closing += HandleClosingEvent;
this.bgWorker.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
while (!this.bgWorker.CancellationPending) {
Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); }));
}
}
private void HandleClosingEvent(object sender, CancelEventArgs e) {
this.bgWorker.CancelAsync();
/////// while (this.bgWorker.CancellationPending) {} // deadlock
}
The only deadlock-safe and exception-safe way to do this that I know is to actually cancel the FormClosing event. Set e.Cancel = true if the BGW is still running and set a flag to indicate that the user requested a close. Then check that flag in the BGW's RunWorkerCompleted event handler and call Close() if it is set.
private bool closePending;
protected override void OnFormClosing(FormClosingEventArgs e) {
if (backgroundWorker1.IsBusy) {
closePending = true;
backgroundWorker1.CancelAsync();
e.Cancel = true;
this.Enabled = false; // or this.Hide()
return;
}
base.OnFormClosing(e);
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (closePending) this.Close();
closePending = false;
// etc...
}
I've found another way. If you have more backgroundWorkers you can make:
List<Thread> bgWorkersThreads = new List<Thread>();
and in every backgroundWorker's DoWork method make:
bgWorkesThreads.Add(Thread.CurrentThread);
Arter that you can use:
foreach (Thread thread in this.bgWorkersThreads)
{
thread.Abort();
}
I used this in Word Add-in in Control, which i use in CustomTaskPane. If someone close the document or application earlier then all my backgroundWorkes finishes their work, it raises some COM Exception(I don't remember exatly which).CancelAsync() doesn't work.
But with this, I can close all threads which are used by backgroundworkers Immediately in DocumentBeforeClose event and my problem is solved.
Here was my solution (Sorry it's in VB.Net).
When I run the FormClosing event I run BackgroundWorker1.CancelAsync() to set the CancellationPending value to True. Unfortunately, the program never really gets a chance to check the value CancellationPending value to set e.Cancel to true (which as far as I can tell, can only be done in BackgroundWorker1_DoWork).
I didn't remove that line, although it doesn't really seem to make a difference.
I added a line that would set my global variable, bClosingForm, to True. Then I added a line of code in my BackgroundWorker_WorkCompleted to check both e.Cancelled as well as the global variable, bClosingForm, before performing any ending steps.
Using this template, you should be able to close your form out at any time even if the backgroundworker is in the middle of something (which might not be good, but it's bound to happen so it might as well be dealt with). I'm not sure if it's necessary, but you could dispose the Background worker entirely in the Form_Closed event after this all takes place.
Private bClosingForm As Boolean = False
Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
bClosingForm = True
BackgroundWorker1.CancelAsync()
End Sub
Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Run background tasks:
If BackgroundWorker1.CancellationPending Then
e.Cancel = True
Else
'Background work here
End If
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If Not bClosingForm Then
If Not e.Cancelled Then
'Completion Work here
End If
End If
End Sub
Can you not wait on the signal in the destructor of the form?
AutoResetEvent workerDone = new AutoResetEvent();
private void HandleClosingEvent(object sender, CancelEventArgs e)
{
this.bgWorker.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!this.bgWorker.CancellationPending) {
Invoke((Action) (() => { this.textBox1.Text =
Environment.TickCount.ToString(); }));
}
}
private ~Form1()
{
workerDone.WaitOne();
}
void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e )
{
workerDone.Set();
}
Firstly, the ObjectDisposedException is only one possible pitfall here. Running the OP's code has produced the following InvalidOperationException on a substantial number of occasions:
Invoke or BeginInvoke cannot be called
on a control until the window handle
has been created.
I suppose this could be amended by starting the worker on the 'Loaded' callback rather than the constructor, but this entire ordeal can be avoided altogether if BackgroundWorker's Progress reporting mechanism is used. The following works well:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!this.bgWorker.CancellationPending)
{
this.bgWorker.ReportProgress(Environment.TickCount);
Thread.Sleep(1);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.textBox1.Text = e.ProgressPercentage.ToString();
}
I kind of hijacked the percentage parameter but one can use the other overload to pass any parameter.
It is interesting to note that removing the above sleep call clogs the UI, consumes high CPU and continually increases the memory use. I guess it has something to do with the message queue of the GUI being overloaded. However, with the sleep call intact, the CPU usage is virtually 0 and the memory usage seems fine, too. To be prudent, perhaps a higher value than 1 ms should be used? An expert opinion here would be appreciated... Update: It appears that as long as the update isn't too frequent, it should be OK: Link
In any case, I can't foresee a scenario where the updating of the GUI has to be in intervals shorter than a couple of milliseconds (at least, in scenarios where a human is watching the GUI), so I think most of the time progress reporting would be the right choice
I really dont see why DoEvents is regarded as such a bad choice in this case if you are using this.enabled = false. I think it would make it quite neat.
protected override void OnFormClosing(FormClosingEventArgs e) {
this.Enabled = false; // or this.Hide()
e.Cancel = true;
backgroundWorker1.CancelAsync();
while (backgroundWorker1.IsBusy) {
Application.DoEvents();
}
e.cancel = false;
base.OnFormClosing(e);
}
Your backgroundworker should not use Invoke to update the textbox. It should ask the UI thread nicely to update the textbox using event ProgressChanged with the value to put in the textbox attached.
During event Closed (or maybe event Closing), the UI thread remembers that the form is closed before it cancels the backgroundworker.
Upon receiving the progressChanged the UI thread checks if the form is closed and only if not, it updates the textbox.
This won't work for everyone, but if you are doing something in a BackgroundWorker periodically, like every second or every 10 seconds, (perhaps polling a server) this seems to work well to stop the process in an orderly manner and without error messages (at least so far) and is easy to follow;
public void StopPoll()
{
MyBackgroundWorker.CancelAsync(); //Cancel background worker
AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
while (!MyBackgroundWorker.CancellationPending)
{
//Do some background stuff
MyBackgroundWorker.ReportProgress(0, (object)SomeData);
AutoResetEvent1.WaitOne(10000);
}
}
I'd pass in the SynchronizationContext associated with the textbox to the BackgroundWorker and use that to perform Updates on the UI thread. Using SynchronizationContext.Post, you can check if the control is disposed or disposing.
What about Me.IsHandleCreated?
Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted
If Me.IsHandleCreated Then
'Form is still open, so proceed
End If
End Sub
Another way:
if (backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();
while (backgroundWorker.IsBusy)
{
Application.DoEvents();
}
}
One solution that works, but too complicated. The idea is to spawn the timer that will keep trying to close the form, and form will refuse to close until said bgWorker is dead.
private void HandleClosingEvent(object sender, CancelEventArgs e) {
if (!this.bgWorker.IsBusy) {
// bgWorker is dead, let Closing event proceed.
e.Cancel = false;
return;
}
if (!this.bgWorker.CancellationPending) {
// it is first call to Closing, cancel the bgWorker.
this.bgWorker.CancelAsync();
this.timer1.Enabled = true;
}
// either this is first attempt to close the form, or bgWorker isn't dead.
e.Cancel = true;
}
private void timer1_Tick(object sender, EventArgs e) {
Trace.WriteLine("Trying to close...");
Close();
}

How to Cleanup a ViewModel in Mvvm Light?

I have a list of items that goes to another page, That page is hooked up to a view model. In the constructor of this view model I have code that grabs data from the server for that particular item.
What I found is that when I hit the back button and choose another item fromt hat list and it goes to the other page the constructor does not get hit.
I think it is because the VM is now created and thinks it does not need a new one. I am wondering how do I force a cleanup so that a fresh one is always grabbed when I select from my list?
I faced the same issue, that's how i solved it.
Have a BaseView class, override OnNavigatedTo
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigatedToCommand != null && NavigatedToCommand.CanExecute(null))
NavigatedToCommand.Execute(null);
}
add DependencyProperty.
public static readonly DependencyProperty NavigatedToCommandProperty =
DependencyProperty.Register("NavigatedToCommand", typeof(ICommand), typeof(BaseView), null);
public ICommand NavigatedToCommand
{
get { return (ICommand)GetValue(NavigatedToCommandProperty); }
set { SetValue(NavigatedToCommandProperty, value); }
}
On the necessary pages, add to xaml (and, of course, inherit BaseView )
NavigatedToCommand="{Binding OnNavigatedToCommand}"
In the ViewModel, make command itself
public RelayCommand OnNavigatedToCommand
{ get { return new RelayCommand(OnNavigatedTo); } }
and implement method you want to call to update list
public async void OnNavigatedTo()
{
var result = await myDataService.UpdateMyList();
if (result.Status == OK)
MyList = result.List;
}
So, now, every time you navigate to page with list, inside of overriden OnNavigatedTo(), a NavigatedToCommand would be executed, which would execute OnNavigatedToCommand (which you set in xaml), which would call OnNavigatedTo, which would update your list.
A bit messy, but MVVM :)
EDIT: What about cleanings, they can be done in OnNavigatedFrom(), which works the same. Or OnNavigatingFrom(), which also can be useful in some cases.

How to wait for an async call to complete on WP7?

I'm loading pivot items based on a call a webservice call. Given that all I have is asynchronous calls available, how do I go about catching when it's finished?
My main reason is that I'd like to keep a loading dialog up while it's waiting for the callback. However, I'm loading in a viewmodel class, and obviously the loading bar is in the page class.
Honestly, if I could just know when one pivot item was loaded, that would be fine, however setting an event handler on loadedpivotitem never seems to trigger.
I assume you are databinding your View to your ViewModel. In that case all you need to do is create a bool property and set it to true while loading/awaiting the async call. You could do something like this:
private bool isSyncing;
public bool IsSynchronizing
{
get { return this.isSyncing; }
set
{
this.isSyncing = value;
this.RaisePropertyChanged(() => this.IsSynchronizing); //Use appropriate RaisePropertyChanged method for your MVVM implementation
}
}
Before starting the async call you would set IsSynchronizing = true. At the end of the eventhandler set IsSynchronizing = false;
From your view you can bind to this bool. For the loadingbar it could be like this:
<ProgressBar Visibility="{Binding IsSynchronizing, Converter={StaticResource booleanToVisibilityConverter}}" IsIndeterminate="{Binding IsSynchronizing}" Style="{StaticResource PerformanceProgressBar}" />
In your scenario you can use an inverted BooleanToVisibilityConverter to hide the pivot while it is still loading.
Hope this helps, let me know if you need more info on using the BooleanToVisibilityConverters
You would need to hook up an event handler similar to as shown in this block of code:
public void LoadData()
{
SampleDataServiceClient client = new SampleDataServiceClient();
client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(client_GetDataCompleted);
client.GetDataAsync();
}
void client_GetDataCompleted(object sender, GetDataCompletedEventArgs e)
{
this.DataContext = e.Result;
}

Resources