In Xamarin Forms I'm trying to create a xaml converter with properties.
This is to be used, for example, to show values from a list in different ways, based on a code behind property.
I based my code on this: https://stackoverflow.com/a/29869734.
Converter:
namespace App2.Converters
{
class MyConverter : IValueConverter
{
public int ConvParam { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return $"value: {value} - ConvParam: {ConvParam}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:conv="clr-namespace:App2.Converters"
x:Class="App2.MainPage"
x:Name="MainPageXaml">
<ContentPage.Resources>
<conv:MyConverter x:Key="cnv" ConvParam="{Binding Source={Reference MainPageXaml}, Path=PropParam}" />
<!--<conv:MyConverter x:Key="cnv" ConvParam="333" />-->
</ContentPage.Resources>
<StackLayout Orientation="Vertical">
<!-- Place new controls here -->
<Label Text="{Binding Source={Reference MainPageXaml}, Path=PropVal}" />
<Label Text="{Binding Source={Reference MainPageXaml}, Path=PropParam}" />
<Label Text="{Binding Source={Reference MainPageXaml}, Path=PropVal, Converter={StaticResource cnv}}" />
</StackLayout>
Code behind:
public partial class MainPage : ContentPage
{
public int PropVal { get; set; } = 111;
public int PropParam { get; set; } = 222;
public MainPage()
{
InitializeComponent();
}
}
The goal is to bind ConvParam of my converter to PropParam in code behind.
But if I use:
<conv:MyConverter x:Key="cnv" ConvParam="{Binding Source={Reference MainPageXaml}, Path=PropParam}" />
the error Position 10:39. No property, bindable property, or event found for 'ConvParam', or mismatching type between value and property is shown and the app doesn't compile.
The property ConvParam itself is recognized inside xaml: if I replace the above line with
<conv:MyConverter x:Key="cnv" ConvParam="333" />
everything works.
The binding expression I used ({Binding Source={Reference MainPageXaml}, Path=PropParam}) actually works, if used as source for the text property of a label:
<Label Text="{Binding Source={Reference MainPageXaml}, Path=PropParam}" />
But if I use it in Resources, It doesn't work.
Thanks to Julipan I could make it work!
As he pointed out, ConvParam must be a BindableProperty, so I modified my converter to inherit from BindableObject and defined ConvParam as BindableProperty.
Converter:
namespace App2.Converters
{
class MyConverter : BindableObject, IValueConverter
{
public static readonly BindableProperty ConvParamProperty = BindableProperty.Create(nameof(ConvParam), typeof(int), typeof(MyConverter));
public int ConvParam
{
get { return (int)GetValue(ConvParamProperty); }
set { SetValue(ConvParamProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return $"value: {value} - ConvParam: {ConvParam}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Related
I have two ImageButtons which should switch their images when clicked.
<ImageButton
Grid.Column="15"
Grid.Row="20"
Clicked="Clicked"
Source="{Binding Game[0], Converter={StaticResource StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="15"
Grid.Row="20"
Clicked="Clicked"
Source="{Binding Game[1], Converter={StaticResource StringToSourceConverter}}"
>
</ImageButton>
The Clicked method swaps the Sources in my Game array and activates INotifyPropertyChanged. It all works out fine. I would just like to know how to implement, that the image should only bind if the image is loaded. Because as you see in the following Images there is a short period where no image is displayed. It's short but it is annoying. The ImageSource is an EmbeddedResource.
I'm wondering if you are using Embedded images or just Local images ? Local images is recommended .
What's more , there is another way to implement this function(swap the image by clicking the button).
Add a bool property in viewmodel.
public class ViewModel : BaseViewModel
{
private bool _isOne;
public bool isOne {
get
{
return _isOne;
}
set
{
_isOne = value;
NotifyPropertyChanged();
}
}
}
Add two Converter for the two ImageButton .
public class FirstConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isOne = (bool)value;
return isOne ? "a.jpg" : "b.jpg";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return true;
}
}
public class SecondConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isOne = (bool)value;
return isOne ? "b.jpg" : "a.jpg";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return true;
}
}
Create binding on the ImageButton .
<ContentPage.Resources>
<ResourceDictionary>
<local:FirstConverter x:Key="first" />
<local:SecondConverter x:Key="second" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<ImageButton Clicked="ImageButton_Clicked" Source="{Binding isOne, Converter={StaticResource first}}"/>
<ImageButton Clicked="ImageButton_Clicked" Source="{Binding isOne, Converter={StaticResource second}}"/>
</StackLayout>
Change the property in Button Click event.
private void ImageButton_Clicked(object sender, EventArgs e)
{
model.isOne = !model.isOne;
}
I make a button and Color picker to xamarin.forms app, but I want to make it when I choose one color (ex. red) and close app, when i reopen it to see this red color automatic picked. I try to use this code but Preferences not working with Color:
public Color ColorPicker
{
get => Preferences.Get(nameof(ColorPicker), color.Red);
set
{
Preferences.Set(nameof(ColorPicker), value);
OnPropertyChanged(nameof(ColorPicker));
}
}
Can someone help me?
You can store Xamarin.Forms.Color as a string like this:
public string ColorPicker
{
get => Preferences.Get(nameof(ColorPicker), Color.Red.ToString());
set
{
Preferences.Set(nameof(ColorPicker), value);
OnPropertyChanged(nameof(ColorPicker));
}
}
Then you can bind it for instance to Label like this:
<Label TextColor="{Binding ColorPicker}" />
Make sure you set BindingContext in your view. You can read more about Binding here.
The Color.FromHex(string value) method needs a string type parameter. Try to convert the value to string type in the custom class.
Check the code:
Custom Converter class
public class StringToColorConverter : IValueConverter, INotifyPropertyChanged
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var color = Color.FromHex(value as string);
return color;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Page.xaml
<ContentPage.Resources>
<local:StringToColorConverter x:Key="myConverter" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout BackgroundColor="{Binding Color_string, Converter={StaticResource myConverter}}">
...
</StackLayout>
</ContentPage.Content>
I is not possible to make it like this. Because i need to use converter after to make string=>color. I am trying this:
public class StringToColor : IValueConverter
{
ColorTypeConverter converter = new ColorTypeConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//return value.ToString(); //not working
//return (Color)(converter.ConvertFromInvariantString(value.ToString())); //not working
return Color.FromHex(value.ToString()); //not working too
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
and add this convetor to xaml
<ContentPage.Resources>
<ResourceDictionary>
<local:StringToColor x:Key="str" />
</ResourceDictionary>
</ContentPage.Resources>
<Label Text="TEST" FontSize="Title" TextColor="{Binding ColorPicker,
Converter={StaticResource str}}"/>
but nothing happend...
Very simplified version of my code:
ViewModels:
public class ViewModel
{
public ObjectViewModel {get; set;}
}
public class ObjectViewModel
{
public string MyString {get; set;}
public bool MyStringIsValid {get; set;}
}
Xaml:
<Entry Text="{Binding ObjectViewModel.MyString}" TextChanged="Entry_TextChanged"/>
<Label Text="Valid!" IsVisible="{Binding ObjectViewModel.MyStringIsValid}"/>
In my code behind, I would like to be able to grab the bound property of Entry.Text by doing something like this:
void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
//Psuedocode
//ObjectViewModel ovm = (sender as Entry).Text.Binding.Source;
}
The reason I want to do this is to perform validation on "MyString" and change its "MyStringIsValid" property if necessary. My question is how do I do this and if it isn't possible, can you recommend a better approach?
In my real code I have used INotifyPropertyChanged to update the view according to viewModel changes, but I have omitted this for brevity.
Thanks!
I guess all you need is a Converter. I don't know why you need to maintain a property for changing the IsVisible based on the validation of a text. The below code works for me for a similar scenario which you mentioned.
<!--Declare the namespace at the top of the XAML-->
xmlns:c="clr-namespace:Demo.Helper"
<!--Register your Converter in the Resources-->
<ContentPage.Resources>
<ResourceDictionary>
<c:TextToBoolConverter x:Key="textToSpeechConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Entry x:Name="entry1" Text="{Binding ObjectViewModel.MyString}" />
<Label Text="Valid!" IsVisible="{Binding Text, Source={x:Reference entry1}, Converter={StaticResource textToSpeechConverter}}"/>
Below is the converter code which I tested.
public class TextToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string stringValue = value?.ToString();
if (!string.IsNullOrEmpty(stringValue))
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a property on my ViewModel that is an enum:
ViewModel:
public MyViewModel {
// Assume this is a DependancyProperty
public AvailableTabs SelectedTab { get; set; }
// Other bound properties
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
}
public enum AvailableTabs {
Tab1,
Tab2,
Tab3
}
I'd like to be able to bind SelectedIndex (or SelectedItem) of my TabControl to this property and have it correctly set the appropriate tab using a converter. Unfortunately, I'm a bit stuck. I know I can easily just use the SelectedIndex in my model, but I want the flexibility of re-ordering the tabs without breaking anything. I've given each TabItem a Tag property of the applicable enum value.
My XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab, Converter={StaticResource SomeConverter}}">
<TabItem Header="Tab 1" Tag="{x:Static local:AvailableTabs.Tab1}">
<TextBlock Text="{Binding Property1}" />
</TabItem>
<TabItem Header="Tab 2" Tag="{x:Static local:AvailableTabs.Tab2}">
<TextBlock Text="{Binding Property2}" />
</TabItem>
<TabItem Header="Tab 3" Tag="{x:Static local:AvailableTabs.Tab3}">
<TextBlock Text="{Binding Property3}" />
</TabItem>
</TabControl>
My problem is that I can't figure out how to get the TabControl into my converter so I can do:
// Set the SelectedIndex via the enum (Convert)
var selectedIndex = MyTabControl.Items.IndexOf(MyTabControl.Items.OfType<TabItem>().Single(t => (AvailableTabs) t.Tag == enumValue));
// Get the enum from the SelectedIndex (ConvertBack)
var enumValue = (AvailableTabs)((TabItem)MyTabControl.Items[selectedIndex]).Tag;
I'm afraid I might be overthinking it. I tried using a MultiValue converter without much luck. Any ideas?
You simply need a converter that casts the value to an index.
public class TabConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (int)value;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (AvailableTabs)value;
}
}
Instead of specifying the values in the XAML, I would bind ItemsSource to an array of values from your enum:
Code:
public AvailableTabs[] AvailableTabs => Enum.GetValues(typeof(AvailableTabs Enum)).Cast<AvailableTabs>().ToArray();
XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab}" ItemsSource="{Binding AvailableTabs}" />
The important thing is that the solution should work if I change the tab order in the TabControl
if this is not important then the elements in the enum type
and the views in the TabControl have to be in the same order
the conversion in this case is to cast the enum value to int value
I name the views in TabControl according to enum values
AppTab.ValidDates (enum value) corresponds to validDatesView (view name)
enum values
public enum AppTab
{
Parameters, ValidDates, ...
}
views in TabControl
<TabControl Name="myTabControl" SelectedIndex="{Binding SelectedTab, Converter={c:AppTabToIntConverter}}"
IsEnabled="{Binding IsGuiEnabled}">
<TabItem Header="{x:Static r:Resource.Parameters}">
<view:ParametersView x:Name="parametersView"/>
</TabItem>
<TabItem Header="{x:Static r:Resource.Dates}">
<view:ValidDatesView x:Name="validDatesView"/>
</TabItem>
fill up ViewNameIndexDictionary and IndexViewNameDictionary
Window_Loaded event is too late, AppTabToIntConverter runs before that
public static Dictionary<AppTab, int> ViewNameIndexDictionary { get; set; } = new Dictionary<AppTab, int>();
public static Dictionary<int, AppTab> IndexViewNameDictionary { get; set; } = new Dictionary<int, AppTab>()
private void Window_Initialized(object sender, EventArgs e)
{
var i = 0;
foreach (TabItem item in myTabControl.Items)
{
var tabContentName = ((FrameworkElement)item.Content).Name;
// Convert TabItem name "validDatesView" to "ValidDates"
var appTabString = tabContentName.FirstCharToUpper().CutLastNCharacter("View".Length);
var appTab = (AppTab)Enum.Parse(typeof(AppTab), appTabString);
ViewNameIndexDictionary.Add(appTab, i);
IndexViewNameDictionary.Add(i, appTab);
i++;
}
}
the converter
// The root tag must contain: xmlns:c="clr-namespace:LedgerCommander.ValueConverter"
class AppTabToIntConverter : BaseValueConverter<AppTabToIntConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is AppTab appTab))
throw new Exception("The type of value is not AppTab");
return MainWindowView.ViewNameIndexDictionary[appTab];
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is int tabIndex))
throw new Exception("The type of value is not int");
return MainWindowView.IndexViewNameDictionary[tabIndex];
}
}
the BaseValueConverter (thanks to AngelSix)
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter where T : class, new()
{
private static T Converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Converter ?? (Converter = new T());
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
I think I've followed the examples given in this post but my property is not changing when button are changed. Any suggestions on where I went wrong?
C# code for enum and class
public enum SystemTypes
{
TypeA,
TypeB
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
SystemTypes systemType = SystemTypes.TypeA;
public SystemTypes SystemType
{
get { return systemType; }
set { systemType = value; }
}
}
public class EnumToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
xaml
<Canvas>
<Canvas.Resources>
<local:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</Canvas.Resources>
<RadioButton x:Name="TypeARadioButton" Content="TypeA" Canvas.Left="10" Canvas.Top="10"
IsChecked="{Binding Path=SystemType, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:SystemTypes.TypeA}}" />
<RadioButton x:Name="TypeBRadioButton" Content="TypeB" Canvas.Left="10" Canvas.Top="31"
IsChecked="{Binding Path=SystemType, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:SystemTypes.TypeB}}" />
</Canvas>
You need to set Binding Mode to TwoWay, then in Converter implement method ConvertBack responsible for converting bool to SystemTypes, in settter of SystemType include
set { systemType = value; OnPropertyChanged(() => "SystemType");}
in order to fill property in that its value was changed.
OnPropertyChanged(() => "SystemType")
can work if you implement interface INotifyPropertyChanged. I cannot you whether you set DataContext, if you did not binding is not working. In order to rectify this after InitializeComponent() add
this.DataContext = this;