Implementing TreeView using MVVM - c#

I'm new to the MVVM pattern and I have an assignment to implement a TreeView which acts like a work space viewer (sort of like eclipse's/vs's solution explorer).
The tree view can contain multiple types of items (like a file, a folder of other items etc).
I have created the Models (the Folder's model have Children (which can be any kind of item), Name etc).
Example model:
public class SystemFolder: IWorkspaceItem
{
string mTitle;
public ObservableCollection<IWorkspaceItem> Children { get; set; }
public string Path
{
get { return mTitle; }
set
{
mTitle = value;
OnPropertyChanged("Title");
}
}
//Constructor
public SystemFolder(string name, ItemType type, string path)
: base(name, type)
{
Path = path;
Children = new ObservableCollection<IWorkspaceItem>();
//add children here...
}
//Some more code here..
}
I've created a ViewModel for each model (Which all store the model's instance).
The ViewModels have the necessary properties that the view needs (like Title, Children etc).
Example ViewModel:
public class SystemFolderViewModel : TreeViewModelItem
{
SystemFolder mFolder;
ObservableCollection<TreeViewModelItem> mChildren;
public ObservableCollection<TreeViewModelItem> Children
{
get { return mChildren; }
}
//====================
// Constructor
//====================
public SystemFolderViewModel(SystemFolder folder, TreeViewModelItem parentWorkspaceItem)
: base(parentWorkspaceItem)
{
mFolder = folder;
mFolder.Attach(OnItemPropertyChanged);
}
public string Name
{
get { return mFolder.Name; }
set { Name = value; }
}
public string IconPath
{
get { return mFolder.ItemType.IconPath; }
set { IconPath = value; }
}
//Some more code here..
}
I've also implemented the View which defined the HierarchicalDataTemplate of each ViewModel.
It all works fine and I actually made a mockup.
Example of HierarchicalDataTemplate in View:
<HierarchicalDataTemplate
DataType="{x:Type local:SystemFolderViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="{Binding Path=IconPath}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
The whole initialization:
Workspace data_model = new Workspace("Main Workspace", PredefinedTypes.GetWorkspaceType(), Database.GetItems());
TreeViewModel vm = new TreeViewModel(data_model);
WorkspaceView viewer = new WorkspaceView(vm);
//add to grid etc..
Now my problem is that my program is dynamic. Meaning, items can be added to the TreeView in run-time.
Now let me explain how I understand MVVM should work and please correct me.
The Data model provides the view with items.
So my program should only add items to the data model.
The main ViewModel of the tree holds a DataModel instance which hold the workspace's main children.
Each item that is added to the model should automatically update the ViewModel which in turn should update the view.
Since I only update the DataModel, when I add a child to one of its items, the ViewModel should find the corresponding item in the ViewModel and add a new child to it.
Why do I have to have two seperate collections? The DataModel's Children and the ViewModel Children.
I need every data model to inherit from INotifyPropertyChanged so it will update its ViewModel.
Also as I said, there are data models which have their own children. If that collection changes I need the collection in the item's ViewModel to change.
This all seems kind of dumb to me. Why should I get through all this trouble? Why can't I just add items to the ViewModel which will update the view?
What am I missing?
How do I need to go about with this?
If you need more info, please ask me and I will provide/explain.
Thank you!
Dolev.

Related

WPF MVVM one view multiple object types

Currently I'm learning WPF with MVVM and have maybe a crazy idea...
I have several simple classes:
public class Car : IProduct
{
public int Id {get;set;}
public string Brand {get;set;}
// some custom properies
}
public class Seat : IProduct
{
public int Id {get;set;}
public string Brand {get;set;}
// some custom properties
}
Idea was that I have one editor view for diferent models.
public class ProductViewModel<T> : ViewModelBase, IProductViewModel<T> where T : IProduct
{
private T m_editorModel;
public T EditorModel
{
get { return m_editorModel; }
set
{
m_editorModel = value;
RaisePropertyChanged(() => EditorModel);
}
}
public Type ModelType
{
get { return typeof(T); }
}
}
Which can be afterwords set to view DataContext
viewModel = ViewModelFactory.CreateViewModel<IProductViewModel<Car>>();
view = ViewFactory.CreateView<ProductView>();
view.DataContext = viewModel;
// etc...
The problem is that I don't know is it possible or how to create in run time
ObservableCollection of same object EditorModel.
Is it maybe easier path to create for each class it's own view and viewmodel or something totally different?
In MVVM in general [I'm not speaking for everyone here], you don't want to be instantiating views from code. Instead we work with and manipulate data. To change views, we change view models and often set the connections between the two in simple DataTemplates:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UsersViewModel}">
<Views:UsersView />
</DataTemplate>
This way, we don't need to explicitly set any DataContexts. We can simply have a BaseViewModel property that each view model extends:
public BaseViewModel ViewModel
{
get { return viewModel; }
set { if (viewModel != value) { viewModel = value; NotifyPropertyChanged("ViewModel"); } }
}
We can change view models and therefore views like this:
ViewModel = new UsersView();
Then we can display the relating view in a ContentControl like this:
<ContentControl Content="{Binding ViewModel}" />
Finally, in my opinion, you really should create a view model for each view... the view model's sole job is to provide the data and functionality for each view. So unless you have multiple identical views, you'd need different view models. It is however possible to have one view model that all of the views bind to, but I'd advise against that for large applications.

WPF Linq to SQL Returning a Custom Object with Data Binding to DataGrid C# MVVM

I am trying to make the jump from WinForms to WPF and I'm trying to learn the correct way. I want to use a correct MVVM model. This is my first WPF project ever and I am having trouble with databinding with a linq result. I am aware that I could do this very easily in the code behind or even not use a custom object and return entire table form the linq query to the datagrid while still using a separate class. But I would like to stick to this format. I don't want to use a datatable.
Assume that I have a dbml with a "Job" table. I want to select columns "Job" (Linq seems to automatically rename this column to Job1), "Customer", and "Order_Date".
Here is my code:
namespace Custom_Teplate.Model
{
public class LINQResult
{
public System.String JobNum
{ get; set; }
public System.String CustomerName
{ get; set; }
public System.DateTime Order_Date
{get; set; }
public static LINQResult Create()
{
DataDataContext dc = new DataDataContext();
dynamic query = (from ddd in dc.Jobs
where (ddd.Status == "Active")
select new LINQResult
{
JobNum = ddd.Job1,
CustomerName = ddd.Customer,
Order_Date = ddd.Order_Date,
});
return query;
}
}
}
I add the namespace to my XMAL:
xmlns:c="clr-namespace:Custom_Teplate.Model"
<Window.Resources>
<c:LINQResult x:Key="ResultListData" />
</Window.Resources>
and set the itemssource as follows:
ItemsSource="{Binding Source={StaticResource ResultListData}}
That's not really how I would do this. Your first mistake is that although you have added an instance of your LINQResult class into the Resources section, it has no data in it because you never call the Create method. This is how I would achieve this:
I access the database using Linq2SQL and fill custom data types like your example, but here's the difference... I have a view model class for each view. In the view model classes, I have all the properties that I want to display in the UI, whether they are collections or singular items. The view model classes implement the INotifyPropertyChanged interface (this is essential) through a base class.
I have DataTemplates set up in App.xaml that link the various view models to views and then I can display a view in the UI like this:
<ContentControl Content="{Binding ViewModel}" />
In the view model constructor, I call the database and fill the collection(s) and set the properties. In your example, you seem to be creating a Linq query that would return multiple items (IEnumerable<LINQResult>), but you're trying to return just one of these items from your Create method.
I would normally extend the ObservableCollection<T> class for each of my collection classes, so if I were you, I would create this:
public class LinqResults : ObservableCollection<LinqResult>
{
public LinqResults(IEnumerable<LinqResult> linqResults) : base(linqResults) { }
}
I would then change your query method to something like this:
public static LinqResults Create()
{
DataDataContext dc = new DataDataContext();
LinqResults linqResults = new LinqResults(
from job in dc.Jobs
where job.Status == "Active"
select new LinqResult
{
JobNum = job.Job1,
CustomerName = job.Customer,
Order_Date = job.Order_Date,
});
return linqResults;
}
Then in a view model, I would add a collection property of type LinqResults and 'plug it in' to the INotifyPropertyChanged` interface:
public LinqResults LinqResults
{
get { return linqResults; }
set { linqResults = value; NotifyPropertyChanged("LinqResults"); }
}
Finally, bind to the collection in a view and define a DataTemplate for your data type (remembering to define an XML namespace to access it):
<ListBox ItemsSource="{Binding LinqResults}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type YourXmlNamespace:LinqResult}">
<StackPanel>
<TextBlock Text="{Binding JobNum}" />
<TextBlock Text="{Binding CustomerName}" />
<TextBlock Text="{Binding Order_Date}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Please take a look at Josh Smith's WPF Apps With The Model-View-ViewModel Design Pattern article for more information on MVVM.

MVVM DataBinding

I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.

Binding a class to a WPF Treeview

I have an object, which holds an observable collection
Im trying to wrap my head around how to stick this all into a treeview.
So the main object would be a parent and items in observable collection are sub-children.
Currently the class has public string property which could be bound to a header.
Here are parts of the class:
public class ServerObject : INotifyPropertyChanged
{
private string _serverName;
ObservableCollection<string> _instanceList;
public ObservableCollection<string> InstanceList
{
get { return _instanceList; }
}
public string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
RaisePropertyChanged("ServerName");
}
}
public ServerObject(string name, string version)
{
ServerName = name;
_instanceList = new ObservableCollection<string>();
}
}
Thanks in advance.
The easiest way to do it is with HierarchicalDataTemplates. Define two or more templates. In the template declaration line add a type indicator for your object. Also, add an ItemsSource attribute which points to the next level down.
<HierarchicalDataTemplate Datatype="{x:Type local:mySerberObject}" ItemsSource="{Binding InstanceList}"/>
Bind the top level collection to the treeview, and you should be off and running. Style the datatemplates to suit your taste.
If you are currently using MVVM (or if you plan to start using it) check out the link below for a really good article about using the treeview with MVVM.

.NET Databinding - Custom DataSource for a recursive tree of folders and items

Rewritten:
I could use some input, suggestions, and samples from some other minds on building a databindable collection.
The collection needs to provide a databindable, editable tree of items, but with a small twist: the items need to be one of two types, with each type providing slightly different characteristics. The two types of items are Folder and TreeItem. A Folder contains it's own list of items (again, either of Folder or TreeItem type) and a TreeItem does not contain a list.
My current approach is fairly close, but feels crufty. Essentially I have an abstract base class, TreeItemBase, which (in a round-about way) inherits from BindableList. Then I have two concrete derived types, Folder and TreeItem which both inherit from the abstract base class. The obvious flaw is that the TreeItem, which can't contain childitems, still inherits from BindingList; so it's up to some ugly hackery to pretend that it's not a collection.
Is BindingList<> a poor choice to base this collection on? Some of the DataBinding interfaces sound like they offer a higher degree of control over the databinding, but I haven't found one that's quite right. My ideal though is to provide a custom implementation that lets me control how databinding walks the collection and can inspect the concrete type of each element to determine if it contains a collection, or if it's a terminus in the tree.
Here's a quick cut of the XML to help visualize what I'm trying to represent; it's easy for me to cleanly code the structure and rules in XSD--but I'm just having a hard time translating it to .NET and supporting databinding.
<Items>
<TreeItem xsi:type="Folder" name="Root">
<TreeItem xsi:type="Folder" name="Sub1">
<TreeItem xsi:type="TreeItem" name="Humm"/>
</TreeItem>
<TreeItem xsi:type="TreeItem" name="Bleh"/>
<TreeItem xsi:type="Folder" name="Sub2">
<TreeItem xsi:type="TreeItem" name="Boo!"/>
</TreeItem>
</TreeItem>
</Items>
Update: I've been working more on my approach and have come close to what I'd like to do using an interface rather than a base-class for the items, but have hit a snag. The issue I've run into is covered on a seperate question.
Ideally I'd like to use the abstract base-class approach so that the XML that's generated considers Folder and TreeItem to be complexTypes (without manual control of the serialization), but it's a negligible requirement.
Maybe my depth of knowledge isn't big enough for this question, but couldn't you do this:
Have an interface, and 2 classes that implement the interface.
interface ITreeItem
{
IEnumerable<ITreeItem> GetChildren();
}
class MyFolder : ITreeItem
{
public IEnumerable<ITreeItem> GetChildren()
{
// TODO: Return the list of children
}
}
class MyITreeItem : ITreeItem
{
public IEnumerable<ITreeItem> GetChildren()
{
// TODO: Return the list of children
}
}
Then if your goal is to databind the collection to some list, you should be able to do so with the IEnumerable collections. In each call to databind the collections, you should be able to check to see which type the item is:
foreach (var node in root.GetChildren())
{
if (node is MyFolder)
{
var folder = (MyFolder)node;
// Bind fields from the folder object
}
else if(node is MyTreeItem)
{
var folder = (MyTreeItem)node;
// Bind fields from the tree item object
}
}
I did something similar (I think) to this when I had a list nested inside another list. To display the data, I setup Nested ListView controls.
Sorry if this isn't what you're looking for, but hope it helps!
SkippyFire's solution seems more elegant than mine, but I figured that I will show you how I solved the problem. The following solution shows what I did to build a collection that can be bound to a tree view, and you can determine which items have been selected. It does not implement any Bindable Lists or anything though. However, it is not clear from your post whether this is what you want.
This is an example of my XML file:
<controls name="Parent" attribute="element">
<control name="Files" attribute="element">
<control name="Cashflow" attribute="element">
<control name="Upload" attribute="leaf"/>
<control name="Download" attribute="leaf"/>
</control>
</control>
<control name="Quotes" attribute="element">
<control name="Client Quotes" attribute="leaf"/>
</control>
</controls>
Then I have a class that represents each item. It contains a Name, a List of child nodes (1 level down), a reference to its parent, and a string that logs the attribute of the element.
public class Items
{
public string Name { get; set; }
public List<Items> SubCategories { get; set; }
public string IsLeaf { get; set; }
public string Parent { get; set; }
}
From there, I populate a list of Items as follows:
List<Items> categories = new List<Items>();
XDocument categoriesXML = XDocument.Load("TreeviewControls.xml");
categories = this.GetCategories(categoriesXML.Element("controls"));
This calls the GetCategories() method
private List<Items> GetCategories(XElement element)
{
return (from category in element.Elements("control")
select new Items()
{
Parent = element.Attribute("name").Value,
Name = category.Attribute("name").Value,
SubCategories = this.GetCategories(category),
IsLeaf = category.Attribute("attribute").Value
}).ToList();
}
After the categories variable has been populated, I just assign the list as the treeview's ItemSource.
controltree.ItemsSource = categories;
And from there, if the choice changes in the tree, I check if the choice is a leaf node, and if so, I raise an event.
private void Selection_Changed(object sender, RoutedEventArgs e)
{
Items x = controltree.SelectedItem as Items;
if (x.IsLeaf.Equals("leaf"))
_parent.RaiseChange(x.Parent+","+x.Name);
}
This solution works for any depth in the tree as well.
I've used: treeviewadv from source forge. It have a very nice MVC way of dealing with tree view type modeling. It is a windows forms control that binds a model to a treeview style control with support for columns. They also provide some nice sample code.
ObservableCollection is perhaps the best bet for you, coupled with two DataTemplates.
public class TreeItem : INotifyPropertyChanged
{
public string Name { get; set; }
// ...
}
public class Folder : TreeItem
{
public ObservableCollection<TreeItem> Items { get; private set; }
public Folder()
{
this.Items = new ObservableCollection<TreeItem>();
}
// ...
}
And your DataTemplate's (including the Secret Sauce HierarchicalDataTemplate):
<HierarchicalDataTemplate DataType="{x:Type local:Folder}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:TreeItem}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
Putting it all together (code behind):
public class FolderList : ObservableCollection<TreeItem>
{
public FolderList()
{
this.Add(new TreeItem { Name = "Hello" });
this.Add(new TreeItem { Name = "World" });
var folder = new Folder { Name = "Hello World" };
folder.Items.Add(new TreeItem { Name = "Testing" });
folder.Items.Add(new TreeItem { Name = "1" });
folder.Items.Add(new TreeItem { Name = "2" });
folder.Items.Add(new TreeItem { Name = "3" });
this.Add(folder);
}
}
XAML:
<Grid>
<Grid.Resources>
<local:FolderList x:Key="MyItems" />
<HierarchicalDataTemplate DataType="{x:Type local:Folder}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:TreeItem}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</Grid.Resources>
<TreeView>
<TreeViewItem ItemsSource="{Binding Source={StaticResource MyItems}}"
Header="Root" />
</TreeView>
</Grid>

Categories

Resources