How to set different dataTemplate to treeViewItems in code behind - c#

I have TreeView (named: treeViewDiagram):
< TreeView x:Name = "treeViewDiagram" Grid.Row="2"
FontFamily="/logicalTree;component/Resources/Fonts/#Oxygen"
BorderThickness="7,7,0,7" BorderBrush="#FFE9E9E9" Padding="86,0,0,0" ItemTemplateSelector="{Binding Source={StaticResource dataTemplateSelector}}" />
And here the three dataTamplate I have:
<local:CostomDataTemplateSelector x:Key="dataTemplateSelector"/>
< DataTemplate x:Key = "BasicDataTemplate">
< Grid Height="Auto" MinWidth="250" HorizontalAlignment="Left" VerticalAlignment="Top" >
<...>
< /Grid >
< /DataTemplate >
<DataTemplate x:Key="ComplexDataTemplate" >
<Grid Height="Auto" MinWidth="250" HorizontalAlignment="Left" VerticalAlignment="Top">
<...>
</Grid>
</DataTemplate>
<DataTemplate x:Key="RootDataTemplate" >
<Grid Height="Auto" MinWidth="250" HorizontalAlignment="Left" VerticalAlignment="Top" >
<...>
</Grid>
</DataTemplate>
Now, I want to add treeViewItems in the code and set for each treeViewItem
different dataTemplate according my code.
(if (basic) => so: BasicDataTemplate)
(if (complex) => so: ComplexDataTemplate)
(if (root) => so: RootDataTemplate)
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (item is MyBasicData)
{
return element.FindResource("BasicDataTemplate") as DataTemplate;
}
else if (item is MyComplexData)
{
return element.FindResource("ComplexDataTemplate") as DataTemplate;
}
else if (item is MyRootData)
{
return element.FindResource("RootDataTemplate") as DataTemplate;
}
return null;
}
I have 3 ObservableCollection in MainWindow:
private ObservableCollection< MyBasicData > _myBasicDataCollection = new ObservableCollection< MyBasicData >();
private ObservableCollection< MyComplexData > _myComplexDataCollection = new ObservableCollection< MyComplexData >();
private ObservableCollection< MyRootData > _myRootDataCollection = new ObservableCollection< MyRootData >();
How can I do that?

Create DataTemplateSelector which iterates through every item and, based on condition, sets its DataTemplate.
class TreeViewTemplateSelector : DataTemplateSelector
{
public DataTemplate BasicTemplate { get; set; }
public DataTemplate ComplexTemplate { get; set; }
public DataTemplate RootTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MyBasicData) return BasicTemplate;
if (item is MyComplexData) return ComplexTemplate;
if (item is MyRootData) return RootTemplate;
return null;
}
}
XAML:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication6">
<Window.Resources>
<DataTemplate x:Key="BasicDataTemplate">
</DataTemplate>
<DataTemplate x:Key="ComplexDataTemplate">
</DataTemplate>
<DataTemplate x:Key="RootDataTemplate">
</DataTemplate>
<local:TreeViewTemplateSelector x:Key="TreeViewDataTemplateSelector" BasicTemplate="{StaticResource BasicDataTemplate}"
ComplexTemplate="{StaticResource ComplexDataTemplate}"
RootTemplate="{StaticResource RootDataTemplate}"/>
</Window.Resources>
<TreeView ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"/>

Related

Display different data templates on a list view in UWP

I am working with visual studio 2017. And I wand to display two type of list view items in a list view. That means two different custom data templates.
And This is my xaml page
<Page
x:Class="InboxModule.ChatMessages"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:InboxModule"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="leftTemplate">
<StackPanel Background="Aqua" Orientation="Horizontal">
<TextBlock Text="left"/>
<TextBlock Text="{Binding LastMessage}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="rightTemplate">
<Grid Background="White">
<TextBlock Text="right"/>
</Grid>
</DataTemplate>
<local:MyDataTemplateSelector x:Key="myPremiumUserDataTemplateSelector" />
</Page.Resources>
<Grid>
<ListView x:Name="myListView" ItemTemplateSelector="{StaticResource myPremiumUserDataTemplateSelector}">
</ListView>
</Grid>
</Page>
And my xaml.cs code is this
public sealed partial class ChatMessages : Page
{
public ChatMessages()
{
this.InitializeComponent();
List<chat> users = new List<chat>();
for (int i = 0; i < 10; ++i)
{
var user = new chat { NewMessages = "Name is mj "};
if (i == 2 || i == 4)
{
user.Name = "Alex Doe";
}
users.Add(user);
}
myListView.ItemsSource = users;
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(InboxChat));
}
}
And my MyDataTemplateSelector class is this
public class MyDataTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
FrameworkElement elemnt = container as FrameworkElement;
chat user = item as chat;
if (user.Name == "Alex Doe")
{
return elemnt.FindName("leftTemplate") as DataTemplate;
}
else
{
return elemnt.FindName("rightTemplate") as DataTemplate;
}
}
}
I tried to use dummy values. And i have tried for hours.. but i could not find a solution. Please help me to solve this problem.
Thank you very much!!
I think you need to pass the data templates to the selector when it is initialized in the view XAML. Here is how to do it.
You selector will look like this (basically accepting the data templates as properties in the class):
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate1 { get; set; }
public DataTemplate DataTemplate2 { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if ([Condition 1] == true)
return DataTemplate1;
if ([Condition 2] == true)
return DataTemplate2;
return base.SelectTemplateCore(item);
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
Then to use this selector, declare it like this in your view's XAML:
<Page.Resources>
<MyDataTemplateSelector x:Key="MySelector">
<MyDataTemplateSelector.DataTemplate1>
<DataTemplate .... />
<MyDataTemplateSelector.DataTemplate1>
<MyDataTemplateSelector.DataTemplate2>
<DataTemplate .... />
<MyDataTemplateSelector.DataTemplate2>
</MyDataTemplateSelector>
</Page.Resources>
you will basically be declaring those data templates inside the xaml code initializing your selector.
Edit: As for the reason your code wasn't working: I suspect this is because it can't find the element you are looking for with FindName and is probably returning a null data template back to the list view using this selector.
Hope this helps you.

XAML - How to write nested CollectionViewSource for hierarchical data

I have a treeview that has a hierarchical data. I'd like to add a filter to it so, I could search any element up to n-level. To do that, I find the concept of nested collectionviewsource interesting. But I'm not sure how it will programmatically work.
Suppose we have a Collection of groups and each group has a collection of elements. So, how do I write a nested collectionviewsource so, I could filter the search result of all the elements in all groups?
Here's a sample code that I thought it should work but it only filters the data on the first level and does not goes down to search 'Elements'. So, I wonder how do I write a right nested collectionviewsource to filter the hierarchical data in a treeview.
<UserControl.Resources>
<CollectionViewSource x:Key="CollectionViewSourceGroups" x:Name="_CollectionViewSourceGroups" Source="{Binding Groups}" Filter="_filterGroups"/>
<CollectionViewSource x:Key="CollectionViewSourceElements" x:Name="_CollectionViewSourceElements" Source="{Binding Elements, Source={StaticResource CollectionViewSourceGroups}}" Filter="_filterElements"/>
</UserControl.Resources>
Here's the code that would trigger the filter command:
var cvs = TryFindResource("CollectionViewSourceGroups") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
cvs = TryFindResource("CollectionViewSourceElements") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
Edit:
Here's the sample MVVM and XAML code:
/// <summary>
/// Group of Views
/// </summary>
public class ViewGroup : INotifyPropertyChanged
{
public string Name { get; set; }
public List<ViewGroup> Groups { get; set; }
private List<ProjectObject> _elements;
public List<ProjectObject> Elements
{
get { return _elements; }
set
{
_elements = value;
OnPropertyChanged();
}
}
public ViewGroup()
{
Groups = new List<ViewGroup>();
Elements = new List<ProjectObject>();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SheetManagerViewModel
{
public static SheetManagerViewModel Instance;
public ObservableCollection<ViewGroup> Groups { get; set; }
public SheetManagerViewModel()
{
Instance = this;
Groups = new ObservableCollection<ViewGroup>();
}
}
<Grid Grid.Row="1">
<Grid>
<TreeView x:Name="ProjectBrowserTreeView" ItemsSource="{Binding Groups}" MouseMove="ProjectBrowserTreeView_OnMouseMove" DragOver="ProjectBrowserTreeView_OnDragOver" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Groups}" DataType="{x:Type local:ViewGroup}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Elements}" DataType="{x:Type local:ViewGroup}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:ProjectObject}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Grid>

WPF: access ElementName in a list within a list on the XAML side

I'm having a hard time solving a potential newbie problem: I've got a ObservableCollection<TopItem> MyTopItems that I display in a ListView. The type TopItem contains a string TopName and an ObservableCollection<NestedItem> NestedItems. The type NestedItem contains only a string NestedName.
My problematic is quite simple: I want to retrieve information on the nested item that I select, on the XAML side.
Right now, I can retrieve the selected item of TopItems quite easily, but I can't retrieve the selected item of NestedItems.
I know that I can bind the selected item (for TopItems and NestedItems) in the view model, but in my case it's almost pointless because I've got no use for it in the view model. Plus, I'd really like to know how to do it on the XAML side!
Enough talk, now comes the code.
A class to implement to INotifyPropertyChanged interface that I'm gonna use in my models and view model; not the cleanest way of doing, but it's for the sake of the demo. This class is just there to see the big picture, just know that it works well:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfSelectItemInDoubleList.Utils
{
public abstract class INPCBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedPropertyChanged([CallerMemberName]string propertyName = null)
{
if (this.PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
else
{
storage = value;
RaisedPropertyChanged(propertyName);
return true;
}
}
}
}
Comes the NestedItem type:
using WpfSelectItemInDoubleList.Utils;
namespace WpfSelectItemInDoubleList.Model
{
public class NestedItem : INPCBase
{
private string _NestedName;
public string NestedName
{
get { return this._NestedName; }
set
{
SetProperty(ref this._NestedName, value);
}
}
public NestedItem(string nestedName)
{
NestedName = nestedName;
}
}
}
The TopItem type:
using System.Collections.ObjectModel;
using WpfSelectItemInDoubleList.Utils;
namespace WpfSelectItemInDoubleList.Model
{
public class TopItem : INPCBase
{
private string _TopName;
public string TopName
{
get { return this._TopName; }
set
{
SetProperty(ref this._TopName, value);
}
}
private ObservableCollection<NestedItem> _NestedItems;
public ObservableCollection<NestedItem> NestedItems
{
get { return this._NestedItems; }
set
{
SetProperty(ref this._NestedItems, value);
}
}
public TopItem(string topName)
{
TopName = topName;
}
}
}
The view model:
using System.Collections.ObjectModel;
using WpfSelectItemInDoubleList.Model;
using WpfSelectItemInDoubleList.Utils;
namespace WpfSelectItemInDoubleList.ViewModel
{
public class MainWindowViewModel : INPCBase
{
private ObservableCollection<TopItem> _TopItems;
public ObservableCollection<TopItem> TopItems
{
get { return this._TopItems; }
set
{
SetProperty(ref this._TopItems, value);
}
}
public MainWindowViewModel()
{
TopItems = new ObservableCollection<TopItem>();
for (int i = 0; i < 5; i++)
{
var topItem = new TopItem($"top item {i}")
{
NestedItems = new ObservableCollection<NestedItem>()
};
for (int j = 0; j < 5; j++)
{
var nestedItem = new NestedItem($"NI {j}");
topItem.NestedItems.Add(nestedItem);
}
TopItems.Add(topItem);
}
}
}
}
Finally, the most important part: the XAML!:
<Window x:Class="WpfSelectItemInDoubleList.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfSelectItemInDoubleList"
xmlns:vm="clr-namespace:WpfSelectItemInDoubleList.ViewModel"
mc:Ignorable="d"
Title="List in list" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<ListView x:Name="TopItemsLV" Grid.Row="0" Margin="10" HorizontalContentAlignment="Stretch" ItemsSource="{Binding TopItems, Mode=TwoWay}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TopNameTB" Grid.Column="0" Text="{Binding TopName}" TextWrapping="Wrap" VerticalAlignment="Center" />
<StackPanel Grid.Column="1">
<ListView x:Name="NestedItemsLV" ItemsSource="{Binding NestedItems}" BorderThickness="0" HorizontalAlignment="Center" SelectionMode="Single">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="Transparent" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NestedName}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl Grid.Row="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Margin="10" Grid.Row="1" Content="{Binding ElementName=TopItemsLV, Path=SelectedItem.TopName}" />
<ContentControl Margin="10" Grid.Row="2" Content="{Binding ElementName=NestedItemsLV, Path=SelectedItem.NestedName}" />
</Grid>
</Window>
The interesting part is the second ContentControl. The first one is working well, but the second doesn't: nothing is showing when I select a nested item. A hint is given to me by intellisense: it sees the TopItemsLV, but not the NestedItemsLV.
Prepare for the most beautiful UI ever. Please don't stole it from me, I'm planning to make millions out of it! Just kidding.
As you can see, the selected item from TopItems is showing, but not the selected item from NestedItems. Any idea why?
Thanks :)
EDIT: Skip the first solution. It's more appropriate for really simple views. Scroll down to my second solution instead.
If you are only binding a single ItemsControl (e.g., ListView) to this list of TopItem instances, then you could just the default collection view manage the selected items for you. That's probably the simplest way to do this.
First, set IsSynchronizedWithCurrentItem="True" on both TopItemsLV and NestedItemsLV.
Then, change your content control bindings as follows:
<ContentControl Content="{Binding Path=TopItems/TopName}" />
<ContentControl Content="{Binding Path=TopItems/NestedItems/NestedName}" />
The / separator in a binding path means "drill down into the currently selected item". The selected item is maintained by the default collection view for both your TopItems collection and each NestedItems collection. The default collection view is what you would get if you called CollectionViewSource.GetDefaultView.
Better Solution
The conventional MVVM approach would be to add a SelectedItem property alongside your TopItems and NestedItems collections. Make sure they fire property change events. The property type should match the corresponding collection's element type. If these properties start out with a null value, then nothing will be selected initially, which is what you want.
On both list views, set SelectedItem="{Binding SelectedItem, Mode=TwoWay}". Remove the IsSynchronizedWithCurrentItem settings from my original answer.
Adjust your content control bindings as follows:
<ContentControl Content="{Binding Path=SelectedItem.TopName}" />
<ContentControl Content="{Binding Path=SelectedItem.SelectedItem.NestedName}" />
Attach a new event handler to NestedItemsLV:
<ListView x:Name="NestedItemsLV"
GotFocus="OnNestedItemsLVGotFocus"
... />
In your view's code-behind, implement the handler as follows:
private void OnNestedItemsLVGotFocus(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as MainWindowViewModel;
var parentItem = (sender as FrameworkElement)?.DataContext as TopItem;
if (viewModel != null && parentItem != null)
viewModel.SelectedItem = parentItem;
}
I think you'll agree that this solution works better.

binding to nested object from the parent user control wpf

I am writing a new user control. It needs to be able to display an ObservableCollection of items. Those items will have a property that is also an observable collection, so it is similar to a 2-d jagged array. The control is similar to a text editor so the outer collection would be the lines, the inner collection would be the words. I want the consumer of the control to be able to specify not only the binding for the lines, but also the binding for the words. The approach I have so far is as follows:
The user control inherits from ItemsControl. Inside this control it has a nested ItemsControl. I would like to be able to specify the binding path of this nested ItemsControl from the parent user control. The XAML for the UserControl is
<ItemsControl x:Class="IntelliDoc.Client.Controls.TextDocumentEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:IntelliDoc.Client"
xmlns:con="clr-namespace:IntelliDoc.Client.Controls"
xmlns:data="clr-namespace:IntelliDoc.Data;assembly=IntelliDoc.Data"
xmlns:util="clr-namespace:IntelliDoc.Client.Utility"
xmlns:vm="clr-namespace:IntelliDoc.Client.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300"
>
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<ItemsPresenter Name="PART_Presenter" />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<ItemsControl Name="PART_InnerItemsControl" ItemsSource="{Binding NestedBinding, ElementName=root}" >
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Name="InnerStackPanel" Orientation="Horizontal" >
<TextBox Text="" BorderThickness="0" TextChanged="TextBox_TextChanged" />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<ContentControl Content="{Binding Path=Data, Mode=TwoWay}" />
<TextBox BorderThickness="0" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
The code behind has this property declared
public partial class TextDocumentEditor : ItemsControl
{
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(BindingBase), typeof(TextDocumentEditor),
new PropertyMetadata((BindingBase)null));
public BindingBase NestedItems
{
get { return (BindingBase)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
...
}
The expected bound object will be as follows:
public class ExampleClass
{
ObservableCollection<InnerClass> InnerItems {get; private set;}
}
public class InnerClass : BaseModel //declares OnPropertyChanged
{
private string _name;
public string Name //this is provided as an example property and is not required
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
....
}
public class ViewModel
{
public ObservableCollection<ExampleClass> Items {get; private set;}
}
The XAML declaration would be as follows:
<Window x:Class="IntelliDoc.Client.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="TestWindow" Height="300" Width="300">
<DockPanel>
<TextDocumentEditor ItemsSource="{Binding Path=Items}" NestedItems={Binding Path=InnerItems} >
<DataTemplate>
<!-- I would like this to be the user defined datatemplate for the nested items. Currently I am just declaring the templates in the resources of the user control by DataType which also works -->
</DataTemplate>
</TextDocumentEditor>
</DockPanel>
In the end, I want the user control I created to provide the ItemsControl template at the outer items level, but I want the user to be able to provide the datatemplate at the inner items control level. I want the consumer of the control to be able to provide the bindings for both the Outer items and the nested items.
I was able to come up with a solution that works for me. There may be a better approach, but here is what I did.
First, on the outer ItemsControl, I subscribed to the StatusChanged of the ItemContainerGenerator. Inside that function, I apply the template of the ContentPresenter and then search for the Inner ItemsControl. Once found, I use the property NestedItems to bind to the ItemsSource property. One of the problems I was having originally was I was binding incorrectly. I fixed that and I changed the NestedItems to be a string. Also, I added a new property called NestedDataTemplate that is of type DataTemplate so that a user can specify the DataTemplate of the inner items control. It was suggested that I not use a UserControl since I don't inherit from a UserControl, so I will change it to a CustomControl. The code changes are below
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(string), typeof(TextDocumentEditor),
new PropertyMetadata((string)null));
public static readonly DependencyProperty NestedDataTemplateProperty = DependencyProperty.Register("NestedDataTemplate", typeof(DataTemplate), typeof(TextDocumentEditor),
new PropertyMetadata((DataTemplate)null));
public DataTemplate NestedDataTemplate
{
get { return (DataTemplate)GetValue(NestedDataTemplateProperty); }
set
{
SetValue(NestedDataTemplateProperty, value);
}
}
public string NestedItems
{
get { return (string)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (((ItemContainerGenerator)sender).Status != GeneratorStatus.ContainersGenerated)
return;
ContentPresenter value;
ItemsControl itemsControl;
for (int x=0;x<ItemContainerGenerator.Items.Count; x++)
{
value = ItemContainerGenerator.ContainerFromIndex(x) as ContentPresenter;
if (value == null)
continue;
value.ApplyTemplate();
itemsControl = value.GetChildren<ItemsControl>().FirstOrDefault();
if (itemsControl != null)
{
if (NestedDataTemplate != null)
itemsControl.ItemTemplate = NestedDataTemplate;
Binding binding = new Binding(NestedItems);
BindingOperations.SetBinding(itemsControl, ItemsSourceProperty, binding);
}
}
}

setting datacontext for a view in a tab item

in my window resources i have a datatemplate for my viewmodel and a static mainviewmodel
<Window.Resources>
<DataTemplate DataType="{x:Type SharedViewModels:DatabaseViewModel}">
<SharedViews:DatabaseView/>
</DataTemplate>
<LocalViewModels:SharedSettingsViewModel x:Key="SharedSettingsViewModel"/>
</Window.Resources>
DatabaseViewModel is an instance in my static SharedSettingsViewModel
and i have this tab control
<TabControl DataContext="{Binding Source={StaticResource SharedSettingsViewModel}}" ItemsSource="{Binding SharedSettingsViewModelsTabList}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
and i add items to my TabControl itemssorce like this
SharedSettingsViewModelTabList.Add(new Shared.Models.TabItem() { Header = "Sql Databases", Content = DatabaseViewModel });
How do set the datacontext of my DatabaseView to the DatabaseViewModel instance that is inside my SharedSettingsViewModel?
in my view DatabaseView XAML which is a user control i tried
DataContext="{Binding DatabaseViewModel}"
but this doesnt seem to work
here is my TabItem Model
public class TabItem : MVVM.ObservableObject
{
public string Header { get; set; }
public MVVM.ObservableObject Content { get; set; }
}
my DatabaseViewModel which will be defined as a Property in SharedSettingsViewModel
private ObservableCollection<Models.Database> databases;
public ObservableCollection<Models.Database> Databases
{
get
{
if (databases == null)
{
databases = new ObservableCollection<Models.Database>();
databases.Add(new Models.Database() { Displayname = "new" });
}
return databases;
}
set
{
SetField(ref databases, value, "Databases");
}
}
and here is my SharedSettingsViewModel which contains the tab items collection
public class SharedSettingsViewModel : MVVM.ObservableObject
{
private ObservableCollection<Shared.Models.TabItem> SharedSettingsViewModelTabList;
public ObservableCollection<Shared.Models.TabItem> SharedSettingsViewModelTabList
{
get
{
if (sharedSettingsViewModelTabList == null)
{
sharedSettingsViewModelTabList = new ObservableCollection<Shared.Models.TabItem>();
}
return sharedSettingsViewModelTabList;
}
}
//my DatabaseViewModel property..as a child view model
private Shared.ViewModels.DatabaseViewModel databaseViewModel;
public Shared.ViewModels.DatabaseViewModel DatabaseViewModel
{
get
{
if (databaseViewModel == null)
{
databaseViewModel = new Shared.ViewModels.DatabaseViewModel();
}
return databaseViewModel;
}
set
{
SetField(ref databaseViewModel, value, "DatabaseViewModel");
}
}
}
You will have to update the code as below and your DatabaseViewModel will become the DataContext of your DatabaseView which will be the DataTemplate for the Content of ContentPresenter in ContentTemplate of your TabControl
<TabControlDataContext="{Binding Source={StaticResource SharedSettingsViewModel}}" ItemsSource="{Binding SharedSettingsViewModelsTabList}">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Content}"/>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

Categories