Reputation: 61
I am trying to control the background color of a label by changing the color of the selected label. I am following the MVVM pattern, and the way I have implemented is like:
In the Model I have created a boolean, with get and set, which has to detect if an item in my listview is selected. public boolean Selected {get; set;}
In my view, I bind the background color property to the boolean, and set the IValueConverter as the Converter
It seems that it only checks once, as the background color is always white. I have checked it with breakpoints in the Converter, and it only gets called when the list is initiated, but not when the items are updated.
IValueConverter:
public class SelectedItemColorConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((Boolean)value)
return Color.Red;
else
return Color.White;
}
return Color.White;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
This is the ListView:
<StackLayout x:Name="standingsStackLayout" IsVisible="False">
<ListView x:Name="standingsList" SeparatorColor="Black" ItemsSource="{Binding StandingsListSource}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="TournamentNameLabel" Text="{Binding TournamentName}"
TextColor="{StaticResource textColor}" HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
BackgroundColor="{Binding Selected, Converter={StaticResource colorConvert}}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ViewModel code:
public HistoricalStandingsData _selectedItem;
public HistoricalStandingsData SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != value)
{
if(_selectedItem != null)
_selectedItem.Selected = false;
_selectedItem = value;
if (_selectedItem != null)
_selectedItem.Selected = true;
TournamentLabelName = _selectedItem.TournamentName;
OnPropertyChanged(nameof(SelectedItem));
//OnPropertyChanged(nameof(_selectedItem.Selected));
}
}
}
I have added the <ContentPage.Resources>
for the Converter
Upvotes: 1
Views: 1262
Reputation: 2165
Let's have a look at your View
<StackLayout x:Name="standingsStackLayout" IsVisible="False">
<ListView x:Name="standingsList" SeparatorColor="Black" ItemsSource="{Binding StandingsListSource}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="TournamentNameLabel" Text="{Binding TournamentName}"
TextColor="{StaticResource textColor}" HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
BackgroundColor="{Binding Selected, Converter={StaticResource colorConvert}}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
We can see there are two major data bindings happening here. First, ListView
's ItemsSource
property is bound to StandingsListSource
property of your view model. Two things can change this: Either the object pointed to by StandingsListSource
as a whole or the collections contents.
The official documentation on bindings has the following to say regarding binding ListView.ItemsSource
:
The
ListView
is quite sophisticated in handling changes that might dynamically occur in the underlying data, but only if take certain steps. If the collection of items assigned to theItemsSource
property of theListView
changes during runtime—that is, if items can be added to or removed from the collection—use anObservableCollection
class for these items.ObservableCollection
implements theINotifyCollectionChanged
interface, andListView
will install a handler for theCollectionChanged
event.
Let's do just that (full implementation of DataSource
class I use as a BindingContext
for the form later):
public ObservableCollection<HistoricalStandingsData> StandingsListSource { get; } = new ObservableCollection<HistoricalStandingsData>();
For simplicity I made StandingsListSource
a C# 6.0 readonly auto property to eliminate the need of tracking it's reassignment.
Now, since ListView.SelectedItem
is bound as well we need some way to notify ListView
that selected item was updated from code behind. Enter the second advice from documentation mentioned before:
If properties of the items themselves change during runtime, then the items in the collection should implement the
INotifyPropertyChanged
interface and signal changes to property values using thePropertyChanged
event.
This has 2 implications:
HistoricalStandingsData
should notify when it's properties change because each row in ListView
binds to this property as per DataTemplate
:
public class HistoricalStandingsData : INotifyPropertyChanged
{
public HistoricalStandingsData(string name)
{
this.TournamentName = name;
}
private bool selected;
public bool Selected
{
get
{
return selected;
}
set
{
selected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selected)));
}
}
public string TournamentName { get; }
// From INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
}
View model class should implement INotifyPropertyChanged
to notify on properties, in this case SelectedItem
changes.
class DataSource : INotifyPropertyChanged
{
public ObservableCollection<HistoricalStandingsData> Items { get; } = new ObservableCollection<HistoricalStandingsData>();
public HistoricalStandingsData SelectedItem
{
// Information on selection is stored in items themselves, use Linq to find the single matching item
get => Items.Where(x => x.Selected).SingleOrDefault();
set
{
// Reset previous selection
var item = SelectedItem;
if (item != null)
item.Selected = false;
// Mark new item as selected, raising HistoricalStandingItem.PropertyChanged
if (value != null)
value.Selected = true;
// Notify observers that SelectedItem changed
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
}
}
// From INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public DataSource()
{
// Helper ICommand used for appending new items to HistoricalStandingsData
AddNew = new Command(() =>
{
var item2 = new HistoricalStandingsData(DateTime.Now.ToString());
// Append, notifies observers that collection has changed.
Items.Add(item2);
// Set as selected, resetting previous selection
SelectedItem = item2;
});
}
public ICommand AddNew { get; }
}
AddNew
command is optional, I added it for testing purposes.
Upvotes: 1