TreeView databind to list of list of list - c#

I am new to WPF and I am stuck in creating a Treeview that is bound to a list of lists of list, etc, the number of childnodes can increase as needed. I have created two HierarchicalDataTemplates to test off the code, but the child nodes are not appearing
My tree view is defined as
<telerik:RadTabItem Header="Lookup Sets">
<telerik:RadTreeView IsLoadOnDemandEnabled="True" ItemsSource="{Binding AttributeLookupSetConversions}">
<telerik:RadTreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type cm:AttributeLookupSetConversion}">
<CheckBox Content="{Binding Path=Name}" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type telerikDocking:RadSplitContainer}}, Path=DataContext.UpdateSelectionCommand}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type cm:AttributeConversion}">
<CheckBox Content="{Binding Path=Name}" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type telerikDocking:RadSplitContainer}}, Path=DataContext.UpdateSelectionCommand}"/>
</HierarchicalDataTemplate>
</telerik:RadTreeView.Resources>
</telerik:RadTreeView>
</telerik:RadTabItem>
Is there something I am missing or will I have to create the nodes in code? I have a tabControl that has treeViews of lists of lists, the problem is one tab can contain items of another treeView that are linked relationally by an Id, so if I have say fro example
stundents
-Student1
-Course 1
-Course 2
- Department 1
-Student 2
-Course 6
Department12
Course
-Qualification 1
-WorkType

Create a class as the root of all your ltems, which has a DisplayName and an observablecollection of other Items:
public class Item : INotifyPropertyChanged
{
string _displayText;
public string DisplayText { get { return _displayText; } set { _displayText = value; RaisePropertyChanged("DisplayText"); } }
ObservableCollection<Item> _items;
public ObservableCollection<Model> Items { get { return _items; } set { _items = value; RaisePropertyChanged("Items"); } }
public event PropertyChangedEventHandler PropertyChanged;
internal void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Any other type (Student, Teacher, Course, Department, etc.) must be derived from this class. They might have their specific properties too.
public class Student : Item
{
}
public class Course : Item
{
}
public class Qualification : Item
{
}
Note that if you have two types of Course class (with different Inner list of items), create two separate classes for them.
Now, you should populate the observableCollection in the view model propertly, and everything will be taken care of in a TreeView such as the following:
<TreeView DataContext="{Binding}" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding DisplayText}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Related

wpf bind different datatemplates to different types of objects in contentcontrol

I am new to WPF and MVVM. What I am trying to do is to bind two different DataTemplates to two different kinds of objects in one ContentControl. Each kind of object corresponds to one DataTemplate.
The two kinds of objects are called Unit and Component respectively. They contain different properties. For example a Unit has 3 properties: Id, Name and Manufacture. A Component has 3 properties Id, Type and Materials. The example code is as below:
public class Unit : INotifyPropertyChanged
{
private int _id;
private string _name;
private string _manufacture;
public int Id
{
get {return this._id}
set
{
this._id = value;
OnPropertyChanged("Id")
}
{
public string Name
{
get {return this._name}
set
{
this._id = value;
OnPropertyChanged("Name")
}
{
public string Manufacture
{
get {return this._manufacture}
set
{
this._id = value;
OnPropertyChanged("Manufacture")
}
{
public event PropertyChangedEventHandler PropertyChanged;
...
}
The Component class has the similar structure.
In the MainWindow, I have a ListBox listing names of objects (I will change it to a TreeView in the future) on the left, and a ContentControl on the right. I want that when I select the name of an object, the details of the object will be shown on the right. The code of the MainWindow is as below:
<Windows.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=UnitItems}"
x:Key="UnitDataView">
</CollectionViewSource>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=ComponentItems}"
x:Key="ComponentDataView">
</CollectionViewSource>
<CompositeCollection x:Key="AllDataView
<CollectionContainer Collection="{Binding Source={StaticResource UnitDataView}}" />
<CollectionContainer Collection="{Binding Source={StaticResource ComponentDataView}}" />
</CompositeCollection>
<local: PartDataTemplateSelector x:Key="MyDataTemplateSelector"
UnitTemplate="{StaticResource unitTemplate}"
ComponentTemplate="{StaticResource componentTemplate}" />
</Windows.Resources>
<Grid>
<Grid.ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition>
</Grid.ColumnDefinition>
<ListBox x:Name="ComponentListView" Grid.Column="0"
ItemsSource="{Binding Source={StaticResource AllDataView}}" />
<TabControl Grid.Column="1"
<TabItem Header="Basic Info">
<ContentControl x:Name="BasicInfoContent"
ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
Content="{Binding Source={StaticResource AllDataView}}">
</ContentControl>
</TabItem>
</TabControl>
</Grid>
The UnitItems and ComponentItems are two ObservableCollection<T> objects defined in App.xaml.cs. And I have defined some DataTemplates in App.xaml. The example code is as below:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..."
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type src:Unit}">
<!-- This template is to show the name of a unit object in the ListBox -->
</DataTemplate>
<DataTemplate DataType="{x:Type src:Component}">
<!-- This template is to show the name of a component object in the ListBox -->
</DataTemplate>
<DataTemplate x:Key="unitTemplate" DataType="{x:Type src:Unit}">
<!-- This template is to show the details of a unit object in the ContentControl -->
</DataTemplate>
<DataTemplate x:Key="componentTemplate" DataType="{x:Type src:Component}">
<!-- This template is to show the details of a component object in the ContentControl -->
</DataTemplate>
</Application.Resources>
And my custom DataTemplateSelector is as below:
class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate UnitTemplate { get; set; }
public DataTemplate ComponentTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
swith (item)
{
case Unit _:
return UnitTemplate;
case Component _:
return ComponentTemplate;
}
return null;
}
}
I have read this article ContentTemplateSelector and tried the ContentTemplateSelector, but since I use a CompositeCollection and CollectionContainer to bind these two kinds of objects in the ContentControl, the item object in my DataTemplateSelector class receives the CompositeCollection type, not a Unit type nor a Component type, so there is no proper template being returned. Also I tried the method mentioned in this article DataType Property, which is to set a DataType property for each of the DataTemplate and set the Path to "/". Maybe I misunderstood it, but it did not work either, where I think it has the same issue with the ContentTemplateSelector one. So anybody can help me on this problem?
It is my very first time to ask a question on Stack Overflow. I know some of my description and codes are trivial to this question, but I just don't want to miss any details that may be related to my problem. I apologise for that. Also if there are any problem with my coding style and data structure, please feel free to point it out. I really appreciate it. Thank you for your reading and help!
You do not need a DataTemplateSelector. Just make sure that the detail DataTemplates can be automatically selected, by not assining a key to them.
It also seems that you don't need two collections for your objects. You might as well derive both Unit and Component from a common base class and have a single collection of base class references.
Finally there should be a view model, which besides the objects collection also has a property for the currently selected object.
Take this simplified example view model:
public class Base
{
public int Id { get; set; }
}
public class Unit : Base
{
public string UnitData { get; set; }
}
public class Component : Base
{
public string ComponentData { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Base> Objects { get; }
= new ObservableCollection<Base>();
private Base selectedObject;
public Base SelectedObject
{
get { return selectedObject; }
set
{
selectedObject = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedObject)));
}
}
}
An instance of it should be assigned to the window's DataContext:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Objects.Add(new Unit { Id = 1, UnitData = "Unit Data" });
vm.Objects.Add(new Component { Id = 2, ComponentData = "Component Data" });
DataContext = vm;
}
Finally, the XAML would be this:
<ListBox ItemsSource="{Binding Objects}"
SelectedItem="{Binding SelectedObject}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<TextBlock>
<Run Text="Unit, Id:"/>
<Run Text="{Binding Id}"/>
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Component}">
<TextBlock>
<Run Text="Component, Id:"/>
<Run Text="{Binding Id}"/>
</TextBlock>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedObject}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding UnitData}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Component}">
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding ComponentData}"/>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>

Combobox within a listbox and a related data binding issue

Alrighty, so I have, as I said, a ComboBox within a ListBox. These are both controls (WPF project using C# in Visual Studio 2010 and MVVM).
I can create them all good and well.
The ListBox contains an ObservableCollection of something called IntWrapper, which just contains and int.
The ComboBox is loaded with an ObservableCollection of something called MissionViewModel, which is just a class that contains a few basic data types.
So the ItemSource for the ListBox and the ComboBox are different.
I can add and items to the ListBox with a button, which just adds a new IntWrapper to the ObservableCollection. Visually this gives me a new ComboBox, which is populated.
What I can't figure out is how to get one of the properties of MissionViewModel to go to the property in the IntWrapper when I select it.
Previously I just used a TextBox in the ListBox, which looked like this:
<ListBox x:Name="lbMissionsToGive" ItemsSource="{Binding MissionsToGive}" SelectedItem="{Binding SelectedMissionToGive}" ContextMenu="{StaticResource RemoveMissionToGiveMenu}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="LightBlue" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" CornerRadius="5,5,5,5">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="1">Mission ID: </TextBlock>
<TextBox Margin="1" Text="{Binding Int, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My (non working) attempt whereby I replace the TextBox with the ComboBox is this:
<ListBox x:Name="lbMissionsToGive" ItemsSource="{Binding MissionsToGive}" SelectedItem="{Binding SelectedMissionToGive}" ContextMenu="{StaticResource RemoveMissionToGiveMenu}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="LightBlue" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" CornerRadius="5,5,5,5">
<StackPanel Orientation="Horizontal">
<ComboBox DisplayMemberPath="MissionNameAndID" SelectedValuePath="Int" ItemsSource="{Binding MissionListViewModel.MissionVMs, Source={StaticResource Locator}}"></ComboBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I had thought SelectedValuePath would be the thing I wanted, but this so far doesn't seem to work (perhaps I'm using it incorrectly).
Any assistance would be appreciated.
Bind the SelectedValue property of the ComboBox to the Int property of the IntWrapper:
<ComboBox DisplayMemberPath="MissionNameAndID" SelectedValuePath="Int" SelectedValue="{Binding Int}"
ItemsSource="{Binding MissionListViewModel.MissionVMs, Source={StaticResource Locator}}" />
This should work provided that the IntWrapper.Int property has a public setter and that the Int property of the MissionViewModel is of the same type as the Int property of the IntWrapper.
I tried to create a solution for your problem based on your description. To help you more qualified, we would need more information about the MissionViewModel and the Properties contained in it.
I suppose, your Binding does not point to the correct ViewModel. I tried to recreate what you describe below and in my case, the combobox is filled with the Name Property of the MissionVm class.
This is the XAML. Here I changed the ItemsSource Binding to directly point to the ObservableCollection of the DataContext of the parent window.
<ListBox x:Name="lbMissionsToGive" ItemsSource="{Binding MissionsToGive}" SelectedItem="{Binding SelectedMissionToGive}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="LightBlue" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" CornerRadius="5,5,5,5">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding }"></TextBlock>
<ComboBox Margin="20" Width="50" ItemsSource="{Binding DataContext.MissionVMs, RelativeSource={RelativeSource AncestorType=Window}}" DisplayMemberPath="Name"></ComboBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this is the ViewModel based on your description:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var vm1 = new MissionVM { Id = 1, Name = "111"};
var vm2 = new MissionVM { Id = 2, Name = "222" };
var vm3 = new MissionVM { Id = 3, Name = "333" };
MissionVMs = new ObservableCollection<MissionVM> { vm1, vm2, vm3};
MissionsToGive = new ObservableCollection<int> {1, 2, 3};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<int> MissionsToGive { get; set; }
public int SelectedMissionToGive { get; set; }
public ObservableCollection<MissionVM> MissionVMs { get; set; }
}
public class MissionVM
{
public int Id { get; set; }
public string Name { get; set; }
}
This is the Output:
I hope this helps. If i misunderstood you, please give further explanation of the problem.

Can not retrieve all selected values from list of Comboboxes mvvm

I have a list box control that has a text block and a combobox for a list item. This is dynamically generated from an ObservableDictionary but once I have selected my item I can not retrieved the selected values. I can see them in the live visual tree it I click on each individual Combobox but I am unable to use them. I need to retrieve a dictionary containing ShoplistItem for the key and the SelectedValue from the availability combobox. Below is some code
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel >
<TextBlock Text="{Binding Key}" />
<StackPanel>
<ComboBox
x:Name="dictCombobox"
ItemsSource="{Binding Value}"
IsEnabled="{Binding IsSelected,
Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
SelectedValuePath="Key"
SelectedValue="{Binding SelectedAvailability, Mode=TwoWay}">
</ComboBox>
</StackPanel>
</StackPanel>
</DataTemplate>
c#
public class ShopAndAvailabilityDataContext : Shop, IPersistent
{
public Shop _Shop;
public Availability _ups =new Availability();
public ObservableDictionary<Shop, Availability> availabilityDict{ get; set;
}
public ShopAndAvailabilityDataContext ()
{
_Shop= new Shop();
availabilityDict= _ups.Availabitity;
}
And I have been using the datacontext to get all the other properties and the code below for the selected value. This only shows me the 1st item in the list with the shop #Index 0 and the 1st item in the availability array. How can I achieve this and what am I doing wrong based on my approach. Any and all ideas are welcome
private KeyValuePair<Shop, Available> selectedAvailability;
public KeyValuePair<Shop, Available>SelectedAvailability
{
get
{
return selectedAvailability;
}
set
{
selectedAvailability = value;
this.OnPropertyChanged("Availability");
}
}

Binding Hierarchical TreeView To Dictionary

I'm trying to get my TreeView to bind to a Dictionary that will be updated. I've implemented INotifyProperyChangedHandler on the data type itself, but will this effect the treeview at all?
What I'm aiming for is:
-FolderName
--->Item1
--->Item2
-FolderName
--->Item1
This is my View:
<UserControl.Resources>
<HierarchicalDataTemplate x:Key="ChildTemplate" >
<TextBlock FontStyle="Italic" Text="{Binding Path=m_Items}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Value}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Foreground="White" Text="{Binding m_Name}" FontWeight="Bold" />
</HierarchicalDataTemplate>
</UserControl.Resources>
<TreeView x:Name="NewTree" ItemsSource="{Binding m_FolderList, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{StaticResource NameTemplate}">
</TreeView>
My ViewModel:
public Dictionary<UInt16, Folder> m_FolderList
{
get { return Manager.Instance.GetFolderDirectory(); }
}
My Folder class:
public class Folder
{
public m_Name { get; set; }
public ObservableCollection<String> m_Items { get; set; }
}
What I'm getting is a blank treeview that never updates. Whenever I add a new item to the "FolderDirectory" in that singleton manager instance, I do an OnPropertyChanged call. Which works for anything else I've been binding to that dictionary or Folder item.
I was trying to follow this, but how the binding observes that it automatically childrens the "FamilyMembers" list escapes me because nowhere do you explicitly tell the XAML template that it should bind itemsource to that collection. http://www.wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/
EDIT:
The control is definitely getting all the values from my singleton. I just was playing around with the naming of the Binding Path and when I accidentally set the TreeView to bind to m_FolderList.Value (dictionaries have Values, not just Value), it gave me as many binding errors in the console as there were Items in the dictionary.
EDIT2:
public List<Folder> m_FolderList
{
get
{
List<Folder> list = new List<Folder>();
list.AddRange(Manager.Instace.GetFolderDirectory().Values);
return list;
}
}
If I do this, instead of Dictionary, the first Level of information appears... Which is a nuisance
Try this
<Window.Resources>
<DataTemplate x:Key="secondLevel">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<HierarchicalDataTemplate x:Key="topLevel" ItemsSource="{Binding Value.m_Items}" ItemTemplate="{StaticResource secondLevel}">
<TextBlock Text="{Binding Value.m_Name}">
</TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<TreeView x:Name="FolderTree" ItemsSource="{Binding m_FolderList}" ItemTemplate="{StaticResource topLevel}">
</TreeView>
</StackPanel>
output
public List<Folder> m_FolderList
{
get
{
List<Folder> list = new List<Process>();
list.AddRange(Manager.Instance.GetFolderDirectory().Values);
return list;
}
}
I hate doing this, but its the only method that worked here with the Heirarchical template. I've used a binding to dictionary Path=Value before numerous times but it just refused to work in this instance with many attempts. Its not the best answer, but its the only working one I have at the moment.

Binding SelectedItem in nested ListBox using Caliburn.Micro

I'm trying to use Caliburn.Micro to bind a view model of a nested ListBox but I'm stuck.
I have a list workspace that is divided in two sections a list of groups containtaining items in a different part of that workspace I want to show the details of either a group or an item in a group depending on what is the SelectedItem. I'm new to Caliburn.Micro and looked at the documentation and samples but don't know how to connect the dots. Specifically I'm trying to model this after the Caliburn.Micro.HelloScreens sample. The code I have so far:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : Conductor<AnalyzerGroupWorkspaceViewModel>, IWorkspace
{
private Selected selected = Selected.AnalyzerGroup;
private const string name = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(analyzerGroups.GetAll().Select(fromMapper.Map<Model.AnalyzerGroup,AnalyzerGroupViewModel>));
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return name; } }
public Selected Selected
{
get { return selected; }
set
{
if (value == selected) return;
selected = value;
NotifyOfPropertyChange(() => Selected);
}
}
private IConductor Conductor { get { return (IConductor) Parent; } }
public void Show()
{
var haveActive = Parent as IHaveActiveItem;
if (haveActive != null && haveActive.ActiveItem == this)
{
DisplayName = name;
Selected = Selected.AnalyzerGroup;
}
else
{
Conductor.ActivateItem(this);
}
}
}
The view:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox x:Name="AnalyzerGroups">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="Analyzers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id }"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Context="{Binding Selected, Mode=TwoWay}"
cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</GroupBox>
</DockPanel>
</UserControl>
Next to that I have two UserControls that display the detailsof a group or item.
My specific question is how can I use the SelectedItem property of the two ListBoxes to modify the Selected property to switch between displaying the AnalyzerGroup details and the Analyzer details?
I've found the solution to the above described problem the solution consists of four parts:
Add a IsSelected property (that notifies changes) to both 'child' ViewModels
Bind the IsSelected property of the ListBox.ItemContainerStyle to the IsSelected property of the respective ViewModels
Attach a Caliburn.Micro Message to the 'outer' ListBox and use the $eventArgs argument
In the ViewModel bound to the entire UserControl implement the method corresponding to the Message and use the AddedItems property of the eventArgs to set the SelectedViewModel property setting the IsSelected property of the previous SelectedViewModel to false
The code then becomes:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : PropertyChangedBase, IAnalyzerGroupWorkspaceViewModel
{
private IAnalyzerViewModel selectedViewModel;
private const string WorkspaceName = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(
analyzerGroups.GetAll().Select(
fromMapper.Map<Model.AnalyzerGroup, AnalyzerGroupViewModel>));
}
public void SelectionChanged(object eventArgs)
{
var typedEventArgs = eventArgs as SelectionChangedEventArgs;
if (typedEventArgs != null)
{
if (typedEventArgs.AddedItems.Count > 0)
{
var item = typedEventArgs.AddedItems[0];
var itemAsGroup = item as IAnalyzerViewModel;
if (itemAsGroup != null)
{
SelectedViewModel = itemAsGroup;
}
}
}
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return WorkspaceName; } }
public IAnalyzerViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
if (Equals(value, selectedViewModel))
{
return;
}
if (SelectedViewModel != null)
{
SelectedViewModel.IsSelected = false;
}
selectedViewModel = value;
NotifyOfPropertyChange(() => SelectedViewModel);
}
}
}
The View:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox SelectionMode="Single"
x:Name="AnalyzerGroups"
cal:Message.Attach="[Event SelectionChanged] = [Action SelectionChanged($eventArgs)]">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="DarkGray">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}" />
<ListBox SelectionMode="Single" ItemsSource="{Binding Analyzers}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="DarkGray">
<StackPanel>
<TextBlock Text="{Binding Name }" Margin="10" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Model="{Binding SelectedViewModel}" />
</GroupBox>
</DockPanel>
</UserControl>
The answer to your specific question is yes, you can.
On the ViewModel of your UserControl. You create a property that is a ViewModel of either of the two details.
public interface IAnalyzerViewModel
{
}
Next, create two ViewModels for the Views of your Analyzer and AnalyzerGroup views.
public class AnalyzerGroupViewModel : IAnalyzerViewModel
{
}
public class AnalyzerViewModel : IAnalyzerViewModel
{
}
Next, create a property in your UserControl's ViewModel that implements INPC or PropertyChangedBase of Caliburn Micro.
public class MainViewModel :
{
private IAnalyzerViewModel _analyzerViewModel;
public IAnalyzerViewModel SelectedViewModel { get { return _analyzerViewModel; } set { _analyzerViewModel = value; OnPropertyChanged(() => SelectedViewModel); }
//Hook up the selected item changed event of your listbox and set the appropriate ViewModel to show, so if you either want to show the AnalyzerGroup or AnalyzerView.
}
And lastly, just update your MainView to
<ContentControl x:Name="SelectedViewModel"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
Caliburn will hook up the appropriate bindings and stuff and will pull the View for the associated ViewModel, and also the Name convention part will automatically map it to any public property of its datacontext as long as the names match.

Categories