Xamarin Forms check is page is Modal - xamarin.forms

So basically I'm try to to find out if a page was pushed modally.
Here is the code I have for my extension method:
public static bool IsModal(this Page page)
{
return page.Navigation.ModalStack.Any(p => page == p);
}
The issue is; p never equals page due to the fact p changes to NavigationPage during runtime although intellisense reports it as a type of Page at compile time.
I've tried casting p to a Page but the type does not change at runtime and intellisense just moans that the cast is redundant.
I call this extension by using CurrentPage.IsModal in my View Model. CurrentPage is a type of Page at compile time but then changes to NavigationPage at runtime.
The confusing thing is that during debugging, p has properties such as CurrentPage and RootPage which show in the debugger, but these are not accessible by using p.CurrentPage as the compiler complains they don't exist !?! I was going to try an compare these but I can't access them but can view them in the debugger.

You need to check the type of page first, a page without navigationbar can also be pushed modally:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private async void Button_Clicked(object sender, EventArgs e)
{
Page1 p = new Page1();
await this.Navigation.PushModalAsync(p, true);
bool b = PageExtensions.IsModal(p);
Console.WriteLine(b);
}
}
public static class PageExtensions
{
public static bool IsModal(this Page page)
{
if (page.GetType() == typeof(NavigationPage))
{
return page.Navigation.ModalStack.Any(p => ((NavigationPage)p).CurrentPage.Equals(page));
}
else
{
return page.Navigation.ModalStack.Any(p => p.Equals(page));
}
}
}

So this code works:
public static class PageExtensions
{
public static bool IsModal(this Page page)
{
return page.Navigation.ModalStack.Any(p=> ((NavigationPage) p).CurrentPage.Equals(page));
}
}
I'm concerned that is not safe as it assumes p is a Type of NavigationPage.

Can you try this, there could be typos, I wrote this freehand
public static bool IsModal(this Page page)
{
if (page.Navigation.ModalStack.Count > 0)
{
foreach (var thisPage in page.Navigation.ModalStack)
{
if (thisPage.Equals(page))
return true;
}
return false;
}
else
return false;
}

This is what I made to check the last pushed modal. Hope it helps to someone.
public async Task NewModalPagePushAsync(Page pageToOpen)
{
var lastModalPage = Application.Current.MainPage.Navigation.ModalStack;
if (lastModalPage.Count >= 1)
{
if (lastModalPage.Last().GetType().Name == pageToOpen.GetType().Name)
return;
}
await Application.Current.MainPage.Navigation.PushModalAsync(pageToOpen);
}

Related

How do you hand over files to the user?

in a #WASM / #UNO-platform project, how do you hand over files to the user?
In my case I’m generation locally a PDF and had to download it or display it in the browser.
Any clue?
Regards,
Michael
There's no API to do that directly, yet. But you can create a data: url on an anchor (a) HTML element.
For this you'll need to create some JavaScript. Here's how you can do it:
IMPORTANT: following code will only work with very recent version of Uno.UI. Version starting with v3.0.0-dev.949+
Create a ContentControl for the <a> tag
[HtmlElement("a")]
public partial class WasmDownload : ContentControl
{
public static readonly DependencyProperty MimeTypeProperty = DependencyProperty.Register(
"MimeType", typeof(string), typeof(WasmDownload), new PropertyMetadata("application/octet-stream", OnChanged));
public string MimeType
{
get => (string) GetValue(MimeTypeProperty);
set => SetValue(MimeTypeProperty, value);
}
public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register(
"FileName", typeof(string), typeof(WasmDownload), new PropertyMetadata("filename.bin", OnChanged));
public string FileName
{
get => (string) GetValue(FileNameProperty);
set => SetValue(FileNameProperty, value);
}
private Memory<byte> _content;
public void SetContent(Memory<byte> content)
{
_content = content;
Update();
}
private static void OnChanged(DependencyObject dependencyobject, DependencyPropertyChangedEventArgs args)
{
if (dependencyobject is WasmDownload wd)
{
wd.Update();
}
}
private void Update()
{
if (_content.Length == 0)
{
this.ClearHtmlAttribute("href");
}
else
{
var base64 = Convert.ToBase64String(_content.ToArray());
var dataUrl = $"data:{MimeType};base64,{base64}";
this.SetHtmlAttribute("href", dataUrl);
this.SetHtmlAttribute("download", FileName);
}
}
}
Use it in Your XAML Page
<myControls:WasmDownload FileName="test.txt" x:Name="download">
Click here to download
</myControls:WasmDownload>
Note you can put anything in the content of your control, as any other XAML ContentControl.
Set the File Content in Code Behind
Loaded += (sender, e) =>
{
download.MimeType = "text/plain";
var bytes = Encoding.UTF8.GetBytes("this is the content");
download.SetContent(bytes);
};
Result
Direct support by Uno
There is a PR #3380 to add this feature to Uno natively for all platforms. You can also wait for it instead of doing custom way.
The PR for FileSavePicker has been merged and the feature is now available in package Uno.UI since version 3.0.0-dev.1353.

How can I create a custom renderer for DatePicker for UWP?

I'm trying to create a custom DatePicker renderer for UWP but I'm getting a compile error.
Trying to get a CalenderDatePicker instead of the normal DataPicker. I am getting the same error whether I try one or the other.
My code is:
CustomControl.cs
namespace myNameSpace.CustomControl
{
public class CustomDatePicker : DatePicker
{
}
}
And my CustomDatePickerRenderer.cs in the UWP folder
[assembly: ExportRenderer(typeof(CustomDatePicker), typeof(CustomDatePickerRenderer))]
namespace myNameSpace.UWP
{
public class CustomDatePickerRenderer : ViewRenderer<DatePicker, Windows.UI.Xaml.Controls.CalendarDatePicker>, ITabStopOnDescendants, IDontGetFocus
{
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if (Control == null)
{
Windows.UI.Xaml.Controls.CalendarDatePicker datePicker = new Windows.UI.Xaml.Controls.CalendarDatePicker();
SetNativeControl(datePicker);
}
}
}
}
The error I get is:
The type 'Windows.UI.Xaml.Controls.DatePicker' cannot be used as type parameter 'TElement' in the generic type or method 'ViewRenderer<TElement, TNativeElement>'. There is no implicit reference conversion from 'Windows.UI.Xaml.Controls.DatePicker' to 'Xamarin.Forms.View'.
From the documentation I can find this should be ok - is it not possible to create a custom renderer for the DatePicker? Please help.
This might not be a big thing by experiences developers, but I'm thrilled that I got it working - substituting the Xamarin.Forms DataPicker with the UWP CalendarDatePicke - so I'll just post my working solution, if someone else could use it.
Thanks to pinedax for solving my initial problem - which I actually changed to my CustomDatePicker in the end, because this is what is in the documentation from MS.
The last thing I needed was to ensure that Date changes where registered between to two different controls, since they use different events for this.
My code is:
CustomDatePicker.cs
namespace myNameSpace.CustomControl
{
public class CustomDatePicker : DatePicker
{
}
}
CustomDatePickerRenderer.cs in the UWP folder
[assembly: ExportRenderer(typeof(CustomDatePicker), typeof(CustomDatePickerRenderer))]
namespace myNameSpace.UWP
{
public class CustomDatePickerRenderer : ViewRenderer<CustomDatePicker, Windows.UI.Xaml.Controls.CalendarDatePicker>
{
protected override void OnElementChanged(ElementChangedEventArgs<CustomDatePicker> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
Control.DateChanged -= OnDateChanged;
Element.DateSelected -= OnDateSelected;
}
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
if (Control == null)
{
Windows.UI.Xaml.Controls.CalendarDatePicker datePicker = new Windows.UI.Xaml.Controls.CalendarDatePicker();
datePicker.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday;
SetNativeControl(datePicker);
}
}
Control.DateChanged += OnDateChanged;
Element.DateSelected += OnDateSelected;
}
}
private void OnDateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs e)
{
DateTimeOffset dto = (DateTimeOffset)e.NewDate;
Element.Date = dto.DateTime;
}
private void OnDateSelected(Object sender, DateChangedEventArgs e)
{
DateTime dt = e.NewDate;
Control.Date = new DateTimeOffset(dt);
}
}
}
And I can now reference my CustomDatePicker (UWP CalendarDatePicker) from my Xamarin.Forms XAML file
<local:CustomDatePicker x:Name="FilterDatePicker" DateSelected="OnDateFilterDateChanged" VerticalOptions="Center"></local:CustomDatePicker>
That's because it's not using the correct DatePicker.
That method expects the Xamarin.Forms.DatePicker but instead it's referencing the Windows.UI.Xaml.Controls.DatePicker.
To fix it either use the long namespace
[assembly: ExportRenderer(typeof(CustomDatePicker), typeof(CustomDatePickerRenderer))]
namespace myNameSpace.UWP
{
public class CustomDatePickerRenderer : ViewRenderer<Xamarin.Forms.DatePicker, Windows.UI.Xaml.Controls.CalendarDatePicker>, ITabStopOnDescendants, IDontGetFocus
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.DatePicker> e)
{
base.OnElementChanged(e);
if (Control == null)
{
Windows.UI.Xaml.Controls.CalendarDatePicker datePicker = new Windows.UI.Xaml.Controls.CalendarDatePicker();
SetNativeControl(datePicker);
}
}
}
}
Or use the using notation at the top of your class to indicate which one to use. Something like
using DatePicker = Xamarin.Forms.DatePicker;
Hope this helps.-

FreshMVVM - best way to open a page from a child ContentView that doesn't inherit from FreshBasePageModel

The following code shows 2 examples of an OpenPage Command. The one in MainPageModel works since it derives directly from FreshBasePageModel. However, the second OpenPage call in the ChildPageModel won't work (or compile). I don't want to pass the parent model all around. So how, using FreshMVVM, do I open a new page from the ChildPageModel (and have the back button work, etc)?
public class MainPageModel : FreshBasePageModel
{
public Command OpenPage
{
get
{
return new Command(() =>
{
CoreMethods.PushPageModel<NewPageModel>();
});
}
}
public ChildPageModel ChildPageModel { get; set; }
}
public class ChildPageModel
{
public Command OpenPage
{
get
{
return new Command(() =>
{
// ??????
CoreMethods.PushPageModel<NewPageModel>();
});
}
}
}
You should also make the ChildPageModel inherit from FreshBasePageModel. All PageModels should inherit from FreshBasePageModel
I make a simple example with three pages (MainPage, SecondPage, ThirdPage). You could download the source file of FreshMVVMDemo folder from HitHub.
https://github.com/WendyZang/Test.git
If you want to open a new page, you could add command in the child page.
#region Commands
public Command GotoPageCommand
{
get
{
return new Command(async () =>
{
await CoreMethods.PushPageModel<ThirdPageModel>(); //replace the ThirdPageModel with the page you want to open
});
}
}
#endregion
If you want to go back, add the command like below.
#region Commands
public Command GoBackSecondCommand
{
get
{
return new Command(async () =>
{
//await CoreMethods.PopPageModel(); //go back to main page
await CoreMethods.PushPageModel<SecondPageModel>(); //Go back to third page
});
}
}
#endregion
The following code will accomplish this...
var page = FreshPageModelResolver.ResolvePageModel<MainPageModel>();
var model = page.GetModel() as MainPageModel;
var navService = FreshMvvm.FreshIOC.Container.Resolve<IFreshNavigationService>();
await navService.PushPage(page, null);

Strangeness with DataContext and GridView / ListView

I have a Windows 8 store app based off of the grouped template project, with some renames etc. However, I'm having a hard time getting the ItemsSource databinding to work for both non-snapped and snapped visual states.
I have a property, that, when set, changes the ItemsSource property, but I can only get one of the controls to bind at a time (either the GridView for non-snapped, or the ListView for snapped).
When I use the following, only the non-snapped binding works and the snapped binding shows no items:
protected PickLeafModel ListViewModel
{
get
{
return (PickLeafModel)m_itemGridView.ItemsSource;
}
set
{
m_itemGridView.ItemsSource = value;
m_snappedListView.ItemsSource = value;
}
}
If I comment out one of the setters, the snapped view shows items but the non-snapped view shows nothing:
protected PickLeafModel ListViewModel
{
get
{
return (PickLeafModel)m_itemGridView.ItemsSource;
}
set
{
//m_itemGridView.ItemsSource = value;
m_snappedListView.ItemsSource = value;
}
}
It's as if I can bind my view model only to one property at a time. What am I doing wrong?
Since I am generating my data model on another thread (yes, using the thread pool), I cannot make it inherit from DependencyObject. If I do, I get a WrongThreadException.
So to make it work I have done the following:
public class PickLeafModel : IEnumerable
{
public PickLeafModel()
{
}
public IEnumerator GetEnumerator()
{
if (m_enumerator == null)
{
m_enumerator = new PickLeafModelViewDataEnumerator(m_data, m_parentLeaf);
}
return m_enumerator;
}
private SerializableLinkedList<PickLeaf> m_data =
new SerializableLinkedList<PickLeaf>();
}
and then my items look like this:
// Augments pick leafs by returning them wrapped with PickLeafViewData.
class PickLeafModelViewDataEnumerator : IEnumerator
{
public PickLeafModelViewDataEnumerator(
SerializableLinkedList<PickLeaf> data, PickLeaf parentLeaf)
{
m_viewDataList =
new System.Collections.Generic.LinkedList<PickLeafViewData>();
foreach (PickLeaf leaf in data)
{
PickLeafViewData viewData = new PickLeafViewData();
viewData.copyFromPickLeaf(leaf, parentLeaf);
m_viewDataList.AddLast(viewData);
}
m_enumerator = m_viewDataList.GetEnumerator();
}
public void Dispose()
{
m_viewDataList = null;
m_enumerator = null;
}
public object Current
{
get
{
return m_enumerator.Current;
}
}
public bool MoveNext()
{
return m_enumerator.MoveNext();
}
public void Reset()
{
m_enumerator.Reset();
}
private IEnumerator<PickLeafViewData> m_enumerator = null;
private System.Collections.Generic.LinkedList<PickLeafViewData>
m_viewDataList;
}
}
Is there something I'm doing fundamentally wrong?
Help appreciated.
Thanks!
Thankfully there is a much easier way to do what you are trying!
Create a class called your ViewModel as shown below:
public class DataViewModel
{
public DataViewModel()
{
Data = new ObservableCollection<PickLeafViewData>(new PickLeafModelViewDataEnumerator(m_data, m_parentLeaf));
}
public ObservableCollection<PickLeafViewData> Data
{
get;
set;
}
}
Now on the code behind set the Page.DataConected to equal an instance of the above class.
And finally on both your snapped listview, and the grid view set the item source to this:-
ItemsSource="{Binding Data}"
That should work nicely for you.
Thanks to Ross for pointing me in the right direction.
I'm not 100% happy with this solution, but it does work. Basically the idea is that after I get back the PickLeafModel from the worker threads, I transplant its internal data into a derived version of the class which is data binding aware.
public class PickLeafViewModel : PickLeafModel, IEnumerable
{
public PickLeafViewModel()
{
}
public PickLeafViewModel(PickLeafModel model)
{
SetData(model);
}
public void SetData(PickLeafModel model)
{
model.swap(this);
}
public IEnumerator GetEnumerator()
{
if (m_observableData == null)
{
m_observableData = new ObservableCollection<PickLeafViewData>();
var data = getData();
PickLeaf parentLeaf = getParentLeaf();
foreach (PickLeaf leaf in data)
{
PickLeafViewData viewData = new PickLeafViewData();
viewData.copyFromPickLeaf(leaf, parentLeaf);
m_observableData.Add(viewData);
}
}
return m_observableData.GetEnumerator();
}
and the page code is as follows:
protected PickLeafViewModel ListViewModel
{
get
{
return DataContext as PickLeafViewModel;
}
set
{
DataContext = value;
}
}
whenever I want to set ListViewModel, I can do this:
ListViewModel = new PickLeafViewModel(model);
and swap looks like:
private static void swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
// Swaps internals with the other model.
public void swap(PickLeafModel other)
{
swap(ref m_data, ref other.m_data);
...
Also, PickLeafModelViewDataEnumerator can be deleted altogether.

Why am I losing object references on the postback?

I am developing an asp.net (3.5) application and I am puzzled with the behavior of the postbacks.
Consider the following scenario: I have a web user control that is basically a form. However each form field is a web user control in itself.
In the click event of the save button I iterate through all controls in my form and I retrieve the field value and the field name that refers to the database field that I am saving the value to.
The click event triggers a postback and it is during the postback that I visit the controls and here is the funny thing: the property value for the database field has become null! Could anyone shed a light here?
Here is some basic code:
[Serializable]
public partial class UserProfileForm : CustomIntranetWebappUserControl
{
protected override void OnInit(EventArgs e)
{
//AutoEventWireup is set to false
Load += Page_Load;
CancelLinkButton.Click += CancelButtonClickEvent;
SaveLinkButton.Click += SaveButtonClickEvent;
base.OnInit(e);
}
private void SaveButtonClickEvent(object sender, EventArgs e)
{
VisitFormFields();
}
private void VisitFormFields()
{
var userProfileVisitor = new UserProfileVisitor();
foreach (var control in Controls)
{
if (control is FormFieldUserControl)
{
var formField = (FormFieldUserControl) control;
formField.Visit(userProfileVisitor);
}
}
userProfileVisitor.Save();
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindText();
}
}
private void BindText()
{
LastNameFormLine.LabelText = string.Format("{0}:", HomePage.Localize("Last Name"));
LastNameFormLine.InputValue = UserProfile.LastName;
LastNameFormLine.IsMandatoryField = true;
LastNameFormLine.IsMultilineField = false;
LastNameFormLine.ProfileField = "UserProfile.LastName";
//... the rest of this method is exactly like the 4 lines above.
}
}
[Serializable]
public abstract class FormFieldUserControl : CustomIntranetWebappUserControl
{
public string ProfileField { get; set; }
public abstract void Visit(UserProfileVisitor userProfileVisitor);
}
[Serializable]
public partial class FormLineTextBox : FormFieldUserControl
{
//... irrelevant code removed...
public override void Visit(UserProfileVisitor userProfileVisitor)
{
if (userProfileVisitor == null)
{
Log.Error("UserProfileVisitor not defined for the field: " + ProfileField);
return;
}
userProfileVisitor.Visit(this);
}
}
[Serializable]
public class UserProfileVisitor
{
public void Visit(FormLineTextBox formLine)
{
// The value of formLine.ProfileField is null!!!
Log.Debug(string.Format("Saving form field type {1} with profile field [{0}] and value {2}", formLine.ProfileField, formLine.GetType().Name, formLine.InputValue));
}
// ... removing irrelevant code...
public void Save()
{
Log.Debug("Triggering the save operation...");
}
}
Remember ASP.NET is stateless. Any properties created are destroyed after the page has been render to the browser. So you have to recreate objects on each post back or store them in View, Session, or Application State.
When you do a property you have to tell it to save the view state it does not do it automatically. Here is a sample of a view state property.
public string SomePropertyAsString
{
get
{
if (this.ViewState["SomePropertyAsString"] == null)
return string.Empty;
return (string)this.ViewState["SomePropertyAsString"];
}
set { this.ViewState["SomePropertyAsString"] = value; }
}
public MyCustomType ObjectProperty
{
get
{
if (this.ViewState["ObjectProperty"] == null)
return null;
return (MyCustomType)this.ViewState["ObjectProperty"];
}
set { this.ViewState["ObjectProperty"] = value; }
}
First guess would be that BindText() shouldn't be in Page_Load, but in Page_Init, so the control state will be saved.
#David Basarab, this is not true afaik, and was only the case in .Net 1.1, in .Net2 and up this is all handled by the framework if you do all the magic stuff in the Init.
Your problem is that 'ProfileField' isn't available on the Postback, right?
The solution is to store the value for that in ViewState (instead of an auto-implemented property). Without that, it won't be available on the postback.

Resources