How to load this viewmodel method to onClick event - c#

I am trying to call this GetProductStatus() method on a page button click event, but it's loading before the button click. Means when the ViewModel is loading, this is also load automatically.
I would like to declared this VM method "GetProductStatus()" to be called only when a button click event occurs.
ViewModel method:
private async void GetProductStatus()
{
try
{
IsBusy = true;
var status = await ProductStatusService.GetProductStatus(new ProductStatus()
{
StoreCode = s_code,
StartTime = StartDateValue.AddMinutes(time1),
EndTime = StartDateValue.AddMinutes(time2)
});
IsBusy = false;
if (status != null)
{
//Process happens
}
else
{
//Array is Null
}
ProductStatus = status;
}
catch (Exception)
{
ProductStatus = null;
}
}
Here, the method is declared.
public ProductViewModel(INavigation nav, Store store)
{
_Nav = nav;
GetProductStatus();
}
Here, the clicked event.
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}

I would like to declared this VM method "GetProductStatus()" to be
called only when a button click event occurs.
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
For above code you posted, we can find that the constructor of your viewmodel will be called as soon as you call code new ProductPage(_ViewModel).
So, you can try to remove code GetProductStatus(); in constructor ProductViewModel
public ProductViewModel(INavigation nav, Store store)
{
_Nav = nav;
// remove code here
//GetProductStatus();
}
and add a command in your ViewModel, and bind it to the button in your page.
Please refer to the following code:
public class ProductViewModel
{
public Command LoadDataCommand { get; set; }
public ProductViewModel() {
LoadDataCommand = new Command(loadData);
// remove code here
//GetProductStatus();
}
private void loadData()
{
GetProductStatus(); // add your code here
}
private async void GetProductStatus()
{
// other code
}
}
Note:
1.In this condition, you can also navigate as follows:
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
2.I don't add parameter to the constructor of ProductViewModel , you can modify above code I posted according to your needs.

Set aside the fact that you are working with views and models. Simply think of them like any other class in c#.
If you need to tell class A "do something under these circumstances`, what are your options?
Pass a parameter in constructor: public ProductViewModel(..., bool doGetProductStatus)..., usage: new ProductViewModel(..., true);
Call a method A.DoSomething(); after you've created it: _ViewModel.DoSomething();
Use MessagingCenter Publish/Subscribe.

Related

How to create a function that receives as a parameter a dynamic function?

I'm creating a dynamic dialog box in xamarin.android and I want the dialog box to receive a function that it will execute upon clicking the action button.
Heres my code:
public static void ShowDialogBox(Context context, Function dynamicFunction)
{
Dialog popupDialog = new Dialog(context);
popupDialog.SetContentView(Resource.Layout.dialog_dynamic);
popupDialog.Window.SetSoftInputMode(SoftInput.AdjustResize);
popupDialog.Window.SetBackgroundDrawableResource(Android.Resource.Color.Transparent);
popupDialog.Show();
TextView title = popupDialog.FindViewById<TextView>(Resource.Id.dialog_title);
TextView content = popupDialog.FindViewById<TextView>(Resource.Id.dialog_content);
ImageView icon = popupDialog.FindViewById<ImageView>(Resource.Id.dialog_icon);
Button actionButton = popupDialog.FindViewById<Button>(Resource.Id.dialog_action_button);
popupDialog.FindViewById<ImageButton>(Resource.Id.dialog_close).Click += delegate { popupDialog.Dismiss(); };
popupDialog.FindViewById<Button>(Resource.Id.dialog_cancel_button).Click += delegate { popupDialog.Dismiss(); };
actionButton.Click += dynamicFunction;
}
*Take note that the Function parameter is just an example. Also I will make the text and content in this dialog box a parameter.
Example scenario:
If I send GetCurrentDate function to this dialog box, when the action button is clicked it will get the current date.
If I send OpenBluetooth function to this dialog box, when the action button is clicked it will open the Bluetooth
It is possible to send another function to this ShowDialogBox function and run it when the action button is clicked? My purpose here it to make my code clean and easy to use.
As #Nico already commented, you can use Func or a delegate as a parameter. I'd suggest you use Action instead.
The Clicked event on Xamarin.Forms.Button uses an event handler with the signature void OnClicked(object? sender, EventArgs e). Therefore, your signature would use a Action<object?, EventArgs> parameter.
A couple things to note:
If you don't use nullable reference types in your project, omit the question marks
You need to check the parameter for null before assigning it to the Clicked event
Invoking an Action does not return anything. If you want to "get the current date", your action must write the current date in a field or control
If your function needs other parameters, you need to pass a delegate (or wrap your function call in a function with the sender and e parameters: ShowDialogBox(context, (sender, e) => MyFunction("other data"))
Object orientation can help you: you can abstract your function into a class and then derive any specilized function class to implement your specific action.
Something like the following:
abstract class ActionClassBase
{
public abstract void Action();
private string data = "";
public string Data { get { return data; } set { data = value; } }
}
class GetCurrentDateAction : ActionClassBase
{
public override void Action()
{
Data = DateTime.Now.ToString();
}
}
class OpenBlueToothAction : ActionClassBase
{
public override void Action()
{
Data = "BlueTooth opened";
}
}
public static void ShowDialogBox(Context context, ActionClassBase action)
{
Dialog popupDialog = new Dialog(context);
popupDialog.SetContentView(Resource.Layout.dialog_dynamic);
popupDialog.Window.SetSoftInputMode(SoftInput.AdjustResize);
popupDialog.Window.SetBackgroundDrawableResource(Android.Resource.Color.Transparent);
popupDialog.Show();
TextView title = popupDialog.FindViewById<TextView>(Resource.Id.dialog_title);
TextView content = popupDialog.FindViewById<TextView>(Resource.Id.dialog_content);
ImageView icon = popupDialog.FindViewById<ImageView>(Resource.Id.dialog_icon);
Button actionButton = popupDialog.FindViewById<Button>(Resource.Id.dialog_action_button);
popupDialog.FindViewById<ImageButton>(Resource.Id.dialog_close).Click += delegate { popupDialog.Dismiss(); };
popupDialog.FindViewById<Button>(Resource.Id.dialog_cancel_button).Click += delegate { popupDialog.Dismiss(); };
actionButton.Click += (sender, ev) => action.Action();
}
Further with this approach it's very easy to add new functions: just derive a new class and override the Action() method
You can create a Dialog in the ActionClassBase and call the Dismiss() method in the Action(). Such as
abstract class ActionClassBase
{
public Dialog popupDialog;
public abstract void Action();
private string data = "";
public string Data { get { return data; } set { data = value; } }
}
class GetCurrentDateAction : ActionClassBase
{
public override void Action()
{
Data = DateTime.Now.ToString();
base.popupDialog.Dismiss();
}
}
class OpenBlueToothAction : ActionClassBase
{
public override void Action()
{
Data = "BlueTooth opened";
base.popupDialog.Dismiss();
}
}
And then, pass the dialog to the ActionClassBase in the ShowDialogBox(Context context, Function dynamicFunction) . Such as:
Dialog popupDialog = new Dialog(context);
popupDialog.SetContentView(Resource.Layout.dialog_dynamic);
popupDialog.Window.SetSoftInputMode(SoftInput.AdjustResize);
popupDialog.Window.SetBackgroundDrawableResource(Android.Resource.Color.Transparent);
dynamicFunction.popupDialog = popupDialog;

How to trigger a method from an observable that is out of scope

I have a ListView that changes. This ListView is inside LinearLayout that also has an Icon that shows as a checkmark if the ListView items include an item of a certain type. It shows an "X" if none of the items are of that type.
In the code below, the Console.WriteLine works.
How do I update the Icon (aka call the Redraw function) after a NotifyDataSetChanged has been called on the ListView adapter. The function is outside of the scope of the observer and cannot be called inside the OnChanged.
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
}
private void Redraw () {
// UPDATE ICON HERE
}
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
Console.WriteLine("Change was observerd");
OnDataChanged(new DataChangedEventArgs() { DataChanged = 1, TimeChanged = DateTime.Now });
// This area is hit, but how do I call the Redraw method above? It is out of scope
}
}
/// EDIT: Something I've Tried THAT WORKS! Anything seem off about it?
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
myObserver.DataChanged += OnDataChanged;
}
private void Redraw () {
// UPDATE ICON HERE
}
private void OnDataChanged(object sender, EventArgs e) {
Redraw();
}
// Added the last four event handler pieces
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
g.ToastShort("Change was observerd");
}
public event EventHandler DataChanged;
protected virtual void OnDataChanged(EventArgs e)
{
EventHandler handler = DataChanged;
handler?.Invoke(this, e);
}
public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);
public class DataChangedEventArgs : EventArgs
{
public int DataChanged { get; set; }
public DateTime TimeChanged { get; set; }
}
}
You can use messaging-center to notify your activity to call Redraw() when OnChanged hit.
The MessagingCenter is a simple way to reduce coupling, especially
between view models. It can be used to send and receive simple
messages or pass an argument between classes. Classes should
unsubscribe from messages they no longer wish to receive.
In the OnChanged(), send a message every time it is hit:
public override void OnChanged()
{
base.OnChanged();
Console.WriteLine("Change was observerd");
// This area is hit, but how do I call the Redraw method above? It is out of scope
MessagingCenter.Send<object>(this, "needRedraw");
}
In your Init(), Subscribe the needRedraw message and call redraw whenever the "needRedraw" message is sent:
private void Init()
{
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
MessagingCenter.Subscribe<object>(this, "needRedraw", (sender) => {
// do something whenever the "needRedraw" message is sent
Redraw();
});
}
Thank you #Tyddlywink for your comment: "youll need to create [an Event] in your MyDataSetObserver class and fire it"
I used this as a resource for adding Events: https://learn.microsoft.com/en-us/dotnet/standard/events/
Here are the updates I added to trigger my Redraw() function:
private void Init () {
view = ((Activity)cx).LayoutInflater.Inflate(Resource.Layout.MyPage, this);
eventsListAdapter?.Dispose();
eventsListAdapter = new EventsAdapter(
context,
EventListDisplay.DefaultView,
dateCurrentlyDisplayed);
var myObserver = new MyDataSetObserver();
eventsListAdapter.RegisterDataSetObserver(myObserver);
myObserver.DataChanged += OnDataChanged;
}
private void Redraw () {
// UPDATE ICON HERE
}
private void OnDataChanged(object sender, EventArgs e) {
Redraw();
}
public class MyDataSetObserver : DataSetObserver
{
public override void OnChanged()
{
base.OnChanged();
// To be honest, I don't know what int DataChanged wants.. so arbitrarily set it to 1.
OnDataChanged(new DataChangedEventArgs() { DataChanged = 1, TimeChanged = DateTime.Now });
}
public event EventHandler DataChanged;
protected virtual void OnDataChanged(EventArgs e)
{
EventHandler handler = DataChanged;
handler?.Invoke(this, e);
}
public delegate void DataChangedEventHandler(object sender, DataChangedEventArgs e);
public class DataChangedEventArgs : EventArgs
{
public int DataChanged { get; set; }
public DateTime TimeChanged { get; set; }
}
}

mvvmcross Viewmodel.Initialize is fired before you can hook InitializeTask.PropertyChanged

In my app, I want to know in the View when Initialize is complete. The problem is that it gets launched before you can hook InitializeTask.PropertyChanged. Here is my ViewModel code:
public override async Task Initialize()
{
ClientID = await MyDataSource.GetClientID();
}
In my View I am doing the following:
protected override void OnViewModelSet()
{
var vm = this.DataContext as MyViewModel;
vm.InitializeTask.PropertyChanged += InitializeTask_PropertyChanged;
base.OnViewModelSet();
}
private void InitializeTask_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSuccessfullyCompleted")
{
vm.InitializeTask.PropertyChanged -= InitializeTask_PropertyChanged;
if (vm.ClientID != "")
posClient = new PosClient(this, vm.ClientID);
}
}
As far as I know, OnViewModelSet is the soonest that you have access to your ViewModel in your View. If I put in breakpoints, Initialize runs before OnViewModelSet is fired. This makes it very likely that Initialize has finished before you can hook the event. Is there another place in the View where you can hook the event before Initialize starts so you will be guaranteed to Initialize.PropertyChanged will fire in the View?
**** Update ****
I followed #fmaccaroni advice and implemented an MvxInteraction called DataLoaded. One thing I did different was to create a separate function to load the data.
public void LoadData()
{
Task.Run(async () =>
{
ClientID = await IHSDataSource.GetClientID();
_DataLoaded.Raise();
});
}
I was concerned about the async task finishing before I got the interaction event wired up. Doing it this way, I added this in the View.
protected override void OnViewModelSet()
{
vm = this.DataContext as InvoiceViewModel;
var set = this.CreateBindingSet<InvoiceView, InvoiceViewModel>();
set.Bind(this).For(view => view.DataLoaded).To(viewModel => viewModel.DataLoaded).OneWay();
set.Apply();
vm.LoadData();
base.OnViewModelSet();
}
This way, LoadData does not start until I am sure the result will trigger the interaction and I am guaranteed to get the result. This was the first I had heard about MvxInteraction and I am now using it all the time.
I'm not sure what you want to achieve, but if you want to take an action in your view when the Initialize ends just do an MvxInteraction and call it after your await, i.e.:
ViewModel:
private MvxInteraction _interaction = new MvxInteraction();
public IMvxInteraction MyMvxInteraction => _interaction;
public override async Task Initialize()
{
ClientID = await MyDataSource.GetClientID();
this._interaction.Raise();
}
View:
private IMvxInteraction _interaction;
public IMvxInteraction MyMvxInteraction
{
get => this._interaction;
set
{
if (this._interaction != null)
this._interaction.Requested -= this.OnInteractionRequested;
this._interaction = value;
this._interaction.Requested += this.OnInteractionRequested;
}
}
private void OnInteractionRequested(object sender, EventArgs e)
{
var vm = this.DataContext as MyViewModel;
if (vm.ClientID != "")
posClient = new PosClient(this, vm.ClientID);
}
and the binding in the view:
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(this).For(view => view.MyMvxInteraction).To(viewModel => viewModel.MyMvxInteraction).OneWay();
set.Apply();

Get result of an int changed in a ContentPage

I have an app with the following page structure:
MainPage
->SettingsPage
->InputLanguagePage
->OutputLanguagePage
All pages are ContentPages.
InputLanguagePage and OutputLanguagePage are the very same pages, so I don't want to program the very same page twice.
I can surely instantiate the same page twice, but I have no idea how I should get the return value.
I spent really long trying to describe my problem.
Perhaps the code below demonstrates the problem much better.
As you can see, I seem to be unable to get the return value of the LanguageSelectionPage since it's shown asynchronous.
Also changing it to Navigation.PushModalAsync() didn't change anything.
What would be the best way to have a ContentPage return a value or change a value and be notified about it?
Thank you.
MainPage:
public class MainPage : ContentPage
{
private async void OnSettingsSelected(object sender, EventArgs e)
{
SettingsPage nSettings = new SettingsPage();
await this.Navigation.PushAsync(nSettings);
}
(...)
SettingsPage:
public class SettingsPage : ContentPage
{
public SettingsPage()
{
protected override async void OnAppearing()
{
base.OnAppearing();
_btnInputLanguage.Clicked += async (sender, e) =>
{
LanguageSelectionPage nInputLanguage = new LanguageSelectionPage();
nInputLanguage.SelectedLCID = App.Settings.InputLanguageLCID;
await Navigation.PushAsync(nInputLanguage);
//The user can change "int SelectedLCID" within the ContentPage, but I have no idea how I could access it
return;
};
_btnOutputLanguage.Clicked += async (sender, e) =>
{
LanguageSelectionPage nOutputLanguage = new LanguageSelectionPage();
nOutputLanguage.SelectedLCID = App.Settings.OutputLanguageLCID;
await Navigation.PushAsync(nOutputLanguage);
//The user can change "int SelectedLCID" within the ContentPage, but I have no idea how I could access it
return;
};
(...)
LanguageSelectionPage:
public class LanguageSelectionPage : ContentPage
{
public int SelectedLCID { get; set; }
protected override async void OnAppearing()
{
base.OnAppearing();
(...)
I will give solutin to pass it ass parameter when yu ask to load page
public partial class InputLanguagePage : ContentPage
{
public InputLanguagePage(string Selection)
{
InitializeComponent();
}
}
public partial class OutputLanguagePage : ContentPage
{
public OutputLanguagePage(string Selection)
{
InitializeComponent();
}
}
In settingsPage Button Click
_btnInputLanguage.Clicked += async (sender, e) =>
{
Navigation.PushAsync(new InputLanguagePage ("SelectLanguage"));
return;
};
_btnOutputLanguage.Clicked += async (sender, e) =>
{
Navigation.PushAsync(new OutputLanguagePage("SelectLanguage"));
return;
};
There are a lot of ways to solve this:
pass a completion handler to the 2nd page
raise an event on the 2nd page and subscribe from the 1st page
pass an object whose value will be set by the 2nd page
use Messaging
*
// on the first page, listen for a message
MessagingCenter.Subscribe<SecondPage, string> (this, "ValueSet", (sender, arg) => {
// arg will contain the value passed by the sender
});
// when the value is set on the 2nd page, send a message
MessagingCenter.Send<SecondPage, string> (this, "ValueSet", someValue);

await for a PushModalAsync form to closed in xamarin forms

I have a page and on clicking a plus button on toolbar i am calling a popup page
from popup page user can add a new entry or cancel / close window without doing anything
Everything is working fine and code is like this
public partial class SelectSchool : ContentPage
{
public SelectSchool()
{
InitializeComponent();
#region toolbar
ToolbarItem tbi = null;
if (Device.OS == TargetPlatform.Android)
{
tbi = new ToolbarItem("+", "plus", async () =>
{
var target_page = new AddSchool();
Navigation.PushModalAsync(target_page);
}, 0,0);
}
ToolbarItems.Add(tbi);
#endregion
this.Title = "Select School";
}
}
And my popup page is like
public partial class AddSchool : ContentPage
{
public AddSchool()
{
InitializeComponent();
}
private async void Button_OK_Clicked(object sender, EventArgs e)
{
//doing some operations like entry to db etc and close page
Navigation.PopModalAsync();
}
private void cancelClicked(object sender, EventArgs e)
{
Navigation.PopModalAsync();
}
}
But now i want to wait for the Popup to get closed to do some additional coding and i tried below code
if (Device.OS == TargetPlatform.Android)
{
tbi = new ToolbarItem("+", "plus", async () =>
{
var target_page = new AddSchool();
await Navigation.PushModalAsync(target_page);
//await till target_page is closed and once its closed call my next function here
}, 0,0);
}
But await is not working . How can i await on this area till the popup getting closed ? Any idea??
Use the Disappearing event on your modal page.
Example:
var modalPage = new ContentPage();
modalPage.Disappearing += (sender2, e2) =>
{
System.Diagnostics.Debug.WriteLine("The modal page is dismissed, do something now");
};
await content.Navigation.PushModalAsync(modalPage);
System.Diagnostics.Debug.WriteLine("The modal page is now on screen, hit back button");
Or use a EventWaitHandle:
var waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
var modalPage = new ContentPage();
modalPage.Disappearing += (sender2, e2) =>
{
waitHandle.Set();
};
await content.Navigation.PushModalAsync(modalPage);
System.Diagnostics.Debug.WriteLine("The modal page is now on screen, hit back button");
await Task.Run(() => waitHandle.WaitOne());
System.Diagnostics.Debug.WriteLine("The modal page is dismissed, do something now");
A bit late on the answer here, but it might be best to listen to the Application's OnModalPagePopping event handler.
First, create the modal page. Give it a property to store the data you want to later retrieve:
public class MyModalPage : ContentPage
{
public string Data { get; set; }
public MyModalPage()
{
InitializeComponent();
// ... set up the page ...
}
private async void PopThisPage()
{
// When you want to pop the page, just call this method
// Perhaps you have a text view with x:Name="PhoneNumber", for example
Data = PhoneNumber.Text; // store the "return value" before popping
await MyProject.App.Current.MainPage.Navigation.PopModalAsync();
}
}
In the parent page, you can create the modal page, and set up the event handler to listen for when the modal page pops:
public class MyPage : ContentPage
{
MyModalPage _myModalPage;
public MyPage()
{
InitializeComponent();
// ... set up the page ...
}
private async void ShowModalPage()
{
// When you want to show the modal page, just call this method
// add the event handler for to listen for the modal popping event:
MyProject.App.Current.ModalPopping += HandleModalPopping;
_myModalPage = new MyModalPage();
await MyProject.App.Current.MainPage.Navigation.PushModalAsync(_myModalPage());
}
private void HandleModalPopping(object sender, ModalPoppingEventArgs e)
{
if (e.Modal == _myModalPage)
{
// now we can retrieve that phone number:
var phoneNumber = _myModalPage.Data;
_myModalPage = null;
// remember to remove the event handler:
MyProject.App.Current.ModalPopping -= HandleModalPopping;
}
}
}
This is better than using the OnDisappearing method, as others have already stated that can be called when the app is backgrounded, etc. And its behavior is not consistent across platforms.
There is also another event OnModalPopped, which is called after the modal is completely popped from the navigation stack. If using that, it should work similarly.
You can try to create an event, call when pop close.
public partial class AddSchool : ContentPage
{
public delegate void PopupClosedDelegate();
public event PopupClosedDelegate PopupClosed;
public AddSchool()
{
InitializeComponent();
}
private async void Button_OK_Clicked(object sender, EventArgs e)
{
//doing some operations like entry to db etc and close page
await Navigation.PopModalAsync();
if (PopupClosed!=null)
{
PopupClosed();
}
}
private async void cancelClicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
if (PopupClosed != null)
{
PopupClosed();
}
}
}
I put it on the button click event, maybe you can put on close or dispose event. Then here is implement
public partial class SelectSchool : ContentPage
{
public SelectSchool()
{
InitializeComponent();
#region toolbar
ToolbarItem tbi = null;
if (Device.OS == TargetPlatform.Android)
{
tbi = new ToolbarItem("+", "plus", async () =>
{
var target_page = new AddSchool();
target_page.PopupClosed += () => { /*Do something here*/ };
Navigation.PushModalAsync(target_page);
}, 0, 0);
}
ToolbarItems.Add(tbi);
#endregion
this.Title = "Select School";
}
}
Hope this help.
I create an extension method for this:
public static class DialogUtils
{
public static async Task ShowPageAsDialog(this INavigation navigation, Page page)
{
int pagesOnStack = navigation.NavigationStack.Count + 1;
var waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
page.Disappearing += (s, e) =>
{
if (navigation.NavigationStack.Count <= pagesOnStack)
waitHandle.Set();
};
await navigation.PushAsync(page);
await Task.Run(() => waitHandle.WaitOne());
}
}
And I can use it:
private async void bShowDialogPage_Clicked(object sender, EventArgs e)
{
var page = new DialogPage();
await page.LoadData();
await Navigation.ShowPageAsDialog(page);
var result = page.PageResult;
}
It supports situations when dialog page show another page. I prefer NavigationStack instead ModalStack due to NavigationPage and BackButton.
Another way of accomplishing this is by calling an event from the page's OnDisapearing method, this event can then be subscribed by the navigation service which you create, and you can then use the "TaskCompletionSource" to wati until your page finishes its work and then complete the task.
For more details about accomplishing this, you can check this blog post.
Here is the base page's implementation, every page in this demo app inherit this page:
public class BasePage<T> : ContentPage
{
public event Action<T> PageDisapearing;
protected T _navigationResut;
public BasePage()
{
}
protected override void OnDisappearing()
{
PageDisapearing?.Invoke(_navigationResut);
if (PageDisapearing != null)
{
foreach (var #delegate in PageDisapearing.GetInvocationList())
{
PageDisapearing -= #delegate as Action<T>;
}
}
base.OnDisappearing();
}
}
Here is an overview of the navigation service you should use:
public async Task<T> NavigateToModal<T>(string modalName)
{
var source = new TaskCompletionSource<T>();
if (modalName == nameof(NewItemPage))
{
var page = new NewItemPage();
page.PageDisapearing += (result) =>
{
var res = (T)Convert.ChangeType(result, typeof(T));
source.SetResult(res);
};
await App.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page));
}
return await source.Task;
}
To call this page with the navigation service, you can use the following code:
var item = await new SimpleNavigationService().NavigateToModal<Item>(nameof(NewItemPage));
Items.Add(item);

Categories

Resources