Problem changing background color in Xamarin form from View model - xamarin.forms

Is there something different about changing the text value of an Entry, and the background color of an entry?
Using the code here...https://github.com/jamesmontemagno/app-compass. I can add an entry
<Entry Placeholder="Degree to follow:"
Text="{Binding Degree}" x:Name="Degree"
Keyboard="Numeric"
Grid.Row="0" BackgroundColor="Green"/>
In the ViewModel define a property
public string _degree;
public string Degree
{
get => _degree;
set => SetProperty(ref _degree, value);
}
I can change the Entry from the View Model..."_degree = "3333";"
I can get it's value..."HeadingDisplay = $"Heading: {Heading.ToString()}" + " Degree: " + _degree;"
Why then, can I not change the background color of one of the entry, label, or page the same way?
I added the binding here...
<Label Grid.Row="2"
Text="{Binding HeadingDisplay}"
x:Name="LabelInfo"
TextColor="{Binding BGColor, Mode=OneWay}"
VerticalOptions="Center"
HorizontalOptions="Center" />
And then tried to change it in the ViewModel, I have tried a few different things...
public Color BGColor { get; set; }
//public Color BgColor
//{
// get => _bgcolor;
// set => SetProperty(ref _bgcolor, value);
//}
//_bgcolor = "#00FF00;
//_bgcolor = Xamarin.Forms.Color.Red;
BGColor = Color.Red;
The background color never changes. Do I need a converter, does the page need to be re-displayed, or what?
As per comments below, I made some changes, I am now binding the label background, instead of the text background
<Label Grid.Row="2"
Text="{Binding HeadingDisplay}"
x:Name="LabelInfo" BackgroundColor="{Binding=BGCOLOR Mode=OneWay}"
VerticalOptions="Center"
HorizontalOptions="Center" />
In the view model, this is the code now
public Color _bgcolor;
public Color BGCOLOR
{
get => _bgcolor;
set => SetProperty(ref _bgcolor, value);
}
//set the color here
_bgcolor = Color.Red;
Still no color change. I am unclear on the comment about two color types. Can you point me to link, or give example? I have also tried sending FromHex.

Here is a sample how to modify Color and Text of Control , you can refer to check where problem is .
Create a ViewModel class :
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Color color { set; get; }
public Color Color
{
set
{
if (color != value)
{
color = value;
OnPropertyChanged("Color");
}
}
get
{
return color;
}
}
private string textValue { set; get; }
public string TextValue
{
set
{
if (textValue != value)
{
textValue = value;
OnPropertyChanged("TextValue");
}
}
get { return textValue; }
}
public ViewModel()
{
color = Color.Red;
textValue = "Default Text";
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The Xaml code :
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding TextValue}" TextColor="{Binding Color}"
HorizontalOptions="Center"/>
<Entry Text="{Binding TextValue}" TextColor="{Binding Color}"/>
<Button Text="Change" Clicked="Button_Clicked"/>
</StackLayout>
In ContenPage can modify the value as follow :
public partial class MainPage : ContentPage
{
ViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = new ViewModel();
BindingContext = viewModel;
}
private void Button_Clicked(object sender, EventArgs e)
{
viewModel.TextValue = "New Value";
viewModel.Color = Color.Green;
}
}
The effect :

Related

Xamarin Forms: How can i correctly bind data from two view models to a single view?

This is the short code for testing purpose. The problem is that the UI is not displaying the Text from the Label which is binded with ViewModelB. In debugging when I hover the mouse in xaml over the Text from the Label I see the right binding data is there, but the UI simply won't display. With ViewModelA there are no problems.
In XAML:
<StackLayout>
<StackLayout>
<StackLayout.BindingContext>
<testbinding:ViewModelA/>
</StackLayout.BindingContext>
<Button Command ="{Binding Get}"/>
</StackLayout>
<StackLayout>
<StackLayout.BindingContext>
<testbinding:ViewModelB/>
</StackLayout.BindingContext>
<Label Text="{Binding Metadata}"/>
</StackLayout>
ViewModelA: where BaseViewModel is a INotifyPropertyChanged interface
public ViewModelA:BaseViewModel
{
public ViewModelA()
{
Get = new Command(SendText);
vmB = new ViewModelB();
}
ViewModelB vmB;
public ICommand Get { get; }
private void SendText()
{
string data = "someText";
vmB.GetMetadata(data);
}
}
ViewModelB is like this:
class ViewModelB:BaseViewModel
{
private string _metadata = string.Empty;
public string Metadata
{
get { return _metadata; }
set
{
_metadata = value;
OnPropertyChanged();
}
}
GetMetadata()
{
Metadata = "Some text";
}
}
In ViewModelA there are more properties which I need and in ViewModelB is just one property which gets data from a function. I could make just one ViewModel from both of them which works fine, but I'm trying to keep them smaller and organized. I already tried so many scenarios and is getting really frustrating.
Thanks for helping.
In the second StackLayout in your xaml file you're not binding it's BindingContext property to the ViewModelB instance from ViewModelA but instead you are creating a new one.
Here's a working solution for you:
public class ViewModelA : BaseViewModel
{
public ViewModelB ViewModelB { get; }
public ICommand GetMetadataCommand { get; }
public ViewModelA()
{
ViewModelB = new ViewModelB();
GetMetadataCommand = new Command((_) => GetMetadata());
}
private void GetMetadata()
{
string data = "someText";
ViewModelB.GetMetadata(data);
}
}
public class ViewModelB : BaseViewModel
{
private string _metadata;
public string Metadata
{
get { return _metadata; }
set
{
_metadata = value;
OnPropertyChanged();
}
}
public void GetMetadata(string data)
{
Metadata = data;
}
}
XAMl:
<StackLayout>
<StackLayout x:Name="StackLayout1">
<StackLayout.BindingContext>
<local:ViewModelA />
</StackLayout.BindingContext>
<Button Command ="{Binding GetMetadataCommand}"/>
</StackLayout>
<StackLayout BindingContext="{Binding Source={x:Reference StackLayout1}, Path=BindingContext.ViewModelB}">
<Label Text="{Binding Metadata}" />
</StackLayout>
</StackLayout>

How to access the BindingContext of custom control in Xamarin.Forms

I have a CollectionView with ItemsSource set to ObservableCollection of type Employee.
The ItemTemplate of the CollectionView is a CustomControl that has 1 BindableProperty of Type Employee
MainPage.xaml:
<CollectionView ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
<CollectionView.ItemTemplate>
<DataTemplate>
<controls:CustomControl Employee="{Binding .}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The CustomControl has an image (checked image to indicate selection).
CustomControl.xaml:
<Frame HasShadow="True"
BackgroundColor="Blue">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Image Source="check.png" />
</StackLayout>
</Frame>
CustomControl.xaml.cs:
public partial class CustomControl : ContentView
{
public CustomControl()
{
InitializeComponent();
}
public static BindableProperty EmployeeProperty = BindableProperty.Create(
propertyName: nameof(Employee),
returnType: typeof(Employee),
declaringType: typeof(CustomControl),
defaultValue: default(Employee),
defaultBindingMode: BindingMode.OneWay);
public Employee Employee
{
get
{
return (Employee)GetValue(EmployeeProperty);
}
set
{
SetValue(EmployeeProperty, value);
}
}
}
Model (Employee):
public class Employee: INotifyPropertyChanged
{
private int name;
public int Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private int isSelected;
public int IsSelected
{
get
{
return isSelected;
}
set
{
isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
#region PropertyChanged
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I am trying to create simple animation (FadeIn/FadeOut) for the checked image in the CustomControl so when an item is selected the image will fade in, and when unselected it will fade out. I could use IsVisible and set it to true/false but that's ugly.
My idea was to listen to PropertyChanged event of the Employee (which supposed to be the context of my CustomControl), and when the property IsSelected is modified, I will start the animation to show/hide the image. something like this
public CustomControl()
{
InitializeComponent();
(this.BindingContext as Employee).PropertyChanged += CustomControl_PropertyChanged;
}
private void CustomControl_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Employee.IsSelected))
{
//do animation to show/hide image
}
}
But couldn't access the Context of my CustomControl!
When I declare the binding in MainPage.xaml I am passing a single Emplyee objet as BindingContext (that dot, right?):
<controls:CustomControl Employee="{Binding .}" />
but after the CustomControl is initializd, the BindingContext is still null!
public CustomControl()
{
InitializeComponent();
var context = this.BindingContext; //this is null
}
How can I observe the changes on the IsSelected property of the Employee object from my CustomControl?
In your custom control override the OnBindingContextChanged method, inside of that method you should be able to access the binding context that is set for your view.
Ex:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var context = this.BindingContext as Employee
}

Change Background Color of ListView Selected Item in Xamarin

I created a Master-Detail type project in Xamarin. When I selected an item from the Master page the background color is orange by default. How can I change this to a color of my choosing?
You can bind BackgroundColor for ContentView of ViewCell , then use ViewModel and ItemTapped method of ListView to modify the selected item background color .
For example , the xaml code as follow:
<ListView x:Name="ListViewMenu"
HasUnevenRows="True" ItemTapped="ListViewMenu_ItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<Grid Padding="10"
BackgroundColor="{Binding SelectedBackgroundColor}">
<Label Text="{Binding Title}" d:Text="{Binding .}" FontSize="20"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then in HomeMenuItem model add SelectedBackgroundColor property :
public enum MenuItemType
{
Browse,
About
}
public class HomeMenuItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MenuItemType Id { get; set; }
public string Title { get; set; }
private Color selectedBackgroundColor;
public Color SelectedBackgroundColor
{
set
{
if (selectedBackgroundColor != value)
{
selectedBackgroundColor = value;
OnPropertyChanged("SelectedBackgroundColor");
}
}
get
{
return selectedBackgroundColor;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in MenuPage modify ItemSource as follow:
public partial class MenuPage : ContentPage
{
MainPage RootPage { get => Application.Current.MainPage as MainPage; }
List<HomeMenuItem> menuItems;
List<HomeMenuItem> tmpItems; // add a tmp list to remove setted backgroud color
public MenuPage()
{
InitializeComponent();
tmpItems = new List<HomeMenuItem>();
menuItems = new List<HomeMenuItem>
{
new HomeMenuItem {Id = MenuItemType.Browse, Title="Browse" },
new HomeMenuItem {Id = MenuItemType.About, Title="About" }
};
menuItems[0].SelectedBackgroundColor = Color.Red; // default set the first item be selected, you can modify as your wants
tmpItems.Add(menuItems[0]); // add the selected item (default is the first)
ListViewMenu.ItemsSource = menuItems;
ListViewMenu.SelectedItem = menuItems[0];
ListViewMenu.ItemSelected += async (sender, e) =>
{
if (e.SelectedItem == null)
return;
var id = (int)((HomeMenuItem)e.SelectedItem).Id;
await RootPage.NavigateFromMenu(id);
};
}
private void ListViewMenu_ItemTapped(object sender, ItemTappedEventArgs e)
{
menuItems[e.ItemIndex].SelectedBackgroundColor = Color.Red;
tmpItems[0].SelectedBackgroundColor = Color.Transparent;
tmpItems[0] = menuItems[e.ItemIndex];
}
}
The effect :
This problem is specific to Android. In the Android project add this line to Resources\values\styles.xml inside the <style> tag:
<item name="android:colorActivatedHighlight">#00FFFFFF</item>

Xamarin Forms how to add behaviors to custom control

I have created a custom control,which is a ContentView with a Label and an Entry
The xaml of the custom controls looks like this:
<Label Text="{Binding Source={x:Reference ValidationControl}, Path=Caption}"/>
<Entry Text="{Binding Source={x:Reference ValidationControl}, Path=Value, Mode=TwoWay}" />
The code behind of the custom control looks like this:
public static readonly BindableProperty CaptionProperty = BindableProperty.Create(
nameof(Caption), typeof(string), typeof(ValidationEntry), default(string));
public string Caption
{
get => (string)GetValue(CaptionProperty);
set => SetValue(CaptionProperty, value);
}
public static readonly BindableProperty ValueProperty = BindableProperty.Create(
nameof(Value), typeof(string), typeof(ValidationEntry), default(string));
public string Value
{
get => (string)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
I’m using the custom control in the following way
<controls:ValidationEntry Caption=”Name:” Value="{Binding FullName, Mode=TwoWay}" />
My question is how to add behaviors to the custom control?
I would like to add them in the place that I’m using the control. i.e.
<controls:ValidationEntry Caption="Name:"
Value="{Binding FullName, Mode=TwoWay}">
<controls:ValidationEntry.EntryBehaviors>
<behaviors:EntryLengthValidatorBehavior IgnoreSpaces="True"/>
</controls:ValidationEntry.EntryBehaviors>
</controls:ValidationEntry>
You can create a behaviors directly, I add a NumericValidationBehavior in my custom entry to check the data if it is double.If type of the data is not double, the color of text will be set to red.
Here is xaml code.
<StackLayout>
<local:MyEntry local:NumericValidationBehavior.AttachBehavior="true">
</local:MyEntry>
</StackLayout>
Here is NumericValidationBehavior.cs
public static class NumericValidationBehavior
{
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached(
"AttachBehavior",
typeof(bool),
typeof(NumericValidationBehavior),
false,
propertyChanged: OnAttachBehaviorChanged);
public static bool GetAttachBehavior(BindableObject view)
{
return (bool)view.GetValue(AttachBehaviorProperty);
}
public static void SetAttachBehavior(BindableObject view, bool value)
{
view.SetValue(AttachBehaviorProperty, value);
}
static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
{
var entry = view as Entry;
if (entry == null)
{
return;
}
bool attachBehavior = (bool)newValue;
if (attachBehavior)
{
entry.TextChanged += OnEntryTextChanged;
}
else
{
entry.TextChanged -= OnEntryTextChanged;
}
}
static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
double result;
bool isValid = double.TryParse(args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}
Update
I create a custom view with ContentView
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BeHavDemo.MyView">
<ContentView.Content>
<StackLayout>
<Label Text="xxxx"/>
<Entry Text="eeeee" />
</StackLayout>
</ContentView.Content>
</ContentView>
Then I create a behavior.
public class MyBeha : Behavior<MyView>
{
protected override void OnAttachedTo(BindableObject view)
{
base.OnAttachedTo(view);
var myview=view as MyView;
StackLayout stackLayout = (StackLayout)myview.Content;
Label label = (Label)stackLayout.Children[0];
Entry entry=(Entry) stackLayout.Children[1];
}
}

Xamarin forms tabbed page not retrieving data from in OnAppearing

I retrieve data from the Azure database to show one of the tabbed pages. when calling the method from ViewModel in OnAppearing not retrieve data, but when click the button it retrieves and shows on the page.
Please advice If I have constructed ViewModel and view correctly? if so why it doesn't work. ?
Connection manager:
public partial class DatabaseManager
{
static DatabaseManager defaultInstance = new DatabaseManager();
MobileServiceClient client;
IMobileServiceTable<Person> personTable;
private DatabaseManager()
{
this.client = new MobileServiceClient(Constants.AzureMobileAppURL);
this.personTable = client.GetTable<Person>();
}
public static DatabaseManager DefaultManager
{
get
{
return defaultInstance;
}
private set
{
defaultInstance = value;
}
}
public MobileServiceClient CurrentClient
{
get { return client; }
}
}
Model:
public class Person
{
[JsonProperty(PropertyName = "FirstName")]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[JsonProperty(PropertyName = "DisplayName")]
public string DisplayName
{
get { return displayName; }
set { displayName = value; }
}
[JsonProperty(PropertyName = "LastName")]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
ViewModel:
public class ProfilePageViewModel : ViewModelBase
{
DatabaseManager manager;
string firstName = "";
string lastName = "";
string displayName = "";;
IMobileServiceTable<Person> personTable;
public ProfilePageViewModel()
{
manager = DatabaseManager.DefaultManager;
this.personTable = manager.CurrentClient.GetTable<Person>();
RefreshCommand = new Command(
execute: async () =>
{
try
{
await GetProfileAsync();
}
catch
{
}
});
}
public async Task GetProfileAsync()
{
try
{
IEnumerable<Person> items = await personTable
.Where(pserson => pserson.Active)
.ToEnumerableAsync();
foreach (var item in items)
{
FirstName = item.FirstName;
LastName = item.LastName;
DisplayName = item.DisplayName;
}
}
catch (Exception e)
{
}
}
public string FirstName
{
private set { SetProperty(ref firstName, value); }
get { return firstName; }
}
public string LastName
{
private set { SetProperty(ref lastName, value); }
get { return lastName; }
}
public string DisplayName
{
private set { SetProperty(ref displayName, value); }
get { return displayName; }
}
public ICommand RefreshCommand { private set; get; }
}
View:
ProfilePage.xaml
<?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="SLSNZ.Views.ProfilePage"
xmlns:controls="clr-
namespace:ImageCircle.Forms.Plugin.Abstractions;
assembly=ImageCircle.Forms.Plugin"
xmlns:local="clr-namespace:SLSNZ.ViewModels"
Title="Profile">
<ContentPage.Resources>
<ResourceDictionary>
<local:ProfilePageViewModel x:Key="viewModel">
</local:ProfilePageViewModel>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="icon-profile.png" />
</OnPlatform>
</ContentPage.Icon>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<StackLayout BindingContext="{StaticResource viewModel}">
<Label Text="Display Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding DisplayName}"
VerticalOptions="Center"
HorizontalOptions="Start"
VerticalOptions="Start/>
<Label Text="First Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding FirstName}"
FontSize="Large"
HorizontalOptions="Start"
VerticalOptions="Start" />
<Label Text="Last Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding LastName}"
FontSize="Large"
HorizontalOptions="Start"
VerticalOptions="Start" />
<Button Text="Refresh"
Command="{Binding RefreshCommand}"
Grid.Row="0" Grid.Column="1"/>
</StackLayout>
</ContentPage>
View:
ProfilePage.cs
public partial class ProfilePage : ContentPage
{
ProfilePageViewModel viewModel;
public ProfilePage()
{
InitializeComponent();
viewModel = new ProfilePageViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
await viewModel.GetProfileAsync();
}
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] string propertyName =
null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName
= null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
In your view by the time you await viewModel.GetProfileAsync(); The view will already render.
Your GetProfileAsync in the View Model does an await so will get the data then update it.
I suggest changing the IMobileServiceTable personTable to a property and implement a on Property change to notify the view that the data has changes.
So your viewmodel should implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Then when the Data Changes you can notify it in the view model like:
OnPropertyChanged("personTable");
Also in your view change your code to:
pre-initialize the viewmodel:
public ProfilePage()
{
InitializeComponent();
SetViewModel();
}
protected async void SetViewModel()
{
viewmodel = await viewModel.GetProfileAsync();
}
This way you wont block the UI thread and when you call the OnPropertyChnage it will notify your view to update.
UPDATE:
I have created a small sample Xamarin project for you to demonstrate how you can bind and notify the view of changes.
You had a few issues in your view as well where your DisplayName label was not closed properly and you had duplicate properties for HorizontalOptions in some labels.
Download this Xamarin sample. It had hard coded data but will show you the flow of setting the data and the Binding Context of the View without locking the UI thread.
https://github.com/loanburger/54430503

Resources