In My View model I have following structure:
class MainWindowViewModel:BaseEntity
{
#region Output Proprties
public ObservableCollection<Customer> Customers { get; private set; }
public ObservableCollection<TreeViewItems> RoorTreeViewItem { get; set; }
public ObservableCollection<Level2Child> l2Childs { get; set; }
public ObservableCollection<Level1Child> l1Childs { get; set; }
public Level1Child l1Child { get; set; }
#endregion
public MainWindowViewModel()
{
ClickCommand = new RelayCommand(paremer =>
{
var customeList = SampleMVVM.Service.Service.GetAllCustomers();
Customers = new ObservableCollection<Customer>(customeList);
ObservableCollection<TreeViewItems> tViewIte = new ObservableCollection<TreeViewItems>();
l1Childs = new ObservableCollection<Level1Child>();
l2Childs = new ObservableCollection<Level2Child>();
Level2Child l2Child1 = new Level2Child { Name = "Zems001", Description = "Zemms as ZemsBond" };
Level2Child l2Child2 = new Level2Child { Name = "Zems002", Description = "Zemms as ZemsBond" };
Level2Child l2Child3 = new Level2Child { Name = "Zems003", Description = "Zemms as ZemsBond" };
Level2Child l2Child4 = new Level2Child { Name = "Zems004", Description = "Zemms as ZemsBond" };
Level2Child l2Child5 = new Level2Child { Name = "Zems005", Description = "Zemms as ZemsBond" };
l2Childs.Add(l2Child1);
l2Childs.Add(l2Child2);
l2Childs.Add(l2Child3);
l2Childs.Add(l2Child4);
l2Childs.Add(l2Child5);
Level1Child l1Child = new Level1Child { Name = "Bond", Description = "Gems Bond", Level2Child = l2Childs };
l1Childs.Add(l1Child);
TreeViewItems rootItem = new TreeViewItems {Name= "Shon Conery",Description= "Octopussy", Level1Child = l1Childs };
tViewIte.Add(rootItem);
RoorTreeViewItem = new ObservableCollection<TreeViewItems>(tViewIte);
NotifyPropertyChanged("Customers");
NotifyPropertyChanged("RoorTreeViewItem");
});
}
#region InputCommands
public ICommand ClickCommand { get; private set; }
#endregion
}
I am trying to show the tree view using XAML:
<Window x:Class="SampleMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:vm="clr-namespace:SampleMVVM.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!--<HierarchicalDataTemplate DataType = "{x:Type vm:Level1Child}" ItemsSource = "{Binding Path=l1Childs}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type vm:Level2Child}" ItemsSource = "{Binding Path=l2Childs}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>-->
<HierarchicalDataTemplate
DataType="{x:Type vm:Level1Child}"
ItemsSource="{Binding Path=Level2Child, diag:PresentationTraceSources.TraceLevel=High}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Name="viewsTreeView" ItemsSource="{Binding RoortTreeViewItem}">
</TreeView>
<Button Height="50" Width="100" Content="Get Records" Command="{Binding ClickCommand}" Margin="132,260,285,10"/>
</Grid>
Ideally I should see the tree structure similar to following image, but just getting namespace value as root, nothing else.
Edit
Made following changes, but still getting Top Level elements only:
<Window.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vm:TreeViewItems}"
ItemsSource="{Binding Path=l2Childs, diag:PresentationTraceSources.TraceLevel=High}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Name="viewsTreeView" ItemsSource="{Binding RootTreeViewItem}"/>
It would be very helpful if you would include all of your code. I'm left guessing at a lot of stuff that's not shown. There may be further problems in the code that you didn't include.
But to start with, it appears that you are binding the wrong thing to ItemsSource in your templates. You left out your class definitions, but it looks like Level1Child keeps its children in a property called Level2Child. In the template for Level1Child, the DataContext is an instance of Level1Child, not an instance of your viewmodel. So bind to the property on Level1Child:
<HierarchicalDataTemplate
DataType="{x:Type vm:Level1Child}"
ItemsSource="{Binding Path=Level2Child}"
>
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
In addition, you have no template at all for your root item type, TreeViewItems. You need a template for that type as well, and as with the others, you need to bind ItemsSource to the correct name of the actual child-collection property on the TreeViewItems class.
You can diagnose these binding issues more easily by adding a trace to a binding that's not doing what you expect. Add the System.Diagnostics namespace to the outermost tag in your XAML file:
<Window
...
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...
>
And add this attached property to the Binding that's not working as you expect:
diag:PresentationTraceSources.TraceLevel=High
...like so:
<HierarchicalDataTemplate
DataType="{x:Type vm:Level1Child}"
ItemsSource="{Binding Path=Level2Child, diag:PresentationTraceSources.TraceLevel=High}"
>
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
In your "Output" pane in Visual Studio at runtime, when it tries to resolve that binding, you'll get a lot of information about what the DataContext actually is, and what the binding is trying to do to resolve itself.
The trace output slows things down, so don't leave it in there permanently. Take it out when you've resolved the problem.
Related
I have an ItemsControl used to display View of items like this:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding View}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Below is short mcve to give you an idea:
public class Item
{
public string Text { get; set; }
public object View { get; set; }
... // more properties used in bindings
}
public partial class MainWindow : Window
{
public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
public MainWindow()
{
InitializeComponent();
// 1
{
var control = new TextBlock();
var item = new Item { Text = "1", View = control };
BindingOperations.SetBinding(control, TextBlock.TextProperty, new Binding(nameof(Item.Text)) { Source = item });
Items.Add(item);
}
// 2
{
var control = new CheckBox();
var item = new Item { Text = "2", View = control };
BindingOperations.SetBinding(control, CheckBox.ContentProperty, new Binding(nameof(Item.Text)) { Source = item });
Items.Add(item);
}
// ... and so on
DataContext = this;
}
}
As you can see each item has pre-created View (unfortunately this can't/shouldn't be changed), which can be anything, includes binding, etc.
My question: how to move creating of View into xaml (as data templates)?
Pseudoxaml:
<SomeContainer.Resources>
<DataTemplate x:Key="type1">
<TextBlock Text="{Binding Text}" />
</DataTemplate>
<DataTemplate x:Key="type2">
<CheckBox Content="{Binding Text}" />
</DataTemplate>
</SomeContainer.Resources>
<ItemsControl ... /> <!-- same definition as early? -->
Pseudo-code
Items.Add(new Item { Text = "1", View = LoadTemplate("type1") });
Items.Add(new Item { Text = "2", View = LoadTemplate("type2") });
object LoadTemplate(string key)
{
var resource = FindResource(key);
... // what next?
}
if you absolutely have to use UIElements in view model (instead of templates), and at the same time want to declare them in xaml, then
don't use DataTemplate
use x:Shared="False" on UIElement
<Window.Resources>
<TextBlock x:Key="type1" x:Shared="False" Text="{Binding Text}"/>
<CheckBox x:Key="type2" x:Shared="False" Content="{Binding Text}"/>
</Window.Resources>
each time you request a resource, you will get a new copy
LoadTemplate method is reduced to FindResource
object LoadTemplate(string key)
{
return FindResource(key);
}
Instead of creating a UI control such as a TextBlock or a CheckBox in the view model you should create a CLR object:
public class MyTextClass
{
public string Text { get; set; }
}
...
var view = new MyTextClass();
var item = new Item { Text = "1", View = control };
You could then use a DataTemplate in the view to associate an instance of your CLR object with a control:
<DataTemplate DataType="local:MyTextClass">
<TextBlock Text="{Binding Text}" />
</DataTemplate>
When you set the DataType property of a DataTemplate without specifying an x:Key, the DataTemplate gets applied automatically to data objects of that type: https://msdn.microsoft.com/en-us/library/system.windows.datatemplate.datatype(v=vs.110).aspx
XAML goes like this:
<UserControl.Resources>
<!-- templates -->
<HierarchicalDataTemplate x:Key="ProjectSteps" ItemsSource="{Binding Steps}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="ProjectsOverview" ItemTemplate="{StaticResource ProjectSteps}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</UserControl.Resources>
<TreeView HorizontalAlignment="Stretch" ItemsSource="{Binding Projects, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{StaticResource ProjectsOverview}">
</TreeView>
Projects is a property on my ViewModel, and it comes from this class:
public class ProjectsRepository : IProjectsRepository {
public IEnumerable<Project> GetProjects() {
return new List<Project> {
new Project {
Name = "Proj1",
Steps = new List<Step> {
new Step { Name = "Step1" },
new Step { Name = "Step2" },
new Step { Name = "Step3" }
}
},
new Project {
Name = "Proj2",
Steps = new List<Step> {
new Step { Name = "OtherStep1" },
new Step { Name = "OtherStep2" },
new Step { Name = "OtherStep3" },
new Step { Name = "OtherStep4" }
}
} };
}
}
First level items (Proj1, Proj2) are displayed, but the second level items (so each Step or OtherStep) are not. I can't even see the dropdown indicator next to the project name, so it seems that the second ProjectSteps template is not rendered anywhere. Suggestions?
Instead of in ProjectSteps set ItemsSource in ProjectsOverview
<HierarchicalDataTemplate x:Key="ProjectsOverview" ItemsSource="{Binding Steps}"
I refer to this article:
WPF TreeView HierarchicalDataTemplate - binding to object with multiple child collections
and modify the tree structure like:
Root
|__Group
|_Entry
|_Source
In Entry.cs:
public class Entry
{
public int Key { get; set; }
public string Name { get; set; }
public ObservableCollection<Source> Sources { get; set; }
public Entry()
{
Sources = new ObservableCollection<Source>();
}
public ObservableCollection<object> Items
{
get
{
ObservableCollection<object> childNodes = new ObservableCollection<object>();
foreach (var source in this.Sources)
childNodes.Add(source);
return childNodes;
}
}
}
In Source.cs:
public class Source
{
public int Key { get; set; }
public string Name { get; set; }
}
In XAML file:
<UserControl.CommandBindings>
<CommandBinding Command="New" Executed="Add" />
</UserControl.CommandBindings>
<TreeView x:Name="TreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Root}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=Name}" IsEnabled="True">
</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Path=Name}" IsEnabled="True">
</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Entry}" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" IsEnabled="True">
<TextBlock.ContextMenu>
<ContextMenu >
<MenuItem Header="Add" Command="New">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Source}" >
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
In UserControl.cs:
public ObservableCollection<Root> Roots = new ObservableCollection<Root>();
public UserControl6()
{
InitializeComponent();
//...Add new node manually
TreeView.ItemsSource = Roots;
}
private void Add(object sender, ExecutedRoutedEventArgs e)
{
Entry ee = (Entry)TreeView.SelectedItem;
Source s3 = new Source() { Key = 3, Name = "New Source" };
ee.Sources.Add(s3);
}
When I click right button on specific node "Entry" to add a new node "Source" under Entry
(call "Add" method), I add a new "Source" object under Entry successfully, but I can't see this new node on treeview. How to refresh treeview when adding/deleting node?
Use ObservableCollection instead of IList if you want to notify the user interface that something in the collection has changed
As far as I'm concerned, changing of type for Items to ObservableCollection<T> will not resolve the problem. You need to implement INotifyPropertyChanged.
I tested both solutions for my tree view, because I faced the same problem.
In my case changing of type from IList to ObservableCollection didn't refreshed GUI. However when I changed my auto property:
public List<SourceControlItemViewBaseModel> Items { get; set; }
to
private IEnumerable<SourceControlItemViewBaseModel> _items;
public IEnumerable<SourceControlItemViewBaseModel> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
Namely, I've implemented INotifyPropertyChanged and that changed the situation. The method that builds the tree structure defines the actual type of Items as new List<T>(), but it works and refreshes the GUI .
Nevertheless my tree was built in pure MVVM pattern without usage code-behind.
I use
<TreeView ItemsSource="{Binding SourceControlStructureItems}" />
and in the view model I use:
currentVm.Items= await SourceControlRepository.Instance.BuildSourceControlStructureAsync(currentVm.ServerPath);
That means I didn't added/removed items, but I rebuilt Node's sub collection.
Use this class and any changes in Sources collection will update/refresh tree in UI.
public class Entry
{
public int Key { get; set; }
public string Name { get; set; }
public ObservableCollection<Source> Sources { get; set; }
public Entry()
{
Sources = new ObservableCollection<Source>();
}
public CompositeCollection Items
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = Sources },
// Add other type of collection in composite collection
// new CollectionContainer() { Collection = OtherTypeSources }
};
}
}
}
I have the following 2 classes:
public class DeviceGroup
{
public String Name { get; set; }
public ObservableCollection<DeviceGroup> DeviceGroups { get; set; }
public ObservableCollection<Device> Devices { get; set; }
public DeviceGroup()
{
Name = String.Empty;
DeviceGroups = new ObservableCollection<DeviceGroup>();
Devices = new ObservableCollection<Device>();
}
}
public class Device
{
public String Name { get; set; }
}
My main class has an ObservableCollection.
In my Xaml - I can create a treeview easily if I just specify DeviceGroup within my HierachicalDataTemplate, as follows:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Device}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:DeviceGroup}" ItemsSource="{Binding DeviceGroups}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding DeviceGroups}"/>
</Grid>
The question is: How can I select the Devices collection as well as the DeviceGroup? I'd like the Devices to appear something like Windows Explorer (Directories and Files). Is there a Xaml solution to this problem? Or will I have to create the TreeViewItems in the codebehind.
Thanks.
The only solution I've found so far is within the code behind:
private void LoadTree()
{
foreach (DeviceGroup dg in ttvm.DeviceGroups)
{
TreeViewItem tvi = new TreeViewItem();
tvi.Header = dg;
treeView1.Items.Add(tvi);
AddTreeItems(tvi, dg);
}
}
private void AddTreeItems(TreeViewItem node, DeviceGroup deviceGroup)
{
foreach (DeviceGroup dg in deviceGroup.DeviceGroups)
{
TreeViewItem groupTVI = new TreeViewItem();
groupTVI.Header = dg;
node.Items.Add(groupTVI);
AddTreeItems(groupTVI, dg);
}
foreach (Device device in deviceGroup.Devices)
{
TreeViewItem deviceTVI = new TreeViewItem();
deviceTVI.Header = device;
node.Items.Add(deviceTVI);
}
}
LoadTree() is called after InitilizeComponent. The Xaml changed to:
window resources:
<HierarchicalDataTemplate DataType="{x:Type local:DeviceGroup}" ItemsSource="{Binding DeviceGroups}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
with just a plain treeview in a grid.
I have a custom class that I would like to bind a WPF TreeView that has three tiers. Each tier needs to be bound like this:
Monitor
--> LCD
--> Samsung 1445 LCD
--> CRT
--> Sony 125 CRT
Here is the example code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SystemInventory sysInventory = new SystemInventory();
//Possibly do something like this.
_myTreeView.DataContext = sysInventory.DeviceGroupInstances;
}
public class SystemInventory
{
public ObservableCollection<DeviceGroup> DeviceGroupInstances { get; set; }
public SystemInventory()
{
DeviceGroupInstances = new ObservableCollection<DeviceGroup>();
DeviceGroupInstances.Add(new DeviceGroup("Monitor"));
}
}
public class DeviceGroup
{
public string DeviceGroupName { get; set; }
public ObservableCollection<DeviceType> DeviceTypeInstances { get; set; }
public DeviceGroup(string deviceGroupName)
{
DeviceTypeInstances = new ObservableCollection<DeviceType>();
DeviceGroupName = deviceGroupName;
if (deviceGroupName == "Monitor")
{
DeviceTypeInstances.Add(new DeviceType("LCD"));
DeviceTypeInstances.Add(new DeviceType("CRT"));
}
}
}
public class DeviceType
{
public string DeviceTypeName { get; set; }
public ObservableCollection<DeviceInstance> DeviceInstances { get; set; }
public DeviceType(string deviceGroupName)
{
DeviceInstances = new ObservableCollection<DeviceInstance>();
DeviceTypeName = deviceGroupName;
if (deviceGroupName == "Monitor")
{
DeviceInstances.Add(new DeviceInstance("Samsung 1445 LCD"));
}
else
{
DeviceInstances.Add(new DeviceInstance("Sony 125 CRT"));
}
}
}
public class DeviceInstance
{
public string DeviceInstanceName { get; set; }
public DeviceInstance(string instanceName)
{
DeviceInstanceName = instanceName;
}
}
}
First FIX
Change CTOR:
public MainWindow()
{
InitializeComponent();
SystemInventory sysInventory = new SystemInventory();
//Possibly do something like this.
this.Content = sysInventory;
}
Second FIX
Use XAML below (add to MainWindow), where {x:Type pc:MainWindow+SystemInventory} used for nested classes
<Window.Resources>
<DataTemplate DataType="{x:Type pc:MainWindow+SystemInventory}">
<TreeView ItemsSource="{Binding DeviceGroupInstances}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type pc:MainWindow+DeviceGroup}" ItemsSource="{Binding DeviceTypeInstances}">
<Label Content="{Binding DeviceGroupName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type pc:MainWindow+DeviceType}" ItemsSource="{Binding DeviceInstances}">
<Label Content="{Binding DeviceTypeName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type pc:MainWindow+DeviceInstance}">
<Label Content="{Binding DeviceInstanceName}"/>
</DataTemplate>
</Window.Resources>
About namespaces
Namespace is what you need to know to use classes. If your classes have namespace:
namespace MyCompany.MyProject.MyComponent
{
public class SystemInventory
{
....
}
}
This namespace should be added to XAML with alias to use:
<Window x:Class="MyCompany.MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myAlias="clr-namespace:MyCompany.MyProject.MyComponent"
Title="Window1" Height="300" Width="350">
<Window.Resources>
...
Now you can use this classes in XAML like:
<DataTemplate DataType="{x:Type myAlias:DeviceInstance}">
<Label Content="{Binding DeviceInstanceName}"/>
</DataTemplate>
Just translate the property names in the answer Charlie already gave you.
<TreeView ItemsSource="{Binding DeviceGroups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceTypeInstances}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceInstances}">
<TextBlock Text="{Binding DeviceInstanceName}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding DeviceTypeName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You need to use a HierarchicalDataTemplate. There are many examples of this on Stack Overflow (and elsewhere) and if you search around you'll quickly find one. I would create a mock-up to illustrate it, but based on your very low accept rate, I'm not sure it would be worth it. You should look over your past questions and accept answers for all of them.