TreeView with multiple types - wpf

I would like to build a treeview like this:
People
Person 1
Relatives
Relative 1
Relative 2
Mom
Dad
Pets
Pet 1
Pet 2
The problem is that a person has 2 lists (Relatives and Pets) and two Single Items (Mom and Dad). I'm pretty familiar with HierarchicalDataTemplates but I haven't figured out a way to do this- there are lots of examples out there but none seem to mix types like this.
To make things even more interesting, there may be People without a Mom or a Dad (sad but true). The list would need to reflect this.
The data Im using originates from the data base using entity framework, so my list of people already has the correct structure- and I would prefer NOT to have all my objects derive from some common composite object (also in many of the examples) where everyone has a 'Name' and 'Children'. I would like to use the natural properties of each object, like MomsFirstName, PersonsName in the bindings of my datatemplates as well.
Is this possible?

Not sure if this helps you, but you can supply your own template selector, to pick a different template per type; They will all be encapsulated by the ItemsPanelTemplate object you choose, but it will allow you to have very different controls for any type you specify. This is my use case, but I imagine you could apply the same idea to the TreeView
<ItemsControl>
<ItemsControl.Resources>
<DataTemplate x:Key="templateFoo">
</DataTemplate>
<DataTemplate x:Key="templateBar">
</DataTemplate>
<DataTemplate x:Key="templateJoe">
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector></local:MyTemplateSelector>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row"
Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
And this somewhere
public class MyTemplateSelector: DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element == null || item == null)
return null;
if(item is Foo)
{
return element.FindResource("templateFoo") as DataTemplate;
}
if (item is Bar)
{
return element.FindResource("templateBar") as DataTemplate;
}
if (item is Joe)
{
return element.FindResource("templateJeo") as DataTemplate;
}
return null;
}
}

Have you tried to use ItemTemplate and ItemTemplateSelector?

Related

Why do I need to specify ElementName and DataContext in a binding?

To familiarize myself with WPF and MVVM concepts I built a visual representation of a Sudoku board.
My (simplified) setup looks like this (no custom code-behind in views anywhere):
I have a MainWindow.xaml:
<Window x:Class="Sudoku.WPF.MainWindow">
<Window.DataContext>
<models:MainWindowViewModel/>
</Window.DataContext>
<ctrl:SudokuBoard DataContext="{Binding Path=GameViewModel}"/>
</Window>
My MainWindowViewModel:
class MainWindowViewModel
{
public MainWindowViewModel()
{
IGame g = new Game(4);
this.GameViewModel = new GameViewModel(g);
}
public IGameViewModel GameViewModel { get; private set; }
}
SudokuBoard is a UserControl. Its DataContext is set to GameViewModel as per above.
Relevant parts of GameViewModel, Elements is populated in the ctor, Possibilities is set via a command:
public IList<CellViewModel> Elements { get; private set; }
private bool _showPossibilities;
public bool ShowPossibilities
{
get { return _showPossibilities; }
set
{
_showPossibilities = value;
OnPropertyChanged();
}
}
In SudokuBoard.xaml I have:
<ItemsControl x:Name="SudokuGrid" ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource ToggleContentStyle}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Elements is a collection of CellViewModels generated in the constructor of GameViewModel.
Now to the question: my ToggleContentStyle as defined in <UserControl.Resources>:
<Style x:Key="ToggleContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource valueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource possibilityTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
(both ContentTemplates just show other properties of a single CellViewModel in different representations)
Question 1: I have to explicitly reference DataContext in order to get to the ShowPossibilities property. If I leave it out, so that Path=ShowPossibilities, I get a UniformGrid with the ToString() representation of CellViewModel. My assumption is that that is because the style is referenced from the ItemTemplate, with it's binding set to a single CellViewModel. Is that assumption valid?
Question 2: When I omit the ElementName part, I also get the ToString() representation of CellViewModel. Now I'm really confused. Why is it needed?
Datacontext is a dependency property which is marked as inherits. That means its inherited down the visual tree.
When you bind the default place it's going to look for a source is in the datacontext.
This is the simple situation.
Say you have a window and that has datacontext set to WindowViewmodel and stick a textbox in that Window. You bind it's Text to FooText. This means the textbox goes and looks for a FooText property in that instance of WindowViewmodel.
All pretty simple so far.
Next...
You use elementname.
What that does is says go and take a look at this element. Look for a property on that. If you did that with our textbox above then it would expect a dependency property FooText on whatever you point it to.
Datacontext is a dependency property.
And when you do:
"{Binding FooProperty
This is shorthand for:
"{Binding Path=FooProperty
Where FooProperty is a property path, not =just the name of a property.
Which is maybe worth googling but means you can use "dot notation" to walk down the object graph and grab a property on an object ( on an object.... ).
Hence DataContext.Foo or Tag.Whatever ( since tag is another dependency property a control will have ).
Let's move on to some other complications.
The datcontext is inherited down the visual tree but there's a few of gotchas here. Since
some things look like they're controls but are not ( like datagridtextcolumn ). Templated things can be tricky. Itemscontrols are a kind of obvious and relevent special case.
For an itemscontrol, the datacontext of anything in each row is whichever item it's presented to from the itemssource. Usually you're binding an observablecollection of rowviewmodel to that itemssource. Hence ( kind of obviously ) a listbox or datagrid shows you the data from each rowviewmodel you gave it in each row.
If you then want to go get a property is not in that rowviewmodel you need to tell it to look somewhere else.
When you specify an element in Binding (eg ElementName=SudokuGrid), the Path has to refer to any property of that element. Because this element is a wpf control, DataContext is one of it's properties but ShowPossibilities isn't. So if you do just Path=ShowPossibilities it will not be able to find that path at all.
If you don't specify element in Binding at all then it defaults to the DataContext associated with the control. If the associated DataContext doesn't have the property ShowPossibilities it will not be able to find it.
PS: If you want to debug wpf UI to see what the DataContext is at run-time you could use utility like Snoop.

Binding a ListBox to an ObservableCollection of ViewModels

Is there a convention when using MVVM to bind the items of a ListBox to a ViewModel?
In the below XAML, I'm creating a ListBox of buttons. The ListBox is bound to an observable collection from my ViewModel. I then want to bind the button's Command property to an ICommand. The problem is that when I add that binding, I'm binding against the data object, not the ViewModel.
Do I just change the MyListOfDataObjects property to be a list of ViewModels? If so, where do I instantiate those new objects? I'd prefer to use dependency injection since they will have several dependencies. Do I change the GetData lambda?
In general: what's considered good practice here? I wasn't able to find any examples for this situation, although I assume it is rather common.
I'm using the MVVMLight framework, but I'm willing to look at any other frameworks.
<Window x:Class="KeyMaster.MainWindow"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MyDataTemplate">
<Button Command="{Binding ButtonPressedCommand}"
CommandParameter="{Binding .}"
Content="{Binding Name}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ListBox ItemsSource="{Binding MyListOfDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</Window>
I'm using the standard MVVMLight ViewModel:
using GalaSoft.MvvmLight;
using KeyMaster.Model;
using System.Collections.ObjectModel;
namespace KeyMaster.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private ObservableCollection<MyData> _myListOfDataObjects;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
if (error != null)
{
return;
}
MyListOfDataObjects = new ObservableCollection<MyData>(item);
});
}
public ObservableCollection<MyData> MyListOfDataObjects
{
get { return _myListOfDataObjects; }
set
{
if (_myListOfDataObjects == value) return;
_myListOfDataObjects = value;
RaisePropertyChanged(() => MyListOfDataObjects);
}
}
}
}
Thanks.
In MVVM, there is a clear seperation between the raw data (also known as the Model) and the ViewModel. The ViewModel is the one who is in charge of parsing the data and even modifying it to whatever form it wishes, before passing it to the View.
A simple example is having the Model as XML and having the ViewModel parse it, take only a specific property (for example a "Name") from each element and add them to a list. Only this list will be shown in the View.
That said, I guess you can see where I'm going - the Command should be in the ViewModel not in the Model. As you stated by yourself, you should keep as much of the UI logic out of both the VM and the Model.
If you have a specific command that does something specific on a certain type of data, you can want it in a more "general" type of ViewModel, you can use the CanExectue to only allow this command in specific cases. But still, the command should sit in the ViewModel.
In your specific case, I don't see a problem having the command in the ViewModel, and when raised it will do whatever you need on your data. You don't need a list of ViewModels, you need only one.
I would say it'd depend where you want the functionality of the button-press. If it is always related to the MyData object then (if possible) would it be so out of place to put the Command in the MyData object? (ps. I wouldn't call your MyData object ViewModels just because you're adding a command property to them, as they're not associated with a view)
Alternatively if you want the command in the VM then you could try bind the command using the datacontext of the window. ie something like;
<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ButtonPressedCommand}"
CommandParameter="{Binding .}"
Content="{Binding Name}" />
Although I've had trouble in the past with that and went with adding the command to the individual objects.

ItemsControl with only custom subelements

I would like to build a custom component that layouts its childs in either a StackPanel or a Grid (with variable row count, which makes me consider the StackPanel instead). The items are custom elements/objects that just hold some configuration, based on which a few controls are created to display them (some labels and text boxes).
Ideally, the component should be used somehow like this (where SpecializedCustomPanelItem is a subtype of CustomPanelItem):
<CustomPanel>
<CustomPanelItem Param1="value A" Param2="value B">Text</CustomPanelItem>
<CustomPanelItem Param1="value C">Other text</CustomPanelItem>
<SpecializedCustomPanelItem>More text</SpecializedCustomPanelItem>
<!-- The number of items is variable -->
</CustomPanel>
I’ve read on the ItemsControl for a while now, and it fits my needs rather well. I would create simply types for the items, and make data templates for them available from inside the ItemsControl. Then they should already render fine.
However I would like to require the items inside that ItemsControl to be of a specific type (i.e. CustomPanelItem or a subtype). I actually thought that the ItemsControl would allow this, just like you within a ComboBox or a MenuItem, but it turns out that it actually allows any subtype, and if necessary wraps them in a item container.
So I have been thinking if an ItemsControl is actually what I am looking for, as I do not want any “fancy” things like selection or scrolling which most of those controls implement. I actually only want to build a simple interface to a common pattern in the application that auto generates those components and layouts them in a Grid/StackPanel.
Should I still be using the ItemsControl or rather build some more custom component?
In this case you don't really need a custom component. Changing the ItemsPanel type to whatever type you need + multiple templates for the Items should do the trick.
However to answer the question in the heading: If you want to force an items control to only accept a certain type of items, you will have to create
a. A CustomItemsControl
b. A CustomItemsControlItem
Then for the CustomItemsControl you should declare the attribute
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CustomItemsControlItem))]
Then you also will need to
protected override DependencyObject GetContainerForItemOverride()
{
return new CustomItemsControlItem();
// You can throw an exception here
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is CustomItemsControlItem;
}
If memory serves this should force the ItemsControl to not allow other types to be added as children and should throw exceptions. You could then do some magic inside CustomItemsControlItem by defining some DependencyProperties which you can then set when adding the items in XAML.
But yet if you have multiple types in your ViewModel that you want to display correctly, the correct way is still to provide multiple templates for the CustomItemsControlItem targetting your ViewModel types.
Hope this helps.
This sounds perfect for an ItemsControl
You can set it's ItemsPanelTemplate to define the kind of panel which will hold your items, and set the ItemContainerTemplate to define how to draw each item.
If items should be drawn differently based on what type they are, I'd suggest using implicit DataTemplates instead of setting the ItemContainerTemplate
<Window.Resources>
<DataTemplate DataType="{x:Type my:BasePanelItem}">
<my:CustomPanelItem Param1="{Binding Param1}" Param2="{Binding Param2}" Content="{Binding SomeValue}" />
</DataTemplate>
<DataTemplate DataType="{x:Type my:SpecializedPanelItem}">
<my:SpecializedCustomPanelItem Content="{Binding SomeValue}" />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding MyItems}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<my:CustomPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You mentioned that you wanted to perhaps use a dynamically created Grid instead of a StackPanel as well. If you do, you might be interested in some GridHelpers I have posted on my blog. This would allow you to bind the number of Columns/Rows on the Grid in the ItemsPanelTemplate
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

Pseudo-Infinite Grid

I've got a bit of a design issue here.
I've got a view:
<ItemsControl x:Name="CellVMs">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row"
Value="{Binding Position.Y}" />
<Setter Property="Grid.Column"
Value="{Binding Position.X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
which is bound to a collection of viewmodels, that have a position property which the style there uses to position it on the ItemPanelTemplate. (Only one viewmodel per cell, and the grid cells are fixed size)
1) I would like that Grid to be pseudo-infinite, ie as EditorVMs get added and subtracted, the Grid should dynamically add and delete Row\Col Definitions, and there should always be enough Grid, and there should always be enough to fill the parent view.
2) In my containing viewmodel, I import an IGridEditor implementation instance which has a Grid property. How can I bind the ItemsPanelTemplateGrid to the IEditor.Grid?
Right now, I add the CellVM's to a collection in the IGridEditor's methods, then when the containing vm imports the instance, sets the containing vm's CellVM collection to the instances collection, and the item control binds to that using Caliburn.Micro's conventions.
I'm using Caliburn.Micro\MEF btw.
Can anyone help me figure this out?
Edit:
Been trying to understand this, but I'm coming up empty.
Only thing I can find is
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid x:Name="EditorGrid"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
and in my viemodel:
[Import("EditorGrid", typeof(Grid))]
public Grid EditorGrid { get; set; }
and a corresponding Export in class that has the methods to add things to the grid/
1) I would like that Grid to be pseudo-infinite, ie as EditorVMs get
added and subtracted, the Grid should dynamically add and delete
Row\Col Definitions, and there should always be enough Grid, and there
should always be enough to fill the parent view.
You could create a custom Grid that automatically adds rows or columns as needed:
public class AutoExpandGrid : Grid
{
protected override System.Windows.Media.Visual GetVisualChild(int index)
{
var visualChild = base.GetVisualChild(index);
var uiElement = visualChild as UIElement;
if (uiElement != null)
EnsureEnoughRowsAndColumns(uiElement);
return visualChild;
}
private void EnsureEnoughRowsAndColumns(UIElement child)
{
int minRows = GetRow(child) + GetRowSpan(child);
int minColumns = GetColumn(child) + GetColumnSpan(child);
while (minRows > RowDefinitions.Count)
{
RowDefinitions.Add(new RowDefinition());
}
while (minColumns > ColumnDefinitions.Count)
{
ColumnDefinitions.Add(new ColumnDefinition());
}
}
}
(not sure GetVisualChild is the best place to do this, but it's the best I could find)

MVVM (with WPF) - Binding Multiple Views to the Same ViewModel

I have recently started investigating the MVVM pattern with WPF for an upcoming project. I started with Josh Smith's MSDN article. I have a question (well many, but let's start with one):
I have an IndividualViewModel which exposes the properties of the model. I need two views "Add Individual" and "Edit Individual" which are very similar as you can imagine. What I have done currently is to have 2 subclasses AddIndividualViewModel and EditIndividualViewModel which expose the Add and Edit commands respectively. I also have 2 similary named views that bind to these.
Now this method works and these classes are fairly small anyway, but I'm wondering if it is possible for me to have just the one view model, which exposes both commands. I would still have 2 views which would bind to this same view model, exposing the appropriate command as a button. I'm not quite sure how to do this. In the main window resources I have something like:
<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
With this method of binding you can only have a one-to-one binding, i.e. the same view is always shown for a given view model. Is there a way to automatically switch the view depending on a property on the view model (e.g. IndividualViewModel.Mode). Is there a different approach I should be considering?
Note that the main window has a collection of view models and shows each in tab.
Thank you!
So you need 2 different views based on a property value. One thing to consider is to refactor your presentation code, so instead of the values of a property you could have real subclasses. Then you can use 2 different DataTemplate for each class.
<DataTemplate DataType="{x:Type ViewModels:AddIndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:EditIndividualViewModel}">
<Views:EditIndividualView />
</DataTemplate>
If you think that is an overkill, you could use a trigger and wrap your specific views into a ContentPresenter.
<DataTemplate x:Key="AddIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
<Views:AddIndividualView />
</DataTemplate>
<DataTemplate x:Key="EditIndividualTemplate" DataType="{x:Type ViewModels:IndividualViewModel}">
<Views:EditIndividualView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:IndividualViewModel}">
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate" Value="{StaticResource AddIndividualTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="{x:Static ViewModels:IndividualMode.Edit}">
<Setter Property="ContentTemplate" Value="{StaticResource EditIndividualTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
Thanks for pointing me in the right direction! I am still new with WPF too and learning about all the different possibilities including binding methods. Anyway for anyone interested, here is the solution I arrived at for this particular case:
I decided I wanted to keep the view models separated in two subclasses AddIndividualViewModel and EditIndividualViewModel which only expose commands, rather than trying to manage state in the one class. However I wanted one view so that I'm not duplicating the XAML. I ended up using two DataTemplates and DataTemplateSelector to switch out the action buttons depending on the view model:
<DataTemplate x:Key="addTemplate">
<Button Command="{Binding Path=AddCommand}">Add</Button>
</DataTemplate>
<DataTemplate x:Key="editTemplate">
<Button Command="{Binding Path=UpdateCommand}">Update</Button>
</DataTemplate>
<TemplateSelectors:AddEditTemplateSelector
AddTemplate="{StaticResource addTemplate}"
EditTemplate="{StaticResource editTemplate}"
x:Key="addEditTemplateSelector" />
and a content presenter at the bottom of the form:
<ContentPresenter Content="{Binding}"
ContentTemplateSelector="{StaticResource addEditTemplateSelector}" />
Here is the code for the template selector:
class AddEditTemplateSelector : DataTemplateSelector
{
public DataTemplate AddTemplate { get; set; }
public DataTemplate EditTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is AddIndividualViewModel)
{
return AddTemplate;
}
else if (item is EditIndividualViewModel)
{
return EditTemplate;
}
return null;
}
}
This may or may not be how implement the final thing (given the requirements) but it's good to see I have this sort of option available.
There's no reason why you shouldn't be able to achieve that. One way of doing this is to provide some flag in your view model stating whether you're in add mode or in edit mode, and styling your view based on that flag using simple bindings, triggers or template selectors.
For reference you may look at Sacha Barber's DataWrapper class that's part of his Cinch framework (not directly applicable to your case, but it's a good starting point) which wraps data fields in the view model in such a way to support a flag to toggle between read only (view record mode), and read-write (edit record mode). You could apply a similar approach to make the distinction between add and edit.
Basically, instead of having simple properties in your view model, instantiate a data wrapper class which includes a Value property, and a IsAdding property. In your view, you can use bindings, triggers or template selectors to modify templates based on that property.
For this task you do not need any DataTemplateSelector at all.
Derive both EditIndividualVM and AddINdividualVM from IndividualVM.
The Edit- and AddCommands route to a setter property in the IndividualVM.
The setter VM = new AddIndividualVM or VM = new EditIndividualVM depending on which button is pressed.
In xaml you bind in the contentgrid to your VM property like this:

Resources