I am using the following method to set the pagenumber of my current page:
protected override void OnAppearing()
{
base.OnAppearing();
// Page number
_pageNumber = Navigation.NavigationStack.Count - 2;
}
Everything works fine, if I am navigating forward and pushing a new page onto the stack like this:
private async void btnContinue_Clicked(object sender, EventArgs e)
{
// Proceed to next view
await Navigation.PushAsync(new MainPage());
}
However, when I am navigating back from one page, the OnAppearing() method seems to be called before the previous page is popped. Thus, the _pageNumber variable will effectively be the one of the previous page. How do I work around that? I thought of placing it inside the constructor but the NavigationStack.Count always returns 0 in there.
My pages are dynamically programmed, therefore, I cannot hardcode the page number into the page because the same page is called but the values are changing based on the number of the page.
This will be an expected effect .
When the PopAsync method is invoked, the following events occur:
The page calling PopAsync has its OnDisappearing override invoked.
The page being returned to has its OnAppearing override invoked.
The PopAsync task returns.
So if you want to get current count of NavigationStack , you could improve the answer like following :
In the demo , I used MessageingCenter to pass the current of NavigationStack to last page . And the label will display the current NavigationStack .
bool isFirstLoad = true;
protected override void OnAppearing()
{
base.OnAppearing();
if(isFirstLoad)
{
label.Text = Navigation.NavigationStack.Count.ToString();
isFirstLoad = false;
}
MessagingCenter.Subscribe<Object,int>(this, "pop", (arg, num) => {
label.Text = (num-1).ToString();
});
}
And when you call Pop
private async void Button_Clicked_1(object sender, EventArgs e)
{
MessagingCenter.Send<Object,int>(this, "pop", Navigation.NavigationStack.Count);
await Navigation.PopAsync();
}
I have a Xamarin form map on my screen and I'm using PropertyChanged event to retrieve geolocation information from my server and display the proper pins on screen.
While coding the solution I noticed the PropertyChanged event is triggered multiple times (up to 10 times) with a single zoom or drag action on the map. This causes unnecessary calls to server which I want to avoid.
Ideally I want to make only one call to server when the final PropertyChanged event is called but I cant's find an easy solution to implement this.
At this point I've added a refresh button to my page that becomes enabled when a PropertyChanged event happens and I disable it after user uses the button.
Obviously this fixed the too many calls to server but made the solution manual.
I was wondering if there is a more elegant way to make the server call but do it automatically.
Thanks in advance.
I just test the PropertyChanged event on iOS side and it just triggered one time with a single zoom or drag action on the map.
While if it really triggered multiple times, you can use a timer to call the server when the final PropertyChanged event is called, for example:
public partial class MapPage : ContentPage
{
Timer aTimer;
public MapPage()
{
InitializeComponent();
customMap.PropertyChanged += CustomMap_PropertyChanged;
}
private void CustomMap_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (aTimer != null)
{
aTimer.Enabled = false;
aTimer.Stop();
aTimer.Close();
}
aTimer = new Timer();
aTimer.Interval = 1000;
aTimer.Enabled = true;
aTimer.Elapsed += ATimer_Elapsed;
aTimer.Start();
}
private void ATimer_Elapsed(object sender, ElapsedEventArgs e)
{
aTimer.Stop();
//do web request
Console.WriteLine(sender);
Console.WriteLine("CustomMap_PropertyChanged");
}
}
In the above code, I set the Interval = 1 second, that means in 1 second, whatever how many times PropertyChanged triggered, only the last call will trigger the ATimer_Elapsed function.
The Interval can be set to any value depending on your requirement.
Here I am showing toast message in Xamarin Forms Android
My Code:
Inside my Android Class
public void ShortAlert(string message)
{
Toast.MakeText(Forms.Context, message, ToastLength.Short).Show();
}
I am showing this toast message when user click on A Button.But When user rapidly click the button then it will shoing continiously(5 times button click showung toast 5 times).
so I want if the user click on this button second time then previous toast should be cancel.
How to do this in Xamarin form android?
You can try cancelling the Toast than it wont show on screen for a long time
public void ShowToast(string message)
{
if (objToast!= null)
{
objToast.Cancel();
}
objToast = Toast.MakeText(Forms.Context, message, ToastLength.Short);
objToast.Show();
}
Declare objToast on class level like this
private Toast objToast;
You could add the effective time of a click, for example, only the first click is valid for multiple clicks within 3 seconds,so you could change the code like this :
public static int MIN_CLICK_DELAY_TIME = 3000;//the effective time
private long lastClickTime = 0;
public void ShortAlert(string message)
{
long currentTime = Calendar.Instance.TimeInMillis;
if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME)
{
lastClickTime = currentTime;
Toast.MakeText(Forms.Context, message, ToastLength.Short).Show();
}
}
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();
}
In my application i have a Custom AlertView, which works quite good so far. I can open it the first time, do, what i want to do, and then close it. If i want to open it again, i'll get
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first
so, here some code:
public Class ReadingTab
{
...
private AlertDialog AD;
...
protected override void OnCreate(Bundle bundle)
{
btnAdd.Click += delegate
{
if (IsNewTask)
{
...
AlertDialog.Builer adb = new AlertDialog.Builer(this);
...
View view = LayoutInflater.Inflate(Resource.Layout.AlertDView15ET15TVvert, null);
adb.setView(view)
}
AD = adb.show();
}
}
}
that would be the rough look of my code.
Inside of btnAdd are two more buttons, and within one of them (btnSafe) i do AD.Dismiss() to close the Alert dialoge, adb.dispose() hasn't done anything.
the first time works fine, but when i call it the secon time, the debugger holds at AD = adb.show(); with the Exception mentioned above.
So what do i have to do, to remove the Dialoge from the parent? i can't find removeView() anywhere.
If you are setting up an AlertView once and then using it in multiple places (especially if you are using the same AlertView across different Activities) then you should consider creating a static AlertDialog class that you can then call from all over the place, passing in the current context as a parameter each time you want to show it. Then when a button is clicked you can simply dismiss the dialog and set the instance to null. Here is a basic example:
internal static class CustomAlertDialog
{
private static AlertDialog _instance;
private const string CANCEL = #"Cancel";
private const string OK = #"OK";
private static EventHandler _handler;
// Static method that creates your dialog instance with the given title, message, and context
public static void Show(string title,
string message,
Context context)
{
if (_instance != null)
{
throw new Exception(#"Cannot have more than one confirmation dialog at once.");
}
var builder = new AlertDialog.Builder(context);
builder.SetTitle(title);
builder.SetMessage(message);
// Set buttons and handle clicks
builder.SetPositiveButton(OK, delegate { /* some action here */ });
builder.SetNegativeButton(CANCEL, delegate { /* some action here */});
// Create a dialog from the builder and show it
_instance = builder.Create();
_instance.SetCancelable(false);
_instance.Show();
}
}
And from your Activity you would call your CustomAlertDialog like this:
CustomAlertDialog.Show(#"This is my title", #"This is my message", this);