I have a set of data that is structured like this:
ItemA.GroupA
ItemB.GroupA
ItemC.GroupB
ItemD.GroupC
I need to present the data in a WPF Tree View like this:
GroupA
--- ItemA
--- ItemB
GroupB
--- ItemC
GroupC
--- ItemD
What XAML can I use to group the leaves by distinct value? For instance, there could be multple items in the collection which are GroupA.ItemA, however, I only want to present the node and leaf once.
I disagree with itowlson here. This isn't a hairy problem and HierarchicalDataTemplate was made for this kind of thing. Before you go haphazardly diving into patterns and view-models and other unnecessary obfuscation, consider that your problem can be solved with two classes and a single Linq GroupBy statement.
Here are your classes:
public class GroupItem
{
public string Name
{
get;
private set;
}
public string Group
{
get;
private set;
}
public GroupItem(string name, string group)
{
Name = name;
Group = group;
}
}
public class Group
{
public IEnumerable<GroupItem> Children
{
get;
set;
}
public string Name
{
get;
private set;
}
public Group(string name)
{
Name = name;
}
}
So far, so good. You've got two straightforward classes to hold all the necessary data. Names and Groups are stored as strings. A Group has a collection of GroupItem. Now for the code in your Window:
public partial class DistinctLeaves : Window
{
public ObservableCollection<GroupItem> Items
{
get;
set;
}
public IEnumerable<Group> Groups
{
get;
set;
}
public DistinctLeaves()
{
Items = new ObservableCollection<GroupItem>();
Items.Add(new GroupItem("Item A", "Group A"));
Items.Add(new GroupItem("Item B", "Group A"));
Items.Add(new GroupItem("Item C", "Group B"));
Items.Add(new GroupItem("Item D", "Group C"));
Groups = Items.
GroupBy(i => i.Group).
Select(g => new Group(g.Key) { Children = g });
InitializeComponent();
}
}
Once again, this is all boilerplate except the group-by line. That statement merits further investigation. This will group your items collection according to their Group property. After the items are in groups, you then create a new instance of the Group class. Pass in the group's name property (which is the key), and set the children to the group itself, and ta-da!
Finally, here is the XAML for the Window, which uses the HierarchicalDataTemplate:
<Window x:Class="TestWpfApplication.DistinctLeaves"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DistinctLeaves" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TreeView ItemsSource="{Binding Groups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And here is the result:
alt text http://img339.imageshack.us/img339/8555/distinctleaves.jpg
You're not going to be able to do that in XAML, at least not in any natural way. A better approach is to introduce a view model -- a class that represents your data in a view-friendly manner so that you can keep your XAML simple and natural.
Thus, in your case, you would have a class that wraps your ItemX collection, and exposes the groups and their distinct members:
public class MyItemsViewModel
{
public IList<MyGroupViewModel> Groups { get; }
}
public class MyGroupViewModel
{
public string GroupName { get; }
public IList<MyItem> DistinctItems { get; }
}
Your HierarchicalDataTemplate will then fall out almost automatically. As a further benefit, because your view model classes are just data objects, you can put them under automated test, whereas complex XAML requires manual testing.
Related
We have an Entity framework model, which contains classes named QuoteStatus and SystemStatus, which model the status of a quote and a system respectively. Each of these classes has a navigation property, which is a collection that contains the emails of people who are to be notified when the status changes. The QuoteStatus class looks like this (simplified)...
public class QuoteStatus {
public int ID { get; set; }
public string Name { get; set; }
public List<QuoteStatusNotification> QuoteStatusNotifications;
}
public class QuoteStatusNotification {
public int ID { get; set; }
public string Email { get; set; }
}
The SystemStatus and SystemStatusNotification classes are remarkably similar.
Now, we want to have a WPF window that can be used to maintain both types of statuses (and any more that come along in the future). The idea is to have a dropdown control at the top of the window, where the user specifies the type of status to be shown (quote or system), and the value is sent to the view model.
The view model would have private variables for the data...
private List<QuoteStatus> _quoteStatuses;
private List<SystemStatus> _systemStatuses;
We want the view model to have a public Statuses property, which can be bound to a grid on the view. Depending on what value the user chooses in the dropdown, the Statuses property would contain either the _quoteStatuses collection, or the _systemStatuses collection.
We did that by creating a base Status class, and having the QuoteStatus and SystemStatus classes inherit from it. That was fine.
We ran into a problem with the child collections. We want the Status base class to have a StatusNotifications collection, which will be a collection of either the QuoteStatusNotification class, or the SystemStatusNotification class. We couldn't work out how to create that StatusNotifications collection.
From another thread here (see the second suggestion in the accepted answer), it looks like I might be able to do this with covariance, but I can't get my head round how to do it.
Anyone able to explain this?
Simple inheritance:
public class StatusBaseClass
{
public List<StatusNotification> StatusNotifications;
}
public class QuoteStatus : StatusBaseClass
{
}
public class SystemStatus : StatusBaseClass
{
}
public class StatusNotification
{
}
public class QuoteStatusNotification : StatusNotification
{
}
public class SystemtatusNotification : StatusNotification
{
}
You can add a QuoteStatusNotificatio or SystemStatusNotification to the base class list
What you can do that is really neat in your xaml is provide different UI for the two classes in a list view for example. See here: DataTemplate for each DataType in a GridViewColumn CellTemplate
For example:
<ListView ItemsSource="{Binding Statuses}">
<ListView.Resources>
<DataTemplate DataType="{x:Type viewModel:SystemStatus}">
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:QuoteStatus}">
</DataTemplate>
</ListView.Resources>
</ListView>
A bit more detail of what you're trying to do and may be able to help a bit more.
Create a base class that has the properties that are common across both types. Then you can pass
List<StatusBaseClass> statuses;
You can put either type into this list.
If it is a mixed list then you can get the individual types out by:
var quoteStatuses = statuses.OfType<QuoteStatus>();
var systemStatuses = statuses.OfType<SystemStatus>();
I have a TypeOfContact model that is made up of an ID, and Text. For example, one type would be Telephone and the ID would be 1. Another type would be Email and the ID 2.
What I would like to do is add the text of the TypeOfContact as an item and the ID as a tag. I imagine it would look something like this, however this isn't working;
contactTypeComboBox.Items.Clear();
foreach (TypeOfContact c in ContactTypes)
{
contactTypeComboBox.Items.Add(c.ContactTypeText);
foreach (ComboBoxItem item in contactTypeComboBox.Items)
{
item.Tag = c.ContactTypeID;
}
}
The reason I want to do this is that when someone selects one of the ComboBox items I want to store the text and the ID. I could do this all through XAML but ContactTypes is a list that is populated by the user, so I cannot hard code the values into the ComboBox as maintaining it and adding new TypesOfContact would be difficult.
I fixed this issue myself by first adding;
DisplayMemberPath="ContactTypeText" SelectedValuePath="ContactTypeID"
to the XAML of the ComboBox then accessing the ID like;
contactTypeComboBox.SelectedValue
In your situation i would bind the list of your TypeOfContacts as ItemsSource to the ComboBox. After that you could set the tag, but i think you don't will need it, because when you also bind the SelectedItem you got back the whole item (ID, type, ...) and can work with it in other parts of your code.
Example for simplifying without a ViewModel (but you should use one):
Codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
FillListWithSomeExamples();
}
private void FillListWithSomeExamples()
{
TypesOfContacts.Add(new TypesOfContact {Id = 1, Type = "Email"});
TypesOfContacts.Add(new TypesOfContact { Id = 2, Type = "Telephone" });
}
public TypesOfContact SelectedTypesOfContact { get; set; }
public ObservableCollection<TypesOfContact> TypesOfContacts { get; set; } = new ObservableCollection<TypesOfContact>();
}
TheTestmodel:
public class TypesOfContact
{
public int Id { get; set; }
public string Type { get; set; }
}
XAML
<Grid>
<ComboBox ItemsSource="{Binding TypesOfContacts}" SelectedItem="{Binding SelectedTypesOfContact}" DisplayMemberPath="Type"/>
</Grid>
Now you can read the selected item in any other method of the MainWindow by looking at SelectedTypesOfContact.
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.
I have an ObservableCollection<T> that holds one type of object. I need this to be the source for several list controls but I also want to transform the data. For simplicity, imagine I have an ObservableCollection<int> and want to get an ICollectionView that returns strings - maybe just the integer converted to a string.
Is there a way to create such a view?
Something nice like:
var view = new MagicalCollectionView(myCollection, x => x.ToString())?
Collection views are about sorting, filtering, grouping. They are not about data transformation.
Actually, there's no direct need in such collection in your task interpretation. You just need to set up view properly, e.g. providing correct DataTemplate for items in your source collection:
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyViewModel
{
public ObservableCollection<MyEntity> Entities { get; private set; }
public ICollectionView EntitiesView
{
if (view == null)
{
view = new ListCollectionView(Entities);
}
return view;
}
private ICollectionView view;
}
XAML:
<DataTemplate DataType="{x:Type local:MyEntity}">
<!-- This "transforms" visual representation of your entity, but the entity itself (and its container) remains unchanged -->
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
Update.
According to you comment, I'd wrap entities into view models. Moreover, this is a purpose of view models:
public class MyEntityViewModel
{
private readonly MyEntity model;
public MyEntityViewModel(MyEntity model)
{
this.model = model;
}
public int MyInt
{
get { return model. // some logic to retrieve int ... }
}
public string MyString
{
get { return model. // some logic to retrieve string ... }
}
}
Then, instead of collection of models, I'd bind the control to the collection of view models:
public class MyViewModel
{
public MyViewModel(ICollection<MyEntity> entities)
{
this.Entities = new ObservableCollection<MyEntityViewModel>(entities.Select(e => new MyEntityViewModel(e)));
// this will keep two collections synchronized:
this.Entities.CollectionChanged += (sender, args) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
entities.Add((MyEntity)e.NewItems[0]);
break;
case NotifyCollectionChangedAction.Remove:
entities.Remove((MyEntity)e.OldItems[0]);
break;
case NotifyCollectionChangedAction.Replace:
entities.Remove((MyEntity)e.OldItems[0]);
entities.Add((MyEntity)e.NewItems[0]);
break;
case NotifyCollectionChangedAction.Reset:
entities.Clear();
break;
}
}
}
public ObservableCollection<MyEntityViewModel> Entities { get; private set; }
}
This will keep your data class clear from extra properties, which are intended for view only.
You can do a projection with LINQ
myCollection.Select(x=> int.Parse(x))
Will parse each element of your collection and return an integer
I mostly agree with Dennis, however, using a binding converter to do the final transformation of the underlying bound value might help you in this situation.
I assume you have one collection of types and want different, live, projections of the data in different places.
You could bind to the same collection in each case and 'insert' a different custom converter for each case, probably in a custom DataTemplate.
http://www.silverlightshow.net/items/Data-Conversion-in-Silverlight-2-Data-Binding.aspx
Silverlight demo, but its the same.
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>