WPF - Binding class with Lists of strings to a ListBox - c#

I have a problem with binding multiple Lists to a ListBox. I want that every List has a different DataTemplate with a different color.
I have following model classes
public class Users
{
public Members Members{ get; set; }
}
public class Members
{
public List<string> Moderators { get; set; }
public List<string> Viewers { get; set; }
}
I have following ViewModel with INotifyPropertyChanged
private Users users;
public Users Users
{
get { return users; }
set
{
users= value;
RaisePropertyChanged("Users");
}
}
And I'm binding to this ListBox
<ListBox ItemsSource="{Binding Users.Members.Viewers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I only have that one List bound to the ListBox. It works great but I want the other list also bound to the same ListBox. Besides that I want that Moderators have a different template.
I tried many different things but nothing seemed to work.

Instead of removing the names from the origination object why not keep it and specify different colors based off of the originating class?
Besides that I want that Moderators have a different template.
If you only have strings that is impossible. Remember the listbox ultimately sees only one list; so in one list, how is it possible to tag a string as either moderator or viewer?
a different DataTemplate with a different color.
If there are only strings I suggest you create wrapper classes, one for moderators and one for viewers, then project the strings into those classes to be held. Then you can follow my suggestion/example below.
Via the use of the Composite collection to hold different items (or one could actually use a base class list or a interface list if the instances have that commonality) and then have specialized data templates which look for the originating class, it can be done.
Example
I have two classes one named Ships and one named Passage. Note that both classes both have a Name property, but one could use something other than Name for either or both in the templates.
Below I define the data templates and my listbox.
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type c:Ship}">
<TextBlock Text="{Binding Path=Name}"
Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type c:Passage}">
<TextBlock Text="{Binding Path=Name}"
Foreground="Blue" />
</DataTemplate>
</Grid.Resources>
<ListBox Name="myListBox"
Height="300"
Width="200"
ItemsSource="{Binding MyCompositeCollection}">
</ListBox>
</Grid>
So what will happen is that my ships will be red and the passages will be blue.
Here is my code in the VM:
private CompositeCollection _MyCompositeCollection;
public CompositeCollection MyCompositeCollection
{
get { return _MyCompositeCollection; }
set { _MyCompositeCollection = value; OnPropertyChanged("MyCompositeCollection"); }
}
Here I load the composite collection:
var temp = new CompositeCollection();
Ships.ForEach(sh => temp.Add(sh));
Passages.ForEach(ps => temp.Add(ps));
MyCompositeCollection = temp;

In order to combine two Lists and set it to ItemsSource use CompositeCollection.
WPF can set distinct template by using ItemTemplateSelector but it entails class to be diffrent in some way. Your type is string so it does not differ in any way. My hint is to create enum as follows
enum MemberType
{
Moderator,
Viewer
}
and following class:
class Person
{
public string Name{get;set;}
public MemberType Type{get;set;}
}
then change to this
public class Members
{
public List<Person> Moderators { get; set; }
public List<Person> Viewers { get; set; }
}
and eventually in ItemTemplateSelector
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate ViewerDataTemplate;
public DataTemplate ModeratorDataTemplate;
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var member = item as Person;
switch (member.Type)
{
case MemberType.Moderator:
return ModeratorDataTemplate;
case MemberType.Viewer:
return ViewerDataTemplate;
}
return null;
}
}

Related

Advanced MVVM scenario in UWP app - How to use a model class for multiple viewmodels?

I have been coding for over 4 years now... and time has come that I need to understand how to use MVVM in a perfect way, in order to be able to accomplish more difficult coding tasks.
More specifically, I would like to know how to use a model object for multiple viewmodels and views.
Sample scenario
Let's have an app that has two views: the first with a list of ItemModel objects (that shows all the objects available) and another one that has a list that only contains the favorites ones.
The ItemModel has an IsFavorite boolean property to determine whether or not it is one.
Until today, I have always accomplished scenarios like this in this way:
ItemModel class (a model-viewmodel class at the same time):
public class ItemModel : BaseBind // This class implements INotifyPropertyChanged
{
public ItemModel()
{
// ...
}
private bool isFavorite;
public Boolean IsFavorite
{
get { return isFavorite; }
set { SetProperty(ref isFavorite, value); OnPropertyChanged(); }
}
// All the model's and viewmodel's properties and functions are stored here...
public void GenericFunc()
{
App.Current.AppViewModel.GenericAppFunc(this);
}
public void FavoriteFunc()
{
App.Current.AppViewModel.FavoriteAppFunc(this);
}
public ItemModel Clone()
{
// Cloning the item with "new" constructor...
}
}
AppViewModel class:
public class AppViewModel
{
public AppViewModel()
{
// Initializing...
}
public ObservableCollection<ItemModel> ItemsList { get; set; }
public ObservableCollection<ItemModel> FavoritesList { get; set; }
public void UpdateFavorites()
{
FavoritesList.Clear();
foreach (var it in ItemsList)
{
if (it.IsFavorite) FavoritesList.Add(it.Clone());
}
}
public void GenericAppFunc(ItemModel item)
{
// ...
}
public void FavoriteAppFunc(ItemModel item)
{
// ...
}
}
First view, with all items listed:
<ListView ItemsSource="{x:Bind ViewModel.ItemsList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ItemModel">
<StackPanel>
<TextBlock Text="{x:Bind ItemTitle, Mode=OneWay}"/>
<Button Content="Generic func" Click="{x:Bind GenericFunc}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Second view, with only favorites listed:
<ListView ItemsSource="{x:Bind ViewModel.FavoritesList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ItemModel">
<StackPanel>
<TextBlock Text="{x:Bind ItemTitle, Mode=OneWay}"/>
<Button Content="Favorite func" Click="{x:Bind FavoriteFunc}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is my way of doing it all... having two completely independent lists and cloning objects everytime the user navigates to the favorites view.
It works, it always did it... BUT this is definitively NOT a good pattern.
To summarize, this is what I'm doing till now...
...and this is what I would like to achieve:
In order to have lightweight models like this...
public class ItemModel
{
public ItemModel()
{
// ...
}
public Boolean IsFavorite { get; set; }
// Only the model's properties and functions are stored here...
}
...and use it for multiple viewmodels/views.
Best regards and thank you for your attention.
In UWP there is no filtering of data on top of a single data source. While in WPF it was possible to use the same ObservableCollection of items and filter only when binding your views using a CollectionViewSource, it seems like the only solution is having two different collections like you are doing.
There is indeed no Filter property in the CollectionViewSource class for UWP so [...] you will have to filter the source collection itself (or use two different collections like you are already doing).
Surely you can remove the need to call UpdateFavorites() by hooking to the PropertyChanged event of your items and move them from one collection to the other when their IsFavorite property is set, like this:
public AppViewModel()
{
// Subscribing to changes in collection of items
ItemsList .CollectionChanged += ItemList_CollectionChanged;
}
private void ItemList_CollectionChanged(object sender, EventArgs e) {
//Here subscribe/unsubscribe to PropertyChanged event of each item
//as they come and go in the collection
}
private void Item_PropertyChanged(object sender, EventArgs e) {
//Here check IsFavorite property and add/remove the item from the favorites list.
//You can even instantiate another type of viewmodel object specific to favorites items.
}
Regarding the MVVM pattern, I would advice to have a lightweight ItemModel model like you suggest and have a collection of them stored in your model. Then you could have two collections, one with ItemViewMode objects and one with FavoriteItemViewModel objects in your AppViewModel. You could also have a single ItemViewModel class with an IsFavorite property and use triggers and styles to have them display differently.
I don't see a reason to clone.
What would be the problem with:
foreach (var it in ItemsList)
{
//if (it.IsFavorite) FavoritesList.Add(it.Clone());
if (it.IsFavorite) FavoritesList.Add(it);
}
or, just set up the FavoritesList as a filtered version:
public List<Item> FavoritesList => ItemsList.Where(x => x.IsFavorit);
as long as you Notify at the right actions this should work.

MVVM Binding Observable Collection to view?

In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.

Databinding ObservableCollection to a ListBox

I have been trying to figure out the proper way to bind an ObservableCollection of a class to a ListBox with a TextBox DataTemplate. I've tried to implement the code in WPF binding: Set Listbox Item text color based on property but that hasn't gotten me very far as of yet. I'm new to WPF DataBinding, having at most programatically set the ItemsSource in simple cases.
I have this class
public class item
{
public string guid;
public bool found;
public bool newItem;
public Brush color;
}
and the following ObservableCollection
public ObservableCollection<item> _items;
public Window()
{
InitializeComponent();
_items = new ObservableCollection<item>();
}
Elsewhere in the code I add items to the collection via
_items.Add(new item() { guid = sdr.GetString(0), found = false, newItem = false, color = Brushes.Red });
Here's simplified XAML for the ListBox
<ListBox x:Name="ListBox_Items">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text=GUID_HERE Foreground=COLOR_HERE/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've tried several different ways to get this to work properly, yet for none of them the ListBox is updating. Is anyone able to help point me in the right direction here?
Four things:
Your item class needs to use public properties:
public class item
{
public string guid { get; set; }
public bool found { get; set; }
public bool newItem { get; set; }
public Brush color { get; set; }
}
You need to set the ItemsSource to the collection, and set the current DataContext
public Window()
{
InitializeComponent();
DataContext = this;
_items = new ObservableCollection<item>();
ListBox_Items.ItemsSource = _items;
}
You need to update your DataTemplate to use the property names of your POCO
<ListBox x:Name="ListBox_Items">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding guid}" Foreground="{Binding color}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I think you forgot to bind your ListBox to collection itself.
Your XAML should look like:
<ListBox x:Name="ListBox_Items" ItemsSource="{Binding _items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text=GUID_HERE Foreground=COLOR_HERE/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And if you want to change properties of items in your collection (and that changes to appear on UI) you should implement INotifyPropertyChanged (see more at MSDN) interface in "item" class.

WinRT XAML data binding a List of objects that contain a List

I have a List of objects that contain another List. I want to bind both Lists to different controls (one nested within the other - a ListView as GridViewItem). But I can't get the xaml to work.
Very close to this question comes Binding List of Lists in XAML?.
And there is an article regarding this in the MSDN documentation:
How to bind to hierarchical data and create a master/details view - could be the solution, but I find it difficult to apply it to my code.
Other articles touch this topic, but not that good and also as a new user I'm not allowed to include more than two hyperlinks in a question.
My code looks similar to this (changed to city / restaurant scenario for clarity):
Model:
public class City
{
string Name { get; set; }
List<Restaurant> RestaurantList { get; set; }
//.. also a constructor with parameters for the properties and an overriding toString method that returns Name
}
public class Restaurant
{
string Name { get; set; }
List<Uri> UriList { get; set; }
//.. also a constructor with parameters for the properties and an overriding toString method that returns Name
}
Code-behind (LoadState method):
//.. getting a List of cities (with restaurants), that is being created in some model class
this.DefaultViewModel["Items"] = Cities;
Some people set the DataContext instead. I got this from the MSDN tutorials and it worked so far. But I'm not sure which is "better".
Okay now the XAML:
I want to have a GridView with the Cities as GridViewItems. Within one GridViewItem there's a Grid, displaying the City's Name in the top row and a ListView below. The ListView contains the Restaurants (only of that City!). The ListViewItems are only TextBlocks showing the Restaurant's Name.
I want only the Restaurants to be clickable.
Like this:
<!-- the following line is at the very top and the reason why it should work without setting DataContext explicitly -->
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
<!-- ... -->
<GridView Grid.Row="1" ItemsSource="{Binding Items}" SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="500" Width="200" Margin="50" Background="Gray">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}"/>
<ListView
Grid.Row="1"
ItemsSource="{Binding RestaurantList}" IsItemClickEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Tapped="Restaurant_Click"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This way only gray boxes are shown. When changing the TextBlock's binding to Text="{Binding}" then at least the Names of the Cities are shown. Which I don't understand and also don't want, because I guess the overriding of the toString methods is not meant to be used this way. The Names of the Restaurants don't show up in both cases.
Also, the scrolling somehow broke in this view, but that's a different story I suppose.
So: What's wrong with the data binding in the XAML?
The databinding engine needs public properties (the link is about WPF but the same concepts apply in WinRT):
You can bind to public properties, sub-properties, as well as
indexers, of any common language runtime (CLR) object.
But if you don't specify it explicit the compiler treats members by default "the most restricted access you could declare for that member" e.g. private in your case.
So you need to declare your properties as public:
public class City
{
public string Name { get; set; }
public List<Restaurant> RestaurantList { get; set; }
}
public class Restaurant
{
public string Name { get; set; }
public List<Uri> UriList { get; set; }
}

Problem with DataBinding and style [WP7]

I have been trying to bind listbox with an observableConnection in Xaml on WP7 with no luck. All I want to do is to make listbox to show an instance of my class that inherits from ObservableConnection and apply some style on listbox. I can do this from code like
public Storage.Categories tmp;
...
tmp = new Storage.Categories();
listBox1.ItemsSource = tmp;
but how to apply style on that?
Here is code:
<ListBox Height="497"
HorizontalAlignment="Left"
Margin="0,104,0,0"
Name="listBox1"
VerticalAlignment="Top"
Width="450">
namespace Genesa.Storage
{
public class Categories : ObservableCollection<Category>
{
public void LoadCategories()
{
// deserialize obiect
}
public void SaveCategories()
{
// serialize obiect
}
public Categories() : base()
{
LoadCategories();
}
}
public class Category
{
public Category() { }
public String name { get; set; }
public String description { get; set; }
public Category(String _name, String _description)
{
name = _name;
description = _description;
}
public override string ToString()
{
return String.Format("{0} - {1}", name, description);
}
}
}
You're going to want to use a DataTemplate. A data template let's you structure the items in your ListBox. For example:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Also, you might want to reconsider inheriting from ObservableCollection. If what you're doing is as simple as it looks above, you probably want to stick to creating a class which contains an ObservableCollection and which implements the INotifiyPropertyChanged interface. This is assuming you're using the MVVM design pattern. If you're not, feel free to disregard this suggestion. If you are implementing MVVM, you also want to make the Category class implement the INotifyPropertyChanged interface.
As Jared suggests, the most appropriate approach to your solution is to provide an ItemTemplate for the ListBox that defines the structure of each item in the ListBox, which enables you to bind directly to properties on your class, instead of having to override the ToString method. However, there is a small mistake in Jared's DataTemplate because it can only contain a single item, so you need to wrap the elements in some kind of container, as shown below:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You only need to implement the INotifyPropertyChanged on your Category class if the properties can change during the lifetime of that object. If the values are constant throughout it's lifetime, then there's no need.
usually the ObservableCollection is member of the ViewModel to which the View binds to. You don't have to inherit from ObservableCollection and the logic from Categories class can be placed inside ViewModel.
Then you need to set DataContext of Page or other object in hierarchy to be the ViewModel and then you can bind for example ListBox.ItemsSource to ViewModel.ObservableCollection.
After that DataTemplate will work in scope of Category (single item in ObservableCollection).
Regarding the logic of loading etc, there is usually one more layer responsible for these operations, which is injected to ViewModel, but if you don't want it, it's just fine.

Categories