Constants for Thickness - xamarin.forms

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>

Related

Binding to ObservableCollection<Tuple<string,string>> results in Error XFC0045 Binding: Property "Item1" not found

I have an ObservableCollection<Tuple<string,string>>. I want to bind this string from the different tuples to my front-end.
When starting the emulator, I get an error that says the following: Error XFC0045 Binding: Property "Item1" not found.
.xaml
<ScrollView>
<CollectionView x:Name="ItemsListView" ItemsSource="{Binding ResultCollection}" SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<!-- Item 1 = UserAnswer -->
<Label Style="{StaticResource SubItem}" Text="{Binding Item1}"/>
<!-- Item 2 = CorrectAnswer -->
<Label Style="{StaticResource SubItem}" Text="{Binding Item2}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>
ViewModel
UserAnswers and CorrectAnswers are both List.
ResultCollection = new ObservableCollection<Tuple<string, string>>();
for(int i = 0; i < UserAnswers.Count; i++)
{
ResultCollection.Add(new Tuple<string, string>(UserAnswers[i], CorrectAnswers[i]));
}
Can someone help me with the binding of this tuple in my .xaml?
You likely have an x:DataType earlier in the page. Given that, for the binding to work inside the CollectionView, you'll need to also have an x:DataType statement on the ItemTemplate or DataTemplate, specifying its type.
But you can't, if the bound data involves a Generic Type (...<...>).
This is a limitation of x:DataType declaration in XAML.
See Enhancement Allow generics in x:DataType.
Fix #1:
REMOVE ALL x:DataType statements in the XAML file.
PRO: XF will figure out what to do at run-time. Will probably work without further changes in your code.
CON: Possible performance issues, if many items in collection.
Fix #2:
Define a custom class for your items, that has the properties. Use that class name in x:DataType on the ItemTemplate's DataTemplate.
MyTuple.cs:
public class MyTuple : Tuple<string, string>
{
public MyTuple(string item1, string item2) : base(item1, item2)
{
}
}
MyContentPage.xaml:
<ContentPage ...
xmlns:local="clr-namespace:YourNamespaceHere" <-- replace YourNamespaceHere
...>
...
<CollectionView ...
...
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:MyTuple">
...
.cs:
public ObservableCollection<MyTuple> ResultCollection { get; set; }
private void Init()
{
ResultCollection = new ObservableCollection<MyTuple> {
new MyTuple("a", "abcabc")
};
}
from the docs
C# tuples, which are backed by System.ValueTuple types, are different
from tuples that are represented by System.Tuple types. The main
differences are as follows:
ValueTuple types are value types. Tuple types are reference types.
ValueTuple types are mutable. Tuple types are immutable.
Data members of ValueTuple types are fields. Data members of Tuple types are properties.
you can only bind to public properties, so binding will only work with System.Tuple
1.Set your ResultCollection property to public.
2.Set the BindingContext in your Background code.
3.Since you set x:name. You can also use similar in your Background code: ItemsListView.ItemsSource = ResultCollection;
I wrote a small example for your reference.
Here is the background code(The xaml code is consistent with the one you provided):
public partial class MainPage : ContentPage
{
public ObservableCollection<Tuple<string, string>> ResultCollection { get; set; }//Set public
public MainPage()
{
InitializeComponent();
ResultCollection = new ObservableCollection<Tuple<string, string>>();
for (int i = 0; i < 5; i++)
{
ResultCollection.Add(new Tuple<string, string>(i.ToString(), i.ToString()));
}
BindingContext = this;// Set BindingContext
}
}

DataTemplate with bindable component

I have a ListView with a data template. I am trying to have a custom component that supports binding for the content of the data template.
Here is the ListView in the page:
<ListView ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="entities:ListItem">
<ViewCell>
<components:ListItemView ListItem="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And this is the ListItemView declaration:
public partial class ListItemView : StackLayout
{
public static readonly BindableProperty ListItemProperty
= BindableProperty.Create(
nameof(ListItem), typeof(ListItem), typeof(ListItemView), null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: ListItemPropertyChanged);
static void ListItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (ListItemView)bindable;
view.ListItem = (ListItem)newValue;
}
public ListItem ListItem
{
get => (ListItem)GetValue(ListItemProperty);
set
{
SetValue(ListItemProperty, value);
if (_viewModel != null) // never hits this break point
_viewModel.ListItem = value;
}
}
I had a breakpoint on the line with the comment. This breakpoint was never hit. ListItemView however does get initialized and created.
Update
I tried a simple demo to ensure the issue was in the binding,
<StackLayout Padding="5">
<Label Text="{Binding Demo.Title}" />
<components:CheckListView ListItem="{Binding Demo}" />
</StackLayout>
The above code was outside the list view and I am able to see the title. The breakpoint is still not hit.

Loading FFImageLoading SVG into ImageButton Source from code?

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.

Xamarin Listview don't show the observable Collection

I'm using Xamarin.Forms MVVM to develop my app, and don't found what I'm doing wrong, I have an ObservableCollection with the values from web API, and when I set a break point all the values are good even in the view when I see the values of the binding source everything have the value, but the values are not showing up in my ListView.
Here is the ViewModel
class DatosMedicosViewModel : BaseViewModel
{
private ApiService apiService;
private ObservableCollection<Land> land;
private bool isRefreshing;
public ObservableCollection<Land> Lands
{
get { return this.land; }
set { SetValue(ref this.land, value); }
}
public bool IsRefreshing
{
get { return this.isRefreshing; }
set { SetValue(ref this.isRefreshing, value); }
}
public DatosMedicosViewModel()
{
this.apiService = new ApiService();
this.LoadLand();
}
private async void LoadLand()
{
this.IsRefreshing = true;
var connection = await this.apiService.CheckConnection();
if (!connection.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
connection.Message,
"Accept");
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
var response = await this.apiService.GetList<Land>(
"url Base",
"prefix",
"Controller");
if (!response.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept"
);
return;
}
var list = (List<Land>)response.Result;
this.Lands = new ObservableCollection<Land>(list);
this.IsRefreshing = false;
}
public ICommand RefreshCommand
{
get
{
return new RelayCommand(LoadLand);
}
}
}
Here is the View
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ARLAPP.Views.ConsultaPage"
BackgroundColor="White"
BindingContext="{Binding Main, Source={StaticResource Locator}}"
Title="Lands">
<ContentPage.Content>
<StackLayout
BindingContext="{Binding Lands}"
Padding="5">
<StackLayout>
<Image
VerticalOptions="Center"
WidthRequest="300"
Source="UserIcon"
BackgroundColor="Transparent"/>
<Label Text="Mark"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
FontAttributes="Bold"
FontSize="Medium"/>
</StackLayout>
<StackLayout>
<ListView
SeparatorVisibility="Default"
FlowDirection="LeftToRight"
BackgroundColor="White"
ItemsSource="{Binding Lands}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label
Grid.Column="2"
VerticalOptions="Center"
TextColor="Black"
Text="{Binding Currency}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here how I call the view
if (this.PageName == "Lands")
{
MainViewModel.GetInstance().Lands= new LandViewModel();
Application.Current.MainPage = new LandMasterPage();
}
Check your BindingContext. I think you are setting it wrong in your view.
In your top-level StackLayout you set the the BindingContext to your property: BindingContext="{Binding Lands}". And in your ListView you set the ItemsSource also to this property: ItemsSource="{Binding Lands}". That won't work because the ListView is trying to bind to a property Lands inside your BindingContext, which is also set to Lands.
Remove the BindingContext from your top-level StackLayout, because you don't need it.
Ensure the BindingContext of your page ConsultaPage is set to your view-model DatosMedicosViewModel.
Sample of setting the bindingcontext (abstract code):
var mypage = new ConsultaPage();
mypage.BindingContext = new DatosMedicosViewModel();
await Navigation.PushAsync(mypage);
// Load your data in OnAppearing() of the page-event
This should solve your binding-problem.
Side-Note: As Abdul Gani said in the comments: Ensure you implement the INotifyPropertyChanged interface, but I assume you do this already in your BaseViewModel and call the NotifyChanged-Event in your SetValue-Method.

HTML In Listview

I need to show list of post titles coming from wordpress. The titles have unicode characters in them. Like this “I Shall Not Forget” I want it to show “I Shall Not Forget”
In code behind System.Net.WebUtility.HtmlDecode(text.Title.Rendered) will take care of it. Can I do that somehow in the XAMAL?
<ListView x:Name="postList" ItemSelected="Handle_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label FormattedText="{Binding Title.Rendered}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code based on what #Jason said: Using a converter.
Create a class called HtmlConverter:
using System;
using Xamarin.Forms;
namespace StackoverflowQ.Converters
{
public class HtmlConverter : IValueConverter
{
#region Converter
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value != null)
{
string text = (string)value;
return System.Net.WebUtility.HtmlDecode(text);
}
else
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
}
In your xaml reference that class (Mine is in a folder called Converters)
xmlns:converters="clr-namespace:StackoverflowQ.Converters;assembly=StackoverflowQ"
Then add it in your ResourceDictionary from there you should be able to reference it
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StackoverflowQ.Views.MainPage"
Title="{Binding Title}">
<ContentPage.Resources>
<ResourceDictionary>
<converters:HtmlConverter x:Key="HtmlConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<ListView x:Name="postList" ItemSelected="Handle_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Title.Rendered , Converter={StaticResource HtmlConverter}}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
You can create a Label Renderer to convert HTML text like this:
CustomLabel:
public class CustomLabel: Label
{
}
Android Renderer:
[assembly: ExportRenderer(typeof(CustomLabel), typeof(HtmlFormattedLabelRenderer))]
namespace XYZ.Droid.Renderer
{
public class HtmlFormattedLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
if (view == null) return;
if(!string.IsNullOrEmpty(Control.Text))
Control.SetText(Html.FromHtml(view.Text.ToString()), TextView.BufferType.Spannable);
}
}
}
iOS Renderer:
[assembly: ExportRenderer(typeof(CustomLabel), typeof(HtmlFormattedLabelRendereriOS))]
namespace XYZ.iOS.Renderer
{
public class HtmlFormattedLabelRendereriOS:LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
if (view == null) return;
var attr = new NSAttributedStringDocumentAttributes();
var nsError = new NSError();
attr.DocumentType = NSDocumentType.HTML;
Control.AttributedText = new NSAttributedString(view.Text, attr, ref nsError);
var mutable = Control.AttributedText as NSMutableAttributedString;
UIStringAttributes uiString = new UIStringAttributes();
uiString.Font = UIFont.FromName("Roboto-Regular",15f);
uiString.ForegroundColor = UIColor.FromRGB(130, 130, 130);
mutable.SetAttributes(uiString, new NSRange(0, Control.Text.Length));
}
}
}
Hope this may solve your issue.
The HtmlText property of Forms9Patch.Label decodes escapes. Forms9Patch is a free, open source (MIT License) NuGet package. Full disclosure: I am the author of Forms9Patch.

Resources