ContentView -> Custom Frame Binding Not Working - xamarin.forms

I copied/wrote a class that inherits from Frame
public class Circle : Frame
{
//private double _radius;
public static readonly BindableProperty RadiusProperty = BindableProperty.Create(nameof(Radius), typeof(double), typeof(Circle), 126.0, BindingMode.TwoWay);
public double Radius
{
get => (double)GetValue(RadiusProperty); //_radius;
set
{
SetValue(RadiusProperty, value);
OnPropertyChanged();
AdjustSize();
}
}
private void AdjustSize()
{
HeightRequest = Radius;
WidthRequest = Radius;
Margin = new Thickness(0,0,0,0);
Padding = new Thickness(0, 0, 0, 0);
CornerRadius = (float) (Radius / 2);
}
public Circle()
{
HorizontalOptions = LayoutOptions.Center;
}
}
The consuming page defines these BinadableProperties
public static readonly BindableProperty InnerColorProperty = BindableProperty.Create("InnerColor", typeof(Color), typeof(CircleProgressView), defaultValue: Color.FromHex("#34495E"), BindingMode.TwoWay);
public Color InnerColor
{
get => (Color)GetValue(InnerColorProperty);
set => SetValue(InnerColorProperty, value);
}
public static readonly BindableProperty InnerRadiusProperty = BindableProperty.Create("InnerRadius", typeof(double), typeof(CircleProgressView), 126.0, BindingMode.TwoWay);
public double InnerRadius
{
get => (double)GetValue(InnerRadiusProperty);
set => SetValue(InnerRadiusProperty, value);
}
And uses the Circle like so
<components:Circle Grid.Row="0" BackgroundColor="{Binding InnerColor}" Radius="{Binding InnerRadius}" >
Alas, the bindable's setter, and hence AdjustSize(), is never called nor is the default value used. Instead of a circle I end up with a rectangle. The BackgroundColor, which is a property of Frame, binds and works fine.
If I remove the BindableProperty and leave behind a regular INotify property
public class Circle : Frame
{
private double _radius;
public double Radius
{
get => _radius;
set
{
_radius = value;
OnPropertyChanged();
AdjustSize();
}
}
private void AdjustSize()
{
HeightRequest = Radius;
WidthRequest = Radius;
Margin = new Thickness(0,0,0,0);
Padding = new Thickness(0, 0, 0, 0);
CornerRadius = (float) (Radius / 2);
}
public Circle()
{
HorizontalOptions = LayoutOptions.Center;
}
}
The compiler complains if I keep the InnerRadius binding
Severity Code Description Project File Line Suppression State
Error Position 17:92. No property, bindable property, or event found for 'Radius', or mismatching type between value and property. ...\Components\CircleProgressView.xaml 17
I can replace the Radius binding with a hardcoded value and it runs fine, a circle appears.
<components:Circle Grid.Row="0" BackgroundColor="{Binding InnerColor}" Radius="126" >
What's wrong with a BindableProperty in a regular C# class?

Firstly, we need to handle data in the property changed event of bindable property instead of the setter method of a normal property. So modify your Circle class like:
public static readonly BindableProperty RadiusProperty = BindableProperty.Create(nameof(Radius), typeof(double), typeof(Circle), 125.0, BindingMode.TwoWay, propertyChanged: RadiusChanged);
public double Radius
{
get => (double)GetValue(RadiusProperty); //_radius;
set => SetValue(RadiusProperty, value);
}
static void RadiusChanged(BindableObject bindableObject, object oldValue, object newValue)
{
Circle circle = bindableObject as Circle;
circle.HeightRequest = (double)newValue;
circle.WidthRequest = (double)newValue;
circle.CornerRadius = (float)((double)newValue / 2);
}
This is because we bind data in XAML we should manipulate the bindable property's changed event directly.
Secondly, I saw you bound the property using the parent page's bindable property. Normally, we won't do that. We will consume a view model as the page's binding context and then bind the property to the binding context. However, if you do want to consume the parent page's bindable property as the Circle's binding context, try this way:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Sample.SecondPage"
xmlns:components="clr-namespace:Sample"
x:Name="Page">
<ContentPage.Content>
<StackLayout>
<components:Circle BackgroundColor="{Binding InnerColor, Source={x:Reference Page}}" Radius="{Binding InnerRadius, Source={x:Reference Page}}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Name your parent page first and change the circle's source to that.
Here, I used a different default Radius value comparing to InnerRadius so the property changed event will be called at the initial time.

Related

Unable To Retrieve Custom Control Value from View

Xamarin Forms Android Autosize Label TextCompat pre android 8 doesn't autosize text
I unfortunately do not have a high enough rep to comment on anyones post.
I was trying some things out and came across the post linked which got me very close to the solution after experimenting with other posts. I am also trying to autosize text within an app, but inside of an MVVM Master Detail project. If I enter values directly in the Droid renderer it works as expected, but that defeats the purpose when I have fonts of all sizes needed.
I have already made sure my return type is correct.
The code behind is initialized prior to the get value.
The fields are public.
There are no other issues by plugging in numeric values instead of bindable properties.
I am not receiving any values from the view. I would assume the view has not been created yet but the code behind has initialized. I am pretty sure I have done everything mostly right but I mostly deal with stock Xamarin so expanding functionality is still pretty new to me. All help is appreciated.
Custom Control (edit: changed default value from default(int) to an integer value to get rid of exception)
/// <summary>Auto scale label font size class.</summary>
public class AutoSizeLabel : Label
{
/// <summary>Minimum font size property.</summary>
public static readonly BindableProperty MinimumFontSizeProperty = BindableProperty.Create(
propertyName: nameof(MinimumFontSize),
returnType: typeof(int),
declaringType: typeof(AutoSizeLabel),
defaultValue: 17);
/// <summary>Maximum font size property.</summary>
public static readonly BindableProperty MaximumFontSizeProperty = BindableProperty.Create(
propertyName: nameof(MaximumFontSize),
returnType: typeof(int),
declaringType: typeof(AutoSizeLabel),
defaultValue: 24);
/// <summary>Gets or sets minimum font size.</summary>
public int MinimumFontSize
{
get
{
return (int)this.GetValue(MinimumFontSizeProperty);
}
set
{
this.SetValue(MinimumFontSizeProperty, value);
}
}
/// <summary>Gets or sets maximum font size.</summary>
public int MaximumFontSize
{
get
{
return (int)this.GetValue(MaximumFontSizeProperty);
}
set
{
this.SetValue(MaximumFontSizeProperty, value);
}
}
}
Droid Renderer
public class AutoSizeLabelRenderer : LabelRenderer
{
protected override bool ManageNativeControlLifetime => false;
protected override void Dispose(bool disposing)
{
Control.RemoveFromParent();
base.Dispose(disposing);
}
private AutoSizeLabel bindingValue = new AutoSizeLabel();
private AppCompatTextView appCompatTextView;
public AutoSizeLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
//v8 and above supported natively, no need for the extra stuff below.
if (DeviceInfo.Version.Major >= 8)
{
Control?.SetAutoSizeTextTypeUniformWithConfiguration(bindingValue.MinimumFontSize, bindingValue.MaximumFontSize, 2, (int)ComplexUnitType.Sp);
return;
}
appCompatTextView = new AppCompatTextView(Context);
appCompatTextView.SetTextColor(Element.TextColor.ToAndroid());
appCompatTextView.SetMaxLines(1);
appCompatTextView.SetBindingContext(autoLabel.BindingContext);SetNativeControl(appCompatTextView);
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, bindingValue.MinimumFontSize, bindingValue.MaximumFontSize, 2, (int)ComplexUnitType.Sp);
}
}
XAML Call
<renderer:AutoSizeLabel MinimumFontSize="17"
MaximumFontSize="24"
Style="{StaticResource SomeStyle}"
Text="{Binding SomeText}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SomeCommand}"></TapGestureRecognizer>
</Label.GestureRecognizers>
</renderer:AutoSizeLabel>
This line is unnecessary.
private AutoSizeLabel bindingValue = new AutoSizeLabel();
Instead reference autoLabel. Alternatively I changed the check to
if (e.NewElement == null || Control == null) { return; }
and cast in the following line using
var autoSizeLabel = e.NewElement as AutoSizeLabel;

How to display a timer using ProgressRing xamarin forms with dynamic time

I have implemented a progress ring with time using code referenced from display a timer using progressBar xamarin forms.
Now I want to bind the value of total time dynamically so that when the time reaches some fixed value, the progress ring starts progressing. How can I accomplish this?
Since ConverterParameter is not a bindable property, we can't use Binding. But we can try to customize this label with a bindable property and pass itself to the ConverterParameter.
Customize this label like:
public class MyLabel : Label
{
public double TotalTime
{
set { SetValue(TotalTimeProperty, value); }
get { return (double)GetValue(TotalTimeProperty); }
}
public static readonly BindableProperty TotalTimeProperty = BindableProperty.Create(nameof(TotalTime), typeof(double), typeof(MyLabel), default(double));
}
Then we can pass itself in the XAML:
<local:MyLabel x:Name="MyLabel"
TotalTime="{Binding TotalTime}"
Text="{Binding Source={x:Reference progressBar}, Path=Progress, Converter={StaticResource countDownTime}, ConverterParameter={x:Reference MyLabel}"
HorizontalOptions ="Center"
FontSize="20"
FontFamily = "Helvetica Neue"
TextColor = "Red" />
At last modify this TotalTime in the code behind:
public class BindModel : INotifyPropertyChanged
{
double totalTime;
public double TotalTime
{
set
{
totalTime = value;
onPropertyChanged("TotalTime");
}
get
{
return totalTime;
}
}
public event PropertyChangedEventHandler PropertyChanged;
void onPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// And set this model as a BindingContext
var model = new BindModel { TotalTime = 60000 };
BindingContext = model;
Also your IValueConverter should be like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double time = 0;
double myTotalTime = ((MyLabel)parameter).TotalTime;
double.TryParse(myTotalTime.ToString(), out var totalTime);
double.TryParse(value.ToString(), out var progress);
time = progress <= double.Epsilon ? totalTime : (totalTime - (totalTime * progress));
var timeSpan = TimeSpan.FromMilliseconds(time);
return $"{timeSpan.Minutes:00;00}:{timeSpan.Seconds:00;00}";
}

Trying to use a value converter with a ListView (Recycle Caching Strategy) with OnBindingContextChanged

Not sure if I formatted the question appropriately, please let me know if I did not. But I am trying to simply bind a background color to a value in my viewcell. I have this working, actually. The issue is when I update a value, I don't see the change in background color. The implementation is a bit complicated, but here's my code.
ViewCell (OnBindingContextChanged)
...
ShowReadOverlay.SetBinding(Xamarin.Forms.VisualElement.BackgroundColorProperty, new Xamarin.Forms.Binding(".", Xamarin.Forms.BindingMode.TwoWay, new XamarinMobile.Converters.GridCellBackgroundColorConverter(), null, null, null));
...
So essentially I just build my layout. I decided to only post the relevant code that sets the binding in my OnBindingContextChanged method. If anyone needs any other code I'd be glad to add it, just don't know if it's relevant. My ViewCell class is a simple class that just inherits ViewCell.
Here's my converter:
public class GridCellBackgroundColorConverter : Xamarin.Forms.IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
try
{
var cell = (XamarinMobile.ViewModels.GridCellViewModel)value;
if(cell.HasRead)
{
//return with shadow
return Xamarin.Forms.Color.FromRgba(0,0,0,0.6);
} else
{
//return no shadow
return Xamarin.Forms.Color.FromRgba(0, 0, 0, 0.0);
}
} catch(System.Exception ex)
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Simple. It works. Now here's the tricky part. So the grid I'm describing, is a listview that contains cells of stories. A user will click on an image which will take them to a story page. When the user is in the story page, they can either go back to the grid to go to another story, or swipe left or right and they can get to another story that way. When a user goes to a story page from our grid, then the cell gets updated fine. BUT if a user swipes to another story NOT from the grid, that's where my issue is. In my story page I have logic that iterates through the grid cells, and finds the story you're currently on (the story you swiped to) and sees if it's in the grid, if it's in the grid, I update the cell's HasRead property. As such:
//find the cell in the grid (if exists)
ViewModels.GridCellViewModel cell = App.GridCells.Where(x => x.StoryId == App.Story.StoryId).FirstOrDefault();
if (cell != null)
{
cell.HasRead = true;
}
This works but... it doesn't trigger the value converter to change the property. What am I doing wrong? How can I get it so that I can update a property, and have it trigger my value converter?
My guess is that you're converter isn't triggering because you've technically bound to the viewcell itself, not the HasRead property. When you set HasRead, it will (assuming it's implementing INotifyPropertyChanged) fire a PropertyChangedEvent which would trigger the binding and call the value converter. However, since your binding is pointing to the viewcell itself, it will only trigger when that changes and ignore property changes elsewhere on that object.
A possible solution is to change the binding to point to HasRead (instead of '.'), and update your converter to expect the boolean directly rather than taking in a viewcell. This would be a better practice for a converter regardless.
That said, this is not really following the mvvm pattern that is generally recommended for xamarin forms apps. My suggestion would be to have a viewmodel that has a property that holds your story models (wrapped in their own StoryViewModels if you need logic there) and make sure the VM and Model classes implement INotifyPropertyChanged. Make the VM the datacontext for the page, bind the list to your listview source and your listview itemtemplate contents will bind to each individual story. Each story can have a HasRead property that binds to the background color via your updated converter.
Like this:
<ContentPage
x:Class="Stack_Stories.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Stack_Stories">
<ContentPage.BindingContext>
<local:StoriesViewModel x:Name="VM" />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:StoryReadBackgroundColorConverter x:Key="HasReadColor" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView ItemsSource="{Binding Stories}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid x:Name="StoryGrid" BackgroundColor="{Binding HasRead, Converter={StaticResource HasReadColor}}">
<Button Command="{Binding ToggleReadCommand}" Text="{Binding Name}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class StoryViewModel : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
private bool _hasRead = false;
public bool HasRead
{
get { return _hasRead; }
set { _hasRead = value; OnPropertyChanged(); }
}
private Command _toggleRead;
public Command ToggleReadCommand
{
get
{
return _toggleRead
?? (_toggleRead = new Command(() => HasRead = !HasRead));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StoriesViewModel : INotifyPropertyChanged
{
public StoriesViewModel()
{
// add sample stories
Stories.Add(new StoryViewModel { Name = "First Story" });
Stories.Add(new StoryViewModel { Name = "Second Story", HasRead=true });
}
private ObservableCollection<StoryViewModel> _stories = new ObservableCollection<StoryViewModel>();
public ObservableCollection<StoryViewModel> Stories
{
get { return _stories; }
set { _stories = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StoryReadBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool)) return null;
return (bool)value ? Color.FromRgba(0, 0, 0, 0.6) : Color.FromRgba(0, 0, 0, 0.0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Xamarin.Forms XAML Rounded Button

I'm trying to place a rounded Button in my Xamarin.Forms application, but I can't do it.
I read something about a custom controller to the button, but I didn't find any docs about rounded buttons in Xamarin.Forms.
Does anyone know how to do it? I'm just building an Android and iOS application.
You can use the BorderRadius property to create rounded corners on a Button
<Button Text="BlueButton"
BorderColor="Blue"
BorderRadius="5"
BorderWidth="2"/>
You need to use CornerRadius instead of BorderRadius because:
'Button.BorderRadius' is obsolete: 'BorderRadius is obsolete as of
2.5.0. Please use CornerRadius instead.'
Example: XButton.CornerRadius = 5;
If you are trying to have a Round button, use the below code. The height and width needs to be same and also proportionate to Border Radius.
<Button HorizontalOptions="Fill" VerticalOptions="Fill" Text="+">
<Button.WidthRequest>
<OnIdiom x:TypeArguments="x:Double" Phone="60" Tablet="80" />
</Button.WidthRequest>
<Button.HeightRequest>
<OnIdiom x:TypeArguments="x:Double" Phone="60" Tablet="80" />
</Button.HeightRequest>
<Button.BorderRadius>
<OnIdiom x:TypeArguments="x:Int32" Phone="30" Tablet="40" />
</Button.BorderRadius>
</Button>
You can ignore the different size for tablets if you are fine in having the same size on phone and tablets.
Note : This won't work on Windows. You will get a square button.
In Android, if your mainactivity is inheriting from AppCompact you will have to add this too.
The side of xaml the property is ConerRadius, Example:
<Button
CornerRadius="20"
Text="{i18n:Translate Cancel}"
Command="{Binding CancelarCommand}"
BackgroundColor="{StaticResource ButtonBackgroundColorbuscar}"
TextColor="White" />
If you want an image button you can use this ButtonCirclePlugin for Xamarin Forms.
Or an ImageCircle such as this ImageCirclePlugin for Xamarin Forms and add a TapGestureRecognizer.
There is no BorderRadius Property in the current version of Xamarin Forms. An alternative is the CornerRadius Property.
example:
<Button Text="Submit"
FontSize="Large"
TextColor="White"
BackgroundColor="Green"
CornerRadius="100"
To create rounded (circular) button try this...
<Button WidthRequest = 100,
HeightRequest = 100,
BorderRadius = 50 />
In general, WidthRequest=x, HeightRequest=x, BorderRadius=x/2
If you do not wish to drop down to using a renderer, and you don't mind not having a circular button on Windows Phone, you can use this code:
private const int BUTTON_BORDER_WIDTH = 1;
// Normal button height
//private const int BUTTON_HEIGHT = 44;
//private const int BUTTON_HEIGHT_WP = 72;
//private const int BUTTON_HALF_HEIGHT = 22;
//private const int BUTTON_HALF_HEIGHT_WP = 36;
//private const int BUTTON_WIDTH = 44;
//private const int BUTTON_WIDTH_WP = 72;
// Large button Height
private const int BUTTON_HEIGHT = 88;
private const int BUTTON_HEIGHT_WP = 144;
private const int BUTTON_HALF_HEIGHT = 44;
private const int BUTTON_HALF_HEIGHT_WP = 72;
private const int BUTTON_WIDTH = 88;
private const int BUTTON_WIDTH_WP = 144;
public RoundButtonPage()
{
var button = new Button
{
HorizontalOptions = LayoutOptions.Center,
BackgroundColor = Color.Accent,
BorderColor = Color.Black,
TextColor = Color.White,
BorderWidth = BUTTON_BORDER_WIDTH,
BorderRadius = Device.OnPlatform(BUTTON_HALF_HEIGHT, BUTTON_HALF_HEIGHT, BUTTON_HALF_HEIGHT_WP),
HeightRequest = Device.OnPlatform(BUTTON_HEIGHT, BUTTON_HEIGHT, BUTTON_HEIGHT_WP),
MinimumHeightRequest = Device.OnPlatform(BUTTON_HEIGHT, BUTTON_HEIGHT, BUTTON_HEIGHT_WP),
WidthRequest = Device.OnPlatform(BUTTON_WIDTH, BUTTON_WIDTH, BUTTON_WIDTH_WP),
MinimumWidthRequest = Device.OnPlatform(BUTTON_WIDTH, BUTTON_WIDTH, BUTTON_WIDTH_WP),
Text = "ClickMe"
};
var stack = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
Orientation = StackOrientation.Vertical,
Children = { button },
};
Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 5);
Content = stack;
}
It will make a button with rounded corners. To make a button totally round you just set the border radius to be half of the height.
The only thing to remember is that your button has to be large enough to contain the contents. You can see what I mean by commenting/uncommenting out the two constant sections at the top. The first set is good for a number or letter, and the second one is good for a phrase, like "ClickMe."
Again, this uses the native buttons of the platform and since WP doesn't support a border radius all buttons on WP will be rectangular so you'll need to use the technique that James shows in the CircularImage control.
Try this C# code
private const int BUTTON_BORDER_WIDTH = 1;
private const int BUTTON_HEIGHT = 65;
private const int BUTTON_HEIGHT_WP = 40;
private const int BUTTON_HALF_HEIGHT = 33;
private const int BUTTON_HALF_HEIGHT_WP = 20;
private const int BUTTON_WIDTH = 65;
private const int BUTTON_WIDTH_WP = 20;
var chkIm = new Button()
{
BackgroundColor = Color.Black,
HorizontalOptions = LayoutOptions.Center,
TextColor = Color.White,
BorderRadius = Device.OnPlatform(BUTTON_HALF_HEIGHT, BUTTON_HALF_HEIGHT, BUTTON_HALF_HEIGHT_WP),
HeightRequest = Device.OnPlatform(BUTTON_HEIGHT, BUTTON_HEIGHT, BUTTON_HEIGHT_WP),
MinimumHeightRequest = Device.OnPlatform(BUTTON_HEIGHT, BUTTON_HEIGHT, BUTTON_HEIGHT_WP),
WidthRequest = Device.OnPlatform(BUTTON_WIDTH, BUTTON_WIDTH, BUTTON_WIDTH_WP),
MinimumWidthRequest = Device.OnPlatform(BUTTON_WIDTH, BUTTON_WIDTH, BUTTON_WIDTH_WP),
};
Yo can use this style and converter to get General Circular Button.
Style in App.xaml
<Style x:Key="CircleButton" TargetType="{x:Type Button}">
<Setter Property="CornerRadius" Value="{Binding Source={RelativeSource Self}, Path=WidthRequest, Converter={StaticResource NumberDivideConverter}, ConverterParameter=2}" />
<Setter Property="HeightRequest" Value="{Binding Source={RelativeSource Self}, Path=WidthRequest}" />
</Style>
Don't forget to add below line to your head of App.xaml:
xmlns:converters="clr-namespace:AlarteInclinometer.Converters"
and
<converters:NumberDivideConverter x:Key="NumberDivideConverter" />
to in App.xaml
Your converter class which divides corner radius to WidthRequest / 2 is:
NumberDivideConverter.cs:
public class NumberDivideConverter : IValueConverter
{
/// <summary>
/// Converts binding property to calculated new property
/// </summary>
/// <param name="value">Source value</param>
/// <param name="targetType">Target type of to be calculated (return) value.</param>
/// <param name="parameter">Converter parameter.</param>
/// <param name="culture">Converter culture.</param>
/// <returns>New calculated value.</returns>
/// <exception cref="ArgumentNullException">If value is null, throws ArgumentNullException</exception>
/// <exception cref="ArgumentException">If value cannot be converted to a integer, throws ArgumentException</exception>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Check is value not null
if (value == null)
throw new ArgumentNullException($"Value is null");
// Check is value integer
if (int.TryParse(value.ToString(), out int intValue))
{
// If there is no parameter value, return same value
if (parameter == null)
return intValue;
// If there is converter parameter, divide number with it and return new result
if (int.TryParse(parameter.ToString(), out int param))
return intValue / param;
}
// Throw an error if value is not an integer
else
{
throw new ArgumentException($"The value must be a integer but it is a/an {value}");
}
return 0;
}
/// <summary>
/// Converts calculated property to binding property
/// </summary>
/// <param name="value">Source value</param>
/// <param name="targetType">Target type of to be calculated (return) value.</param>
/// <param name="parameter">Converter parameter.</param>
/// <param name="culture">Converter culture.</param>
/// <returns>New calculated value.</returns>
/// <exception cref="ArgumentNullException">If value is null, throws ArgumentNullException</exception>
/// <exception cref="ArgumentException">If value cannot be converted to a integer, throws ArgumentException</exception>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Check is value not null
if (value == null)
throw new ArgumentNullException($"Value is null");
// Check is value integer
if (int.TryParse(value.ToString(), out int intValue))
{
// If there is no parameter value, return same value
if (parameter == null)
return intValue;
// If there is converter parameter, divide number with it and return new result
if (int.TryParse(parameter.ToString(), out int param))
return intValue * param;
}
// Throw an error if value is not an integer
else
{
throw new ArgumentException($"The target must be a integer but it is a/an {value}");
}
return 0;
}
}
After that, you can use this style in buttons where you want like:
<Button Text="Circular" WidthRequest="120" Style="{StaticResource CircleButton}" />
This is the best solution I think :)

Multi-Line text Button Xamarin.Forms

theres is a way to set a multi-line text to a Xamarin.Forms Button??
I've tried Button.Text = "something \n xxjjjxx" But don't work.
A simple solution will use:
There is an excellent example on how to achieve this on Github. It is quite simple really. Just create your own control that inherits from ContentView and contains a grid.
[ContentProperty("Content")]
public class MultiLineButton : ContentView
{
public event EventHandler Clicked;
protected Grid ContentGrid;
protected ContentView ContentContainer;
protected Label TextContainer;
public String Text
{
get
{
return (String)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
OnPropertyChanged();
RaiseTextChanged();
}
}
public new View Content
{
get { return ContentContainer.Content; }
set
{
if (ContentGrid.Children.Contains(value))
return;
ContentContainer.Content = value;
}
}
public static BindableProperty TextProperty = BindableProperty.Create(
propertyName: "Text",
returnType: typeof(String),
declaringType: typeof(MultiLineButton),
defaultValue: null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: TextValueChanged);
private static void TextValueChanged(BindableObject bindable, object oldValue, object newValue)
{
((MultiLineButton)bindable).TextContainer.Text = (String)newValue;
}
public event EventHandler TextChanged;
private void RaiseTextChanged()
{
if (TextChanged != null)
TextChanged(this, EventArgs.Empty);
}
public MultiLineButton()
{
ContentGrid = new Grid
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand
};
ContentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
ContentGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
ContentContainer = new ContentView
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
TextContainer = new Label
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
ContentContainer.Content = TextContainer;
ContentGrid.Children.Add(ContentContainer);
var button = new Button
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
BackgroundColor = Color.FromHex("#01000000")
};
button.Clicked += (sender, e) => OnClicked();
ContentGrid.Children.Add(button);
base.Content = ContentGrid;
}
public void OnClicked()
{
if (Clicked != null)
Clicked(this, new EventArgs());
}
}
Then it can be used like this:
<local:MultiLineButton x:Name="AssessmentToolDetailButton"
WidthRequest="100" HeightRequest="60" BackgroundColor="Blue">
<StackLayout HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
<Label Text="Hello" TextColor="White" Font="16"/>
<Label Text="World" TextColor="White" Font="16"/>
</StackLayout>
</local:MultiLineButton>
You can also place an image in the button by setting its content.
In my example I modified Dans original code in order to make the text bindable. Just set the Text value instead of the Content like this:
<local:MultiLineButton Text="{Binding Description}" />
All credit goes to Danvanderboom for his example:
ConentButton by Danvanderboom
This is mainly a problem with iOS because Android will wrap the text
by default. I tried the solution provided by Kasper and it worked
however the buttons do not have rounded corners and the appearance is
not consistent with other buttons in my app.
A simple solution is to use a custom renderer (ButtonRenderer) to set the LineBreakMode to WordWrap. If you then set the width of the button in the Xaml you get words to appear on different lines.
iOS
public class WrappedButtonRenderer: ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
Control.TitleEdgeInsets = new UIEdgeInsets(4, 4, 4, 4);
Control.TitleLabel.LineBreakMode = UILineBreakMode.WordWrap;
Control.TitleLabel.TextAlignment = UITextAlignment.Center;
}
}
Android does not require a custom renderer because it wraps by default.
This is a known issue with Xamarin Forms.
I don't think I've seen two lined buttons often. You have two options that I think might work:
Create a Custom Renderer and Extend the respective Button Class to do more on each native platform. Might be a harder
Create a Xamarin.Forms Class that extends a View that can contains a StackLayout and smaller elements such as multi-line labels, then you can use a TapGestureRecognizer to use with your view and treat it like a button.
Expanding on fireydude's answer, I created a MultilineButton control and renderer for iOS so I could add text alignment. This uses the Xamarin.Forms.TextAlignment enum.
MultilineButton.cs
using Xamarin.Forms;
namespace APP_NAMESPACE.Controls
{
public class MultilineButton : Button
{
public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create(
propertyName: "HorizontalTextAlignment",
returnType: typeof(TextAlignment),
declaringType: typeof(MultilineButton),
defaultValue: TextAlignment.Start
);
public TextAlignment HorizontalTextAlignment
{
get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
set { SetValue(HorizontalTextAlignmentProperty, value); }
}
}
}
MultilineButtonRenderer.cs
using APP_NAMESPACE.Controls;
using APP_NAMESPACE.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MultilineButton), typeof(MultilineButtonRenderer))]
namespace APP_NAMESPACE.iOS.Renderers
{
public class MultilineButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control == null) { return; }
UIControlContentHorizontalAlignment horizontalAlignment;
UITextAlignment textAlignment;
// We have to use ButtonRenderer, so cast the Element to MultilineButton to get the HorizontalTextAlignment property
var button = (MultilineButton)Element;
if (button == null) { return; }
switch(button.HorizontalTextAlignment)
{
case TextAlignment.Center:
horizontalAlignment = UIControlContentHorizontalAlignment.Center;
textAlignment = UITextAlignment.Center;
break;
case TextAlignment.End:
horizontalAlignment = UIControlContentHorizontalAlignment.Right;
textAlignment = UITextAlignment.Right;
break;
default:
horizontalAlignment = UIControlContentHorizontalAlignment.Left;
textAlignment = UITextAlignment.Left;
break;
}
Control.HorizontalAlignment = horizontalAlignment;
Control.TitleLabel.LineBreakMode = UILineBreakMode.WordWrap;
Control.TitleLabel.TextAlignment = textAlignment;
}
}
}
Then use it within XAML:
<controls:MultilineButton Text="This Button is Centered!" HorizontalTextAlignment="Center" />

Resources