Loading FFImageLoading SVG into ImageButton Source from code? - xamarin.forms

This post shows how to make SvgCachedImage act like a button. However, how to load SvgCachedImage into XamarinForm's ImageButton Source ?
My non-working code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SharedSvgSample"
x:Class="SharedSvgSample.MainPage"
xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms">
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<ImageButton
x:Name="myButton"
HeightRequest="200"
Clicked="myButton_Clicked"
WidthRequest="200" />
</StackLayout>
</ContentPage>
Code-behind:
using FFImageLoading.Svg.Forms;
using System;
using Xamarin.Forms;
namespace SharedSvgSample
{
public partial class MainPage : ContentPage
{
private bool _myButtonValue;
private SvgImageSource _visibilityOn = null;
private SvgImageSource _visibilityOff = null;
public MainPage()
{
InitializeComponent();
_visibilityOn = SvgImageSource.FromResource("SharedSvgSample.Resources.visibility_on.svg");
_visibilityOn.VectorHeight = 100;
_visibilityOn.VectorWidth = 100;
_visibilityOff = SvgImageSource.FromResource("SharedSvgSample.Resources.visibility_off.svg");
_visibilityOff.VectorHeight = 100;
_visibilityOff.VectorWidth = 100;
myButton.Source = _visibilityOff;
}
private void myButton_Clicked(object sender, EventArgs e)
{
_myButtonValue = !_myButtonValue;
myButton.Source = _myButtonValue ? _visibilityOn.ImageSource : _visibilityOff.ImageSource;
}
}
}

Unluckily, Xamarin.Forms.Button only supports FileImageSource, so at the moment you can't just load an SVG into the Button Image.
However, you can just load the SVG image, and add the TapGestureRecognizer to simulate a Button.

Related

Constants for Thickness

I am looking for a way to define Thickness in Xaml based on application wide defined constants, e.g.
<StackLayout Margin="MSpace,SSpace,LSpace,MSpace">
<Label Text="Just an example"/>
</StackLayout>
where SSpace, MSpace and LSpace are constants defined once in the app.
If I was only dealing with my own custom controls only I could probably write my own TypeConverter (c# how to implement type converter) and decorate each property where appropriate with something like
[TypeConverter(typeof(ConstantStringToThicknessConverter))]
I don't think this is an option since I want to use my string of constants with any type of Maui layout. I am looking for a solution where everything is done in xaml with the exception of defining the constants.
You can achieve this function by using c# .
please refer to the following code:
1.create class Constants.cs
public class Constants
{
public static readonly int MSpace = 20;
public static readonly int SSpace = 20;
public static readonly int LSpace = 20;
}
2.A simple usage :
public class TestPage1 : ContentPage
{
Thickness thickness = new Thickness (Constants.MSpace, Constants.SSpace, Constants.LSpace, Constants.MSpace);
public TestPage1()
{
Content = new StackLayout
{
BackgroundColor= Color.Yellow,
Children = {
new Label { Text = "Welcome to Xamarin.Forms!" ,Margin= thickness }
}
};
}
}
Update:
can I do the same thing in xaml?
You can bind the defined thickness to your control as a whole in your xaml.
Please refer to the following code:
public class Constants
{
public static readonly int MSpace = 50;
public static readonly int SSpace = 50;
public static readonly int LSpace = 50;
public Thickness thickness
{
get
{
return new Thickness(Constants.MSpace, Constants.SSpace, Constants.LSpace, Constants.MSpace);
}
}
}
And a simple usage :
<ContentPage.BindingContext>
<formapp908:Constants></formapp908:Constants>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<BoxView HorizontalOptions="FillAndExpand" BackgroundColor="Red" HeightRequest="100"></BoxView>
<Label Text="Welcome to Xamarin.Forms!" Margin="{ Binding thickness}" BackgroundColor="Green" HeightRequest="80"
HorizontalOptions="Center" />
</StackLayout>
Define Thickness in XAML Resources:
<x:Double x:Key="left">10</x:Double>
<x:Double x:Key="top">20</x:Double>
<x:Double x:Key="right">30</x:Double>
<x:Double x:Key="bottom">40</x:Double>
<Thickness x:Key="thickness"
Left="{StaticResource left}"
Top="{StaticResource top}"
Right="{StaticResource right}"
Bottom="{StaticResource bottom}"/>
Usage in XAML StackLayout:
<StackLayout Margin="{StaticResource thickness}" />
Thickness can also be used like this in a StackLayout Margin:
<StackLayout>
<StackLayout.Margin>
<Thickness Left="{StaticResource left}"
...
</StackLayout.Margin>
...
With constants:
namespace ConstantsNamespace
{
public static class Constants
{
public const double Left = 10;
...
Usage of constants in XAML Thickness:
xmlns:ns="clr-namespace:ConstantsNamespace;assembly=Constants.Assembly"
...
<Thickness Left="{x:Static ns:Constants.Left}"
...
Here's what I've been doing lately.
namespace MyApp.UI;
public static class UiConstants
{
public static readonly Thickness DefaultMargin = new Thickness(10, 10, 10, 10);
}
<ContentPage ...
xmlns:ui="clr-namespace:MyApp.UI">
<StackLayout Margin="{x:Static ui:UiConstants.DefaultMargin}">
<Label Text="Just an example"/>
</StackLayout>
</ContentPage>
Maybe using styles without keys?
In your App.xaml.cs
const double MSpace = 30;
const double SSpace= 30;
const double LSpace= 30;
public App()
{
InitializeComponent();
// You can replace this with a constant file if you want too
Application.Current.Resources.Add("MSpace", MSpace);
Application.Current.Resources.Add("SSpace", SSpace);
Application.Current.Resources.Add("LSpace", LSpace);
MainPage = new MainPage();
}
Then in your App.xaml
<Application.Resources>
<Style TargetType="StackLayout">
<Setter Property="Margin" Value="{DynamicResource MSpace, SSpace, LSpace}" />
</Style>
</Application.Resources>
Then wherever in your app
<StackLayout>
<Label Text="Just an example" />
</StackLayout>

Xamarin Forms -> Activity Indicator not working if Commands of statements to be executed

Using Visual Studio 2017 Community 15.8.1
This is after going through all options of stackoverflow regarding ActivityIndicator. So though it may be a duplication but nothing is helping me out.
So finally decided to post my workouts and get best help from here.
What I have tried till now:-
1. {Binding IsLoading} + INotifyPropertyChanged + public void RaisePropertyChanged(string propName) + IsLoading = true; concept.
2. ActivityIndicator_Busy.IsVisible = false; (Direct control accessed)
These two approaches were mostly recommended and I went into depth of each since lot of hours in last few weeks. But nothing got crack.
What I achieved?:-
ActivityIndicator_Busy.IsVisible = false; concept is working smooth only when I put return before executing the statements (for testing purpose); statement on Button Clicked event. (Attached Image)
But as soon as I remove the return; On Pressing Button, directly after some pause, the HomePage Opens.
MY Questions:-
1. This is particular with the current scenario how to get the ActivityIndicator run Immediately when user clicks the Welcome Button.
2. Pertaining to same, When app starts there is also a blank white screen coming for few seconds almost 30 seconds which I also I want to show ActivityIndicator. But dont know how to impose that logic at which instance.
My Inputs
My MainPage.xaml File:-
(Edited 06-Sept-2018 09.11 pm)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Name="page_main_page"
NavigationPage.HasBackButton="False"
NavigationPage.HasNavigationBar="False"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:appNutri"
BindingContext="{x:Reference page_main_page}"
x:Class="appNutri.MainPage">
<ContentPage.Content>
<StackLayout BackgroundColor="White"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<StackLayout>
<Image x:Name="Image_Welcome"
Source="welcome.png"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
WidthRequest="300"
HeightRequest="300" />
<Button x:Name="Button_Welcome"
Clicked="Button_Welcome_Clicked"
Text="Welcome!"
BackgroundColor="DeepSkyBlue"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
TextColor="White"
HeightRequest="60" />
</StackLayout>
<StackLayout BackgroundColor="White"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ActivityIndicator
x:Name="ActivityIndicator_Busy"
Color="Black"
IsEnabled="True"
HorizontalOptions="Center"
VerticalOptions="Center"
IsRunning="{Binding Source={x:Reference page_main_page}, Path=IsLoading}"
IsVisible="{Binding Source={x:Reference page_main_page}, Path=IsLoading}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
My MainPage.cs Code:-
(Edited on 06-Sept-2018 09.13 pm)
using appNutri.Model;
using SQLite;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace appNutri
{
public partial class MainPage : Xamarin.Forms.ContentPage, INotifyPropertyChanged
{
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
isLoading = value;
RaisePropertyChanged("IsLoading");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public MainPage()
{
InitializeComponent();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = this;
}
protected async void Button_Welcome_Clicked(object sender, EventArgs e)
{
IsLoading = true;
await Select_Local_User_Information();
IsLoading = false;
}
private async Task Select_Local_User_Information()
{
IsLoading = true;
string where_clause = "";
try
{
Sql_Common.Database_Folder_Path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
string Database_Full_Path = Path.Combine(Sql_Common.Database_Folder_Path, Sql_Common.Database_Name);
SQLiteConnection connection = new SQLiteConnection(Database_Full_Path);
//connection.DropTable<User_Master>();
//connection.Delete(connection.Table<User_Master>());
//connection.CreateTable<User_Master>(CreateFlags.ImplicitPK | CreateFlags.AutoIncPK);
connection.CreateTable<User_Master>();
int count = connection.ExecuteScalar<int>("Select count(*) from User_Master");
if (count == 0)
{
connection.DropTable<User_Master>();
connection.CreateTable<User_Master>();
//IsLoading = false;
//IsBusy = false;
await Navigation.PushAsync(new User_Register_Page());
}
else
{
Sql_Common.User_Logged = true;
var Local_User_Data = connection.Table<User_Master>().ToList();
User_Master.Logged_User_Details_Container.First_Name = Local_User_Data[0].First_Name;
User_Master.Logged_User_Details_Container.Cell1 = Local_User_Data[0].Cell1;
where_clause = " Upper ( First_Name ) = " + "'" + User_Master.Logged_User_Details_Container.First_Name.ToUpper().Trim() + "'" + " and " +
" Cell1 = " + "'" + User_Master.Logged_User_Details_Container.Cell1.Trim() + "'";
int records = Sql_Common.Get_Number_Of_Rows_Count("User_Master", where_clause);
if (records == 0)
{
connection.DropTable<User_Master>();
connection.CreateTable<User_Master>();
IsLoading = false;
await Navigation.PushAsync(new User_Register_Page());
}
else
{
User_Master.User_Master_Table(where_clause, User_Master.Logged_User_Details_Container);
IsLoading = false;
await Navigation.PushAsync(new User_Home_Page());
}
}
connection.Close();
}
catch (SQLiteException ex)
{
string ex_msg = ex.Message;
}
IsLoading = false;
}
}
}
04-Oct-2018
Finally resolved with This Answer
Update 2018-09-10
You think that you have implemented INotifyPropertyChanged by adding INotifyPropertyChanged to your class definition and adding the event
public event PropertyChangedEventHandler PropertyChanged;
along with its event invocator
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Anyway, since ContentPage already implements INotifyPropertyChanged, adding those did not implement INotifyPropertyChanged. ContentPage already defines the event (or rather BindableObjectfrom which ContentPage indirectly inherits). Any object that relies on being informed about property changes in your page will subscribe to the PropertyChanged event of the ancestor and not the PropertyChanged event you defined, hence the ActivityIndicator will not update.
Just remove the event you defined and call OnPropertyChanged instead of RaisePropertyChanged() and you should be fine.
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
isLoading = value;
OnPropertyChanged();
}
}
Since OnPropertyChanged is declared as
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
you don't have to pass the property name by hand. The compiler will do that for you beacsue of the CallerMemberNameAttribute.
End Update
The XAML extension {Binding IsLoading} binds the ActivityIndicator to the BindingContext of your page. By default the BindingContext is null, hence there is nothing to bind to and all your efforts are to no avail.
With a viewmodel
The preferred solution would be to use a viewmodel and assign it to MainPage.BindingContext, e.g.
var page = new MainPage()
{
BindingContext = new MainPageViewModel()
}
but if you take that road, you should move all of your UI logic to that viewmodel and encapsulate your SQL access and business logic in other classes, to keep the viewmodel clean from resource accesses and business logic. Having the resource accesses and logic in code behind may work for that small example, but is likely to become an unmaintainable mess.
Without a viewmodel
Anyway, you don't have to use a viewmodel to use bindings. You can set the BindingContext for the page (or some children) or use the Source of the BindingExtension
Setting the BindingContext
The BindingContext is passed from any page or view to it's children. You first have to give your page a name with x:Name="Page" (don't have to use Page, anyway, you can't use the class name of your page) and set the BindingContext to that page
<ContentPage ...
x:Name="Page"
BindingContext="{x:Reference Page}"
...>
now binding to IsLoading should work.
Using Source in the Binding
If you want to reference something else than the BindingContext of a view, BindingExtension has a property Source. You have to give a name to your page, too (see above)
<ContentPage ...
x:Name="Page"
...>
and now you can reference this in your binding
<ActivityIndicator
...
IsRunning="{Binding Path=IsLoading, Source={x:Reference Page}}"
IsVisible="{Binding Path=IsLoading, Source={x:Reference Page}}"/>

ArcGis Map does not disapear in navigation Xamarin.Forms

Hello guys :D I am having a problem with the android part of the Xamarin.Forms
When I Navigate from a AbsoluteLayout with a Map and TabbleView (enter image description here) to a Grid with only a Map, the Map from the previous page stays static on top of the second one(enter image description here). This problem does not manifest in iOS, only in Android. If any of you guys know the problem please tell me so I can quickly fix :D
Page with First Map
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SGPI.Intervencao.CriarIntervencao"
xmlns:local="clr-namespace:SGPI.Shared"
xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms"
Padding="5,5">
<ContentPage.Resources>
<ResourceDictionary>
<local:MapViewModel x:Key="MapViewModel" />
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout>
<esriUI:MapView Map="{Binding Map, Source={StaticResource MapViewModel}}" x:Name="map" AbsoluteLayout.LayoutBounds="0,0, 1, 0.6" AbsoluteLayout.LayoutFlags="All"/>
<TableView Intent="Form" HasUnevenRows="True" AbsoluteLayout.LayoutBounds="0,1, 1, 0.4" AbsoluteLayout.LayoutFlags="All">
<TableRoot>
<TableSection Title="Information">
<EntryCell Label="Nome" Text="{Binding Name}" Placeholder="Nome"/>
<EntryCell Label="Codigo" Text="{Binding Code}" Placeholder="Codigo"/>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Status" HorizontalOptions="Start" VerticalOptions="Center"/>
<Picker x:Name="pick" SelectedIndex="{Binding Index}" SelectedItem="{Binding Status}" Title="Status" HorizontalOptions="FillAndExpand">
<Picker.Items>
<x:String>Em Construção</x:String>
<x:String>Construido</x:String>
</Picker.Items>
</Picker>
</StackLayout>
</ViewCell>
<ViewCell>
<Button Image="editMap.png" Clicked="Button_Clicked" />
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
</AbsoluteLayout>
<ContentPage.ToolbarItems>
<ToolbarItem Icon="dan.png" Order="Primary" x:Name="done" Clicked="Done"/>
</ContentPage.ToolbarItems>
Code Behind
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace SGPI.Intervencao
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CriarIntervencao : ContentPage
{
public event EventHandler<IntervencaoClass> IntervencaoAdded;
public event EventHandler<IntervencaoClass> IntervencaoUpdated;
public CriarIntervencao(IntervencaoClass intervencao)
{
if(intervencao == null)
throw new ArgumentNullException(nameof(intervencao));
InitializeComponent();
BindingContext = new IntervencaoClass
{
Code = intervencao.Code,
Name = intervencao.Name,
Status = intervencao.Status,
Index = intervencao.Index,
Id = intervencao.Id,
Polygons = intervencao.Polygons
};
if(intervencao.Polygons != null)
map.GraphicsOverlays.Add(intervencao.Polygons);
}
private async void Done(object sender, EventArgs e)
{
var intervencao = BindingContext as IntervencaoClass;
if (filled())
{
await DisplayAlert("Erro", "Preenche tudo", "OK");
return;
}
map.GraphicsOverlays.Clear();
if (!intervencao.Id.HasValue)
{
intervencao.Id = 1;
IntervencaoAdded?.Invoke(this, intervencao);
}
else
{
IntervencaoUpdated?.Invoke(this, intervencao);
}
await Navigation.PopAsync();
}
public bool filled()
{
var intervencao = BindingContext as IntervencaoClass;
return String.IsNullOrEmpty(intervencao.Name) || String.IsNullOrEmpty(intervencao.Code) || pick.SelectedIndex == -1;
}
private async void Button_Clicked(object sender, EventArgs e)
{
map.GraphicsOverlays.Clear();
var page = new MapPages.MapPage((BindingContext as IntervencaoClass).Polygons);
page.AcceptedMap += (send, graphics) => {
var intervencao = BindingContext as IntervencaoClass;
intervencao.Polygons = graphics;
map.GraphicsOverlays.Add(graphics);
Navigation.PopAsync();
};
await Navigation.PushAsync(page);
}
}
}
Second Page
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SGPI.Shared"
xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms"
x:Class="SGPI.MapPages.MapPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:MapViewModel x:Key="MapViewModel" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<esriUI:MapView Map="{Binding Map, Source={StaticResource MapViewModel}}" x:Name="map" GeoViewTapped="Map_GeoViewTapped"/>
</Grid>
<ContentPage.ToolbarItems>
<ToolbarItem Icon="dan.png" Clicked="Done"/>
</ContentPage.ToolbarItems>
</ContentPage>
Second Page Code Behind
using Esri.ArcGISRuntime.Data;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Symbology;
using System.Collections.Generic;
using Xamarin.Forms;
using Esri.ArcGISRuntime.UI;
using System;
using System.Threading;
namespace SGPI.MapPages
{
public partial class MapPage : ContentPage
{
public event EventHandler<GraphicsOverlay> AcceptedMap;
List<MapPoint> points;
static SimpleLineSymbol symbol = new SimpleLineSymbol()
{
Style = SimpleLineSymbolStyle.Dash,
Color = System.Drawing.Color.Black,
Width = 1
};
static SimpleMarkerSymbol marker = new SimpleMarkerSymbol()
{
Color = System.Drawing.Color.Pink,
Outline = symbol,
Style = SimpleMarkerSymbolStyle.Diamond,
Size = 10
};
static SimpleLineSymbol line = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, System.Drawing.Color.CadetBlue, 1);
static SimpleFillSymbol fill = new SimpleFillSymbol(SimpleFillSymbolStyle.Solid, System.Drawing.Color.Black, line);
public MapPage(GraphicsOverlay graphic)
{
InitializeComponent();
map.Map = new SGPI.Shared.MapViewModel().Map;
if(graphic == null)
graphic = new GraphicsOverlay();
map.GraphicsOverlays.Add(graphic);
points = new List<MapPoint>();
}
private Graphic AddPolygonInMap(MapPoint[] points)
{
var pointCollection = new PointCollection(points[0].SpatialReference);
foreach (MapPoint p in points)
pointCollection.Add(p);
var sPolygon = new Polygon(pointCollection);
return new Graphic(sPolygon, fill);
}
private void AddPointinMap(MapPoint point)
{
Graphic graphic = new Graphic(point, marker);
map.GraphicsOverlays[0].Graphics.Add(graphic);
}
private async void Map_GeoViewTapped(object sender, Esri.ArcGISRuntime.Xamarin.Forms.GeoViewInputEventArgs e)
{
var tolerance = 10d; // Use larger tolerance for touch
var maximumResults = 1; // Only return one graphic
var onlyReturnPopups = false; // Don't return only popups
// Use the following method to identify graphics in a specific graphics overlay
IdentifyGraphicsOverlayResult identifyResults = await map.IdentifyGraphicsOverlayAsync(
map.GraphicsOverlays[0],
e.Position,
tolerance,
onlyReturnPopups,
maximumResults);
// Check if we got results
if (identifyResults.Graphics.Count > 0)
{
// Make sure that the UI changes are done in the UI thread
Device.BeginInvokeOnMainThread(async () => {
await DisplayAlert("", "Tapped on graphic", "OK");
});
} else
{
points.Add(e.Location);
AddPointinMap(e.Location);
}
}
private async void Done(object sender, EventArgs e)
{
if(points.Count > 2) {
GraphicsOverlay graphics = new GraphicsOverlay();
graphics.Graphics.Add(AddPolygonInMap(points.ToArray()));
map.GraphicsOverlays.Add(graphics);
Thread.Sleep(500);
var accepted = await DisplayAlert("Aviso", "Este é o polígono certo?", "Sim", "Não");
if (accepted)
{
GraphicsOverlay graph = graphics;
map.GraphicsOverlays.Clear();
AcceptedMap?.Invoke(this, graph);
}
else
{
for(int i = 1; i < map.GraphicsOverlays.Count; i++)
map.GraphicsOverlays[i] = new GraphicsOverlay();
}
}
}
}
}

How to manage(hide) Back Button and Master Page in navigation while Deeplinking?

On HomePage of Button Update Profile, it redirects and working fine. But when I try to go to Update Profile page from any other place like DeepLink, it shows Back Button with a word Master Page. Can anybody please suggest me what I am missing here?
HomePage(Master)
<?xml version="1.0" encoding="UTF-8"?>
<local:MasterDetailPageWithLifecycle xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject;assembly=MyProject"
x:Class="MyProject.HomePage"
OnAppearingCommand="{Binding OnAppearingCommand}"
Title="Master Page">
<MasterDetailPage.Master>
<ContentPage Title="Home page" Icon="hamburger.png">
<ContentPage.Resources>
<ResourceDictionary>
<local:MenuItemDataTemplateSelector x:Key="menuItemDataTemplateSelector" HighlitedTemplate="{StaticResource highlitedTemplate}"
NormalTemplate="{StaticResource normalTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout BackgroundColor="{DynamicResource d8Purple}" VerticalOptions="FillAndExpand" Padding="0, 48, 0, 0">
<StackLayout Padding="0, 40, 0, 0" Spacing="0">
<ListView x:Name="listView" Margin="0,9,0,0" VerticalOptions="FillAndExpand" SeparatorVisibility="None"
ItemSelected="OnItemSelected" ItemTemplate="{StaticResource menuItemDataTemplateSelector}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ContentPage>
</MasterDetailPage.Master>
</local:MasterDetailPageWithLifecycle>
HomePage.cs
public HomePage()
{
InitializeComponent();
BindingContext =_vm = App.Locator.Home;
NavigationPage.SetHasNavigationBar(this, false);
_masterPageItems = new List<MasterPageItem>();
_masterPageItems.Add(new MasterPageItem
{
Title = "Update Profile",
TargetType = nameof(EditProfilePage)
});
listView.ItemsSource = _masterPageItems;
}
public void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterPageItem;
if (item != null)
{
var name = item.TargetType;
if (name == "EditProfilePage")
{
Detail = new MyProjectNavigationPage(new EditProfilePage());
listView.SelectedItem = null;
IsPresented = false;
}
}
}
public class MyProjectNavigationPage : NavigationPage
{
public MyProjectNavigationPage(Page root) : base(root)
{
if (Device.OS == TargetPlatform.iOS)
{
BarTextColor = Colors.d8Grey;
BarBackgroundColor = Color.White;
Title = root.Title;
}
}
}
EditProfile XAML
<?xml version="1.0" encoding="UTF-8"?>
<local:ContentPageWithCustomBackButton
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject;assembly=MyProject"
x:Class="MyProject.EditProfilePage"
OnAppearingCommand="{Binding OnAppearingCommand}"
Title="Update Profile">
<ContentPage.Content>
<Grid RowSpacing="0">
//Design content
</Grid>
</ContentPage.Content>
</local:ContentPageWithCustomBackButton>
EditProfile CS
public EditProfilePage()
{
InitializeComponent();
BindingContext=_editProfileViewModel = App.Locator.EditProfile;
_editProfileViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
EditProfileDeeplink
public override void Navigate(string uri)
{
_navigationService.NavigateTo(nameof(EditProfilePage));
}
It's just because of when your trying to Navigate from HomePage(Masterpage) to EditProfile Page you set EditProfile Page as Master Detaill Page Like,
if (name == "EditProfilePage")
{
Detail = new MyProjectNavigationPage(new EditProfilePage());
listView.SelectedItem = null;
IsPresented = false;
}
but when you Come from other page you Only Navigate to that Page Like,
_navigationService.NavigateTo(nameof(EditProfilePage));
So you have to handle this Navigation by set page as MasterDetail(DetailPage) Like,
App.Current.MainPage = new MenuMaster {Detail = new NavigationPage(new EditProfile())};
With presenters :
Create class for your presenter
public class IosPagePresenter : MvxFormsIosViewPresenter
{
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues?["NavigationCommand"] == "StackClear")
FormsApplication.MainPage = new ContentPage();
base.Show(request);
}
public IosPagePresenter(IUIApplicationDelegate applicationDelegate, UIWindow window, MvxFormsApplication formsApplication) : base(applicationDelegate, window, formsApplication)
{
}
}
Register this presenter in the setup.IOS
protected override IMvxIosViewPresenter CreatePresenter()
{
var presenter = new IosPagePresenter(ApplicationDelegate, Window, FormsApplication);
Mvx.RegisterSingleton<IMvxFormsViewPresenter>(presenter);
return presenter;
}
And call from ViewModel
var bundle = new MvxBundle(new Dictionary<string, string> { { "NavigationCommand", "StackClear" } });
await _navigationService.Navigate<SavedTankViewModel>(bundle);

Xaamrin Forms BoxView Width Too Long when using to Underline

I am using a BoxView to accomplish underlining in my app. I have a couple of labels that are very short - Text such as Yes or No etc. Here is the XAML for one of the labels with the BoxView for underlining:
<StackLayout Orientation="Vertical" Grid.Row="5" Grid.Column="1" Margin="0,4,0,4" HorizontalOptions="Start" BackgroundColor="Purple" MinimumWidthRequest="1">
<Label x:Name="txtUseMetric" TextColor="Blue" FontSize="Small" Text="{Binding UseMetricText}" BackgroundColor="Yellow">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Value_Tapped" CommandParameter="usemetric" />
</Label.GestureRecognizers>
</Label>
<BoxView BackgroundColor="Green" HeightRequest="1" MinimumWidthRequest="1" />
</StackLayout>
My problem is that the width of the BoxView is always extending past my text I have tried overriding the MinWidthRequest in my App.Xaml file as seen below:
<Style TargetType="BoxView">
<Setter Property="MinimumWidthRequest" Value="3" />
</Style>
But this has not effect. I have included screen shots for you to see.
FYI - The yellow is the width of the Label. You don't see any purple (the background color of the StackLayout) because the StackLayout and Label are the same width. The second screen shot shows what the screen looks like if I remove the BoxView - i.e. the Label and StackLayout are sized correctly.
Any suggestions on how to fix this?
Screen shot with BoxView Too Long making label and StackLayout too long
Screen shot with BoxView removed and Label and Stack Layout sizing correctly
Please note the default HorizontalOptions and that Label derives from View:
Default value is LayoutOptions.Fill unless otherwise documented.
Add HorizontalOptions="Start" on the "Use Metric" Label:
<Label x:Name="txtUseMetric" TextColor="Blue" FontSize="Small"
Text="{Binding UseMetricText}" BackgroundColor="Yellow"
HorizontalOptions="Start">
<BoxView BackgroundColor="Green" HeightRequest="1"
WidthRequest="{Binding Path=Width, Source={x:Reference txtUseMetric}"
HorizontalOptions="Start"/>
One option is to replace the label/box underline with a custom renderer that adds an underline capability to the label.
Here is how to do it:
User Control
public class CustomLabel : Label
{
public static readonly BindableProperty IsUnderlinedProperty =
BindableProperty.Create(nameof(IsUnderlined), typeof(bool), typeof(CustomLabel), false);
public bool IsUnderlined
{
get { return (bool)GetValue(IsUnderlinedProperty); }
set
{
SetValue(IsUnderlinedProperty, value);
}
}
}
Android renderer
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Incident.Droid.CustomRenderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
var control = Control;
UpdateUi(view, control);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomLabel)Element;
if (e.PropertyName == CustomLabel.IsUnderlinedProperty.PropertyName)
{
Control.PaintFlags = view.IsUnderlined ? Control.PaintFlags | PaintFlags.UnderlineText : Control.PaintFlags &= ~PaintFlags.UnderlineText;
}
}
static void UpdateUi(CustomLabel view, TextView control)
{
if (view.FontSize > 0)
{
control.TextSize = (float)view.FontSize;
}
if (view.IsUnderlined)
{
control.PaintFlags = control.PaintFlags | PaintFlags.UnderlineText;
}
}
}
}
iOS Renderer
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Incident.iOS.CustomRenderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
UpdateUi(view, Control);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomLabel)Element;
if (e.PropertyName == CustomLabel.IsUnderlinedProperty.PropertyName)
{
UpdateUi(view, Control);
}
}
private static void UpdateUi(CustomLabel view, UILabel control)
{
var attrString = new NSMutableAttributedString(control.Text);
if (view != null && view.IsUnderlined)
{
attrString.AddAttribute(UIStringAttributeKey.UnderlineStyle,
NSNumber.FromInt32((int)NSUnderlineStyle.Single),
new NSRange(0, attrString.Length));
}
control.AttributedText = attrString;
}
}
}

Resources