Scalling font size in xamarin forms - xaml

I am working with xamarin forms and facing problems with the layout. Now, with the fontsize..
I have two labels and one of them is Micro. I need that the other get the size of the Micro label / 2 as its size...I am reading about relative layout, but I don't know if it's the best way to do that...does someone have any idea to help me?
This is my labels:
<StackLayout Spacing="0">
<Label x:Name="menu_lbl_promocoes" Text="0000" FontAttributes="Bold" TextColor="Black" HorizontalOptions="Center" Style="{Binding labelsfont}"/>
<Label x:Name="menu_lbl_disponiveis" Text="Disponíveis" TextColor="Black" HorizontalOptions="Center" FontSize="Small" Style="{Binding labelsfont}"/>
</StackLayout>
</StackLayout> //yeah, there is another stacklayout
<Label Text="Promoções" FontSize="Micro" TextColor="White" HorizontalOptions="Center" Style="{Binding labelsfont}"/>
I need the second label get the half size of the third (which has a micro size)...

Simply extend Label to have two bindable properties - FontSizeFactor and NamedFontSize - and have them calculate the font size for you:
public class MyLabel : Label
{
public static readonly BindableProperty FontSizeFactorProperty =
BindableProperty.Create(
"FontSizeFactor", typeof(double), typeof(MyLabel),
defaultValue: 1.0, propertyChanged: OnFontSizeFactorChanged);
public double FontSizeFactor
{
get { return (double)GetValue(FontSizeFactorProperty); }
set { SetValue(FontSizeFactorProperty, value); }
}
private static void OnFontSizeFactorChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).OnFontSizeChangedImpl();
}
public static readonly BindableProperty NamedFontSizeProperty =
BindableProperty.Create(
"NamedFontSize", typeof(NamedSize), typeof(MyLabel),
defaultValue: NamedSize.Small, propertyChanged: OnNamedFontSizeChanged);
public NamedSize NamedFontSize
{
get { return (NamedSize)GetValue(NamedFontSizeProperty); }
set { SetValue(NamedFontSizeProperty, value); }
}
private static void OnNamedFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).OnFontSizeChangedImpl();
}
protected virtual void OnFontSizeChangedImpl()
{
if (this.FontSizeFactor != 1)
this.FontSize = (this.FontSizeFactor * Device.GetNamedSize(NamedFontSize, typeof(Label)));
}
}
Sample Usage:
<Label FontSize="Large" Text="Large Size" />
<local:MyLabel NamedFontSize="Large" FontSizeFactor="0.9" Text="90% Large Size" />
<Label FontSize="Medium" Text="Medium Size" />
<local:MyLabel NamedFontSize="Medium" FontSizeFactor="0.75" Text="75% Medium Size" />
<Label FontSize="Micro" Text="Micro Size" />
<local:MyLabel NamedFontSize="Micro" FontSizeFactor="0.5" Text="50% Micro Size" />

So ima give an example on how to do it:
We got 2 labels.
First one with micro
<Label x:Name="statuslabel" FontSize="Micro" Text="Filter status:" VerticalOptions="Center" WidthRequest="100" />
Second one without size
Than in the codein the constructur, at the last line you write:
typelabel.FontSize = statuslabel.FontSize / 2;
the result:

Related

Retrieving ItemsSource from a BindableLayout

I have a FlexLayout with a BindableLayout.
<FlexLayout BindableLayout.ItemsSource="{Binding CardItems}" x:Name="SourceLayout" Background="green"
Direction="Row" Wrap="Wrap">
<BindableLayout.ItemTemplate>
<DataTemplate>
<ContentView>
<Frame CornerRadius="20" Padding="0" WidthRequest="150" Margin="10"
HeightRequest="150"
BackgroundColor="{Binding .,
Converter={StaticResource AlternateColorConverter},
ConverterParameter={x:Reference SourceLayout}}">
<StackLayout>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.CardItemNavCommand, Source={x:Reference SourceLayout}}"
CommandParameter="{Binding NavTarget}"/>
</StackLayout.GestureRecognizers>
<Label Text="{Binding Text}" TextColor="Black" FontSize="20" VerticalOptions="FillAndExpand"/>
<Label Text="{Binding Text}" TextColor="Black" FontSize="20" VerticalOptions="FillAndExpand"/>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
Is it possible to get the index of the current item inside the converter so I can change the color accordingly? I know this can be achieved with a ListView because I can access the items source property but I can't access the resource from the BindableLayout.
Is it possible to get the index of the current item inside the converter so I can change the color accordingly
BindableLayout is a static class, so we cannot get it from the layout to get the itemsSource.
For this function ,try to create an 'Identifier' property in the model class and set binding for the backgroundColor. Then get the value in the converter class to obtain the index of the current item from the data collection. Sepcify the background color according to the index.
Check the code:
App.xaml.cs
public partial class App : Application
{
public static TestPageViewModel viewModel;
public App()
{
InitializeComponent();
viewModel = new TestPageViewModel();
MainPage = new NavigationPage(new TestPage());
}
}
Page.xaml
<StackLayout BindableLayout.ItemsSource="{Binding DataCollection}" ...>
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid Padding="0,2,3,0" BackgroundColor="{Binding Identifier, Converter={StaticResource _converter}}">
...
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
Page.xaml.cs
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
BindingContext = App.viewModel;
}
}
Model class
public class TestPageModel
{
public string Content { get; set; }
public string Identifier { get; set; }
}
ValueConverter class
public class TestPageValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var index = GetValue(value);
switch (index)
{
case 1:
return Color.LightBlue;
break;
case 2:
return Color.LightPink;
break;
case 3:
return Color.LightYellow;
break;
default:
break;
}
return Color.White;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return -1;
}
double GetValue(object value)
{
var viewModel = App.viewModel;
foreach (var item in viewModel.DataCollection)
{
if (item.Identifier == (string)value)
{
return viewModel.DataCollection.IndexOf(item) + 1;
}
}
return -1;
}
}

Can't create Bindable Property for custom ContentView

I have created a ContentView with a single Label (I plan to add more later).
PageHeadingView.xaml
<ContentView.Content>
<StackLayout Orientation="Vertical" BackgroundColor="Red">
<Label x:Name="HeadingLabel" Text="{Binding HeadingText}" />
</StackLayout>
</ContentView.Content>
I defined a BindableProperty in my code behind. I also set the BindingContext of my view to be itself.
PageHeadingView.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PageHeadingView : ContentView
{
public PageHeadingView()
{
InitializeComponent();
this.BindingContext = this;
}
public static readonly BindableProperty HeadingTextProperty = BindableProperty.Create(nameof(HeadingText), typeof(string), typeof(PageHeadingView), default(string));
public string HeadingText { get => (string)GetValue( HeadingTextProperty); set => SetValue(HeadingTextProperty, value); }
}
I then added the View to my ContentPage. I also added a test Label inside a StackLayout to ensure my bindings were working correctly.
HomePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MyProject.Views"
x:Class="MyProject.HomePage">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<views:PageHeadingView HeadingText="{Binding Name}" />
<Label Text="{Binding Name}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
And set my BindingContext in code.
HomePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
//ViewModel contains a string property named: Name;
this.BindingContext = new ViewModel();
}
}
When I run my code, my PageHeadingView does not display any text. I can see the red background color, so I know the control has been added to the Page correctly. The test Label I placed in StackLayout also works correctly, and I am able to see the bound value.
What do I need to do to make my CustomView display Bindable content?
From your code, you may have some problems when using bindableproperty bidning, I create simple sample that you can take a look:
PageHeadingView.xaml:
<ContentView.Content>
<StackLayout BackgroundColor="Red" Orientation="Vertical">
<Label x:Name="HeadingLabel" />
</StackLayout>
</ContentView.Content>
PageHeadingView.cs, you can update HeadingText value by HeadingTextPropertyChanged, then display HeadingLabel.
public partial class PageHeadingView : ContentView
{
public static BindableProperty HeadingTextProperty= BindableProperty.Create(
propertyName: "HeadingText",
returnType: typeof(string),
declaringType: typeof(PageHeadingView),
defaultValue: "",
defaultBindingMode: BindingMode.OneWay,
propertyChanged: HeadingTextPropertyChanged);
public string HeadingText
{
get { return base.GetValue(HeadingTextProperty).ToString(); }
set { base.SetValue(HeadingTextProperty, value); }
}
private static void HeadingTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (PageHeadingView)bindable;
control.HeadingLabel.Text = newValue.ToString();
}
public PageHeadingView()
{
InitializeComponent();
}
}
<StackLayout>
<customcontrol:PageHeadingView HeadingText="{Binding Name}" />
<Label Text="{Binding Name}" />
</StackLayout>
You are binding the wrong element in PageHeadingView.
Option 1:
Add "Content" in PageHeadingView.xaml.cs
this.Content.BindingContext = this;
Option 2:
Remove "Content" in PageHeadingView.xaml
<!--<ContentView.Content>-->
<StackLayout Orientation="Vertical" BackgroundColor="Red">
<Label x:Name="HeadingLabel" Text="{Binding HeadingText}" />
</StackLayout>
<!--</ContentView.Content>-->
Edited as quoted the wrong class previously.

xamarin forms app freezes while binding to nullable datetime

While trying to show data over form in Xamarin Forms app, a property in the model which is nullable DateTime is freezing the app. I know this is the offending property because if I remove Xaml for this binding, everything works fine.
While stepping through the code, the LoginTime property from source (fetched from db) and the getter from ViewModel's User property are going into infinite loop. I can't possibly imagine why.
Model:
public class User : PersistentEntity
{
private string _userId;
public string UserId
{
get => _userId;
set => SetProperty(ref _userId, value);
}
private string _userName;
public string UserName
{
get => _userName;
set => SetProperty(ref _userName, value);
}
private string _email;
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
private DateTime? _loginTime;
public DateTime? LoginTime
{
get => _loginTime;
set => SetProperty(ref _loginTime, value);
}
}
ViewModel:
public class SettingsViewModel : ViewModelBase
{
private readonly SettingsSession _settingsSession;
private User _user;
public User User
{
get => _user;
set => SetProperty(ref _user, value);
}
public SettingsViewModel(INavigationService navigationService, SettingsSession settingsSession)
: base(navigationService)
{
Title = "Settings";
_settingsSession = settingsSession;
}
private DelegateCommand _fetchUserCommand;
public DelegateCommand FetchUserCommand =>
_fetchUserCommand ?? (_fetchUserCommand = new DelegateCommand(ExecuteFetchUserCommand, CanExecuteFetchUserCommand));
async void ExecuteFetchUserCommand()
{
User = await _settingsSession.FetchUser();
}
bool CanExecuteFetchUserCommand()
{
return true;
}
}
View:
<TableView Intent="Settings">
<TableRoot>
<TableSection Title="User">
<ViewCell>
<StackLayout Orientation="Horizontal"
Padding="2">
<Label Text="User Id"
WidthRequest="150"
Margin="5,0,0,0"
VerticalOptions="CenterAndExpand"/>
<Entry WidthRequest="250"
HorizontalTextAlignment="Start"
VerticalTextAlignment="Center"
Placeholder="UserId"
Text="{Binding User.UserId}"/>
</StackLayout>
</ViewCell>
<ViewCell>
<StackLayout Orientation="Horizontal"
Padding="2">
<Label Text="User Name"
WidthRequest="150"
Margin="5,0,0,0"
VerticalOptions="CenterAndExpand"/>
<Entry WidthRequest="250"
HorizontalTextAlignment="Start"
VerticalTextAlignment="Center"
Placeholder="User Name"
Text="{Binding User.UserName}"/>
</StackLayout>
</ViewCell>
<ViewCell>
<StackLayout Orientation="Horizontal"
Padding="2">
<Label Text="Login Time"
WidthRequest="150"
Margin="5,0,0,0"
VerticalOptions="CenterAndExpand"/>
<Entry WidthRequest="250"
HorizontalTextAlignment="Start"
VerticalTextAlignment="Center"
Placeholder="Login Time"
Text="{Binding User.LoginTime, StringFormat='{0:dd-MM-yyyy HH:mm}'}"/>
</StackLayout>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
Any help is appreciated.

Two ways Binding between a Slider and Entry

I would like to bind an Entry with a Slider and vice versa. I wrote something like this:
<Entry x:Name="myEntry" Text="{Binding Value, Mode=TwoWay}" BindingContext="{x:Reference slider}"/>
<Slider x:Name="slider" Maximum="100" Minimum="0" BindingContext="{x:Reference myEntry}"/>
When I use the slider, the value in the entry is updated, but when I put manually some value in the Entry, the value append a 0 or change to 0. What can be the problem. I am working on android.
You should bind both your Slider and Entry to string/ integer in a backing View Model.
class MyViewModel
{
private int _sliderValue;
public string EntryText
{
get => _sliderValue.ToString();
set => SetProperty(ref _sliderValue, int.Parse(value) );
}
public int SliderValue
{
get => _sliderValue;
set => (ref _sliderValue, value);
}
}
And in the view
<Entry Text="{Binding EntryText}" />
<Slider Value="{Binding SliderValue}" />
More MVVM Info
Fresh MVVM for Xamarin
Caliburn Micro for Xamarin
please refer the following xaml code
<Frame HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand">
<StackLayout>
<Entry Text="{Binding Path=Value}"
FontSize="18"
x:Name="label"
BindingContext="{x:Reference Name=slider}"/>
<Slider x:Name="slider"
Maximum="1500"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</Frame>
The same can be achieved by using the view model please refer the code
Xaml
<Frame HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand">
<StackLayout>
<Entry x:Name="NameEntry" Placeholder="Enter Name" Text="{Binding Forename,Mode=TwoWay}" />
<Slider Value="{Binding Forename}" Minimum="0" Maximum="10"/>
</StackLayout>
</Frame>
------------------- now see the model in a c# file-----------------------------------
public partial class MainPage : ContentPage
{
public class DetailsViewModel : INotifyPropertyChanged
{
int forename;
public int Forename
{
get
{
return forename;
}
set
{
if (forename != value)
{
forename = value;
OnPropertyChanged ("Forename");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public MainPage()
{
InitializeComponent();
BindingContext = new DetailsViewModel();
}
}

Why are the borders on my BoxViews round?

I´m trying to create a separator between StackLayouts in a XAML page in Xamarin.Forms and I can do that easily with BoxView.
But when I try to add a border to the BoxView (by adding a Frame) I can´t get it to not curve at the ends.
I have tried all kinds of ways to make this work without luck. I even tried to stretch the BoxView out of the screen (take a look at the latter image) to have the curve off the screen (that would just do fine at this moment even)
Here is just few of of the things I tried out without any luck.
<!--Test 1. -->
<Frame OutlineColor="{DynamicResource CardOutlineColor}" HasShadow="False" Padding="0" VerticalOptions="FillAndExpand">
<BoxView x:Name="boxViewSeparator" HeightRequest="15" WidthRequest="10000" BackgroundColor="{DynamicResource WindowsBackgroundColor}" />
</Frame>
<!--Test 2. -->
<AbsoluteLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Frame OutlineColor="{DynamicResource CardOutlineColor}" HasShadow="False" Padding="0" VerticalOptions="FillAndExpand">
<BoxView AbsoluteLayout.LayoutBounds="1.0, 1.0, 1.0, 1.0"
Color="#f2f3f3"
AbsoluteLayout.LayoutFlags="All" BackgroundColor="{DynamicResource WindowsBackgroundColor}" />
</Frame>
</AbsoluteLayout>
<!--Test3. -->
<Frame OutlineColor="{DynamicResource CardOutlineColor}" HasShadow="False" Padding="0" VerticalOptions="FillAndExpand">
<ContentView HeightRequest="15" BackgroundColor="{DynamicResource WindowsBackgroundColor}" />
</Frame>
<!--Test 4. -->
<Frame OutlineColor="{DynamicResource CardOutlineColor}" HasShadow="False" Padding="0" VerticalOptions="FillAndExpand">
<ContentView HeightRequest="15" BackgroundColor="{DynamicResource WindowsBackgroundColor}" MinimumWidthRequest="500"/>
</Frame>
I came the closest with this this code
<AbsoluteLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Frame OutlineColor="{DynamicResource CardOutlineColor}" HasShadow="False"
Padding="0" VerticalOptions="FillAndExpand"
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="1.5, 1.5, 1.2, 1.0">
<BoxView HeightRequest="15" BackgroundColor="{DynamicResource WindowsBackgroundColor}" />
</Frame>
</AbsoluteLayout>
But the result is that I only get the other part of the ViewBox out of the window
Does anybody have any other idea?
Ok I found out how to do this and like everything you know, it´s easy :-)
Just use 3 BoxViews in a StackLayout without padding/spacing.
<StackLayout Orientation ="Vertical" Padding="0" Spacing="0">
<BoxView BackgroundColor="#d6dbdb" HeightRequest="1" VerticalOptions="End" HorizontalOptions="FillAndExpand"/>
<BoxView HeightRequest="15" BackgroundColor="#f2f3f3"/>
<BoxView BackgroundColor="#d6dbdb" HeightRequest="1" VerticalOptions="End" HorizontalOptions="FillAndExpand"/>
</StackLayout>
And the result is the following.
p.s
I would like to see how you would off set the frame off the screen...
I've found more elegant solution(IMHO):
Create custom box view:
public class Border : BoxView
{
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor),
typeof(Color),
typeof(Border),
Color.Transparent);
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(nameof(BorderWidth),
typeof(double),
typeof(Border),
0d);
public Color BorderColor
{
get { return (Color)GetValue(BorderColorProperty); }
set { SetValue(BorderColorProperty, value); }
}
public double BorderWidth
{
get { return (double)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
}
Implement renderers:
ANDROID
public class BorderRenderer : VisualElementRenderer<BoxView>
{
public BorderRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
{
base.OnElementChanged(e);
SetWillNotDraw(false);
Invalidate();
}
public override void Draw(Canvas canvas)
{
var border = Element as Border;
base.Draw(canvas);
var paint = new Paint();
paint.StrokeWidth = (float)border.BorderWidth;
paint.SetStyle(Paint.Style.Stroke);
paint.SetARGB(ConvertTo255ScaleColor(border.BorderColor.A), ConvertTo255ScaleColor(border.BorderColor.R), ConvertTo255ScaleColor(border.BorderColor.G), ConvertTo255ScaleColor(border.BorderColor.B));
SetLayerType(Android.Views.LayerType.Software, paint);
var number = (float)border.BorderWidth / 2;
var rectF = new RectF(
number, // left
number, // top
canvas.Width - number, // right
canvas.Height - number // bottom
);
canvas.DrawRoundRect(rectF, 0, 0, paint);
}
private int ConvertTo255ScaleColor(double color)
{
return (int)Math.Ceiling(color * 255);
}
}
iOS
public class BorderRenderer : VisualElementRenderer<BoxView>
{
public BorderRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
Layer.MasksToBounds = true;
Layer.CornerRadius = 0f;
}
public override void Draw(CGRect rect)
{
var border = (Border)Element;
using (var context = UIGraphics.GetCurrentContext())
{
context.SetFillColor(border.Color.ToCGColor());
context.SetStrokeColor(border.BorderColor.ToCGColor());
context.SetLineWidth((float)border.BorderWidth);
var rCorner = Bounds.Inset((int)border.BorderWidth / 2, (int)border.BorderWidth / 2);
var radius = (nfloat)border.CornerRadius;
radius = (nfloat)Math.Max(0, Math.Min(radius, Math.Max(rCorner.Height / 2, rCorner.Width / 2)));
var path = CGPath.FromRoundedRect(rCorner, radius, radius);
context.AddPath(path);
context.DrawPath(CGPathDrawingMode.FillStroke);
}
}
}
Don't forget to add attribute above namespace on each renderer:
[assembly: ExportRenderer(typeof(Border), typeof(BorderRenderer))]