Is it possible to get a control’s background color from a property in ViewModel?
The purpose is to be able to change color of Border or Button based on user actions. Because I’m using the MVVM approach, ideally, I simply set the background color of my control through a property in my view model.
I tried the following but it didn’t work:
<Border
BackgroundColor="{Binding MyBorderBackgroundColor}">
<Label Text=“Hello World” />
</Border>
In my view model, I use a string property and set it to either a color name such as Red or a Hex value such as #FF0000. I have an Init() method that I call from OnAppearing() and set the value -- see below:
[ObservableProperty]
string myBorderBackgroundColor;
...
public void Init()
{
MyBorderBackgroundColor = "Red"; // Or Hex value => MyBorderBackgroundColor = "#FF0000";
}
The app simply ignores the color setting and defaults to page background. No error but just doesn't use the value set through the view model property.
Any suggestions?
Your backing property is a string, while it should be a Color. However, I do understand you would think this works, because in XAML you can simply add a string to it.
Background
To understand why that works in XAML but not code, we'll have to learn about TypeConverter. Since XAML can only contain strings, we have to find a way to convert that string into a type that is actually useable. With a TypeConverter we do just that.
On an object where we expect people to use a string in XAML but still expect the desired outcome we add this attribute, here it is for Color:
[TypeConverter(typeof(Converters.ColorTypeConverter))]
public class Color
{
/// The class
}
Here is the full implementation of ColorTypeConverter in case you're curious.
This means that when the XAML parser finds this property and the corresponding (string) value, it will invoke that TypeConverter first and that results into the concrete type, in our case Color, to be used.
When you do a binding like you're doing, the TypeConverter is not invoked, and thus we're now binding a string value to a property that expects a Color object and therefore: not working.
The Fix
To fix this, change the backing property to a Color and initialize it as such.
[ObservableProperty]
Color myBorderBackgroundColor;
// ...
public void Init()
{
MyBorderBackgroundColor = Colors.Red; // Or Hex value => MyBorderBackgroundColor = Color.FromArgb("#FF0000");
}
Your XAML contains a small error with no quotes around the binding. Not sure if that is in your actual code, but just to be sure. Make sure there is quotes around the value of BackgroundColor like so:
<Border
BackgroundColor="{Binding MyBorderBackgroundColor}">
<Label Text="Hello World" />
</Border>
First of all, you need to make sure that the binding is set up correctly using double quotes:
<Border
BackgroundColor="{Binding MyBorderBackgroundColor}">
<Label Text=“Hello World” />
</Border>
Then, I would recommend using the Color type in the ViewModel instead of a string since there is no implicit conversion between those types. That's how I am doing it in my own apps, too.
So, you could change your code to the following and define the color in a variety of different ways (the list is not final):
[ObservableProperty]
string myBorderBackgroundColor;
//...
public void Init()
{
// use hex value
MyBorderBackgroundColor = Color.FromArgb("#FF0000");
// parse string
MyBorderBackgroundColor = Color.Parse("Red");
// use RGB
MyBorderBackgroundColor = Color.FromRgb(255,0,0);
// use named color
MyBorderBackgroundColor = Colors.Red;
//...
}
You can find more information about Colors in the official documentation.
Related
I am trying to implement my first unity editor code to make a custom array property drawer with indented named entries and indented and dynamicaly resized value fields.
I am using the following simple git solution as base for my code, which allows to set the labels of an array in the inspector : HERE
replacing the example shown in the gitHub solution, I am working with this enum as my array element name container:
[System.Serializable]
public enum HealthNames
{
General,
Head,
Body,
RightArm,
LeftArm,
Rightleg,
leftLeg,
}
and sets it up on a array in a monobehaviour class :
[ LabeledArray( typeof( HealthNames ) ) ]
public int[] m_aHealth = new int[ Enum.GetNames( typeof( HealthNames ) ).Length ];
I have added EditorGUI.indentLevel++; and EditorGUI.indentLevel--; at the start and end of the try statement to indent the label of the array elements, making them stand out from the size property.
Going from there, I have searched ways to add an indent on the elements' value fields or remove it from the size property's value field. but have found no answer using EditorGUI
I also looked to be able to resize all value fields dynamicaly, but there again, no answer came using EditorGUI only. there is no way to use EditorStyle.AnyField.WordWrap = true; on a propertyfield. Passing the PropertyField to an IntField, using a EditorStyles.NumberField and setting having its wrodwrapping set to true beforehand has no effect.
I also have found a small number of EditorGUILayout from a few years ago, but which do not work since the solution is built from the ground with EditorGUI
In hope of your enlightment on the matter
If I understand you correctly you want the labels and the value fields indented.
I think it could be done like e.g.
private const int INDENT = 15;
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(rect, label, property);
var fieldsRect = new Rect(rect.x + INDENT, rect.y, rect.width - INDENT, rect.height);
try
{
var path = property.propertyPath;
int pos = int.Parse(path.Split('[').LastOrDefault().TrimEnd(']'));
EditorGUI.PropertyField(fieldRect, property, new GUIContent(ObjectNames.NicifyVariableName(((LabeledArrayAttribute)attribute).names[pos])), true);
}
catch
{
EditorGUI.PropertyField(fieldRect, property, label, true);
}
EditorGUI.EndProperty();
}
Model:
public class Question : INotifyPropertyChanged
{
private float? _answer;
public float? Answer
{
get => _answer;
set
{
_answer = value;
NotifyPropertyChanged();
}
}
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View model:
public class QuestionViewModel
{
private ObservableCollection<Question> _questions;
public ObservableCollection<Question> Questions
{
get => _questions;
set
{
if (_questions != value)
{
_questions = value;
}
}
}
}
XAML:
<ListView x:Name="ListViewQuestions" SelectionMode="Single" HasUnevenRows="True" HeightRequest="250" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Entry x:Name="EntryAnswer" Text="{Binding Answer,Mode=TwoWay}" Keyboard="Numeric" FontSize="Medium" VerticalOptions="End"
HorizontalOptions="FillAndExpand" Grid.Row="0" Grid.Column="1" >
<Entry.Behaviors>
<behaviors:EntryMaxValueBehavior MaxValue="{Binding MaxVal}" BindingContext="{Binding BindingContext, Source={x:Reference EntryAnswer}}" />
<behaviors:EntryMinValueBehavior MinValue="{Binding MinVal}" BindingContext="{Binding BindingContext, Source={x:Reference EntryAnswer}}" />
</Entry.Behaviors>
</Entry>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my page OnAppearing method, I set the ListViewQuestions like this:
var questions = await DataStore.GetQuestions(_inspection.Id);
var questionsViewModel = new QuestionViewModel { Questions = new ObservableCollection<Question>(questions) };
ListViewQuestions.ItemsSource = null;
ListViewQuestions.ItemsSource = questionsViewModel.Questions;
However, when values are entered into EntryAnswer, the setter in the Question model is not called, as I would expect. I thought that maybe this was because the BindingContext for the ListView needed to be set, so I set it like this:
ListViewQuestions.BindingContext = questionsViewModel;
However, the setter in the Question model is still not called. I also tried implementing INotifyPropertyChanged in the QuestionViewModel, but still no joy. I checked that the ObservableCollection in the View Model is set correctly, with actual data, and it is. Can anyone spot what might be going wrong here?
Edit 1: I also tried not setting the ItemSource, but only setting the ListViewQuestions.BindingContext to the view model, but then the ListView was not being populated with any data.
Here is how this works together.
The BindingContext is the object that will be the scope for whatever bindings that are in the page or it's children, unless you specify a different context for a certain child object, but let's not overcomplicate things for now.
This means, that when you have set the BindingContext, all Bindings will now start looking into the object referenced in the BindingContext. In your case, you set the BindingContext to an instance of QuestionViewModel.
You want your ListView, to get its items from the QuestionViewModel.Questions property. So, you set a binding like this:
<ListView x:Name="ListViewQuestions" ItemsSource="{Binding Questions}" ...>.
Questions needs to be a public property in the BindingContext, in our case QuestionViewModel. You got this right already.
Now, whenever you assign something to Questions this should also propagate to your ListView because of the binding.
Inside your ListView you are using a ViewCell, now note, that the scope does change here. Each cell represents an instance of an object inside the ItemsSource. In our case, each cell will hold a Question. You are using this:
<Entry x:Name="EntryAnswer" Text="{Binding Answer,Mode=TwoWay}" ...>
This means Answer needs to be a public property inside Question. You got this right already.
When you implement it like this, basically the only thing you do is fill your view model and assign that to the BindingContext of your page. If you are using an MVVM framework, this might happen automatically.
At some point, you might run into some trouble that the UI doesn't update and you will have to implement the INotifyPropertyChanged interface. Have a close look at what object doesn't update on screen and implement the interface on that object along with the needed plumbing, but from what I can see in this code, this isn't needed right now. And besides, you have implemented it the right way in your Question right now.
I hope this makes sense! It's a bit hard to wrap your head around the first time, but once you get the swing of it, it is pretty easy!
In your Answer Setter try:
set
{
float? temp = null;
if(float.TryParse(value, out temp)
{
_answer = temp;
NotifyPropertyChanged("Answer");
}
}
It seems like for this to work though your setter would have to be called, and you indicate that it is not, so I think it must be the min, max binding where this is kicking out the error. For now perhaps get rid of that and see if the setter will get called.
In WPF using a converter is typical and I think will work with the Xamarin as well. See this for a good example of how to implement IValueConverter.
So I've been wanting to bind the items of two pickers in Xamarin.Forms to my ViewModel. I have mainly used binding for textfields, where I just write something like:
<Label Text="{Binding CurrentDate}" />
and simply by setting the binding context, defining a property in the viewmodel
public System.DateTime CurrentDate{
get { return currentDate; }
set { currentDate = value; PropertyChanged(this, new PropertyChangedEventArgs("CurrentDate")); }
}
I am done binding. Now I have two pickers. The pickers represent a map/dictionary. Dictionary>. "A" is mapped to {"1","2"} and "B" is mapped to {"4","5"}. The first picker should show "A" and "B" as options. The second one should display the values associated with the chosen value from the first picker.
So there are two questions. 1) How do I bind a picker? 2) How do I bind a picker that has data that depends on another pickers selection?
I tried
<Picker Items="{Binding ItemsA}"></Picker>
With a matching property
public List<string> ItemsA
{
get { return itemsA;}
set { itemsA = value;PropertyChanged(this, new PropertyChangedEventArgs("ItemsA")); }
}
Am I missing out on something here? Help would be appreciated.
This functionality is unfortunately missing from the standard component, but relatively easy to add as detailed at https://forums.xamarin.com/discussion/30801/xamarin-forms-bindable-picker. Using this derived component you will then be able to bind ItemsSource and SelectedItem properties. It's also relatively easy to add WPF-like DisplayMemberPath and ValueMemberPath properties if required.
Looks like this functionality is built into the regular Xamarin.Forms Picker now. It is currently in the pre-release NuGet package for version 2.3.4-pre1, but should be in the stable 2.3.4+ versions once it is released. Instead of binding to Items you bind to ItemsSource and SelectedItem.
I am trying to relate the label text to the radio buttons value e.g. if radio is checked, then the label text is "x". If not, it's "y".
In my XML:
<RadioButton x:Name="radio1" Content="Option1" GroupName="Group1" IsChecked="{Binding BoolValue, Converter={StaticResource BooleanConverter}, ConverterParameter='true', Mode=TwoWay}" />
<RadioButton Content="Option2" GroupName="Group2" IsChecked="{Binding BoolValue, Converter={StaticResource BooleanConverter}, ConverterParameter='false', Mode=TwoWay}"/>
...
...
<Label Content="{Binding LabelText, UpdateSourceTrigger=PropertyChanged}" Width="70"/>
In the code:
(The _shape is bind to the radio button IsChecked;)
private bool _boolValue;
public bool BoolValue
{
get { return _boolValue; }
set
{
_boolValue= value;
PackLengthLabel = (_boolValue == true)? "x" : "y";
OnPropertyChanged("BoolValue");
}
}
And the label text property:
private string _labelText;
public string LabelText
{
get { return _labelText; }
set
{
_labelText = value;
OnPropertyChanged("LabelText");
}
}
The problem is that the changes don't affect the label text - it is the same all the time, no matter which checkbox is checked. The boolean value and the text value are changing (checked in the setters). I've also checked if the label is trying to get the _labelText from the getter but it doesn't. I also tried different binding modes, but the text was all the same.
The only way it affects the other controls is by binding directly to the other properties e.g.:
IsEnabled="{Binding IsChecked, ElementName=radio1}"
Edit1:
I can get it working in two ways:
setting the label content value in the View code behind, refering to the elements properties
using the code here: https://stackoverflow.com/a/23642108/3974198
But I'm still curious, why the simple getter and setter of the label text value didn't do the job.
Finally I got it. I had the property setters and getters in the View and the ViewModel. The problem was that the View had inherited from INotifyPropertyChanged, but the ViewModel didnt THOUGH in the ViewModel I could use the OnPropertyChanged, without getting any errors.
How do I set different localized strings in different visual states in WP7 using Blend without any code behind?
I can set different non-localized strings in different visual states (although it flickers). That works, but how about localized strings?
If I change the string using data binding in Blend, Blend just overrides the data binding in Base state and not the actual state where I'm recording.
EDIT:
This is how I localize my strings:
I have a resources file named AppPresources.resx. Then I would do this in code:
// setting localized button title
mainButton.Content = AppResources.MainButtonText;
Then I have a GlobalViewModelLocator from MVVM Light Toolkit with the following Property for Databinding.
private static AppResources _localizedStrings;
public AppResources LocalizedStrings
{
get
{
if (_localizedStrings == null)
{
_localizedStrings = new AppResources();
}
return _localizedStrings;
}
}
And in xaml file:
<Button x:Name="mainButton" Content="{Binding LocalizedStrings.MainButtonText, Mode=OneWay, Source={StaticResource Locator}}" ... />
What you need to do, is very close to what you're already doing. First, define a class named Resources.cs with following content
public class Resources
{
private static AppResources resources = new AppResources();
public AppResources LocalizedStrings
{
get
{
return resources;
}
}
}
This allows us to create a instance of your Resource File in XAML. To do this, open App.xaml and add following
<Application.Resources>
<local:Resources x:Key="Resources" />
</Application.Resources>
Now when you need to do bindings in your XAML, you do it like this:
<Button Content="{Binding LocalizedStrings.MainButtonText,
Source={StaticResource Resources}}" />
What you'll notice is that it doesn't work in Blend, yet. To make it work in Expression Blend,
add the following file: DesignTimeResources.xaml in the Properties Folder, and add following content
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNameSpace">
<local:Resources x:Key="Resources" />
</ResourceDictionary>
Now, you press F6 in Visual Studio to recompile, and voila, your localized strings are available in Expression Blend!
A real-world example from one of my projects:
AppResources.cs
DesignTimeResources.xaml
App.xaml