wpf treeview mvvm - wpf

I am trying to populate a treeview using mvvm but the tree does not display any data.
I have a Employee list which is a property in my vm which contains he employee data.
the xaml is as follows.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding EmpList}" >
<TextBlock Text="{Binding EmpName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Is there anything i am missing here.
thanks

Hi Ian's suggested article indeed is a great read!
The trick is that you should specify how the Treeview shows its items through type specific (Hierarchical)DataTemplates. You specify these datatemplates in the Treeview's resources (or higher up the visual tree if you want to reuse them in more treeviews).
I tried to simulate what you want:
<Window x:Class="TreeViewSelection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewSelection"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TreeView ItemsSource="{Binding Enterprises}">
<TreeView.Resources>
<!-- template for showing the Enterprise's properties
the ItemsSource specifies what the next nested level's
datasource is -->
<HierarchicalDataTemplate DataType="{x:Type local:Enterprise}"
ItemsSource="{Binding EmpList}">
<Label Content="{Binding EntName}"/>
</HierarchicalDataTemplate>
<!-- the template for showing the Employee's properties-->
<DataTemplate DataType="{x:Type local:Employee}">
<Label Content="{Binding EmpName}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace TreeViewSelection
{
public partial class Window1 : Window
{
public ObservableCollection<Enterprise> Enterprises { get; set; }
public Window1()
{
InitializeComponent();
Enterprises = new ObservableCollection<Enterprise>
{
new Enterprise("Sweets4Free"),
new Enterprise("Tires4Ever")
};
DataContext = this;
}
}
public class Enterprise : DependencyObject
{
public string EntName { get; set; }
public ObservableCollection<Employee> EmpList { get; set; }
public Enterprise(string name)
{
EntName = name;
EmpList = new ObservableCollection<Employee>
{
new Employee("John Doe"),
new Employee("Sylvia Smith")
};
}
}
public class Employee : DependencyObject
{
public string EmpName { get; set; }
public Employee(string name)
{
EmpName = name;
}
}
}

Check out Josh Smith's article on exactly this topic... Helped me no end!
p.s. Looks like you're missing the DataType property on the HierarchicalDataTemplate, e.g.
<HierarchicalDataTemplate
DataType="{x:Type local:ItemType}"
ItemsSource="{Binding ...}" >

Related

WPF: How to bind object to ComboBox

Trying to learn how to bind objects to various types of controls. In this instance, I want to get sample data in my object to appear in ComboBox. The code runs but what appears instead of values (David, Helen, Joe) is text "TheProtect.UserControls.Client")
XAML: (ucDataBindingObject.xaml)
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
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"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ComboBox Width="310"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
this.DataContext = this;
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}
There are several ways to tell the framework what to display
1) Use DisplayMemberPath on the ComboBox (this will display the named property):
<ComboBox ItemsSource="{Binding Path=Clients}"
DisplayMemberPath="Name"
/>
2) Set ItemTemplate on the ComboBox. This is like #1, except allows you to define a template to display, rather than just a property:
<ComboBox ItemsSource="{Binding Path=Clients}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Padding="5">
<TextBlock Text="{Binding Path=Name,StringFormat='Name: {0}'}" />
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
3) Add a ToString() override to source class. Useful if you always want to display the same string for a given class. (Note that the default ToString() is just the class type name, which is why you see "TheProtect.UserControls.Client".)
public class Client
{
// ...
public override string ToString()
{
return string.Format("{0} ({1})", Name, ID);
}
}
4) Add a DataTemplate to the XAML resources. This is useful for associating a given class type with a more complex or stylized template.
<UserControl xmlns:local="clr-namespace:TheProject.UserControls">
<UserControl.Resources>
<DataTemplate DataType="local:Client">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</UserControl.Resources>
// ...
</UserControl>
In DisplayMemberPath, give the name of the property which you want to show in the comboBox. In SelectedValuePath, give the name of the property which you want to select. When you do a ComboBox.SelectedValue, you will get the value of this property.
Trying to get selected value from combobox returns System.Data.Entity.DynamicProxies.x
private void Button_Click(object sender, RoutedEventArgs e){
string _scanner0 = int.Parse(mycmb.SelectedValue.ToString());
string _scanner1 = mycbr.SelectedItem.ToString();
string _scanner2 = mycbr.SelectedValuePath.ToString();
string _scanner3 = mycbr.text.ToString();
}
all these Returns System.Data.Entity.DynamicProxies.x
What should i do?

How do I bind to something outside a datacontext

I have a listbox in WPF that is in the Layout Root.
I also have a Frame that is in the Layout Root as well.
The listbox is composed of items that have a string(Name) and a framework element(UI).
How do I bind the frame's content to be the UI property of the listbox's selected item property?
If you need a codebehind, how would you do this in MVVM
I used a ContentControl instead of Frame since I had problem binding to Content property, I never got it to refresh after binding changed. I didn't do proper MVVM, Data should not be hosted inside the view.
XAML:
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding}" />
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource CVS}}">
<ListBox
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name">
</ListBox>
<ContentControl Content="{Binding Path=UI}" />
</StackPanel>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace BindDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Data = new List<DataItem>();
Data.Add(new DataItem("TextBox", new TextBox(){ Text="hello" }));
Data.Add(new DataItem("ComboBox", new ComboBox()));
Data.Add(new DataItem("Slider", new Slider()));
DataContext = Data;
}
public List<DataItem> Data
{
get; private set;
}
}
public class DataItem
{
public DataItem(string name, FrameworkElement ui)
{
Name = name;
UI = ui;
}
public string Name { get; private set; }
public FrameworkElement UI { get; private set; }
}
}
It sounds as you want to display list of objects and details for selected object. If I am right, solution in MVVM may be following:
<ListView ItemsSource="{Binding ObjectsList}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Content="{Binding ObjectsList}">
<ContentControl.ContentTemplate>
<DataTemplate>
<!-- details template -->
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

Strange Behaviour WPF TreeView ItemContainerStyle and ItemTemplate

I just noticed some strange behaviour of WPF's TreeView. I added both ItemContainerStyle to bind to "IsSelected" of my ViewModel and an ItemsTemplated for custom display of my data. But now the user cannot change the selected node anymore. For testing purposes I created a similar UI using ListView and Expander. This version works as excepted. Any tips why TreeView does fail?
<TreeView ItemsSource="{Binding ElementName=frame, Path=list}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TreeViewItem Header="{Binding}">
<TextBlock Text="{Binding Path= Item.SomeData}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
EDIT: My data are not hierachical. I just want to get the "collapse" feature on displaying a list. Item.SomeData is not a list. Display of data is as desired. Only selection by mouse fails!
alt text http://img682.imageshack.us/img682/3702/bildy.png
TreeViews work differently. The Items inside a HierarchicalDataTemplate are TreeViewItems and any control you specify inside the HierarchicalDataTemplate will function as its Header. So, basically you are specifying that the Items in your TreeView are TreeViewItems with TreeViewItems as their headers! Instead try this:
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path= Item.SomeData}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
EDIT: I could not reproduce a DataSource that produces the properties you want to bind to, so I wrote some simple code of my own that shows how it all works. Hopefully you will be able to adapt it to your needs:
<TreeView ItemsSource="{Binding}" Name="Tree">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path= SomeData}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace TreeViewSpike
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List = new List<ItemList>
{
new ItemList
{
Name = "MyList",
Items = new List<Item> {new Item("1"),
new Item("2")}
},
new ItemList
{
Name = "MySecondList",
Items = new List<Item> {new Item("3"),
new Item("4")}
}
};
Tree.DataContext = List;
List[1].IsSelected = true;
}
public List<ItemList> List { get; set; }
}
public class ItemList: INotifyPropertyChanged
{
public string Name{ get; set;}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSelected"));
if(_isSelected)
MessageBox.Show(Name + " selected");
}
}
public List<Item> Items { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
public class Item
{
public string SomeData { get; set; }
public Item(string data)
{
SomeData = data;
}
}
}

WPF Show data from multiple DataContexts in ToolTip of ItemsControl

I am trying to display a tooltip for an item generated by an ItemsControl that needs to pull data from conceptually unrelated sources. For example, say I have an Item class as follows:
public class Item
{
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
I can display the Item within an ItemsControl with a tooltip as follows:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ItemDescription}" />
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But say I have another property that can be accessed via the DataContext of the ItemsControl. Is there any way to do this from within the tooltip? E.g.,
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" Text="{Bind this to another property of the ItemsControl DataContext}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The code for the test Window I used is as follows:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>() {
new Item() { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item() { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
So in this example I want to show the value of the GlobalText property (in reality this would be another custom object).
To complicate matters, I am actually using DataTemplates and show two different types of objects within the ItemsControl, but any assistance would be greatly appreciated!
After an hour of hair pulling I have come to the conviction that you can't reference another DataContext inside a DataTemplate for a ToolTip. For other Bindings it is perfectly possible as other posters have proven. That's why you can't use the RelativeSource trick either. What you can do is implement a static property on your Item class and reference that:
<Window x:Class="ToolTipSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Name="Root"
xmlns:ToolTipSpike="clr-namespace:ToolTipSpike">
<Grid>
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1"
Text="{Binding Source={x:Static ToolTipSpike:Item.GlobalText},
Path=.}"
/>
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
using System.Collections.Generic;
using System.Windows;
namespace ToolTipSpike
{
public partial class Window1 : Window
{
public List<Item> Items { get; private set; }
public Window1()
{
InitializeComponent();
var itemList = new List<Item>
{
new Item { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.DataContext = this;
}
}
public class Item
{
static Item()
{
GlobalText = "Additional Text";
}
public static string GlobalText { get; set; }
public string ItemName{ get; set;}
public string ItemDescription{ get; set;}
}
}
Second attempt
Ok, the Relative Source Binding doesn't work in this case. It actually works from a data template, you can find many examples of this on the Internets. But here (you were right, David, in your comment) ToolTip is a special beast that is not placed correctly in the VisualTree (it's a property, not a control per se) and it doesn't have access to the proper name scope to use relative binding.
After some more searching I found this article, which describes this effect in details and proposes an implementation of a BindableToolTip.
It might be an overkill, because you have other options -- like using a static property on a class (as in Dabblernl's response) or adding a new instance property to your Item.
First attempt :)
You should consult with the Relative Source Binding types (in this cheat sheet for example):
So your binding will look somehow similar to this:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path= GlobalText}
Almost correct Yacoder, and guessed way wrong there Dabblernl ;)
Your way of thinking is correct and it is possible to reference the DataContext of your ItemsControl
You are missing the DataContext property in path:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.GlobalText}
Second attempt ;)
http://blogs.msdn.com/tom_mathews/archive/2006/11/06/binding-a-tooltip-in-xaml.aspx
Here is an article with the same problem. They can reference the DataContext of their Parent control by the PlacementTarget property:
<ToolTip DataContext=”{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Parent}”>
If you would place the DataContext on a deeper level, you avoid changing your Item DataContext
A second suggestion (Neil and Adam Smith) was that we could use PlacementTarget in the binding. This is nice, as I am actually inheriting the DataContext already from the page that hosts the DataControl, and this would allow the ToolTip to gain access back to the origial control. As Adam noted, though, you have to be aware of the parent/child structure off your markup:
This is a case where I think it's conceptually more appropriate to do this in the view model than it is in the view anyway. Expose the tooltip information to the view as a property of the view model item. That lets the view do what it's good at (presenting properties of the item) and the view model do what it's good at (deciding what information should be presented).
I had a very similar problem and arrived at this question seeking answers. In the end I came up with a different solution that worked in my case and may be useful to others.
In my solution, I added a property to the child item that references the parent model, and populated it when the children were generated. In the XAML for the ToolTip, I then simply referenced the property from the parent model on each element and set the DataContext to the parent model property.
I felt more comfortable with this solution than creating proxy elements in XAML and referencing them.
Using the example code for this question, you would do the following. Note I have not tested this scenario in a compiler, but have done so successfully implemented this solution in the code for my own scenario.
Item:
public class Item
{
public List<Item> Parent { get; set; }
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
Window:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>();
itemList.Add(new Item() { Parent = this, ItemName = "First Item", ItemDescription = "This is the first item." });
itemList.Add(new Item() { Parent = this, ItemName = "Second Item", ItemDescription = "This is the second item." });
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
XAML:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" DataContext={Binding Parent} Text="{Bind this to aproperty of the parent data model}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

multiple userControl instances in tabControl

I have a tabControl that is bound to an observable collection.
In the headerTemplate, I would like to bind to a string property, and in the contentTemplate I have placed a user-control.
Here's the code for the MainWindow.xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="contentTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="itemTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</Grid.Resources>
<TabControl IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Pages}"
ItemTemplate="{StaticResource itemTemplate}"
ContentTemplate="{StaticResource contentTemplate}"/>
</Grid>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public ObservableCollection<PageViewModel> Pages { get; set; }
public MainWindowViewModel()
{
this.Pages = new ObservableCollection<PageViewModel>();
this.Pages.Add(new PageViewModel("first"));
this.Pages.Add(new PageViewModel("second"));
}
}
public class PageViewModel
{
public string Name { get; set; }
public PageViewModel(string name)
{
this.Name = name;
}
}
So the problem in this scenario (having specified an itemTemplate as well as a controlTemplate) is that I only get one instance for the user-control, where I want to have an instance for each item that is bound to.
Try this:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}">
<TabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:UserControl1/>
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ContentTemplate" Value="{StaticResource contentTemplate}"/>
</Style>
</TabControl.Resources>
</TabControl>
Try setting
x:Shared="False"
When set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
You need to override the Equals() Method of your PageViewModel class.
public override bool Equals(object obj)
{
if (!(obj is PageViewModel)) return false;
return (obj as PageViewModel).Name == this.Name;
}
Something like this should work.
Now it is looking for the same property of the value Name. Otherwise you could also add a ID Property which is unique.

Resources