Master detail page using MVVM - xamarin.forms

public MainPageView()
{
InitializeComponent();
MasterPage.NavMenuMListView.ItemSelected += OnItemSelected;
}
void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
MasterNavigationItem item = (MasterNavigationItem)e.SelectedItem;
if (item != null)
{
Detail = new NavigationPage((Page)Activator.CreateInstance(item.Target));
MasterPage.NavMenuMListView.SelectedItem = null;
IsPresented = false;
}
}
I have a master detail page(this is the code behind) and i want to respect mvvm conventions. this approach that is used in the docs uses the model inside the view. how can i make that onitemselected function in the viewmodel?

how can i make that onitemselected function in the viewmodel?
Download the source file from the link below. https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/navigation-masterdetailpage/
I make some changes with the source file. Use the command to binding for the listview with the ItemSelected event.
Install the package on NuGet.
Xamarin.Forms.BehaviorsPack: https://github.com/nuitsjp/Xamarin.Forms.BehaviorsPack
MasterPage.xaml
<ListView.Behaviors>
<behaviorsPack:SelectedItemBehavior Command="{Binding SelectedItemCommand}" />
</ListView.Behaviors>
MainPage.cs
public partial class MainPage : MasterDetailPage
{
public ICommand SelectedItemCommand { get; set; }
public MainPage()
{
InitializeComponent();
SelectedItemCommand = new Command(execute: () =>
{
var item = masterPage.listView.SelectedItem as MasterPageItem;
if (item != null)
{
Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
masterPage.listView.SelectedItem = null;
IsPresented = false;
}
});
masterPage.BindingContext = this;
//masterPage.listView.ItemSelected += OnItemSelected;
//if (Device.RuntimePlatform == Device.UWP)
//{
// MasterBehavior = MasterBehavior.Popover;
//}
}
//void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
//{
// var item = e.SelectedItem as MasterPageItem;
// if (item != null)
// {
// Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
// masterPage.listView.SelectedItem = null;
// IsPresented = false;
// }
//}
}

Related

How to change DatePicker Ok and Cancel button text in Xamarin forms?

I have an Xamarin.Forms app that supports many languages. How do I show the Calender for the DatePicker with DatePicker Ok and Cancel button text in local language text from resource file?
My custom renderer
[assembly: ExportRenderer(typeof(CustomImageDatePicker), typeof(CustomImageDatePickerRenderer))]
namespace AMS.Droid.Renderers
{
public class CustomImageDatePickerRenderer : DatePickerRenderer
{
public CustomImageDatePickerRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
try
{
base.OnElementChanged(e);
CustomImageDatePicker element = (CustomImageDatePicker)this.Element;
if (Control == null)
return;
Control.Background = null;
if (Control != null && this.Element != null && !string.IsNullOrEmpty(element.Image))
{
Control.Background = AddPickerStyles(element.Image);
}
}
catch (Exception ex)
{
var message = ex.Message;
}
}
public LayerDrawable AddPickerStyles(string imagePath)
{
GradientDrawable gd = new GradientDrawable();
gd.SetColor(Android.Graphics.Color.Transparent);
gd.SetCornerRadius(25);
gd.SetStroke(3, Android.Graphics.Color.Black);
this.Control.SetBackgroundColor(Android.Graphics.Color.Transparent);
this.Control.SetPadding(20, 10, -50, 10);
Drawable[] layers = { gd, GetDrawable(imagePath) };
LayerDrawable layerDrawable = new LayerDrawable(layers);
layerDrawable.SetLayerInset(1, 0, 0, 30, 0);
return layerDrawable;
}
private BitmapDrawable GetDrawable(string imagePath)
{
try
{
int resID = Resources.GetIdentifier(imagePath.ToLower(), "drawable", this.Context.PackageName);
var drawable = ContextCompat.GetDrawable(this.Context, Resource.Drawable.brandIcon);
drawable.SetBounds(0, 0, (int)(drawable.IntrinsicWidth * 0.5),
(int)(drawable.IntrinsicHeight * 0.5));
var bitmap = ((BitmapDrawable)drawable).Bitmap;
var result = new BitmapDrawable(Resources, Bitmap.CreateScaledBitmap(bitmap, 60, 60, true));
result.Gravity = Android.Views.GravityFlags.Right;
//result.SetBounds(10, 10, 50, 0);
return result;
}
catch(Exception ex)
{
var message = ex.Message;
}
return null;
}`
Xaml:
<customDatePicker:CustomDatePicker
x:Name="dpFromDate"
DateSelected="FromDate_Selected"
Margin="10,5,10,0"
Image="brandIcon.png"/>
You could do this with custom renderer.
On Android, the DatePicker dialog can be customized by overriding the CreateDatePickerDialog method in a custom renderer.
[assembly: ExportRenderer(typeof(Xamarin.Forms.DatePicker), typeof(CustomDatePickerRenderer))]
namespace App10.Droid
{
public class CustomDatePickerRenderer : DatePickerRenderer
{
public CustomDatePickerRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.DatePicker> e)
{
base.OnElementChanged(e);
//Disposing
if (e.OldElement != null)
{
datePicker = null;
}
//Creating
if (e.NewElement != null)
{
datePicker = e.NewElement;
}
}
DatePickerDialog pickerDialog;
private Xamarin.Forms.DatePicker datePicker;
protected override DatePickerDialog CreateDatePickerDialog(int year, int month, int day)
{
pickerDialog = new DatePickerDialog(Context, (o, e) =>
{
datePicker.Date = e.Date;
((IElementController)datePicker).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
}, year, month, day);
//ok
pickerDialog.SetButton((int)DialogButtonType.Positive, "(FR)-DONE", OnDone);
//cancel
pickerDialog.SetButton((int)DialogButtonType.Negative, "(FR)-CLEAR", OnCancel);
return pickerDialog;
}
private void OnCancel(object sender, DialogClickEventArgs e)
{
datePicker.Unfocus();
}
private void OnDone(object sender, DialogClickEventArgs e)
{
datePicker.Date = ((DatePickerDialog)sender).DatePicker.DateTime;
datePicker.Unfocus();
}
}
}
You could replace the (FR)-DONE and (FR)-CLEAR with your own local language text from resource file
//ok
pickerDialog.SetButton((int)DialogButtonType.Positive, "(FR)-DONE", OnDone);
//cancel
pickerDialog.SetButton((int)DialogButtonType.Negative, "(FR)-CLEAR", OnCancel);

How to properly redirect a Webview that contains a pdf to Google Drive

On an Xamarian.Forms app, which is trying to redirect links that contain a PDF in from a WebView to a Google Drive URL. My code works as expected on iOS but just spins on an Android.
public partial class Balance : ContentPage
{
public Balance()
{
InitializeComponent();
website.Navigated += WebView_Navigated;
website.Navigating += WebView_Navigating;
UrlWebViewSource source = new UrlWebViewSource();
source.Url = "https://www.google.com";
website.Source = source;
}
private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
website.IsVisible = false;
}
public void WebView_Navigated(object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
website.IsVisible = true;
if (e.Url.Contains("pdf") && !e.Url.Contains("drive.google.com"))
{
var webview = (WebView)sender;
website.Source = "https://drive.google.com/viewerng/viewer?embedded=true&url=" + e.Url;
}
}
}
On Android the site never loads and the activity indicator just sits and spins.
According to your description, I use your code and redirect a webview, here is my code, there is no issue on android, nospins on android :
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="website"
HeightRequest="1000"
Navigated="Website_Navigated"
Navigating="Website_Navigating"
WidthRequest="1000" />
</StackLayout>
</ContentPage.Content>
public partial class Page19 : ContentPage
{
public Page19 ()
{
InitializeComponent ();
UrlWebViewSource source = new UrlWebViewSource();
//source.Url = "https://www.google.com";
source.Url = "http://www.pdf995.com/samples/pdf.pdf";
website.Source = source;
}
private void Website_Navigated(object sender, WebNavigatedEventArgs e)
{
website.IsVisible = false;
}
private void Website_Navigating(object sender, WebNavigatingEventArgs e)
{
website.IsVisible = true;
if (e.Url.Contains("pdf") && !e.Url.Contains("drive.google.com"))
{
var webview = (WebView)sender;
website.Source = "https://drive.google.com/viewerng/viewer?embedded=true&url=" + e.Url;
}
}
}

Xamarin.Forms 3.3 WKWebView inject javascript

I have followed this link Xamarin.Forms WKWebView to inject Javascript into WebView for iOS. It has worked until Xamarin.Forms 3.3.
In Xamarin.Forms 3.3, the default custom renderer for iOS can be changed from UIWebView to WKWebView. I have followed the changes in AssemblyInfo.cs. Xamarin.Forms 3.3.0. Unfortunately, the changes break the codes.
Below are the changes
//protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//if (Control == null)
//{
userController = new WKUserContentController();
userController.RemoveAllUserScripts();
userController.RemoveScriptMessageHandler("invokeAction");
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
//var config = new WKWebViewConfiguration { UserContentController = userController };
webView = NativeView as WKWebView;
webView.Configuration.UserContentController = userController;
webView.WeakUIDelegate = Self;
view = Element as BibleWebView;
//webView = (WKWebView)Control;
// var cgRect = new CoreGraphics.CGRect(view.X, view.Y, view.WidthRequest, view.HeightRequest);
// webView = new WKWebView(cgRect, config)
// {
// WeakUIDelegate = Self,
// };
// SetNativeControl(webView);
//}
//if (e.OldElement != null)
//{
// userController.RemoveAllUserScripts();
// userController.RemoveScriptMessageHandler("invokeAction");
// var hybridWebView = e.OldElement as BibleWebView;
// hybridWebView.Cleanup();
// e.OldElement.ShowPopup -= OnShowPopup;
//}
//if (Control != null)
//{
// BibleWebView webview = Element as BibleWebView;
HtmlWebViewSource htmlSource = (HtmlWebViewSource)view.Source;
string html = htmlSource.Html;
webView.LoadHtmlString(new NSString(html), NSBundle.MainBundle.ResourceUrl);
view.ShowPopup += OnShowPopup;
//}
}
The old codes are commented and the new codes are uncommented. Any help will be much appreciated.
I'm using a custom renderer with Xamarin.Forms 3.3 using EvaluateJavaScript to load javascript in a loaded page.
HybridWebView is a class in my shared project that inherit from Xamarin.Forms.WebView
First option
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>
{
WKUserContentController userController;
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var formsWebView = sender as WebView;
if (formsWebView != null)
{
userController = new WKUserContentController();
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
Control.AllowsBackForwardNavigationGestures = true;
Control.NavigationDelegate = new CustomWebViewClient(Element);
if((formsWebView.Source as UrlWebViewSource) != null)
{
string url = System.Web.HttpUtility.UrlPathEncode((formsWebView.Source as UrlWebViewSource).Url);
Control.LoadRequest(new NSUrlRequest(new NSUrl(url)));
}
else if((formsWebView.Source as HtmlWebViewSource) != null)
{
Control.LoadHtmlString((formsWebView.Source as HtmlWebViewSource).Html, new NSUrl(""));
}
}
}
public class CustomWebViewClient : WKNavigationDelegate, INSUrlConnectionDataDelegate
{
private HybridWebView _webclient;
private WKWebView _webView;
public CustomWebViewClient(HybridWebView webclient)
{
_webclient = webclient;
}
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
string allowZoom = #"javascript:
var div = document.createElement('div');
div.setAttribute('id', 'div1');
div.innerHTML = 'Test';
document.getElementsByClassName('container')[0].appendChild(div);
div.onclick = document.getElementById('div1').onclick = function() { div.innerHTML = 'Change text'; }";
webView.EvaluateJavaScript(allowZoom, null);
_webView = webView;
}
}
}
Second option
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var formsWebView = sender as WebView;
string allowZoom = #"javascript:
var div = document.createElement('div');
div.setAttribute('id', 'div1');
div.innerHTML = 'Test';
document.getElementsByClassName('container')[0].appendChild(div);
div.onclick = document.getElementById('div1').onclick = function() { div.innerHTML = 'Change text'; }";
if (formsWebView != null)
{
userController = new WKUserContentController();
//Using WKUserScript
var script = new WKUserScript(new NSString(allowZoom), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
var config = new WKWebViewConfiguration { UserContentController = userController };
var webView = new WKWebView(Frame, config);
SetNativeControl(webView);
Control.AllowsBackForwardNavigationGestures = true;
Control.NavigationDelegate = new CustomWebViewClient(Element);
if((formsWebView.Source as UrlWebViewSource) != null)
{
string url = System.Web.HttpUtility.UrlPathEncode((formsWebView.Source as UrlWebViewSource).Url);
Control.LoadRequest(new NSUrlRequest(new NSUrl(url)));
}
else if((formsWebView.Source as HtmlWebViewSource) != null)
{
Control.LoadHtmlString((formsWebView.Source as HtmlWebViewSource).Html, new NSUrl(""));
}
}
}

Xamarin Forms Custom Map Pin

In one of the apps I'm working on I require the use of custom map pins and I've followed the guide on Xamarin https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/customized-pin/ as well as borrowed their sample code to try and make my own example.
It works to a degree in such that the info window is actually updated to the custom layout but the map pin never changes.
My CustomMapRenderer:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Android.Content;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Maps.Android;
using WorkingWithMaps.Droid.Renderers;
using WorkingWithMaps;
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace WorkingWithMaps.Droid.Renderers
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback
{
GoogleMap map;
List<CustomPin> customPins;
bool isDrawn;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
map.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
((MapView)Control).GetMapAsync(this);
}
}
void IOnMapReadyCallback.OnMapReady(GoogleMap googleMap)
{
map = googleMap;
map.SetInfoWindowAdapter(this);
map.InfoWindowClick += OnInfoWindowClick;
this.NativeMap = googleMap;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
{
map.Clear();
foreach (var pin in customPins)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
marker.SetTitle(pin.Pin.Label);
marker.SetSnippet(pin.Pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
map.AddMarker(marker);
}
isDrawn = true;
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (changed)
{
isDrawn = false;
}
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (!string.IsNullOrWhiteSpace(customPin.Url))
{
var url = Android.Net.Uri.Parse(customPin.Url);
var intent = new Intent(Intent.ActionView, url);
intent.AddFlags(ActivityFlags.NewTask);
Android.App.Application.Context.StartActivity(intent);
}
}
public Android.Views.View GetInfoContents(Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;
if (inflater != null)
{
Android.Views.View view;
var customPin = GetCustomPin(marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (customPin.Id == "Xamarin")
{
view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);
}
else
{
view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
}
var infoTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowTitle);
var infoSubtitle = view.FindViewById<TextView>(Resource.Id.InfoWindowSubtitle);
if (infoTitle != null)
{
infoTitle.Text = marker.Title;
}
if (infoSubtitle != null)
{
infoSubtitle.Text = marker.Snippet;
}
return view;
}
return null;
}
public Android.Views.View GetInfoWindow(Marker marker)
{
return null;
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in customPins)
{
if (pin.Pin.Position == position)
{
return pin;
}
}
return null;
}
}
}
and my map page, also heavily borrowed from Xamarin's working with maps guide
using Plugin.Geolocator;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Xaml;
namespace WorkingWithMaps
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : ContentPage
{
CustomMap map;
Geocoder geoCoder;
String navAdd;
public MainPage()
{
InitializeComponent();
var maplocator = CrossGeolocator.Current;
maplocator.DesiredAccuracy = 1;
geoCoder = new Geocoder();
map = new CustomMap
{
HeightRequest = 100,
WidthRequest = 960,
VerticalOptions = LayoutOptions.FillAndExpand,
IsShowingUser = true
};
map.MapType = MapType.Street;
map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(55.237208, 10.479160), Distance.FromMeters(500)));
map.IsShowingUser = true;
var street = new Button { Text = "Street" };
var hybrid = new Button { Text = "Hybrid" };
var satellite = new Button { Text = "Satellite" };
street.Clicked += HandleClickedAsync;
hybrid.Clicked += HandleClickedAsync;
//satellite.Clicked += OnReverseGeocodeButtonClicked;
var segments = new StackLayout
{
Spacing = 30,
HorizontalOptions = LayoutOptions.CenterAndExpand,
Orientation = StackOrientation.Horizontal,
Children = { street, hybrid, satellite }
};
Content = new StackLayout
{
HorizontalOptions = LayoutOptions.Center,
Children = { map, segments }
};
Device.BeginInvokeOnMainThread(async () =>
{
try
{
//var currentpos = await maplocator.GetPositionAsync(1000);
//map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(currentpos.Latitude, currentpos.Longitude), Distance.FromMeters(500)));
if (!maplocator.IsListening)
{
await maplocator.StartListeningAsync(1000, 50, true);
}
}
catch (Exception ex)
{
Debug.WriteLine("Fail" + ex);
}
});
var pin = new CustomPin
{
Pin = new Pin
{
Type = PinType.Place,
Position = new Position(55.240121, 10.469895),
Label = "Testing Pins"
}
};
map.CustomPins = new List<CustomPin> { pin };
map.Pins.Add(pin.Pin);
map.PropertyChanged += (sender, e) =>
{
Debug.WriteLine(e.PropertyName + " just changed!");
if (e.PropertyName == "VisibleRegion" && map.VisibleRegion != null)
CalculateBoundingCoordinates(map.VisibleRegion);
};
maplocator.PositionChanged += (sender, e) =>
{
var position = e.Position;
map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(position.Latitude, position.Longitude), Distance.FromKilometers(2)));
};
}
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//async void OnReverseGeocodeButtonClicked(object sender, EventArgs e)
//{
// var possibleAddresses = await geoCoder.GetAddressesForPositionAsync(pin.Position);
// navAdd += possibleAddresses.ElementAt(0) + "\n";
// switch (Device.OS)
// {
// case TargetPlatform.iOS:
// Device.OpenUri(new Uri(string.Format("http://maps.apple.com/?q={0}", WebUtility.UrlEncode(navAdd))));
// break;
// case TargetPlatform.Android:
// Device.OpenUri(new Uri(string.Format("geo:0,0?q={0}", WebUtility.UrlEncode(navAdd))));
// break;
// case TargetPlatform.Windows:
// case TargetPlatform.WinPhone:
// Device.OpenUri(new Uri(string.Format("bingmaps:?where={0}", Uri.EscapeDataString(navAdd))));
// break;
// }
//}
void HandleClickedAsync(object sender, EventArgs e)
{
var b = sender as Button;
switch (b.Text)
{
case "Street":
map.MapType = MapType.Street;
break;
case "Hybrid":
map.MapType = MapType.Hybrid;
break;
case "Satellite":
map.MapType = MapType.Satellite;
break;
}
}
static void CalculateBoundingCoordinates(MapSpan region)
{
var center = region.Center;
var halfheightDegrees = region.LatitudeDegrees / 2;
var halfwidthDegrees = region.LongitudeDegrees / 2;
var left = center.Longitude - halfwidthDegrees;
var right = center.Longitude + halfwidthDegrees;
var top = center.Latitude + halfheightDegrees;
var bottom = center.Latitude - halfheightDegrees;
if (left < -180) left = 180 + (180 + left);
if (right > 180) right = (right - 180) - 180;
Debug.WriteLine("Bounding box:");
Debug.WriteLine(" " + top);
Debug.WriteLine(" " + left + " " + right);
Debug.WriteLine(" " + bottom);
}
}
}
On top of the mentioned issue the implementation has also caused IsShowingUser = True to no longer function as well as
var currentpos = await maplocator.GetPositionAsync(1000);
to throw an exception.
Github repository: https://github.com/Mortp/CustomMapPinsXamarin
First of all I would like to provide 2 links that helped me to understand the problem. Thank you guys.
Xamarin.Forms.Maps 2.3.4 custom MapRenderer disables everything and https://forums.xamarin.com/discussion/92565/android-ionmapreadycallback-forms-2-3-4
Latest Xamarin Maps broke OnElementPropertyChanged with VisibleRegion. They defined that MapRenderer now implements IOnMapReadyCallback and that broke somehow OnElementPropertyChanged (I didn't investigate how and why). As you can see in link I provided there are 2 methods you can go. To keep your renderer implementing IOnMapReadyCallback or not. When I kept IOnMapReadyCallback I started to get 2 pins - one of top another - our custom pin and regular pin. I didn't dig more how that happens and removed IOnMapReadyCallback. After that things became really simple because if you let Xamarin handle it and create NativeMap you can remove some code and make renderer simpler.
Before I post the code I also want to mention that when I fixed it the app started crashing with OutOfMemory exception and I found out that your pin image is 2000 pixels width. I changed it to 40. Below is the code:
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter//, IOnMapReadyCallback
{
bool isDrawn;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
}
bool isMapReady;
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (!isMapReady && (NativeMap != null))
{
NativeMap.SetInfoWindowAdapter(this);
NativeMap.InfoWindowClick += OnInfoWindowClick;
isMapReady = true;
}
if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
{
NativeMap.Clear();
foreach (var pin in ((CustomMap)Element).CustomPins)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
marker.SetTitle(pin.Pin.Label);
marker.SetSnippet(pin.Pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
NativeMap.AddMarker(marker);
}
isDrawn = true;
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (changed)
{
isDrawn = false;
}
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (!string.IsNullOrWhiteSpace(customPin.Url))
{
var url = Android.Net.Uri.Parse(customPin.Url);
var intent = new Intent(Intent.ActionView, url);
intent.AddFlags(ActivityFlags.NewTask);
Android.App.Application.Context.StartActivity(intent);
}
}
public Android.Views.View GetInfoContents(Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;
if (inflater != null)
{
Android.Views.View view;
var customPin = GetCustomPin(marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (customPin.Id == "Xamarin")
{
view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);
}
else
{
view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
}
var infoTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowTitle);
var infoSubtitle = view.FindViewById<TextView>(Resource.Id.InfoWindowSubtitle);
if (infoTitle != null)
{
infoTitle.Text = marker.Title;
}
if (infoSubtitle != null)
{
infoSubtitle.Text = marker.Snippet;
}
return view;
}
return null;
}
public Android.Views.View GetInfoWindow(Marker marker)
{
return null;
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in ((CustomMap)Element).CustomPins)
{
if (pin.Pin.Position == position)
{
return pin;
}
}
return null;
}
}

ASP.NET: How to persist Page State accross Pages?

I need a way to save and load the Page State in a persistent manner (Session). The Project i need this for is an Intranet Web Application which has several Configuration Pages and some of them need a Confirmation if they are about to be saved. The Confirmation Page has to be a seperate Page. The use of JavaScript is not possible due to limitations i am bound to. This is what i could come up with so far:
ConfirmationRequest:
[Serializable]
public class ConfirmationRequest
{
private Uri _url;
public Uri Url
{ get { return _url; } }
private byte[] _data;
public byte[] Data
{ get { return _data; } }
public ConfirmationRequest(Uri url, byte[] data)
{
_url = url;
_data = data;
}
}
ConfirmationResponse:
[Serializable]
public class ConfirmationResponse
{
private ConfirmationRequest _request;
public ConfirmationRequest Request
{ get { return _request; } }
private ConfirmationResult _result = ConfirmationResult.None;
public ConfirmationResult Result
{ get { return _result; } }
public ConfirmationResponse(ConfirmationRequest request, ConfirmationResult result)
{
_request = request;
_result = result;
}
}
public enum ConfirmationResult { Denied = -1, None = 0, Granted = 1 }
Confirmation.aspx:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.UrlReferrer != null)
{
string key = "Confirmation:" + Request.UrlReferrer.PathAndQuery;
if (Session[key] != null)
{
ConfirmationRequest confirmationRequest = Session[key] as ConfirmationRequest;
if (confirmationRequest != null)
{
Session[key] = new ConfirmationResponse(confirmationRequest, ConfirmationResult.Granted);
Response.Redirect(confirmationRequest.Url.PathAndQuery, false);
}
}
}
}
PageToConfirm.aspx:
private bool _confirmationRequired = false;
protected void btnSave_Click(object sender, EventArgs e)
{
_confirmationRequired = true;
Response.Redirect("Confirmation.aspx", false);
}
protected override void SavePageStateToPersistenceMedium(object state)
{
if (_confirmationRequired)
{
using (MemoryStream stream = new MemoryStream())
{
LosFormatter formatter = new LosFormatter();
formatter.Serialize(stream, state);
stream.Flush();
Session["Confirmation:" + Request.UrlReferrer.PathAndQuery] = new ConfirmationRequest(Request.UrlReferrer, stream.ToArray());
}
}
base.SavePageStateToPersistenceMedium(state);
}
I can't seem to find a way to load the Page State after being redirected from the Confirmation.aspx to the PageToConfirm.aspx, can anyone help me out on this one?
If you mean view state, try using Server.Transfer instead of Response.Redirect.
If you set the preserveForm parameter
to true, the target page will be able
to access the view state of the
previous page by using the
PreviousPage property.
use this code this works fine form me
public class BasePage
{
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(this);
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
//Save the last search and if there is no new search parameter
//Load the old viewstate
try
{ //Define name of the pages for u wanted to maintain page state.
List<string> pageList = new List<string> { "Page1", "Page2"
};
bool IsPageAvailbleInList = false;
foreach (string page in pageList)
{
if (this.Title.Equals(page))
{
IsPageAvailbleInList = true;
break;
}
}
if (!IsPostBack && Session[this + "State"] != null)
{
if (IsPageAvailbleInList)
{
NameValueCollection formValues = (NameValueCollection)Session[this + "State"];
String[] keysArray = formValues.AllKeys;
if (keysArray.Length > 0)
{
for (int i = 0; i < keysArray.Length; i++)
{
Control currentControl = new Control();
currentControl = Page.FindControl(keysArray[i]);
if (currentControl != null)
{
if (currentControl.GetType() == typeof(System.Web.UI.WebControls.TextBox))
((TextBox)currentControl).Text = formValues[keysArray[i]];
else if (currentControl.GetType() == typeof(System.Web.UI.WebControls.DropDownList))
((DropDownList)currentControl).SelectedValue = formValues[keysArray[i]].Trim();
else if (currentControl.GetType() == typeof(System.Web.UI.WebControls.CheckBox))
{
if (formValues[keysArray[i]].Equals("on"))
((CheckBox)currentControl).Checked = true;
}
}
}
}
}
}
if (Page.IsPostBack && IsPageAvailbleInList)
{
Session[this + "State"] = Request.Form;
}
}
catch (Exception ex)
{
LogHelper.PrintError(string.Format("Error occured while loading {0}", this), ex);
Master.ShowMessageBox(enMessageType.Error, ErrorMessage.GENERIC_MESSAGE);
}
}
}

Resources