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
}
Related
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>
I have created bindable property called Text in TargetClass.cs. That Text property is nested bindable property.
TargetClass.cs :
public class TargetClass : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TargetClass), "Default", BindingMode.TwoWay, null,
OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
}
ViewModel.cs :
public class ViewModel : INotifyPropertyChanged
{
private string m_text = "New Value";
public string TextValue
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("TextValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I have created MyView.cs
public class MyView : ContentView
{
private TargetClass target;
Label label;
public TargetClass Target
{
get
{
return target;
}
set
{
target = value;
label.Text = target.Text;
}
}
public MyView()
{
label = new Label();
label.FontSize = 50;
Content = label;
}
}
MainPage.xaml :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:BindingDemo"
x:Class="BindingDemo.MainPage">
<StackLayout>
<local:MyView >
<local:MyView.Target>
<local:TargetClass Text="{Binding TextValue}" >
<local:TargetClass.BindingContext>
<local:ViewModel />
</local:TargetClass.BindingContext>
</local:TargetClass>
</local:MyView.Target>
</local:MyView>
<Button Text="Click" Clicked="Button_Clicked" />
</StackLayout>
MainPage.xaml.cs:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
(BindingContext as ViewModel).TextValue = "Latest Value";
}
}
TextValue property is binded with Text bindble property.
I have changed value of TextValue property in button click in MainPage.xaml.cs.
If I click the button. it should show Latest value. But it shows new value,it did not changed from new value to latest value.
Is it possible to change the value of TextValue property in Button click dynamically ?
If you want to use the method above,you could check below (But it's not recommended.If TargetClass is not used anywhere else, this class seems redundant, you can add a BindableProperty to MyView directly):
in TargetClass.cs:
public class TargetClass : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TargetClass), "Default", BindingMode.TwoWay, propertyChanged: OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
((TargetClass)bindable).View.label.Text = (string)newValue;
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly BindableProperty ViewProperty =
BindableProperty.Create("View", typeof(MyView), typeof(TargetClass), null, BindingMode.TwoWay);
public MyView View
{
get
{
return (MyView)GetValue(ViewProperty);
}
set
{
SetValue(ViewProperty, value);
}
}
}
in the page.xaml:
<StackLayout >
<local:MyView x:Name="MyView">
<local:MyView.Target>
<local:TargetClass x:Name="MyClass" View="{Binding ., Source={x:Reference MyView}}" Text="{Binding TextValue}" >
<local:TargetClass.BindingContext>
<local:ViewModel />
</local:TargetClass.BindingContext>
</local:TargetClass>
</local:MyView.Target>
</local:MyView>
<Button Text="Click" Clicked="Button_Clicked" />
</StackLayout>
in page.xaml.cs:
private void Button_Clicked(object sender, EventArgs e)
{
(MyClass.BindingContext as ViewModel).TextValue = "Latest Value";
}
I cant find solution about this orange color? Do i need to write a renderer or can change from resources in Android and IOS?
Yes,if you want to change ListView selecteditem background color, need to use custom render to do this in Xamarin.Forms.
In the PCL, create a class name is ExtendedViewCell which should inherit any ViewCell.
public class ExtendedViewCell : ViewCell
{
public static readonly BindableProperty SelectedBackgroundColorProperty =
BindableProperty.Create("SelectedBackgroundColor",
typeof(Color),
typeof(ExtendedViewCell),
Color.Default);
public Color SelectedBackgroundColor
{
get { return (Color)GetValue(SelectedBackgroundColorProperty); }
set { SetValue(SelectedBackgroundColorProperty, value); }
}
}
In Android project, create a class name as ExtendedViewCellRenderer and make sure to add renderer registration for our ExtendedViewCell class above the namespace.
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
namespace demo3.Droid
{
public class ExtendedViewCellRenderer : ViewCellRenderer
{
private Android.Views.View _cellCore;
private Drawable _unselectedBackground;
private bool _selected;
protected override Android.Views.View GetCellCore(Cell item,
Android.Views.View convertView,
ViewGroup parent,
Context context)
{
_cellCore = base.GetCellCore(item, convertView, parent, context);
_selected = false;
_unselectedBackground = _cellCore.Background;
return _cellCore;
}
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnCellPropertyChanged(sender, args);
if (args.PropertyName == "IsSelected")
{
_selected = !_selected;
if (_selected)
{
var extendedViewCell = sender as ExtendedViewCell;
_cellCore.SetBackgroundColor(extendedViewCell.SelectedBackgroundColor.ToAndroid());
}
else
{
_cellCore.SetBackground(_unselectedBackground);
}
}
}
}
}
Then you can set color for listview SelectedBackgroundColor.
<ListView ItemsSource="{Binding students}">
<ListView.ItemTemplate>
<DataTemplate>
<local:ExtendedViewCell SelectedBackgroundColor="White">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Username}" TextColor="Yellow" />
<Label Text="{Binding Age}" TextColor="Blue" />
</StackLayout>
</local:ExtendedViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
More detail info and steps in ios platform, you can take a look:
https://blog.wislon.io/posts/2017/04/11/xamforms-listview-selected-colour
Update:
In ios project, create a class name as ExtendedViewCellRenderer and make sure to add renderer registration for our ExtendedViewCell class above the namespace.
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
namespace xamformsdemo.iOS
{
public class ExtendedViewCellRenderer : ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell = base.GetCell(item, reusableCell, tv);
var view = item as ExtendedViewCell;
cell.SelectedBackgroundView = new UIView
{
BackgroundColor = view.SelectedBackgroundColor.ToUIColor(),
};
return cell;
}
}
}
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];
}
}
I have a question about Databinding which I'm really struggling to understand. I have two ComboBox on my extended splash screen. What I want to achieve is when you select an item from the first ComboBox, the items in the 2nd ComboBox should change. Please see the code below.
First let me try to explain how my Data looks like and what problem I'm facing.
Collection
A
AA
AB
AC
B
BA
BB
C
CA
The First ComboBox should show A,B and C as items. Now lets say you selected A, the ComboBox 2 should show AA,AB and AC as items. The problem I have is that ComboBox 2 is showing AA only not all 3 items.
My ViewModel Called MainViewModel looks like this:-
public class ItemViewModel : INotifyPropertyChanged
{
private string _befattning;
public string Befattning
{
get
{
return _befattning;
}
set
{
if (value != _befattning)
{
_befattning = value;
NotifyPropertyChanged("Befattning");
}
}
}
private string _befattning2;
public string Befattning2
{
get
{
return _befattning2;
}
set
{
if (value != _befattning2)
{
_befattning2 = value;
NotifyPropertyChanged("Befattning2");
}
}
}
private string _befattning3;
public string Befattning3
{
get
{
return _befattning3;
}
set
{
if (value != _befattning3)
{
_befattning3 = value;
NotifyPropertyChanged("Befattning3");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
//NotifyPropertyChanged Code
}
}
public class MainViewModelGroups : INotifyPropertyChanged
{
public MainViewModelGroups(String enhet)
{
this._enhetsNamn = enhet;
}
private string _enhetsNamn;
public string EnhetsNamn
{
get { return _enhetsNamn; }
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (value != _selectedItem)
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
private ObservableCollection<ItemViewModel> _items = new ObservableCollection<ItemViewModel>();
public ObservableCollection<ItemViewModel> Items
{
get
{
return this._items;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
//NotifyPropertyChangedCode
}
}
public sealed class MainViewModel
{
private static MainViewModel _mainViewModel = new MainViewModel();
private ObservableCollection<MainViewModelGroups> _collection = new ObservableCollection<MainViewModelGroups>();
public ObservableCollection<MainViewModelGroups> Collection
{
get { return this._collection; }
}
public MainViewModel()
{
var enhet1 = new MainViewModelGroups("Akutmottagning");
enhet1.Items.Add(new ItemViewModel() { Befattning = "Ledningsansvarig sjuksköterska" });
Collection.Add(enhet1);
}
And my XAML code looks like this
<ComboBox x:Name="EnhetLista"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnhetsNamn}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="BefattningsLista"
DataContext="{Binding ElementName=EnhetLista, Path=SelectedItem, Mode=OneWay}"
ItemsSource="{Binding Path=Items, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Can someone please helps by explaining what is wrong with my code and how to achieve my goal?
Add a second ObservableCollection to your viewmodel, then change that based on the selected item.
public sealed class MainViewModel
{
private static MainViewModel _mainViewModel = new MainViewModel();
private ObservableCollection<MainViewModelGroups> _collection = new ObservableCollection<MainViewModelGroups>();
public ObservableCollection<MainViewModelGroups> Collection
{
get { return this._collection; }
}
public ObservableCollection<ItemViewModel> Items
{
get { return this._items; }
set { this._items = value; OnPropertyChanged("Items"); }
}
public MainViewModelGroups SelectedGroup
{
get { return this._selectedGroup; }
set { this._selectedGroup = value; Items = value.Items; }
}
public MainViewModel()
{
var enhet1 = new MainViewModelGroups("Akutmottagning");
enhet1.Items.Add(new ItemViewModel() { Befattning = "Ledningsansvarig sjuksköterska" });
Collection.Add(enhet1);
}
}
Your Xaml will change to:
<ComboBox x:Name="EnhetLista"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnhetsNamn}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="BefattningsLista"
ItemsSource="{Binding Path=Items}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Your viewmodel may need to implement INotifyPropertyChanged. It may not though, as ObservableCollection may take care of that for you.