In a UWP/Template 10 app we require an AutoSuggestBox to update the Customer property on a ViewModel. The AutoSuggestBox filters and selects from the customer list as expected, but the Customer property of the ViewModel remains null.
The AutoSuggestBox populates from a database. I've omitted that code as it is working well.
This demo project is called Redstone. Below are what I believe to be the relevant code excerpts
MainPage.xaml
<Page x:Class="Redstone.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Behaviors="using:Template10.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:Template10.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:uc="using:Redstone.UserControls"
xmlns:local="using:Redstone.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:Redstone.Validation"
xmlns:behaviors="using:Template10.Behaviors"
xmlns:vm="using:Redstone.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<AutoSuggestBox Name="CustomerAutoSuggestBox"
Width="244"
Margin="0,5"
TextMemberPath="{x:Bind ViewModel.Customer.FileAs, Mode=TwoWay}"
PlaceholderText="Customer"
QueryIcon="Find"
TextChanged="{x:Bind ViewModel.FindCustomer_TextChanged}"
SuggestionChosen="{x:Bind ViewModel.FindCustomer_SuggestionChosen}">
and the relevant excerpts from MainPageViewModel
public class MainPageViewModel : ViewModelBase
{
Customer _Customer = default(Customer);
public Customer Customer { get { return _Customer; } set { Set(ref _Customer, value); } }
public void FindCustomer_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
sender.ItemsSource = CustomerLookupList.Where(cl => cl.Lookup.IndexOf(sender.Text, StringComparison.CurrentCultureIgnoreCase) > -1).OrderBy(cl => cl.FileAs);
}
}
public void FindCustomer_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
CustomerLookup selectedCustomer = args.SelectedItem as CustomerLookup;
sender.Text = selectedCustomer.FileAs;
}
Finally the Customer class
public class Customer
{
public Guid CustomerId { get; set; } = Guid.NewGuid();
public string FileAs { get; set; }
}
The AutoSuggestBox is filtering and displaying as expected. Why does the Customer property of MainPageViewModel remain null?
Following advice from #tao I have modified the ViewModel as below and all is now tickety-boo
public void FindCustomer_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
CustomerLookup selectedCustomer = args.SelectedItem as CustomerLookup;
this.Customer = CustomersService.GetCustomer(selectedCustomer.CustomerId);
}
Related
MAUI.NET
I have a special picker for my own that I want to reuse in many places
<ContentView ...
x:Class="XYZ.Views.ABCPicker"
x:DataType="views:ABCPicker">
<Grid>
<Picker ItemsSource="{Binding ...}" SelectedItem="{Binding ChoosenDeviceTypeProperty}"/>
</Grid>
</ContentView>
and its code behind with my AttachedProperty:
public partial class ABCPicker : ContentView
{
public static readonly BindableProperty ChoosenDeviceTypeProperty = BindableProperty.CreateAttached("ChoosenDeviceType", typeof(string), typeof(ABCPicker), "Detect by connect", BindingMode.TwoWay);
public static string GetChoosenDeviceType(BindableObject view)
{
return (string)view.GetValue(ChoosenDeviceTypeProperty);
}
public static void SetChoosenDeviceType(BindableObject view, string value)
{
view.SetValue(ChoosenDeviceTypeProperty, value);
}
public ABCPicker()
{
this.BindingContext = this;
InitializeComponent();
}
}
I want to consume it in my broader control ViewModel
public class BroaderControlViewModel : BaseViewModel
{
...
private string myResult;
public string MyResult
{
get { return myResult; }
set { SetPropertyAndNotify(ref myResult, value); }
}
public ICommand MyCommand { private set; get; }
public BroaderControlViewModel()
{
MyCommand = new Command(() =>
{
// here I want the to get a choosen value to proceed with it after the user have choosen value in my special picker
});
}
}
with a view like below:
<ContentView ...
x:Class="XYZ.Views.BroaderControlView"
x:DataType="viewModels:BroaderControlViewModel">
<VerticalStackLayout ...>
<views:ABCPicker ChoosenDeviceType="{Binding MyResult, Mode=TwoWay}" />
<Button Text="Connect" Command="{Binding MyCommand}"/>
</VerticalStackLayout>
</ContentView>
I tried different binding types, also with x:Reference this and also with normal binding properties - not attached ones.
How it should be matched?
Please assist:
I have implemented the MVVM design on a simple app using Xamarin.
I have one Model (User) and one ViewModel (UserViewModel).
Please note that this app is my first Xamarin/MVVM app and that I am new to this.
The issue that i have is that adding or removing a User, the View does NOT update.
When I add or remove a user I can confirm that the Database is updated, but not my view.
Please see my code below, what am i missing?
User Model:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
public List<Role> RolesList { get; set; }
}
UserViewModel Code:
public class UsersViewModel : INotifyPropertyChanged
{
private UserServices UserServ { get; set; }
public User UserSelected { get; set; }
private ObservableCollection<User> userList;
public ObservableCollection<User> UserList
{
get
{
return userList;
}
set
{
if (userList != value)
{
userList = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public UsersViewModel()
{
UserServ = new UserServices();
UsersLoadAsync();
}
public async void UsersLoadAsync()
{
UserList = await UserServ.UsersGetAsync();
}
}
User Helper Service code (Added for completeness)
public class UserServices
{
public async Task<ObservableCollection<User>> UsersGetAsync()
{
ObservableCollection<User> UserList = await App.UserService.GetAsync();
return UserList;
}
public async Task<bool> UsersAddAsync(User user)
{
bool success = await App.UserService.PostAsync(user);
return success;
}
public async Task<bool> UsersRemoveAsync(User user)
{
bool success = await App.UserService.DeleteAsync(user.Id, user);
return success;
}
}
View Xaml Code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PB_Logbook"
x:Class="PB_Logbook.MainPage"
xmlns:ViewModels="clr-namespace:PB_Logbook.ViewModels;assembly:PB_Logbook">
<ContentPage.BindingContext>
<ViewModels:UsersViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<ListView ItemsSource="{Binding UserList, Mode=TwoWay}" HasUnevenRows="True" ItemSelected="Item_SelectedAsync" IsPullToRefreshEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Username}" FontSize="24"/>
<Label Text="{Binding FirstName}" FontSize="18" Opacity="0.6"/>
<Label Text="{Binding LastName}" FontSize="18" Opacity="0.6"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Add" Clicked="AddButton_ClickedAsync"></Button>
<Button Text="Remove" Clicked="RemoveButton_ClickedAsync"></Button>
</StackLayout>
</ContentPage>
View code behind:
public partial class MainPage : ContentPage
{
private UserServices UserServices { get; set; }
private UsersViewModel UsersVM { get; set; }
public MainPage()
{
InitializeComponent();
UserServices = new UserServices();
UsersVM = new UsersViewModel();
}
private async void AddButton_ClickedAsync(object sender, EventArgs e)
{
await AddUserAsync();
}
private async void RemoveButton_ClickedAsync(object sender, EventArgs e)
{
await RemoveUserAsync();
}
private async void Item_SelectedAsync(object sender, EventArgs e)
{
UsersVM.UserSelected = ((User)((ListView)sender).SelectedItem);
}
private async void Pull_RefreshAsync(object sender, EventArgs e)
{
//UsersVM.UsersLoadAsync();
}
private async Task AddUserAsync()
{
Random rnd = new Random();
int rndNumber = rnd.Next(1, 100);
User user = new User()
{
Username = "User " + rndNumber,
FirstName = "Firstname " + rndNumber,
LastName = "Surname " + rndNumber,
IsActive = true
};
bool success = await UserServices.UsersAddAsync(user);
if (success)
{
if (!UsersVM.UserList.Contains(user))
UsersVM.UserList.Add(user);
}
}
private async Task RemoveUserAsync()
{
bool success = await UserServices.UsersRemoveAsync(UsersVM.UserSelected);
if (success)
{
if (UsersVM.UserList.Contains(UsersVM.UserSelected))
UsersVM.UserList.Remove(UsersVM.UserSelected);
}
}
}
The issue is with adding/removing users that does not update in my view.
Thank you.
If you're new to Xamarin MVVM, this link will help you understand the basics of MVVM in Xamarin Forms
https://deanilvincent.github.io/2017/06/03/basic-understanding-of-mvvm-and-databinding-in-xamarin-forms/
I would suggest as well, please lessen your behind the codes and just implement everything including the commands in your ViewModel.
You've written that your codes are working when saving and updating but not reflecting the view right? You should put your method in fetching the list right after your save command.
Like this in your xaml
<Button Text="Save" Command="{Binding SaveCommand}"/>
In your ViewModel, you should use Command from Xamarin
public Command SaveCommand{
get{
return new Command(async()=>{
// your command save here
// then put your method for fetching the updated list: your UsersLoadAsync();
});
}
}
If you're new to MVVM, you can also check this link. It uses Xamarin MVVM. When you finish, you'll have simple weather app with simple mvvm implementations
I hope it helps you
I have a very simple form that I'm using to experiment with BindableProperty. Here's the XAML for the form
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyBindableProperty"
x:Class="MyBindableProperty.MainPage">
<StackLayout>
<local:MyLabel x:Name="BindingLabel" Text="{Binding Text}" MyText="{Binding Text}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Entry x:Name="BindingEntry" Text="{Binding Text, Mode=TwoWay}"/>
<Entry x:Name="BindingEntry2" Text="{Binding Text, Mode=TwoWay}"/>
<Button x:Name="BindingButton" Text="Reset"/>
</StackLayout>
</ContentPage>
And here is the code behind
public partial class MainPage : ContentPage
{
public DataSourceClass DataSourceObject { get; set; }
public MainPage()
{
DataSourceObject = new DataSourceClass { Text = "Test1" };
BindingContext = DataSourceObject;
InitializeComponent();
BindingButton.Clicked += BindingButton_Clicked;
}
private void BindingButton_Clicked(object sender, EventArgs e)
{
var boundText = this.BindingLabel.Text;
var boundMyText = this.BindingLabel.MyText;
DataSourceObject.Text = "Test2";
}
}
Finally, here is the custom label class used in the XAML -
public class MyLabel : Label
{
public string MyText
{
get
{
return (string)GetValue(MyTextProperty);
}
set
{
SetValue(MyTextProperty, value);
}
}
public static readonly BindableProperty MyTextProperty = BindableProperty.Create(nameof(MyText), typeof(string), typeof(MyLabel), "Test", BindingMode.TwoWay, propertyChanged: MyTextChanged);
public static void MyTextChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).TextChanged(newValue.ToString());
}
public void TextChanged(string newText)
{
Device.BeginInvokeOnMainThread(() => this.Text = newText);
}
}
The issues I'm having are
when the page initialises the MyTextChanged handler fires, but not after any subsequent changes
when the Reset button is clicked the value in DataSourceObject.Text is correctly updated with the value from the Entry element
no matter how I try to set the values of BindingLabel and BindingEntry2 they never reflect the values of DataSourceObject.Text after the page has loaded.
Any help would be greatly appreciated.
I stumbled across this so I updated the DataSourceClass from this
public class DataSourceClass
{
public string Text { get; set; }
}
to this
public class DataSourceClass : INotifyPropertyChanged
{
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and now everything works.
I thought BindableProperty was meant to supersede INotifyPropertyChanged?
I'm trying to create a TemplateSelector which recognizes if an implements an interface and applies a DataTemplate for it.
I'd like to use this selector in following way:
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<DataTemplate x:Key="IMedia">
<Image Source="{Binding PreviewImage}" />
</DataTemplate>
<DataTemplate x:Key="IDocument">
<TextBlock Text="test" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
I end up with following implementation:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<Type, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<Type, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var result = (
from t in Items
where t.Key.GetTypeInfo().IsAssignableFrom(item.GetType().GetTypeInfo())
select t.Value).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
It of course doesn't work, otherwise I wouldn't write this question :) Application crushes with a message a xaml cannot be parsed:
A first chance exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in Hicron.ProductCatalog.MainUI.exe
WinRT information: E_UNKNOWN_ERROR [Line: 47 Position: 39]
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in Hicron.ProductCatalog.MainUI.exe but was not handled in user code
WinRT information: E_UNKNOWN_ERROR [Line: 47 Position: 39]
Additional information: Unspecified error
What's wrong with that dictionary? Normally I'd use CompositeCollection and merge multiple sources but this class is missing in WinRT :(
EDIT
In terms of fixing dictionary problem I've changed dictionary to list of custom types. Still can't create a custom type with Type set from XAML. I could use a string but than I can't manage it in code unless I specify fully qualified type name.
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public List<InterfaceAwareTemplateSelectorItem> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new List<InterfaceAwareTemplateSelectorItem>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var result = (
from t in Items
where t.Type.GetTypeInfo().IsAssignableFrom(item.GetType().GetTypeInfo())
select t.Template).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
public class InterfaceAwareTemplateSelectorItem
{
public Type Type { get; set; }
public DataTemplate Template { get; set; }
}
Corresponding XAML:
// somewhere in page tag
xmlns:bo="using:/*long long namespace*/.BusinessObjects"
// somewhere in XAML file
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<selectors:InterfaceAwareTemplateSelectorItem Type="bo:IMedia">
<selectors:InterfaceAwareTemplateSelectorItem.Template>
<DataTemplate>
<Image Source="{Binding PreviewImage}"
Tapped="ImageTapped" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelectorItem.Template>
</selectors:InterfaceAwareTemplateSelectorItem>
<selectors:InterfaceAwareTemplateSelectorItem Type="bo:IDocument">
<selectors:InterfaceAwareTemplateSelectorItem.Template>
<DataTemplate>
<TextBlock Text="pa8u4mrapwu" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelectorItem.Template>
</selectors:InterfaceAwareTemplateSelectorItem>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Okay, so using this:
public interface IFake1 { }
public interface IFake2 { }
public class TemplateItem
{
public DataTemplate Template { get; set; }
public string Interface { get; set; }
}
public class MySelector : DataTemplateSelector
{
public List<TemplateItem> Templates { get; set; }
}
I could do this:
<GridView>
<GridView.ItemTemplateSelector>
<local:MySelector>
<local:MySelector.Templates>
<local:TemplateItem Interface="IFake1">
<local:TemplateItem.Template>
<DataTemplate>
<!-- TODO -->
</DataTemplate>
</local:TemplateItem.Template>
</local:TemplateItem>
<local:TemplateItem Interface="IFake2">
<local:TemplateItem.Template>
<DataTemplate>
<!-- TODO -->
</DataTemplate>
</local:TemplateItem.Template>
</local:TemplateItem>
</local:MySelector.Templates>
</local:MySelector>
</GridView.ItemTemplateSelector>
</GridView>
The error appears in the Type you are using. I could not get that to work. Had to use String. Should be simple to parse form there.
Best of luck!
See if it works if you replace Dictionary<Type, DataTemplate> with ResourceDictionary. I'm betting at least one of the problems is that the key in x:Key="IMedia" can't be implicitly converted to Type. You could also just try using string as the key type.
I finally managed to fix it. Unfortunately I couldn't get a conversion from string to Type in XAML so I had to stick to strings :/ Not very convenient but at least works. That's what I ended up with:
XAML
<ListView Grid.Column="0"
ItemsSource="{Binding Media}"
SelectionMode="None">
<ListView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<DataTemplate x:Key="IMedia">
<Image Source="{Binding PreviewImage}" Tapped="ImageTapped"/>
</DataTemplate>
<DataTemplate x:Key="IDocument">
<commonItems:DocumentItemPresenter TappedCommand="{Binding DataContext.OpenDocument, ElementName=PageRoot}"/>
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Selector itself:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<string, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<string, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var result = (
from ii in item.GetType().GetTypeInfo().ImplementedInterfaces
from dt in Items
where ii.Name == dt.Key
select dt.Value).FirstOrDefault();
return result ?? DefaultTemplate;
}
}
I upvoted Filip & Jerry because I found your tips helpful. Thank you guys.
If anyone is interested how this problem was solved below is final implementation and a usage example.
Usage:
<GridView ....>
<GridView.ItemTemplateSelector>
<selectors:InterfaceAwareTemplateSelector>
<!-- ReSharper disable once Xaml.RedundantResource -->
<DataTemplate x:Key="INewsContainer" selectors:InterfaceAwareTemplateSelector.Priority="1">
<ctrls:ItemsContainerTile Width="350" Height="350"
ItemTappedCommand="{Binding DataContext.OpenNewsDetails, ElementName=PageRoot}"/>
</DataTemplate>
<!-- ReSharper disable once Xaml.RedundantResource -->
<DataTemplate x:Key="ISimpleMaterial" selectors:InterfaceAwareTemplateSelector.Priority="0">
<ctrls:GenericTile Width="350" Height="350"
TappedCommand="{Binding DataContext.OpenDetails, ElementName=PageRoot}" />
</DataTemplate>
</selectors:InterfaceAwareTemplateSelector>
</GridView.ItemTemplateSelector>
.... the rest of XAML
The priority is to control the order in which datatamples should be checked. This way we can control what happens if multiple keys matches the object being converted.
Implementation:
[ContentProperty(Name = "Items")]
public class InterfaceAwareTemplateSelector: DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public Dictionary<string, DataTemplate> Items { get; set; }
public InterfaceAwareTemplateSelector() {
Items = new Dictionary<string, DataTemplate>();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) {
if (item == null) {
return DefaultTemplate;
}
var results = (
from ii in item.GetType().GetTypeInfo().ImplementedInterfaces
from dt in Items
where ii.Name == dt.Key
select dt)
.ToArray();
if (results.Length > 1) {
var orderedResults =
from r in results
where IsPrioritySet(r.Value)
orderby GetPriority(r.Value) descending
select r;
if (orderedResults.Any()) {
return orderedResults.First().Value;
}
throw new AmbigiousResolveTemplateFound(item.GetType(), results.Select(x => x.Key));
}
else if (results.Length == 1) {
return results[0].Value;
}
return DefaultTemplate;
}
#region PriorityProperty
public static readonly DependencyProperty PriorityProperty =
DependencyProperty.RegisterAttached(
"Priority",
typeof(int),
typeof(InterfaceAwareTemplateSelector),
new PropertyMetadata(0));
public static int GetPriority(DependencyObject item) {
if (item == null) { throw new ArgumentNullException("item"); }
return (int)item.GetValue(PriorityProperty);
}
public static void SetPriority(DependencyObject item, int value) {
if (item == null) { throw new ArgumentNullException("item"); }
item.SetValue(PriorityProperty, value);
}
public static bool IsPrioritySet(DependencyObject item) {
if (item == null) { throw new ArgumentNullException("item"); }
var result = item.ReadLocalValue(PriorityProperty);
return result != DependencyProperty.UnsetValue;
}
#endregion
}
Hope someone will find this implementation helpful. Once again, many thanks to Filip and Jerry for your help.
I'm trying to populate a list view control on a XAML page in a Win8 application. I've added the following attributes to the page XAML:
<common:LayoutAwarePage x:Class="SecAviTools.ViewWeatherHome"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="using:MyNameSpace.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MyNameSpace"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="using:MyNameSpace.Win8ViewModel"
x:Name="pageRoot"
DataContext="{Binding DefaultViewModel,
RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<!-- ... -->
<ListView ItemsSource="{Binding Path=viewmodel:Stations}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My source class is:
namespace MyNameSpace.Win8ViewModel
{
public class Stations : INotifyPropertyChanged, INotifyCollectionChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged<T>(NotifyCollectionChangedAction action, ObservableCollection<T> items)
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, items));
}
public Stations()
{
AllStations = new ObservableCollection<Station>();
AddStations(new List<Station>());
}
public ObservableCollection<Station> AllStations { get; private set; }
public void AddStations(List<Station> stations)
{
AllStations.Clear();
foreach (var station in stations)
AllStations.Add(station);
OnCollectionChanged(NotifyCollectionChangedAction.Reset, AllStations);
OnPropertyChanged("AllStations");
}
}
public class Station
{
public int Id { get; set; }
public string Name { get; set; }
}
}
There is also a button on the page (not shown here) that does the following:
public sealed partial class MyPage : MyNameSpace.Common.LayoutAwarePage
{
private Stations m_Stations = new Stations();
//...
private async void SearchButtonClick(object sender, RoutedEventArgs e)
{
var list = new List<Station>();
list.Add(new Station() { Id = 0, Name = "Zero" });
list.Add(new Station() { Id = 1, Name = "One" });
m_Stations.AddStations(list);
}
}
However, when I run the code, nothing appears in the list view. What am I missing?
TIA
You don't show what DefaultViewModel is, but I'll assume it's set to be an instance of the class you show, Stations. In that case, you need to binding to be:
<ListView ItemsSource="{Binding Path=AllStations}">
The Path of a binding usually refers to a property somewhere; with no further specification, such as a Source, it's a property on the object that is set to be the DataContext.
Regardless, you don't need the namespace qualifier "viewmodel:".
As an aside, if you do end up binding to the ObservableCollection, you don't need to implement INotifyCollectionChanged, only INotifyPropertyChanged for when the AllStations property is set.