I hope someone can help me here. I'm completely new to app development and Xamarin, but I've been working my way through some bits and pieces over the last few weeks. I have an issue with my data binding I hope you can help with. I've created a custom variant of a Stack Layout to provide list items that looks like this...
public class StackedList : StackLayout
{
public static readonly BindableProperty ModelProperty =
BindableProperty.Create<StackedList, StackedListViewModel> (p => p.Model, StackedListViewModel.Empty);
public StackedListViewModel Model
{
get { return (StackedListViewModel)GetValue (ModelProperty); }
set
{
SetValue (ModelProperty, value);
BindChildren ();
OnPropertyChanged ("Model");
}
}
#region | Events |
public event EventHandler ItemSelected;
protected virtual void OnItemSelected(EventArgs e)
{
EventHandler handler = ItemSelected;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region | Event Handlers |
protected override void OnAdded (View view)
{
base.OnAdded (view);
if (!(view is StackedListItem)) {
Children.Remove (view);
} else {
var sli = (StackedListItem)view;
sli.GestureRecognizers.Add (new TapGestureRecognizer {
Command = TapCommand(sli)
});
}
}
protected override void OnBindingContextChanged ()
{
base.OnBindingContextChanged ();
BindChildren ();
}
#endregion
public void ClearSelection()
{
foreach (var childItem in Children) {
childItem.BackgroundColor = Styles.ListBackgroundColor;
}
Model.SelectedItem = null;
}
#region | Private Methods |
private void BindChildren()
{
Children.Clear ();
foreach (var item in Model.Items) {
Children.Add (new StackedListItem { BindingContext = item });
}
}
private Command TapCommand(StackedListItem item)
{
return new Command ((parameter) => {
foreach (var childItem in Children) {
childItem.BackgroundColor = Styles.ListBackgroundColor;
}
item.BackgroundColor = Styles.SelectedItemHighlightColor;
Model.SelectedItem = (StackedListItemViewModel)item.BindingContext;
OnItemSelected(new EventArgs());
});
}
This is used in XAML like so...
<v:StackedList x:Name="activeClientsList"
VerticalOptions="Start"
Spacing="1"
Model="{Binding ActiveClients}"
ItemSelected="ActiveClientsItemSelected"
BackgroundColor="{x:Static st:Styles.BackgroundColor}"
IsVisible="{Binding ActiveClients.Any}" />
If I change the underlying value of ActiveClients (or InactiveClients or UserClients) in the containing Page's ViewModel (Below) the list content doesn't update, so when I execute the search code nothing changes. What am I missing here?
Here's the code for the ViewModel that the XAML above uses...
public class HomeViewModel : NavigableViewModelBase (Provides an INavigation, State Manager and implements INotifyPropertyChanged)
{
private ClientSet _currentClientSetState;
private string _searchQuery;
private IClientSetManager _clientSetManager;
#region | Construction |
public HomeViewModel(INavigation navigation, IAppState appState)
: base(navigation, appState)
{
_clientSetManager = new ClientSetManager(appState.Repos, appState.CurrentUser);
BindClientSets();
}
#endregion
public StackedListViewModel ActiveClients { get; private set; }
public StackedListViewModel InactiveClients { get; private set; }
public StackedListViewModel UserClients { get; private set; }
public ClientSet CurrentClientSetState
{
get { return _currentClientSetState; }
set
{
_currentClientSetState = value;
OnPropertyChanged("CurrentClientSetState");
}
}
public string SearchQuery
{
get { return _searchQuery; }
set
{
_searchQuery = value;
OnPropertyChanged("SearchQuery");
}
}
#region | Events |
public event EventHandler<SearchEventArgs> SearchClientsCompleted;
protected virtual void OnSearchClientsCompleted(SearchEventArgs e)
{
var handler = SearchClientsCompleted;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region | Commands |
public ICommand SearchClientsCommand
{
get
{
return new Command (async () => {
ExecuteSearch();
OnSearchClientsCompleted(new SearchEventArgs(SearchQuery));
});
}
}
#endregion
#region | Private Methods |
private void BindClientSets()
{
ActiveClients = new StackedListViewModel(
from client in _clientSetManager.ActiveClients
select client);
InactiveClients = new StackedListViewModel(
from client in _clientSetManager.InactiveClients
select client);
UserClients = new StackedListViewModel(
from client in _clientSetManager.UserClients
select client);
OnPropertyChanged("ActiveClients");
OnPropertyChanged("InactiveClients");
OnPropertyChanged("UserClients");
}
private void ExecuteSearch()
{
if (String.IsNullOrWhiteSpace(SearchQuery))
_clientSetManager.ClearFilter();
else
_clientSetManager.Filter(SearchQuery);
BindClientSets();
}
#endregion
}
And the code behind file for the XAML...
public partial class Home : ContentPage
{
#region | Constructor |
public Home ()
{
InitializeComponent();
Model = new HomeViewModel(this.Navigation, new AppState());
BindingContext = Model;
InitUi();
Model.SearchClientsCompleted += SearchClientsCompleted;
}
#endregion
protected HomeViewModel Model { get; private set; }
#region | Event Handlers |
protected void SearchClientsCompleted(object sender, SearchEventArgs args)
{
DisplayAlert ("Alert", "You searched for " + args.Query, "OK");
}
#endregion
#region | Private Methods |
private void InitUi()
{
signOutButton.SetActive();
}
#endregion
}
(code reduced for brevity)