Reference Implementation for MVVM with Hierarchical Objects - c#

I read a couple of MVVM Tutorials (1,2,3,4) but I can't find a proper answer to my problem.
My model is a hierarchical tree like this:
public class MyModel {
public string Name { get; set; }
public Guid Id { get; set; }
public List<MyModel> Children { get; set; }
}
Now I want to display this in a TreeView like this (each entry if from the type MyModel):
Root
|-SubElement
|-SubElement2
| |-SubSubElement1
|-SubElement3
Now my questions:
How would the corresponding View-Model look like? Is there a reference implementation for Collections?
Should the Model or the View-Model or both implement INotifyPropertyChanged, INotifyCollectionChanged or should the List of Children be of the Type ObservableCollection<MyModel>? If so, when to call OnPropertyChanged()?
Now for the displaying:
I would like to display the Name of the object in the TreeView, but when selecting the element, I want to be notified of that event (and get the corresponding element). The ViewModel must somehow support this. Something like holding a list (say MyModelList) to which I can bind in XAML, like:
<TreeView ...
ItemsSource="{Binding ElementName=MyModelList, Path=SelectedItem.Name}"
... >
and where I can use InputBindings or EventTriggers.

Try using Hierarchical data template for showing tree structure within TreeView
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"
DataType="{x:Type local:MyModel}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Grid .Resources>
<TreeView ItemsSource="{Binding MyModelList}"/>
</Grid>
Go through this MSDN Blog to get a better understanding - TreeView and HierarchicalDataTemplate, Step-by-Step

How would the corresponding View-Model look like?
In this situation will be Model and the collection, which will be in the ViewModel.
Should the Model or the View-Model or both implement INotifyPropertyChanged
I advise you to use ObservableCollection<T>, because all changes for Collection will automatically (add, remove, etc) appear in it. Properties that will be in Model must implement INotifyPropertyChanged interface. I personally do it on the side of the Model, but there are opponents of this idea, so where to implement it - your personal desire.
Example of this idea:
Model
public class MyModel : NotificationObject // he implement INotifyPropertyChanged
{
...
}
ViewModel
public ObservableCollection<MyModel> MyObjects
{
get;
set;
}
// in Constructor of ViewModel
MyLogObjects = new ObservableCollection<MyModel>();
When selecting the element, I want to be notified of that event
In WPF and Silverlight TreeView.SelectedItem is a readonly property. In this case you can see this example:
<StackPanel x:Name="LayoutRoot">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" ItemsSource="{Binding Path=Children}" >
<TextBlock Text="{Binding Path=Name}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView x:Name="myTreeView"
Width="400"
Height="300"
ItemsSource="{Binding HierarchicalAreas}"
ItemTemplate="{StaticResource NameTemplate}"
local:Attached.TreeViewSelectedItem="{Binding SelectedArea, Mode=TwoWay}" />
</StackPanel>
Prefix sdk: needed for Silverlight
For notified event you can create an PropertyChangedEventHandler event handler for ViewModel in constructor, to know that the SelectedItem changed:
public MyViewModel()
{
MyModel = new MyModel();
MyModel.PropertyChanged += new PropertyChangedEventHandler(MyModel_PropertyChanged);
}
private void MyModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("SelectedItem"))
{
System.Diagnostics.Debug.WriteLine("SelectedItem changed");
}
}

Related

WPF TreeView does not display items from observable collection

I have a TreeView that I'm binding to an observable collection, which has one observable collections inside of it.
I have the following classes:
the base class:
public class BaseObject : BindableBase, IBaseObject
{
public string Name { get; set; }
}
and 2 other class for directory and files which inherit from this BaseObject. Right now the difference between File and Directory class is the collection:
public ObservableCollection<BaseObject> DirectoryItems
{
get => _directoryItems;
set
{
_directoryItems = value;
RaisePropertyChanged();
}
}
In my view model I add files and directories to fill the observable collection, but the problem is that the TreeView is empty.
The following is the xaml:
<UserControl.Resources>
<DataTemplate
x:Key="ProjectExplorerFileTemplate">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
<HierarchicalDataTemplate
x:Key="ProjectExplorerFolderTemplate"
ItemTemplate="{StaticResource ProjectExplorerFileTemplate}"
ItemsSource="{Binding Path=DirectoryItems}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</UserControl.Resources>
<Grid Margin="10">
<TreeView
Name="ProjectExplorerTreeView"
ItemTemplate="{StaticResource ProjectExplorerFolderTemplate}"
ItemsSource="{Binding Path=ProjectExplorerNodes}" />
</Grid>
I am not sure where I am doing something wrong.
In the observable collection I have all data that I need.
The problem it is in the xaml?
EDIT
ProjectExplorerNodes is the observable collections where I add the files and directories from root path, in view model.
public ObservableCollection<BaseObject> ProjectExplorerNodes
{
get => _projectExplorerNodes;
set
{
_projectExplorerNodes = value;
RaisePropertyChanged();
}
}

WPF Data Binding an ObservableCollection (or LinkedList) to a WrapPanel

Ok. First of all, I've looked at a bunch of other questions and a ton of other places on the internet on how to do this, and none of them are helping, so please don't mark this as a duplicate, and also, heads up cause I probably made a really stupid mistake.
I'm trying to bind an ObservableCollection to a WrapPanel using an ItemsControl and a DataTemplate. The following is my XAML Code:
<ItemsControl x:Name="wPanel">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--<Border BorderBrush="DarkGray" Background="Transparent">-->
<StackPanel MinWidth="250">
<Grid>
<TextBlock Text="{Binding address}" />
</Grid>
<Grid>
</Grid>
</StackPanel>
<!--</Border>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note: I did have (for the ItemsSource property of ItemsControl) {Binding properties}
This is my declaration of properties:
public ObservableCollection<Property> properties = new ObservableCollection<Property>();
And the Property Class is the following plus many more properties:
private string address { get; set; }
private string city { get; set; }
private string postcode { get; set; }
private double price { get; set; }
private LinkedList<Tennant> tennants { get; set; }
...
I thought I had solved the problem with this,
Binding binding = new Binding();
binding.Source = properties;
wPanel.SetBinding(ItemsControl.ItemsSourceProperty, binding);
But, then the line in the xaml: <TextBlock Text="{Binding address}" /> didn't work.
Then I came to the conclusion that it had to do with the properties object, and how it wouldn't bind unless I did it through code.
What am I doing wrong, for it not bind through XAML, etc.? What do I need to change about the properties object, or what do I need to do?
Thanks in advance.
You can bind ItemsControl's ItemsSource to properties just like #AjS answer. But before that, you need to change properties declaration to be property instead of field.
public ObservableCollection<Property> properties { get; set; }
And also address property of your Property class need to be public.
public string address { get; set; }
Isn't the 'properties' a property on the window?
If you are binding in xaml, make sure you use declare 'properties' as 'Property', set datacontext of window to itself and then set binding path:-
<ItemsControl x:Name="wPanel" ItemsSource="{Binding properties}">
this.DataContext=this; //set datacontext on parent window or control
If you are doing it in code, setting the ItemsSource directly on wPanel should work:-
wPanel.ItemsSource=properties;
This is easy trick for check Binding. Every WPF developer must know this. For example:
<TextBlock Text="{Binding}" />
Run and see it.
If TextBlock has any object in DataContext, object class name displayed in TextBlock.
If DataContext has empty. TextBlock is Empty

Binding to collections in WPF

So I finally decided to move from WinForms to WPF, and I'm having quite an interesting journey. I have a simple application in which I bind an ObservableCollection to a ListBox.
I have an Animal entity:
namespace MyTestApp
{
public class Animal
{
public string animalName;
public string species;
public Animal()
{
}
public string AnimalName { get { return animalName; } set { animalName = value; } }
public string Species { get { return species; } set { species = value; } }
}
}
And an AnimalList entity:
namespace MyTestApp
{
public class AnimalList : ObservableCollection<Animal>
{
public AnimalList() : base()
{
}
}
}
And finally here's my main window:
<Window x:Class="MyTestApp.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyTestApp"
Title="Window3" Height="478" Width="563">
<Window.Resources>
<local:AnimalList x:Key="animalList">
<local:Animal AnimalName="Dog" Species="Dog"/>
<local:Animal AnimalName="Wolf" Species="Dog"/>
<local:Animal AnimalName="Cat" Species="Cat"/>
</local:AnimalList>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical" Margin="10,0,0,0">
<TextBlock FontWeight="ExtraBold">List of Animals</TextBlock>
<ListBox ItemsSource="{Binding Source={StaticResource animalList}, Path=AnimalName}"></ListBox>
</StackPanel>
</Grid>
Now when I run the application, I see the listbox populated with three items: "D", "o", and "g" instead of "Dog", "Wolf", and "Cat":
I have a strong feeling that I'm doing something stupid somewhere (AnimalList constructor maybe?) but I can't figure out what it is. Any help is appreciated.
You need to set the DisplayMemberPath (as opposed to the Path property in the binding).
<Grid>
<StackPanel Orientation="Vertical" Margin="10,0,0,0">
<TextBlock FontWeight="ExtraBold">List of Animals</TextBlock>
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}" DisplayMemberPath="AnimalName"></ListBox>
</StackPanel>
</Grid>
Since you are binding to a list of Animal objects, DisplayMemberPath specifies the name of the property in the Animal class that you want to show up as a list item.
If the property is itself an object, you can use dot notation to specify the full path to the property you want displayed ie..
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}" DisplayMemberPath="PropertyInAnimalClass.PropertyInTheChildObject.PropertyToDisplay" />
You're binding your listbox to the animalname. Instead you should bind your listbox to your collection:
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}"></ListBox>
Notice that I've removed the path=AnimalName from the binding.
Now you will see the class name, since the ListBox doesn't know how to display an Animal and therefore it calls its ToString-method.
You can solve this by giving it an ItemTemplate like so:
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding AnimalName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Inside the itemtemplate your DataContext is an instance of Animaland you can then bind to the properties on that instance. In my example I have bound the AnimalName, but you basically construct any template you want using normal XAML-controls and binding to the different properties of your bound object.

How to choose which ObservableCollection to bind to a listbox?

Is that even possible? I have two ObservableCollections. I want to bind and populate a listbox with one of them. For example let's say that we have 2 buttons - one for Twitter and one for Facebook. Clicking on a Facebook button it will populate listbox with friend's names from facebook observable collection and it will bind it. Clicking on Twitter it will populate listbox with Twitter followers and populate listbox and bind it.
How to choose which collection will be populated in listbox?
I would just use one observable collection and fill based on the users choice. You could also fill it with the names from both sources and have a filter to filter out one or the other (apparently you need a wrapper object where you can indicate whether the name is a facebook friend or twitter follower).
Edit: Here is some quick code example of how you can do it:
public interface ISocialContact
{
string Name { get; }
}
public class FacebookContact : ISocialContact
{
public string Name { get; set; }
public string FacebookPage { get; set; }
}
public class TwitterContact : ISocialContact
{
public string Name { get; set; }
public string TwitterAccount { get; set; }
}
Then in your data context:
public ObservableCollection<ISocialContact> Contacts { get; set; }
...
Contacts = new ObservableCollection<ISocialContact> {
new FacebookContact { Name = "Face", FacebookPage = "book" },
new TwitterContact { Name = "Twit", TwitterAccount = "ter" }
};
And in your xaml:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:FacebookContact}">
<TextBlock Text="{Binding FacebookPage}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TwitterContact}">
<TextBlock Text="{Binding TwitterAccount}"/>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Contacts}" Width="100" Height="100"/>
</Grid>
This will apply the appropriate template to each object in your collection. So you can have collection with just facebook contacts or just twitter contacts or mixed.
Also note: You do not need the common interface. It will also work if you just make your ObservableCollection of type object. But given that they are being displayed by the same app in the same list box indicates that you can find some kind of common base and either can create a comon interface or base class.
In your ViewModel, create a property that exposes one or the other ObservableCollection, and swap it out when the button is clicked:
private ObservableCollection<string> _twitterFriendList;
private ObservableCollection<string> _facebookFriendList;
private ObservableCollection<string> _selectedFriendList;
public ObservableCollection<string> SelectedFriendList
{
get { return _selectedFriendList; }
set
{
if (value != _selectedFriendList)
{
_selectedFriendList = value;
RaisePropertyChanged("SelectedFriendList");
}
}
}
void TwitterButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _twitterFriendList;
}
void FacebookButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _facebookFriendList;
}
Then in your XAML you can just bind to the property:
<ListBox ItemsSource="{Binding SelectedFriendList}"/>
A non-elegant way of accomplishing this is to put 2 listboxes in the same location and bind 1 to the twitter collection and the other to the facebook collection. Bind their visibility to a property that changes based upon the button clicks. Personally, I'd have 2 radio buttons and display the listbox based upon which one is selected.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=TwitterCollection}" SelectedItem="{Binding Path=SelectedTwitterItem}" Visibility="{Binding Path=IsTwitterSelected, Converter={StaticResource visibilityConverter}}" />
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=FacebookCollection}" SelectedItem="{Binding Path=SelectedFacebookItem}" Visibility="{Binding Path=IsFacebookSelected, Converter={StaticResource visibilityConverter}}" />
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="rdoOptions" Content="{Binding Path=TwitterLabel}" IsChecked="{Binding Path=IsTwitterSelected}" />
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="rdoOptions" Content="{Binding Path=FacebookLabel}" IsChecked="{Binding Path=IsFacebookSelected}" />

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