Switch not obeying style OnColor setting - xamarin.forms

I am having an issue where my Switch seems to only obey the Style set when it is Toggled On. I have set properties for its ThumbColor and OnColor for the two states where it is either Toggled On or Off as follows:
<Style x:Key="SwitchThemeMomentum" TargetType="Switch">
<Setter Property="IsToggled" Value="{Binding MomentumToggled, Mode=TwoWay}"/>
<Style.Triggers>
<DataTrigger TargetType="Switch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="True">
<Setter Property="ThumbColor" Value="{StaticResource Green}"/>
<Setter Property="OnColor" Value="#CCEDED" />
<Setter Property="IsToggled" Value="True" />
</DataTrigger>
<DataTrigger TargetType="Switch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="False">
<Setter Property="ThumbColor" Value="{StaticResource LightGray}" />
<Setter Property="OnColor" Value="Red" />
<Setter Property="IsToggled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
This is how I am calling it:
<Switch
x:Name="momentumSwitch"
HorizontalOptions="Start"
IsToggled="{Binding MomentumToggled, Mode=TwoWay}"
ScaleX="0.8"
ScaleY="0.8"
Style="{StaticResource SwitchThemeMomentum}"
VerticalOptions="Start" />
ThumbColor is properly obeyed, when I set it to Red I can see it changing, however the same cannot be said for the OnColor only when IsToggled is false; where am I going wrong?

Thanks to some googling, and writing a custom renderer:
Change color of UISwitch in "off" state
https://forums.xamarin.com/discussion/62450/how-can-i-customize-the-color-in-switch
and
https://blog.wislon.io/posts/2017/05/15/xamforms-change-switch-colour
The solution is as follows: Create new CustomSwitch control which will add a new Color property called OffColor as so:
public class CustomSwitch : Switch
{
public static readonly BindableProperty OffColorProperty =
BindableProperty.Create(nameof(CustomSwitch), typeof(Color), typeof(CustomSwitch));
public CustomSwitch()
{
OffColor = Color.Transparent;
}
public Color OffColor {
get => (Color) GetValue(OffColorProperty);
set => SetValue(OffColorProperty, value);
}
}
Follow this app by creating both an Android and iOS renderer:
Android:
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context): base(context) { }
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(Control == null)
{
return;
}
var element = (CustomSwitch) Element;
if(!element.IsToggled)
{
Control.TrackTintList = ColorStateList.ValueOf(element.OffColor.ToAndroid());
}
}
}
iOS:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(Control == null)
{
return;
}
var element = (CustomSwitch) Element;
var maskView = new UIView(Control.Frame)
{
BackgroundColor = element.IsToggled ? element.OnColor.ToUIColor() : element.OffColor.ToUIColor(),
ClipsToBounds = true
};
maskView.Layer.CornerRadius = Control.Frame.Height / 2;
Control.MaskView = maskView;
if(!element.IsToggled)
{
Control.TintColor = element.OffColor.ToUIColor();
Control.BackgroundColor = element.OffColor.ToUIColor();
} else
{
Control.TintColor = element.OnColor.ToUIColor();
Control.OnTintColor = element.OnColor.ToUIColor();
Control.BackgroundColor = element.OnColor.ToUIColor();
}
}
iOS is a bit more involved since you pretty much have to create the Android equivalent of the TrackTintList because for iOS all that TintColor does is just apply the color to the border of the switch. The mask is there to fill it in accordingly.
Finally, maintaining the spirit of using a Style resource dictionary, update the style as follows:
<Style x:Key="SwitchThemeMomentum" TargetType="customcontrols:CustomSwitch">
<Setter Property="IsToggled" Value="{Binding MomentumToggled, Mode=TwoWay}"/>
<Style.Triggers>
<DataTrigger TargetType="customcontrols:CustomSwitch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="True">
<Setter Property="ThumbColor" Value="{StaticResource Green}"/>
<Setter Property="OffColor" Value="Transparent" />
<Setter Property="OnColor" Value="{StaticResource SwitchOn}" />
<Setter Property="IsToggled" Value="True" />
</DataTrigger>
<DataTrigger TargetType="customcontrols:CustomSwitch" Binding="{Binding MomentumToggled, Mode=TwoWay}" Value="False">
<Setter Property="ThumbColor" Value="{StaticResource LightGray}" />
<Setter Property="OffColor" Value="{StaticResource Gray80}" />
<Setter Property="IsToggled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
Important: Note how we update the TargetType to be our CustomSwitch instead of a default Switch; this is because the default Switch control obviously lacks a OffColor property.

Related

Command RelativeSource not working in Xamarin

I have Binding Data problem in subpages. I have a Page that shows a list of products. When clicking on the product it will go to the Product Details subpage. Everything works fine:
Product.xaml
<RefreshView x:DataType="locals:DashboardViewModel" Padding="0" Command="{Binding LoadDashboardCommand}" IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout BindableLayout.ItemsSource="{Binding ProductNew}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame x:DataType="model:Product">
<StackLayout>
<Label FontSize="14" MaxLines="2" LineBreakMode="TailTruncation" HeightRequest="40" Text="{Binding Name}"/>
</StackLayout>
<Frame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type locals:DashboardViewModel}}, Path=ProductTappedView}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
Product.xaml.cs
DashboardViewModel dashboardViewModel;
public Product()
{
BindingContext = dashboardViewModel = new DashboardViewModel(Navigation);
dashboardViewModel.OnAppearing();
}
DashboardViewModel.cs
public class DashboardViewModel : BaseDashboardViewModel
{
public Command LoadDashboardCommand { get; }
public Command ProductTappedView { get; }
public ObservableCollection<Product> ProductNew { get; }
public DashboardViewModel(INavigation _navigation)
{
LoadDashboardCommand = new Command(async () => await ExecuteLoadDashboardCommand());
ProductNew = new ObservableCollection<Product>();
ProductTappedView = new Command<Product>(OnViewDetailProduct);
Navigation = _navigation;
}
private async void OnViewDetailProduct(Product detailProduct)
{
await Navigation.PushAsync(new ProductDetail(detailProduct));
}
............
}
Next in my Productdetail page show product details. I have Read more. When clicked it will redirect to another subpage: ContentDetailProd.xaml
ProductDetail.xaml
<ContentPage.BindingContext>
<locals:ViewDetailProductViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="15">
<Label Text="{Binding ProductNews.Name}" FontFamily="RobotoMedium" FontSize="18" TextTransform="Uppercase"/>
<Label Text="Read more"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Source={RelativeSource AncestorType={x:Type locals:ViewDetailProductViewModel}}, Path=ContentProductTappedView}" CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>
</StackLayout>
</ContentPage.Content>
ViewDetailProductViewModel.cs
public class ViewDetailProductViewModel : BaseDashboardViewModel
{
public Command ContentProductTappedView { get;}
public ViewDetailProductViewModel()
{
ProductNews = new Product();
ContentProductTappedView = new Command<Product>(OnViewContentDetailProduct);
}
private async void OnViewContentDetailProduct(Product detailProduct)
{
await Navigation.PushAsync(new ContentDetailProd(detailProduct));
}
}
ContentDetailProd.xaml.cs
public ContentDetailProd(Product detailProduct)
{
InitializeComponent();
//Load Readmore
}
However when I Debug it actually doesn't run on the event: ContentProductTappedView and as a result it doesn't redirect to the ContentDetailProd.xaml page. I've tried everything but still can't solve the problem.
Looking forward to everyone's help. Thanks
If you want to pass the data through a Page Constructor, you could refer to the link below. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical#passing-data-when-navigating
I changed like this based on the documentation #Wendy Zang - MSFT provided. Everything looks good.
ProductDetail.xaml
<StackLayout x:Name="_readmore" Padding="15">
<Label Text="{Binding ProductNews.Name}" FontFamily="RobotoMedium" FontSize="18" TextTransform="Uppercase"/>
<Label x:Name="_content" MaxLines="2" LineBreakMode="TailTruncation" Text="{Binding ProductNews.Contents}" FontFamily="RobotoMedium" FontSize="18" TextTransform="Uppercase"/>
<Label Text="Read more"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="_readmore_Tapped" />
</StackLayout.GestureRecognizers>
</StackLayout>
ProductDetail.xaml.cs
private async void _readmore_Tapped(object sender, EventArgs e)
{
var contentget = _content.Text;
var detailProduct = new Product
{
Contents = contentget,
};
var detailContentPage = new ContentDetailProd(detailProduct);
detailContentPage.BindingContext = detailProduct;
await Navigation.PushAsync(detailContentPage);
}
ContentDetailProd.xaml.cs
public ContentDetailProd(Product detailProduct)
{
InitializeComponent();
//Load Readmore
if (detailProduct != null)
{
_contentmore.Text = detailProduct.Contents;
}
}

radio button not pre-selecting on screen loading

I feel this is weird. i can't get the Radio button to pre-select a saved value and it's driving me mad. I have this xaml:
<StackLayout Orientation="Horizontal" RadioButtonGroup.GroupName="Parities"
RadioButtonGroup.SelectedValue="{Binding Parity}">
<RadioButton Value="1" Content="Income" />
<RadioButton Value="-1" Content="Expense" />
<RadioButton Value="0" Content="Neutral" />
</StackLayout>
Furthermore, even if I replace SelectedValue with a hard coded literal value "1" (for Income), the radio button still show up blank. The only way that works is by setting IsChecked on each of the 3 options to have the them pre-selected.
What am I missing?
Based on your code ,I created a simple demo, but I couldn't reproduce this problem. It just works properly.
You can refer to the following code:
MyPage.xaml
<ContentPage.BindingContext>
<radiobuttondemos:MyViewModel></radiobuttondemos:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<StackLayout Orientation="Horizontal" RadioButtonGroup.GroupName="{Binding GroupName}"
RadioButtonGroup.SelectedValue="{Binding Parity}">
<RadioButton Value="1" Content="Income" />
<RadioButton Value="-1" Content="Expense" />
<RadioButton Value="0" Content="Neutral" />
</StackLayout>
</StackLayout>
The MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
string groupName;
object parity;
public object Parity
{
get => parity;
set
{
parity = value;
OnPropertyChanged(nameof(Parity));
}
}
public MyViewModel () {
GroupName = "Parities";
Parity = "1";
}
public string GroupName
{
get => groupName;
set
{
groupName = value;
OnPropertyChanged(nameof(GroupName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note:
In the constructor of MyViewModel, I initialize the value of variable Parity as follows:
Parity = "1";
And if we initialize a value as follows, the UI will not pre-select the saved value :
Parity = 1;

Focus and Unfocus events are triggering multiple times for datepicker and timepicker in xamarin forms

I'm using a date picker and time picker in my application.
And I implemented focus and unfocus events for date picker and time picker.
Validating the time for the maximum date by using focus and unfocus events.
Those focus and unfocus events are triggering multiple times.
Need to trigger once when we hit the date picker or time picker.
XAML:
<controls:DatePickerCustom x:Name="DatePicker" FontSize="14" HeightRequest="35" Date="{Binding CustomDate,Mode=TwoWay}" MaximumDate="{Binding MaximumDate}" FontFamily="Segoe UI" Format="D" HorizontalOptions="FillAndExpand" Unfocused="DatePickerUnfocused" TranslationX="-4">
<DatePicker.Triggers>
<DataTrigger TargetType="DatePicker" Binding="{Binding IsCustomSelected}" Value="true">
<Setter Property="TextColor" Value="{DynamicResource HeadingTextColor}" />
<Setter Property="TextColor" Value="{DynamicResource HeadingTextColor}" />
</DataTrigger>
</DatePicker.Triggers>
</controls:DatePickerCustom>
<controls:TimePickerCustom x:Name="TimePicker" FontSize="14" HeightRequest="35" Time="{Binding CustomTime,Mode=TwoWay}" FontFamily="Segoe UI" Focused="TimePickerFocused" Unfocused="TimePickerUnfocused" >
<TimePicker.Triggers>
<DataTrigger TargetType="TimePicker" Binding="{Binding IsCustomSelected}" Value="true">
<Setter Property="TextColor" Value="{DynamicResource HeadingTextColor}" />
</DataTrigger>
</TimePicker.Triggers>
</controls:TimePickerCustom>
CS:
protected void TimePickerUnfocused(object sender, FocusEventArgs e)
{
TimePicker picker = sender as TimePicker;
if (DatePicker.Date.ToString("dd/MM/yyyy") == DatePicker.MaximumDate.ToString("dd/MM/yyyy"))
{
//var t1 = Convert.ToDateTime(DateTime.Now).ToString("HH:mm");
//var t2 = Convert.ToDateTime(picker.Time.ToString()).ToString("HH:mm");
var result = DateTime.Compare(Convert.ToDateTime(Convert.ToDateTime(DateTime.Now).ToString("HH:mm")), Convert.ToDateTime(Convert.ToDateTime(picker.Time.ToString()).ToString("HH:mm")));
if (result > 0)
{
if (endValue.ToString() != "00:00:00")
{
TimePicker.Time = endValue;
}
else
{
endValue = picker.Time;
TimePicker.Time = endValue;
}
}
else
{
DisplayAlert("Alert!", "Please select the valid time", "OK");
TimePicker.Time = TimeSpan.Parse(DateTime.Now.AddHours(-1).ToString("HH:mm"));
}
}
}
protected void DatePickerUnfocused(object sender, FocusEventArgs e)
{
if (DatePicker.Date.ToString("dd/MM/yyyy HH:mm") == DatePicker.MaximumDate.ToString("dd/MM/yyyy HH:mm"))
{
if(endValue.ToString() != "00:00:00")
{
TimePicker.Time = endValue;
}
else if (startValue.ToString() != "00:00:00")
{
TimePicker.Time = startValue;
}
else
{
startValue= TimeSpan.Parse(DateTime.Now.AddHours(-1).ToString("HH:mm"));
TimePicker.Time = startValue;
}
}
}
protected void TimePickerFocused(object sender, FocusEventArgs e)
{
if (DatePicker.Date.ToString("dd/MM/yyyy HH:mm") == DatePicker.MaximumDate.ToString("dd/MM/yyyy HH:mm") && startValue != null)
{
TimePicker.Time = startValue;
}
}
And MinimumDate and MaximumDate attributes of the date picker are not working in the Android 10 version real device. same attributes are working in the emulator.
Possible work-around:
Have a boolean that you set based on whether a specific picker is focused or not. Ignore any duplicate focus messages.
Example for DatePicker:
private bool _datePickerFocused = false;
protected void DatePickerFocused(...)
{
// Skip if already focused.
if (_datePickerFocused)
return;
_datePickerFocused = true;
...
}
protected void DatePickerUnfocused(...)
{
// Skip if not focused.
if (!_datePickerFocused)
return;
_datePickerFocused = false;
...
}

set custom font for alert messages for both android and ios using xamarin.forms

I am using xamarin forms for my project. I need to set custom font family(Montserrat-Medium.ttf) for the following alert messages for both android and iOS.
var ans = await App.Current.MainPage.DisplayAlert("", "Are you want to leave the application?", "Yes", "No");
Anyone help me to resolve this issue.
This is not possible in shared Xamarin project, but you can simply create a custom alert, and do what ever you want with it, like this:
Add your font to the solution
Add the font somewhere in shared project and then right click on it and choose Properties.
In Properties, choose Embedded resource as a Build Action.
Add this line
[assembly: ExportFont("Montserrat-Medium.ttf", Alias = "MyMontserrat")]
to the AssemblyInfo.cs
Create custom popup view like this
<?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="MyApp.MainPage">
<AbsoluteLayout Padding="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Button Text="Display Alert!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Clicked="OnButtonClicked" />
</StackLayout>
<!--Here's the custom popupView-->
<ContentView x:Name="popupView" BackgroundColor="#C0808080" Padding="10, 0" IsVisible="false" AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="All">
<StackLayout VerticalOptions="Center" HorizontalOptions="Center">
<StackLayout Orientation="Vertical" HeightRequest="150" WidthRequest="200" BackgroundColor="White">
<Label x:Name="myLabel" FontSize="16" FontFamily="MyMontserrat" Text="Are you want to leave the application?" />
<Button Text="Yes" FontSize="16" FontFamily="MyMontserrat" BackgroundColor="White" TextTransform="None" Clicked="Yes_Clicked" />
<Button Text="No" FontSize="16" FontFamily="MyMontserrat" BackgroundColor="White" TextTransform="None" Clicked="No_Clicked" />
</StackLayout>
</StackLayout>
</ContentView>
</AbsoluteLayout>
</ContentPage>
Use your popup in .cs
void OnButtonClicked(object sender, EventArgs args)
{
//This will show the pop up
popupView.IsVisible = true;
}
private void Yes_Clicked(object sender, EventArgs e)
{
popupView.IsVisible = false;
//Your stuff here
}
private void No_Clicked(object sender, EventArgs e)
{
popupView.IsVisible = false;
//Your stuff here
}
You could use DependencyService to call the native dialog,and define the font in the native dialog.
Here is a simple sample for Android (ios is similar to this,you could refer to this):
define a interface in your forms project:
public interface ICustomAlert
{
void Show(string message);
}
then in your Android project:
public class CustomAlert : ICustomAlert
{
public void Show(string message)
{
AndroidX.AppCompat.App.AlertDialog.Builder alertdialogbuilder = new AndroidX.AppCompat.App.AlertDialog.Builder(MainActivity.Instance);
alertdialogbuilder.SetMessage(message);
alertdialogbuilder.SetPositiveButton("Yes",OkEvent);
alertdialogbuilder.SetNeutralButton("No", NoEvent);
AndroidX.AppCompat.App.AlertDialog alertdialog = alertdialogbuilder.Create();
alertdialog.Show();
TextView textView = alertdialog.FindViewById<TextView>(Android.Resource.Id.Message);
textView.SetTextColor(Android.Graphics.Color.Red);
Typeface face = Typeface.CreateFromAsset(MainActivity.Instance.Assets, "fonts/Montserrat-Medium.tff");
textView.SetTypeface(face, TypefaceStyle.Normal);
}
private void NoEvent(object sender, DialogClickEventArgs e)
{
}
private void OkEvent(object sender, DialogClickEventArgs e)
{
}
}
then you could call like
DependencyService.Get<ICustomAlert>().Show("your message");

Change image in code with Xamarin.Forms

I've tried a few different methods based on the official documentation and from debugging my if/else condition is firing correctly, but I can't seem to get my image to change.
As you can see, I've tried two methods here but my understanding is that ImageSource.FromFile isn't needed because of implicit conversion.
Here's the XAML:
<TableView Intent="Settings">
<TableRoot>
<TableSection Title="Getting Started">
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="10" >
<Image Source="bulb.png" x:Name="imgBulb" />
<Switch x:Name="bulbSwitch" IsToggled="false" Toggled="bulbSwitchToggled" HorizontalOptions="EndAndExpand" VerticalOptions="Center" />
</StackLayout>
</ViewCell>
</TableSection>
<TableSection Title="Time to slide....">
<ViewCell>
<StackLayout Orientation="Vertical" Padding="10" >
<Slider Maximum="100" Minimum="0" Value="50" ValueChanged="sliderChanged" ></Slider>
<Label x:Name="lblSliderOutput" HorizontalOptions="CenterAndExpand" Text="Placeholder" />
</StackLayout>
</ViewCell>
</TableSection>
</TableRoot>
</TableView>
And the code behind:
public partial class TableViewsPage : ContentPage
{
public TableViewsPage ()
{
InitializeComponent ();
lblSliderOutput.Text = "50";
}
public void sliderChanged(object sender, ValueChangedEventArgs args) {
var simpleValue = Convert.ToInt32 (args.NewValue);
lblSliderOutput.Text = simpleValue.ToString();
}
public void bulbSwitchToggled(object sender, ToggledEventArgs args) {
if (imgBulb.Source == ImageSource.FromFile("bulb.png"))
{
imgBulb.Source = ImageSource.FromFile ("biggerBulb.png");
Debug.WriteLine ("imgbulb = bulb.png");
}
else
{
imgBulb.Source = "bulb.png";
Debug.WriteLine ("imgbulb = biggerBulb.png");
}
}
}

Resources