I'm having trouble getting my model to show up when trying to databind it in XAML setting Xamarin's BindingContext...here's my simplified model code:
public class LeagueRootObject
{
public League League { get; set; }
public ObservableCollection<Match> Matches { get; set; }
public object Error { get; set; }
}
public class League
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public DateTime PlayingDate { get; set; }
}
public class Match
{
public MatchDetail MatchDetail { get; set; }
public ObservableCollection<Player> Players { get; set; }
}
public class MatchDetail
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
public class Player
{
public PlayerDetail PlayerDetail { get; set; }
public object Rating { get; set; }
public int MatchPlayerId { get; set; }
}
public class PlayerDetail
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
and here's simplified XAML snippet:
<ListView ItemsSource="{Binding Path=Matches}" VerticalOptions="StartAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Path=MatchDetail.Name}"/>
<ListView ItemsSource="{Binding Path=Players}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Path=PlayerDetail.FirstName}"/>
<!-- another ViewCell here seems to hide data somehow -->
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
which is placed in the root StackLayout element of the Xamarin ContentPage and whose BindingContext is set to the LeagueRootObject instance loaded properly from json (which I've checked in debugger as working).
The XAML code above is the best I've been able to do since adding Label inside the StackLayout contained in the inner ListView's ViewCell just doesn't show up properly...how can you add a ViewCell (or any other type of cell for that matter) to the inner ListView and build its View so it can show all the data belonging to all of the inner collection objects (I'm currently only getting the first one to show up also which is a problem when there's more than one player)...what am I doing wrong, should a ListView not be used that way and have some other 'View` control be used for outer objects, all I want is to show all inner objects grouped inside outer ones somehow please...
Thanks
In addition to me maybe being bad at Xamarin-flavoured XAML you also maybe right about there being bugs in the way the listviews work...since I just tried the above code on Windows 10 mobile and there it would not show any data of the inner collection but running it on Android worked by showing multiple inner collection last names...and after scrolling down past that outer collection object it would crash the app.
So, this may not be the way to show such a list of inner objects (and something like a button action beside each of them) grouped by belonging to outer collection onjects, but I still can't find another one example in XAML.
Could it be that you can only do that in foreach looped code in Xamarin.Forms 2.0?
If I understood correctly your issue, I could suggest you solution that helped me to show ListView in ListView.
https://stackoverflow.com/a/35598179/5912513
Related
I'm trying to bind ListView using ItemsSource="{Binding modelname}" it is working fine with one model(Ads), when I added two Models (Ads) and (AdsImg) it's return nothing :
public class Ads
{
public string Titel { get; set; }
public string Description { get; set; }
public List<AdsImg> AdsImg { get; set; }
}
public class AdsImg
{
public int Id { get; set; }
public string ImgPath { get; set; }
public int AdId { get; set; }
}
I combine them in ViewModels (AdsViewModel)
public class AdsViewModel
{
public AdsImg AdsImg { get; set; }
public Ads Ads { get; set; }
}
Xaml page :
<ListView x:Name="AdsListView" ItemsSource="{Binding AdsViewModel}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Image Source="{Binding AdsImg.ImgPath}" />
<Label Text="{Binding Ads.Titel}" TextColor="Black"></Label>
<Label Text="{Binding Ads.Description}" TextColor="Black"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Xaml.cs
public AdsPage()
{
InitializeComponent();
GetAds();
}
private async void GetAds()
{
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("https://example.net/api/Ad");
var Ads = JsonConvert.DeserializeObject<List<AdsViewModel>>(response);
AdsListView.ItemsSource = Ads;
}
Update I replaced this code:
AdsImg img = new AdsImg() {ImgPath = "imagePath"};
Ads ads = new Ads() { Titel = "title",Description = "des" };
with this:
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("https://example.com/api/Ad");
var Ads = JsonConvert.DeserializeObject<List<AdsViewModel>>(response);
but it is not working
I would give you some suggestions:
Don't name the property's name the same as the class name, for example: public AdsImg AdsImg, it looks confusing when you use AdsImg.
If you want to bind the ItemSource in xaml, you should bind to a List<AdsViewModel> instead of a single ViewModel.
I wrote a example and hope you can get some idea from it:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
AdsViewModel vm = new AdsViewModel();
vm.getData();
BindingContext = vm;
}
}
public class AdsViewModel : INotifyPropertyChanged
{
private List<AdsViewModel> _modelDatas;
public List<AdsViewModel> modelDatas
{
get { return _modelDatas; }
set
{
_modelDatas = value;
OnPropertyChanged("modelDatas");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
public AdsImg AdsImgModel { get; set; }
public Ads AdsModel { get; set; }
public AdsViewModel()
{
}
public void getData() {
AdsImg img = new AdsImg() {ImgPath = "imagePath"};
Ads ads = new Ads() { Titel = "title",Description = "des" };
modelDatas = new List<AdsViewModel>();
modelDatas.Add( new AdsViewModel() { AdsImgModel = img, AdsModel = ads });
modelDatas.Add(new AdsViewModel() { AdsImgModel = img, AdsModel = ads });
modelDatas.Add(new AdsViewModel() { AdsImgModel = img, AdsModel = ads });
}
}
public class Ads
{
public string Titel { get; set; }
public string Description { get; set; }
public List<AdsImg> AdsImg { get; set; }
}
public class AdsImg
{
public int Id { get; set; }
public string ImgPath { get; set; }
public int AdId { get; set; }
}
And in Xaml:
<ListView x:Name="AdsListView" ItemsSource="{Binding modelDatas}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Image Source="{Binding AdsImg.ImgPath}" />
<Label Text="{Binding AdsModel.Titel}" TextColor="Black"></Label>
<Label Text="{Binding AdsModel.Description}" TextColor="Black"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Sample project has been uploaded here
and please feel free to ask me any question!
How do you call a custom method in a bind that already has a x:DataType set to a class? The error message shows: The property 'TotalMembers' was not found in type 'Family'. When adding the method from the page load to the Family class, the error remains.
// model
public class Family
{
public int ID { get; set; }
public string Surname { get; set; }
public int TotalMembers() { return "some code..." }
}
public class Person
{
public int ID { get; set; }
public int FamilyID { get; set; }
public string FirstName { get; set; }
}
// page load
public ObservableCollection<Family> ocFamily { get; set; }
ocFamily = new ObservableCollection<Family>();
// xaml
<ListView ItemsSource="{x:Bind ocFamily}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Family>
<StackPanel>
<TextBlock Text="{x:Bind Surname}" />
<TextBlock Text="{x:Bind TotalMembers()}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The second textblock should show the total number of members of the family.
To use functions with {x:Bind}, your app's minimum target SDK version
must be 14393 or later. You can't use functions when your app targets
earlier versions of Windows 10.
You will need to set the Min Version to at least 14393 for x:Bind to function to work.
You can read more from here.
In your case, you should be able to create a TotalMembers property instead of a method.
I am trying to set the ItemsSource of a UWP ComboBox to a property of the ViewModel, but I get an error:
Error: BindingExpression path error: 'componentsLookup' property not found on 'Orders.Component'
The relevant bit of XAML looks like this:
<Page.DataContext>
<local:OrderPageViewModel x:Name="OrderPageViewModel" />
</Page.DataContext>
<ListView
Name="ComponentsList"
ItemsSource="{Binding Components}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox
ItemsSource="{Binding componentsLookup,Mode=TwoWay}"
DisplayMemberPath="ComponentCode"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The relevant bit of the ViewModel looks like this:
public class OrderPageViewModel
{
public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();
public List<Component> componentsLookup = new List<Component>();
Edit 1: The models look like this
public class Product
{
public string ProductCode { get; set; }
public string ProductDescription { get; set; }
public List<Component> Components { get; set; }
public override string ToString()
{
return this.ProductCode;
}
}
public class Component
{
public Guid ComponentId { get; set; }
public Product Product { get; set; }
public string ComponentCode { get; set; }
public string ComponentDescription { get; set; }
public string ComponentColor { get; set; }
public decimal ComponentHeight { get; set; }
public decimal ComponentWidth { get; set; }
public override string ToString()
{
return this.ComponentCode;
}
}
How do I set the ItemsSource to componentsLookup
Nested binding is what you actually want to do. Since the ComboBox is nested inside the ListView, ItemsSource of ComboBox need to be a sub collection of ListView. componentsLookup should be a property of class Orders.Component in your code snippet. You can use a nested source structure like follows for binding:
public class OrderPageViewModel
{
public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>()
{
new Product
{
productname="productname",
componentsLookup=new List<Component>
{
new Component {componentname="test1" },
new Component {componentname="test2" }
}
},
new Product
{
componentsLookup=new List<Component>
{
new Component {componentname="test1" },
new Component {componentname="test2" }
}
}
};
}
public class Component
{
public string componentname { get; set; }
}
public class Product
{
public string productname { get; set; }
public List<Component> componentsLookup { get; set; }
}
XAML Code
<Page.DataContext>
<local:OrderPageViewModel x:Name="OrderPageViewModel" />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >
<ListView Name="ComponentsList" ItemsSource="{Binding Products}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding productname}"></TextBlock>
<ComboBox DisplayMemberPath="ComponentCode" ItemsSource="{Binding componentsLookup, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
More details please reference the official data binding document.
How would I go about implementing this?
Let's say this is my model:
public interface IAnimal
{
string Name { get; }
}
public class Fish : IAnimal
{
public string Name { get; set; }
public int ScalesCount { get; set; }
}
public class Dog : IAnimal
{
public string Name { get; set; }
public string CollarManufacturerName { get; set; }
}
public class ViewModel
{
public ObservableCollection<IAnimal> Animals { get; set; }
public ViewModel()
{
this.Animals = new ObservableCollection<IAnimal>();
this.Animals.Add(new Fish { Name = "Carl", ScalesCount = 9000 });
this.Animals.Add(new Dog { Name = "Fifi", CollarManufacturerName = "Macrosoft" });
}
}
For the sake of the amount of code in this question please assume that INotifyPropertyChanged is implemented where necessary, and that the ViewModel is correctly initialized in the page.
How can I use my own corresponding DataTemplates? In WPF I would just define multiple DataTemplates without an x:Key but with a defined DataType and let the ListView chose which to use based on the type of the item. UWP doesn't like that; the compiler simply states Dictionary Item "DataTemplate" must have a Key attribute. So how do I accomplish my goal?
Current Attempt
My current attempt is to make a custom DataTemplateSelector, which seems rather straight forward.
public class MyDataTemplateSelector: Windows.UI.Xaml.Controls.DataTemplateSelector
{
public ObservableCollection<TemplateMatch> Matches { get; set; }
public DataTemplateSelector()
{
this.Matches = new ObservableCollection<TemplateMatch>();
}
protected override DataTemplate SelectTemplateCore(object item)
{
return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template;
}
}
public class TemplateMatch
{
public Type TargetType { get; set; }
public DataTemplate Template { get; set; }
}
Define it in XAML like this:
<ListView ItemsSource="{x:Bind ViewModel.Animals}">
<ListView.ItemTemplateSelector>
<cmp:MyDataTemplateSelector>
<cmp:MyDataTemplateSelector.Matches>
<cmp:TemplateMatch TargetType="model:Dog" Template="{StaticResource DogTemplate}"/>
<cmp:TemplateMatch TargetType="model:Fish" Template="{StaticResource FishTemplate}"/>
</cmp:MyDataTemplateSelector.Matches>
</cmp:MyDataTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Unfortunately when I run this, an Exception occurs during runtime, stating Failed to create a 'Ui.Components.TemplateMatch' from the text 'model:Dog'. So it seems binding to a Type property is not that easy.
Any help is appreciated!
Please note that I'd like to use a property of type Type, as opposed to string where I would pass the CLR type name and using reflection to invoke the type, mostly because I don't want mixed CLR and XML namespaces appear in XAML. If you can find a way to invoke the type using the XML namespace, I'll gladly take that as an answer.
I found workaround. If you able to create instances of these types - you can use it for detecting types:
[ContentProperty(Name = nameof(Matches))]
public class TypeTemplateSelector : DataTemplateSelector
{
public ObservableCollection<TemplateMatch> Matches { get; set; }
public TypeTemplateSelector()
{
this.Matches = new ObservableCollection<TemplateMatch>();
}
protected override DataTemplate SelectTemplateCore(object item)
{
return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent;
}
}
[ContentProperty(Name = nameof(ItemOfType))]
public class TemplateMatch
{
public object ItemOfType { get; set; }
public DataTemplate TemplateContent { get; set; }
}
XAML:
<controls:TypeTemplateSelector>
<controls:TemplateMatch TemplateContent="{StaticResource FishTemplate}">
<models:Fish/>
</controls:TemplateMatch>
<controls:TemplateMatch TemplateContent="{StaticResource DogTemplate}">
<models:Dog/>
</controls:TemplateMatch>
</controls:TypeTemplateSelector>
The clue is in the error message.
Failed to create a 'Ui.Components.TemplateMatch' from the text
'model:Dog'
Note the 'model:Dog' is coming to your selector as text not a type.
Change your TemplateMatch class TargetType property to string instead of type like this:-
public class TemplateMatch
{
public string TargetType { get; set; }
public DataTemplate Template { get; set; }
}
Then change your template selector class to read
public class MyDataTemplateSelector : DataTemplateSelector
{
public ObservableCollection<TemplateMatch> Matches { get; set; }
public MyDataTemplateSelector()
{
Matches = new ObservableCollection<TemplateMatch>();
}
protected override DataTemplate SelectTemplateCore(object item)
{
return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template;
}
}
Finally change your xaml to read
<ListView ItemsSource="{x:Bind ViewModel.Animals}">
<ListView.ItemTemplateSelector>
<cmp:MyDataTemplateSelector>
<cmp:MyDataTemplateSelector.Matches>
<cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Dog" Template="{StaticResource DogTemplate}"/>
<cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Fish" Template="{StaticResource FishTemplate}"/>
</cmp:MyDataTemplateSelector.Matches>
</cmp:MyDataTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
The point is to forget trying to pass it to your selector as a type, and pass the typename as a string instead (Full namespace not Xaml namespace).
I'm new to LINQ and WPF and I'm trying to bind a custom query from LINQ and the rows barely appear although when debugging the IEnumerable<OrderSummary> orderSummary has info in it. I believe the empty rows showing are the amount of rows returned from the query:
XAML:
<DataGrid Name="dgrOrders" Margin="59,54,161,285" />
Code Behind:
OrderITDataClassesDataContext dc = new OrderITDataClassesDataContext();
IEnumerable<OrderSummary> orderSummary = dc.ExecuteQuery<OrderSummary>("SELECT * FROM [Order]",1);
dgrOrders.ItemsSource = orderSummary;
public class OrderSummary
{
int OrderId { get; set; }
DateTime OrderDate { get; set; }
int CustomerId { get; set; }
}
I suppose you should use properties instead of fields. Check the examples on MSDN
public class OrderSummary
{
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public int CustomerId { get; set; }
}
The second thing you may try - specify the dgrOrders DataContext, not the ItemSource.