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}"
Related
I'm new to WPF and MVVM ... i created a class WorkstationItem
public class WorkstationItem
{
public WorkstationItem() { }
public string Name { get; set; }
public string OS { get; set; }
public List<UpdateItem> Updates { get; set; }
}
UpdateItem is another class:
public class UpdateItem
{
public UpdateItem() { }
public string Title { get; set; }
public string KB { get; set; }
}
I create some dummy data:
private List<WorkstationItem> workstations = new List<WorkstationItem>();
workstations.Add(new WorkstationItem
{
Name = "PC01",
OS = "Windows Server 2019",
Updates = new List<UpdateItem>{
new UpdateItem { Title = "Test", KB = "KB123123" },
new UpdateItem { Title = "Test2", KB = "KB123123" }
}
});
workstations.Add(new WorkstationItem
{
Name = "PC02",
OS = "Windows Server 2016",
Updates = new List<UpdateItem>{
new UpdateItem { Title = "Test5", KB = "KB123123" },
new UpdateItem { Title = "Test3", KB = "KB123123" }
}
});
Now i show the workstations in a listbox:
<ListBox x:Name="lbPCs" ItemsSource="{Binding WorkstationItemList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding Name}" FontSize="12" FontWeight="Bold" />
<TextBlock Text="{Binding OS}" FontSize="9" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public ObservableCollection<WorkstationItem> WorkstationItemList { get; set; }
WorkstationManager workstationmanager = WorkstationManager.GetInstance();
WorkstationItemList = new ObservableCollection<WorkstationItem>();
foreach (var k in workstationmanager.GetUpdatelist())
{
WorkstationItemList.Add(k);
}
This is working fine ... but how can i show the List<UpdateItem> Updates in another list in relation to the selected workstation?
So i select a workstation in list1 and want to show the related updates in list2?
Thanks in advance!
Basically add another ListBox or ItemsControl that refers to SelectedItem of the existing ListBox and bind to the SelectedItem's Updates collection; roughly like (untested):
<ListBox ItemsSource="{Binding ElementName=lbPCs, Path=SelectedItem.Updates}">
<ListBox .ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- TODO improve alignment+layout -->
<TextBlock Text="{Binding KB}" />
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
<ListBox .ItemTemplate>
</ListBox>
In case you want to do something more complex with the selected item but to display its Updates, it might be more appropriate to bind the SelectedItem of lbPCs to some new ViewModel property and to bind the new ListBox' itemsource to that VM property's Updates collection.
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
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.
So I have a task of building a mail application for school. I am having a problem with one of the parts.
I have a TreeView and a ListBox. TreeView has few items in it (Inbox, trash, draft). Now what I am trying to do, is that when I select and TreeView item certain ListBox Items will appear in the ListBox. (purpose of the ListBox is to show the mails in that "folder").
I have been looking into this, and there are some suggestions with ListAray and DataBinding, but I am very new and have no idea how to implement any of those.
What I have at this poit is:
<TreeView Grid.Row="2" Grid.ColumnSpan="1" VerticalAlignment="Stretch" HorizontalAlignment="Left" Margin="10,10,0,10" Name="treeView1" Width="100" FontSize="14" SelectedItemChanged="treeView1_SelectedItemChanged">
<TreeViewItem Header="Prejeto" IsSelected="True">
<TreeViewItem Header="Prebrano" />
<TreeViewItem Header="Neprebrano" />
</TreeViewItem>
<TreeViewItem Header="Poslano" />
<TreeViewItem Header="Osnutki" />
<TreeViewItem Header="Izbrisano" />
<TreeViewItem Header="Nezaželeno" />
<TreeViewItem />
</TreeView>
XAML ListBox:
<ListBox Name="seznamSporocil" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" Margin="10,10,0,10" VerticalAlignment="Stretch" Width="100" FontWeight="Bold" FontFamily="Arial" MouseDoubleClick="seznamSporocil_MouseDoubleClick" />
SelectedItemChanged:
private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
When working with WPF, data binding is your best friend. Just bind ItemsSource of list box with some collection property of tree view's selected item.
Update.
Here's the complete sample (just create WPF Application project).
Model:
public class MailFolder
{
public string Name { get; set; }
public ObservableCollection<MailItem> Items
{
get
{
return items ?? (items = new ObservableCollection<MailItem>());
}
}
private ObservableCollection<MailItem> items;
public ObservableCollection<MailFolder> SubFolders
{
get
{
return subFolders ?? (subFolders = new ObservableCollection<MailFolder>());
}
}
private ObservableCollection<MailFolder> subFolders;
}
public class MailItem
{
public string Subject { get; set; }
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TreeView x:Name="MailTreeView" ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MailFolder}" ItemsSource="{Binding SubFolders}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<ListBox Grid.Column="1" ItemsSource="{Binding Path=SelectedItem.Items, ElementName=MailTreeView}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MailItem}">
<TextBlock Text="{Binding Subject}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And this is data context setup:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new MailFolder
{
Name = "Prejeto",
SubFolders =
{
new MailFolder
{
Name = "Prebrano",
Items =
{
new MailItem { Subject = "A" },
new MailItem { Subject = "B" },
new MailItem { Subject = "C" },
}
},
new MailFolder
{
Name = "Neprebrano",
Items =
{
new MailItem { Subject = "D" },
new MailItem { Subject = "E" },
}
},
},
Items =
{
new MailItem { Subject = "M" },
new MailItem { Subject = "N" },
}
},
new MailFolder
{
Name = "Poslano",
Items =
{
new MailItem { Subject = "F" },
new MailItem { Subject = "G" },
}
},
new MailFolder
{
Name = "Osnutki",
Items =
{
new MailItem { Subject = "H" },
}
},
new MailFolder
{
Name = "Izbrisano",
Items =
{
new MailItem { Subject = "I" },
new MailItem { Subject = "J" },
new MailItem { Subject = "K" },
}
},
new MailFolder
{
Name = "Nezaželeno",
Items =
{
new MailItem { Subject = "L" },
}
}
};
}
}
Note, that if you want to reflect changes, made to properties of your model classes, you need to implement INotifyPropertyChanged interface.
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.