I need to dig into a nested observable collection in UWP, which consists another observable collection inside it, and then bind it to my XAML.
How could I do it?
Allen Rufolo's Solution works. But Here is another way of approaching this.
x:Bind is newly implemented and available for UWP. My Answer is based on x:Bind
Sample Classes
public class MainItems
{
public string ItemName { get; set; }
public ObservableCollection<SubItems> SubItemsList { get; set; }
}
public class SubItems
{
public string SubItemName { get; set; }
}
Sample Data
ObservableCollection<MainItems> _data = new ObservableCollection<MainItems>();
for (int i = 1; i <= 5; i++)
{
MainItems _mainItems = new MainItems();
_mainItems.ItemName = "Main" + i.ToString();
_mainItems.SubItemsList = new ObservableCollection<SubItems>();
for (int j = 1; j <= 3; j++)
{
SubItems _subItems = new SubItems()
{
SubItemName = "SubItem" + i.ToString()
};
_mainItems.SubItemsList.Add(_subItems);
}
_data.Add(_mainItems);
}
My XAML
<ListView x:Name="MyMainList">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MainItems">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{x:Bind ItemName}" />
<ListView ItemsSource="{x:Bind SubItemsList}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:SubItems">
<TextBlock Foreground="Red" Text="{x:Bind SubItemName}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
x:Bind gives you an easy way to Bind your Nested Observable Collection
Output
A code example of your observable collections would help but you could do something like this...
public class MyViewModel
{
public ObservableCollection<MyObject> MyObjectCollection { get; set;}
}
public class MyObject
{
public string ObjectName {get; set;}
public ObservableCollection<AnotherObject> AnotherObjectCollection { get; set; }
}
And in your XAML you can bind to these collection similar to this
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView x:Name="ListView1" Grid.Column="0"
ItemsSource="{Binding MyObjectCollection}">
<ListView.ItemTemplate>
<Datatemplate>
<TextBlock Text="{Binding ObjectName}"/>
</Datatemplate
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column=1 DataContext="{Binding ElementName=ListView1, Path=SelectedItem}">
<ListView ItemsSource="{Binding AnotherObjectCollection}"/>
</Grid>
</Grid>
In this example, the DataContext of the second Grid is bound to the selected item in ListView1.
I am not sure that I get what do you need, but I guess that it could be the same as for WPF.
Check out questions and answers for the next questions:
Binding nested ItemsControls to nested collections
Nested ObservableCollection data binding in WPF
WPF Binding on Nested ItemControls with Sub Collection
Databinding for nested collections in XAML (WPF and Silverlight)
Related
I'm building a UWP app using C# and having trouble getting multiple listviews to scroll simultaneously.
I have 3 listviews: ListViewA, ListViewB and ListViewC.
If I scroll ListViewA, then both ListViewB and ListViewC should scroll.
If I scroll ListView B, then ListViewA and ListViewC will scroll, and the same is true if I scroll ListViewC.
I've done the usual searching and trying examples here on Stackoverflow, along with following links provided by other members. Still not getting the solution to this problem. Hoping someone here might be able to shed some light, or offer some insight on where to look?
I'm using an MVVM approach to populate the listviews and that functions as expected.
It's now just getting all 3 listviews to scroll at once. Let me know if you need any additional info. Thanks.
public class TestModel
{
int ID {get; set;}
string ColumnA {get; set;}
string ColumnB {get; set;}
string ColumnC {get; set;}
}
public class MainPage : Page
{
public TestModelViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
ViewModel = new TestModelViewModel("Filename=TestModelDB.db");
}
}
<ListView x:Name="ListViewA" ItemsSource="{x:Bind ViewModel.CollectionOfTestModelData, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:TestModelViewModel" >
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind ColumnA, Mode=OneWay}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView x:Name="ListViewB" ItemsSource="{x:Bind ViewModel.CollectionOfTestModelData, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:TestModelViewModel" >
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind ColumnB, Mode=OneWay}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView x:Name="ListViewC" ItemsSource="{x:Bind ViewModel.CollectionOfTestModelData, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:TestModelViewModel" >
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind ColumnC, Mode=OneWay}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
First,you can subscribe PointerEntered event to judge which listView is scrolled.Below I only use ListViewA and ListViewB as an example.
public bool ScrollViewerAScrolled = false;
public bool ScrollViewerBScrolled = false;
ListViewA.PointerEntered += ListViewA_PointerEntered;
ListViewB.PointerEntered += ListViewB_PointerEntered;
private void ListViewB_PointerEntered(object sender, PointerRoutedEventArgs e)
{
ScrollViewerBScrolled = true;
ScrollViewerAScrolled = false;
}
private void ListViewA_PointerEntered(object sender, PointerRoutedEventArgs e)
{
ScrollViewerAScrolled = true;
ScrollViewerBScrolled = false;
}
Then,you can get the ScrollViewer in the listView by VisualTreeHelper(Need to be called after the ListView is loaded).
And subscribe the ViewChanged event.For example,When ListViewA scrolls,the event will be triggered,you can scoll ListViewB and ListViewC in this event.The same applies to ListViewB and ListViewC.
var scrollViewerA = FindVisualChild<ScrollViewer>(ListViewA, "ScrollViewer");
var scrollViewerB = FindVisualChild<ScrollViewer>(ListViewB, "ScrollViewer");
scrollViewerA.ViewChanged += (s, e) =>
{
if (ScrollViewerAScrolled)
{
scrollViewerB.ChangeView(null, scrollViewerA.VerticalOffset, null, false);
}
};
scrollViewerB.ViewChanged += (s, e) =>
{
if (ScrollViewerBScrolled)
{
scrollViewerA.ChangeView(null, scrollViewerB.VerticalOffset, null, false);
}
};
Here is how to get the ScrollViewr in ListView:
protected T FindVisualChild<T>(DependencyObject obj, string name) where T : DependencyObject
{
//get number
int count = VisualTreeHelper.GetChildrenCount(obj);
//Traversing each object based on the index
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
//According to the parameters to determine whether we are looking for the object
if (child is T && ((FrameworkElement)child).Name == name)
{
return (T)child;
}
else
{
var child1 = FindVisualChild<T>(child, name);
if (child1 != null)
{
return (T)child1;
}
}
}
return null;
}
I have an ItemsControl who has an ItemsSource that is an ObservableCollection. The DataTemplate contains Label controls. My goal is to set the Content property of each of these Labels to the elements in the ObservableCollection but right now, the Content is entirely blank for each of the Label.
It is worth noting that this ItemsControl is nested within another, parent ItemsControl, but let me show:
<ItemsControl ItemsSource={Binding StudentCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
// This is the ItemsControl that is not working properly with the Labels
<ItemsControl ItemsSource="{Binding StudentActivitiesCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.Template>
</ItemsControl>
This is my StudentsActivities class:
public class StudentActivities : INotifyPropertyChanged
private string sport;
public string Sport
{
get
{
return this.sport;
}
set
{
this.sport = value;
OnPropertyChanged("Sport");
}
}
}
}
And my working View Model:
private ObservableCollection<StudentActivities> studentActivitiesCollection;
public ObservableCollection<StudentActivities> StudentActivitiesCollection
{
get
{
if (studentActivitiesCollection == null)
studentActivitiesCollection = new ObservableCollection<StudentActivities>();
return studentActivitiesCollection;
}
}
This is the method I am using to populate my ObservableCollection in my ViewModel:
private void PopulateStudentActivitiesCollection(ObservableCollection<Student> Students)
{
foreach (Student s in Students)
{
StudentActivitiesCollection.Add(new StudentActivities () { Sport = StudentSport });
}
}
}
Change
<ItemsControl ItemsSource={StudentCollection}">
to
<ItemsControl ItemsSource={Binding StudentCollection}">
and
<Label Content="{Binding Sport, UpdatedSourceTrigger=PropertyChanged}"/>
to
<Label Content="{Binding Sport}"/>
The last change is not needed but not necessary either.
I have a Json file saved in "IsolatedStorage" (json.html) and i need populate a "ListBox" with the field "FName" (from Json file).
The Json below, is stored in file (json.html) and saved in
IsolatedStorage. The file will be changed as necessary (synchronized
with web server). So I need that fields as "FNome" and "FEstado"
received information from "json.html".
My Json file:
{"xId":"52","result":{"type":"Basico.Bean.MunicipioClass.TMunicipio","id":1,"fields":{"FRefCount":0,"FId":52,"FNome":"Sumare","FEstado":"SP","FPais":"Brasil"}}}
My class from Json:
public class Fields
{
public int FId { get; set; }
public string FNome { get; set; }
public string FEstado { get; set; }
public string FPais { get; set; }
}
My ListBox:
<ListBox x:Name="listBox1"
Height="192" Width="456"
HorizontalAlignment="Center"
VerticalAlignment="Top"
BorderThickness="5"
Padding="5"
BorderBrush="White"
FontSize="30"
HorizontalContentAlignment="Stretch" Foreground="{x:Null}">
<ListBox.Background>
<SolidColorBrush Color="White" Opacity="0.5"/>
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding FNome}"/>
<TextBlock Grid.Column="10" Text="{Binding FEstado}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm assuming you are not using MVVM design pattern or anything "fancy", so here's (almost) minimal working example for you to study.
First, right-click on your project, select Manage NuGet Packages and install Json.NET dependency. Json.NET package helps you handle JSON formatted data.
After installation, this would be your MainWindow.xaml. Here you define your ListBox and template you want to use when showing each ListBox item.
<Window x:Class="MyWpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="100"
Width="420">
<Grid>
<!-- Your ListBox, notice the ItemsSource -->
<ListBox
Height="50"
Width="400"
FontSize="30"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Items}">
<!-- Template to display each item -->
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Bind UI controls to properties you want to display -->
<TextBlock Grid.Column="0" Text="{Binding FId}"/>
<TextBlock Grid.Column="1" Text="{Binding FPais}"/>
<TextBlock Grid.Column="2" Text="{Binding FNome}"/>
<TextBlock Grid.Column="3" Text="{Binding FEstado}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And your code-behind, namely MainWindow.xaml.cs. For clarity, json is defined as const string.
using System.Windows;
using System.Collections.ObjectModel;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MyWpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
// Your JSON string
const string json =
"{'xId':'52'," +
" 'result':{" +
" 'type':'Basico.Bean.MunicipioClass.TMunicipio'," +
" 'id':1," +
" 'fields':{" +
" 'FRefCount':0," +
" 'FId':52," +
" 'FNome':'Sumare'," +
" 'FEstado':'SP'," +
" 'FPais':'Brasil'" +
" }" +
" }" +
"}";
// Parse as JObject
JObject jObj = JObject.Parse(json);
// Extract what you need, the "fields" property
JToken jToken = jObj["result"]["fields"];
// Convert as Fields class instance
Fields fields = jToken.ToObject<Fields>();
// Assign to Items property, which is used as ListBox's ItemsSource
// In real application you'd probably want to have more than one item :)
Items = new ObservableCollection<Fields>() { fields };
}
public ObservableCollection<Fields> Items { get; set; }
}
}
And then, of course, you need you Fields class, too. You can "map" JSON to C# class autamagically if property names match. If you want to use other name in your class properties, say plain Id instead of FId, then you need to mark the property with JsonProperty attribute. For example [JsonProperty(PropetyName = "FId)].
public class Fields
{
[JsonProperty(PropertyName = "FId")]
public int FId { get; set; }
public string FNome { get; set; }
public string FEstado { get; set; }
public string FPais { get; set; }
}
Running the "application" gives you the following output
If I have a Model like this:
public class LayerViewModel
{
public LayerViewModel(string name, string alias, UIElement representation)
{
Name = name;
Alias = alias;
Representation = representation;
}
public string Name { get; set; }
public string Alias { get; set; }
public UIElement Representation
{
get; private set;
}
}
Using the following DataTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding Representation, Mode=OneTime}"/>
<TextBlock
VerticalAlignment="Center"
Grid.Column="1" Text="{Binding Alias}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Margin="16 21 0 20" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
If you bind a collection of these models to a ListBox, navigate away and back to the ListBox's containing page, your application will crash. This is because LayerViewModel.Representation is not released from the VisualTree even if the ListBox's ItemSource is set to null when its containing page is being unloaded. I used DependencyObject.GetVisualAncestors() to confirm this.
How do I get a ListBox to completely frees all of the UIElements that comprsises of its VisualTree? Setting its ItemsSource to null doesn't achieve this.
Im learning about data binding on windows phone , so far i've been able to bind single objects to the visual side of the app, but now im trying to understand how i can get visual elements to be created according to the number of objects i have on a list
I have a class for a person
public class Person
{
public Person() { }
public string name { get; set; }
public string fotoPath { get; set; }
}
and i have a class for the collection of persons
public class PersonCollection
{
public PersonCollection() { }
public List<Person> personGroup { get; set; }
}
Then i have my page's code behind, where i generate my list of persons
public partial class TestPage : PhoneApplicationPage
{
public TestPage()
{
InitializeComponent();
Loaded += TestPage_Loaded;
}
void TestPage_Loaded(object sender, RoutedEventArgs e)
{
PersonCollection lista = new PersonCollection();
lista.personGroup.Add(new Person(){name = "Mr.T", fotoPath = "/images/foto1.jpg"});
lista.personGroup.Add(new Person(){name = "John", fotoPath = "/images/foto2.jpg"});
}
}
on my page i want to have a grid that shows on each cell a photo and the name of the person, for each person on my list(2 persons per line). As far as i understood i'll be needing to use DataTemplate, but for now my efforts have failed.
can anyone give me some pointers?
Here's how you could present 2 people per line. First, put the source collection into groups of 2:
List<Tuple<Person, Person>> groupedItems = lista.personGroup
.GroupBy(item => lista.personGroup.IndexOf(item) / 2)
.Select(grp => new Tuple<Person, Person>(grp.First(), grp.Skip(1).FirstOrDefault()))
.ToList();
items.ItemsSource = groupedItems;
Now use a DataTemplate that presents "Item1" and "Item2" in left and right columns:
<ItemsControl x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ColumnTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}" Width="150" />
<Image Source="{Binding fotoPath}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding Item1}"
ContentTemplate="{StaticResource ColumnTemplate}" />
<ContentControl Grid.Column="1"
Content="{Binding Item2}"
ContentTemplate="{StaticResource ColumnTemplate}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>