TreeView with CheckBoxes WPF - wpf

I tried this sample How to develop treeview with checkboxes in wpf? to create a TreeView with checkbox but in this example you cannot have a family in another family.
So my question is how to create a full treeview with check box not only limited in one level?
So this is what I did
IParent.cs
interface IParent<T>
{
IEnumerable<T> GetChildren();
}
DataModel.cs
using System;
using System.Collections.Generic;
using System.Windows;
public class Family : DependencyObject, IParent<object>
{
public string Name { get; set; }
public List<Person> Members { get; set; }
IEnumerable<object> IParent<object>.GetChildren()
{
return Members;
}
}
public class Person : DependencyObject
{
public string Name { get; set; }
}
ItemHelper.cs
using System.Linq;
using System.Windows;
public class ItemHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(ItemHelper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IParent<object> sect = d as IParent<object>;
DependencyObject depObj = d as DependencyObject;
if (sect != null)
{
if (((bool?)e.NewValue).HasValue)
{
foreach (DependencyObject p in sect.GetChildren())
{
SetIsChecked(p, (bool?)e.NewValue);
}
}
}
if (depObj != null)
{
var parentObject = depObj.GetValue(ParentProperty) as IParent<object>;
var parentDO = depObj.GetValue(ParentProperty) as DependencyObject;
int ch = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == true).Count() ?? 0;
int un = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == false).Count() ?? 0;
if (un > 0 && ch > 0)
{
SetIsChecked(parentDO, null);
return;
}
if (ch > 0)
{
SetIsChecked(parentDO, true);
return;
}
SetIsChecked(parentDO, false);
}
}
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element?.SetValue(IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element?.GetValue(IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty =
DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(ItemHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element?.SetValue(ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return element?.GetValue(ParentProperty);
}
}
MainWindow.xaml
<Window x:Class="WpfApplication102.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication102"
Title="MainWindow" Height="220" Width="250">
<StackPanel>
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Families}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Person}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Button Content="?" Click="Button_PrintCrew_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication102
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Family> Families { get; set; }
public MainWindow()
{
InitializeComponent();
this.Families = new ObservableCollection<Family>();
this.Families.Add(new Family() { Name = "Simpsons", Members = new List<Person>() { new Person() { Name = "Homer" }, new Person() { Name = "Bart" } } });
this.Families.Add(new Family() { Name = "Griffin", Members = new List<Person>() { new Person() { Name = "Peter" }, new Person() { Name = "Stewie" } } });
this.Families.Add(new Family() { Name = "Fry", Members = new List<Person>() { new Person() { Name = "Philip J." } } });
foreach (Family family in this.Families)
foreach (Person person in family.Members)
person.SetValue(ItemHelper.ParentProperty, family);
}
private void Button_PrintCrew_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Family family in this.Families)
foreach (Person person in family.Members)
if (ItemHelper.GetIsChecked(person) == true)
crew += person.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Your crew: " + crew;
}
}
}

Like many other aspects of WPF development, this becomes much easier if you follow the MVVM pattern.
Create a common ViewModel base class to represent each node of the tree, with a collection of the same type to represent the node's children. This is where you would include the logic to handle ticking / unticking the parent / child items as appropriate. The TreeView's ItemSource is then bound to a simple collection of items to form the root level. The U.I. layout of the View is defined by a HierachicalDataTemplate for each distinct node type.
I covered this in a recent blog post, with sample code here.

Related

Different Expanded/Selected state in multiple TreeViews with same ViewModel

TLDR:
How to manage a different IsExpanded/IsSelected state of the same ViewModel in multiple views?
Long Version:
I have a good working App with a huge TreeView. The app has a lot few functions that select, expand and collapse the TreeView items. So the ViewModel contains a IsExpanded/IsSelected property for each TreeNode (bound to the TreeViewItem by a Style).
Now I have the requirement to avoid to much scrolling in this Tree. So the idea is to provide a second TreeView (maybe more..) with a root element selected from the first Tree).
The problem is that both TreeView's depend on the same IsExpanded/IsSelected properties, but I want to control these states for each Tree differently) and I have no good idea how update the IsExpanded/IsSelected logic to make it work with multiple TreeViews.
All functions that manipulate these states know the in which Tree they are used, because they depend on a parent TreeVM ViewModel.
I thought to have a IsExpanded/IsSelected ObservableCollection but can't find a good way to bind the TreeViewItem-properties to the collection (two-way).
App.xaml:
<Application
x:Class="MultiTree.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiTree"
>
<Application.Resources />
<Application.MainWindow>
<local:MainWindow />
</Application.MainWindow>
</Application>
App.xaml.cs:
using System.Windows;
namespace MultiTree
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow.Loaded += (ws, we) => {
MainWindow.DataContext = new AppVM();
};
MainWindow.Show();
}
}
}
MainWindow.xaml:
<Window
x:Class="MultiTree.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiTree"
Title="MainWindow"
Width="800"
Height="450">
<Window.Resources />
<ItemsControl ItemsSource="{Binding Views}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TreeView
Margin="5"
Background="Gainsboro"
ItemsSource="{Binding Root.Nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
AppVM.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MultiTree
{
public class Bindable : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class TreeNode : Bindable
{
public TreeNode(string name, IEnumerable<TreeNode> nodes = null)
{
Name = name;
if (nodes == null)
_nodes = new ObservableCollection<TreeNode>();
else
_nodes = new ObservableCollection<TreeNode>(nodes);
}
private string _name;
public string Name
{
get => _name;
set {
_name = value;
OnPropertyChanged();
}
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set {
_isSelected = value;
OnPropertyChanged();
}
}
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set {
_isExpanded = value;
OnPropertyChanged();
}
}
private ObservableCollection<TreeNode> _nodes;
public ObservableCollection<TreeNode> Nodes => _nodes;
}
/// <summary>
/// "Root" for each TreeView, known by all functions that expand/collapse&select
/// </summary>
public class TreeVM : Bindable
{
private TreeNode _root;
public TreeNode Root
{
get => _root;
set {
_root = value;
OnPropertyChanged();
}
}
}
public class AppVM : Bindable
{
/// <summary>
/// Contains all TreeNodes
/// </summary>
private readonly TreeNode RootNode;
public AppVM()
{
RootNode = new TreeNode("ROOT", new[] {
new TreeNode("A", new [] {
new TreeNode("B", new [] {
new TreeNode("C", new [] {
new TreeNode("D")
})
}),
new TreeNode("E", new [] {
new TreeNode("F", new [] {
new TreeNode("G", new [] {
new TreeNode("H"),
new TreeNode("I")
})
})
})
})
});
Views.Add(new TreeVM() { Root = RootNode });
Views.Add(new TreeVM() { Root = RootNode.Nodes.First() });
}
private ObservableCollection<TreeVM> _views = new ObservableCollection<TreeVM>();
/// <summary>
/// Each Tree can contain all or a subset of TreeNodes
/// </summary>
public ObservableCollection<TreeVM> Views
{
get => _views;
}
}
}
Any help is very welcome :-)

DataGridComboBoxColumn binding trouble

How can I make DataGridComboBoxColumn display the user phone numbers (right now it displays a blank combobox)?
EDIT: Code below has been updated and now works.
<Window x:Class="DataGridTest.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:DataGridTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid
x:Name="myDataGrid"
Grid.Row="1"
AutoGenerateColumns="False"
CanUserAddRows="True"
ItemsSource="{Binding PersonList}"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="LastName"
Binding="{Binding LastName}"/>
<DataGridTextColumn
Header="FirstName"
Binding="{Binding FirstName}" />
<DataGridComboBoxColumn
Header="Phone Number"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
<Setter Property="SelectedValue" Value="{Binding SelectedPhoneNumber}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
<Setter Property="SelectedValue" Value="{Binding SelectedPhoneNumber}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
// code behind
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DataGridTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Person> PersonList { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
PersonList = new ObservableCollection<Person>();
Person jjim = new Person() { FirstName = "jimmy", LastName = "jim" };
jjim.PhoneNumbers.Add("123-4567");
jjim.PhoneNumbers.Add("234-5678");
Person mmark = new Person() { FirstName = "mike", LastName = "mark" };
mmark.PhoneNumbers.Add("345-6789");
mmark.PhoneNumbers.Add("456-7890");
PersonList.Add(jjim);
PersonList.Add(mmark);
}
}
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> PhoneNumbers { get; set; }
private string selectedPhoneNumber;
public string SelectedPhoneNumber
{
get
{
return this.selectedPhoneNumber;
}
set
{
this.selectedPhoneNumber = value;
RaisePropertyChanged("SelectedPhoneNumber");
}
}
public Person()
{
PhoneNumbers = new ObservableCollection<string>();
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string firstName;
private string lastName;
public string FirstName
{
get
{
return this.firstName;
}
set
{
this.firstName = value;
RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return this.lastName;
}
set
{
this.lastName = value;
RaisePropertyChanged("LastName");
}
}
}
}
A DataGridColumn is not a visual element and doesn't have a DataContext and therefore your binding won't work. You could easily fix this by specifying an ElementStyle and and EditingElementStyle for the DataGridComboBoxColumn:
<DataGridComboBoxColumn Header="Phone Number">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
This works because the actual ComboBox element that eventually gets added to the visual tree by the DataGridComboBoxColumn and that the styles are getting applied to always has a DataContext.
Please refer to the following link for more examples of how to data bind to a ComboBox in WPF: https://code.msdn.microsoft.com/windowsdesktop/Best-ComboBox-Tutorial-5cc27f82#content
Have a look at my anser to this question: DataGridComboBoxColumn is empty
Seems like the same problem. The DataGridComboBoxColumn is a bit buggy.

ContextMenu is not setted by DataTrigger, but it is displayed

I use MVVM mode.
There are two ViewModels:
ClientViewModel and DiskViewModel, ClientViewModel has a collection ObservableCollection<DiskViewModel>.
I want to display their hierarchical relationship and do something via their ContextMenu. I set different ContextMenu via TypeToBooleanConverter in DataTrigger in TreeViewItem like this:
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Value="true" Binding="{Binding Converter={cvt:TypeToBooleanConverter},ConverterParameter={x:Type vm:ClientViewModel}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Client"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
But there is a problem:
If I only set ContextMenu for ClientViewModel via above method, not set ContextMenu for DiskViewModel, it will be show "Client" if right-click on Disk TreeViewItem. But I never set it.
With a picture: this is not I need. I want to nothing if I right-click on Disk TreeViewItem.
Above scene is the simplest. Actually hierarchical relationship is that:
ClientList
|---Client
|---DiskList
|---Disk
|---PartitionList
|---Partition
Different types of item or one type time with different status has different ContextMenu. Enumerate all cases may be a hadr work.
I Need your help.
Code files:
// MainViewModel.cs
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
namespace WpfApplication12.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ClientList = new ObservableCollection<ClientViewModel>();
var disk = new DiskViewModel();
disk.Name = "disk1";
var client = new ClientViewModel();
client.Name = "client1";
client.DiskList.Add(disk);
ClientList.Add(client);
}
#region ClientList
public const string ClientListPropertyName = "ClientList";
private ObservableCollection<ClientViewModel> _clientList;
public ObservableCollection<ClientViewModel> ClientList
{
get
{
return _clientList;
}
set
{
if (_clientList == value)
return;
_clientList = value;
RaisePropertyChanged(ClientListPropertyName);
}
}
#endregion // ClientList
}
public class ClientViewModel : ViewModelBase
{
public ClientViewModel()
{
DiskList = new ObservableCollection<DiskViewModel>();
}
#region Name
public const string NamePropertyName = "Name";
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
return;
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion // Name
#region DiskList
public const string DiskListPropertyName = "DiskList";
private ObservableCollection<DiskViewModel> _diskList;
public ObservableCollection<DiskViewModel> DiskList
{
get
{
return _diskList;
}
set
{
if (_diskList == value)
return;
_diskList = value;
RaisePropertyChanged(DiskListPropertyName);
}
}
#endregion // DiskList
}
public class DiskViewModel : ViewModelBase
{
public DiskViewModel()
{
}
#region Name
public const string NamePropertyName = "Name";
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
return;
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion // Name
}
}
.
// MainWindow.xaml
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication12.ViewModel"
xmlns:cvt="clr-namespace:InfoCore.StreamerConsole.Converters"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource Locator},Path=Main}">
<Grid>
<TreeView Background="AliceBlue" Margin="10"
ItemsSource="{Binding ClientList}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:ClientViewModel}" ItemsSource="{Binding DiskList}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:DiskViewModel}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Value="true" Binding="{Binding Converter={cvt:TypeToBooleanConverter},ConverterParameter={x:Type vm:ClientViewModel}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Client"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
<!--<DataTrigger Value="true" Binding="{Binding Converter={cvt:TypeToBooleanConverter},ConverterParameter={x:Type vm:DiskViewModel}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Disk"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>-->
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Window>
.
// ToBooleanConverter.cs
[MarkupExtensionReturnType(typeof(TypeToBooleanConverter))]
[ValueConversion(typeof(Type), typeof(bool))]
public class TypeToBooleanConverter : MarkupExtension, IValueConverter
{
private static TypeToBooleanConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _converter ?? (_converter = new TypeToBooleanConverter());
}
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value == null)
return Binding.DoNothing;
return Type.Equals(value.GetType(), parameter);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}

Adding context menu to leaf node in TreeView WPF

In my WPF application, I want to add a context menu and its handler in ViewModel to the leaf nodes of my TreeView control. Here is how I have added the TreeView Control.
<TreeView Name="treePads" ItemsSource="{Binding pads}" Width="190">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:Pad}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:PadInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="["></TextBlock>
<TextBlock Text="{Binding SlotID}" />
<TextBlock Text="] ["></TextBlock>
<TextBlock Text="{Binding WellID}" />
<TextBlock Text="]"></TextBlock>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Here is how the output of this code looks like:
I want to add a ContextMenu having two options: Rename, Delete and add there event handlers to the ViewModel. How can I do that?
This is good article on TreeView
http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode
Below I have shared a sample app which has a Rename Context Menu. The sample should help you to fix your problem.
XAML:
<TreeView Name="treeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!--<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />-->
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenameCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubElements}">
<StackPanel Orientation="Horizontal">
<!--<Image Margin="2" Source="{Binding ImageLocation}" Height="30" Width="30"/>-->
<TextBlock Margin="2" Text="{Binding HeaderText}" ></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<TreeViewElement> elements = new List<TreeViewElement>();
TreeViewElement mainElement = new TreeViewElement() { ImageLocation = "Images/1.png", HeaderText = "MainElement1" };
mainElement.SubElements = new List<TreeViewElement>();
mainElement.SubElements.Add(new TreeViewElement() { ImageLocation = "Images/2.png", HeaderText = "SubElement1" });
mainElement.SubElements.Add(new TreeViewElement() { ImageLocation = "Images/2.png", HeaderText = "SubElement2", BackgroundColor = "Blue" });
elements.Add(mainElement);
TreeViewElement mainElement2 = new TreeViewElement() { HeaderText = "MainElement2" };
elements.Add(mainElement2);
this.treeView.ItemsSource = elements;
}
}
public class TreeViewElement
{
public string ImageLocation { get; set; }
public string HeaderText { get; set; }
public string BackgroundColor { get; set; }
public List<TreeViewElement> SubElements { get; set; }
private ICommand _RenameCommand;
public ICommand RenameCommand
{
get {
if (_RenameCommand == null)
{
_RenameCommand = new RelayCommand((o) =>
{
// Your logic should go here
MessageBox.Show("HeaderText is " +HeaderText);
});
}
return _RenameCommand; }
}
}
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}

Using DataGridComboBoxColumn as autocompletecombobox in a DataGrid

I want to use the DataGridComboBoxColumn as a autocomplete combobox.
I've got it partially working. When the Row is in EditMode I can type text in the ComboBox, also in ViewMode the control returns the text. Only how to get the Label (in template) to EditMode by mouse doubleclick?
Up front, I don't want to use the DataGridTemplateColumn control because it just doesn't handle keyboard and mouse entry like the DataGridComboBoxColumn does (tabs, arrows, edit/view mode/ double click etc..).
It looks like:
I fixed it adding a behavior to the TextBox to get a link to the parent DataGrid then setting the Row into Edit Mode by calling BeginEdit().
The solution I used:
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Model.Things}" Name="MyGrid" ClipboardCopyMode="IncludeHeader">
<DataGrid.Resources>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Object" MinWidth="140" TextBinding="{Binding ObjectText}" ItemsSource="{Binding Source={StaticResource proxy}, Path=Data.Model.ObjectList}" >
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="Text" Value="{Binding ObjectText}"/>
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBox IsReadOnly="True" Text="{Binding Path=DataContext.ObjectText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}">
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:CellSelectedBehavior.IsCellRowSelected" Value="true"></Setter>
</Style>
</TextBox.Resources>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Model
public class Model : BaseModel
{
//List of objects for combobox
private List<string> _objectList;
public List<string> ObjectList { get { return _objectList; } set { _objectList = value; } }
//Rows in datagrid
private List<Thing> _things;
public List<Thing> Things
{
get { return _things; }
set { _things = value; OnPropertyChanged("Things"); }
}
}
public class Thing : BaseModel
{
//Text in combobox
private string _objectText;
public string ObjectText
{
get { return _objectText; }
set { _objectText = value; OnPropertyChanged("ObjectText"); }
}
}
ViewModel
public class ViewModel
{
public Model Model { get; set; }
public ViewModel()
{
Model = new WpfApplication1.Model();
Model.ObjectList = new List<string>();
Model.ObjectList.Add("Aaaaa");
Model.ObjectList.Add("Bbbbb");
Model.ObjectList.Add("Ccccc");
Model.Things = new List<Thing>();
Model.Things.Add(new Thing() { ObjectText = "Aaaaa" });
}
}
Behavior
public class CellSelectedBehavior
{
public static bool GetIsCellRowSelected(DependencyObject obj) { return (bool)obj.GetValue(IsCellRowSelectedProperty); }
public static void SetIsCellRowSelected(DependencyObject obj, bool value) { obj.SetValue(IsCellRowSelectedProperty, value); }
public static readonly DependencyProperty IsCellRowSelectedProperty = DependencyProperty.RegisterAttached("IsCellRowSelected",
typeof(bool), typeof(CellSelectedBehavior), new UIPropertyMetadata(false, OnIsCellRowSelected));
static void OnIsCellRowSelected(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TextBox item = depObj as TextBox;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.MouseDoubleClick += SelectRow;
else
item.MouseDoubleClick -= SelectRow;
}
static void SelectRow(object sender, EventArgs e)
{
TextBox box = sender as TextBox;
var grid = box.FindAncestor<DataGrid>();
grid.BeginEdit();
}
}
Helper (to find DataGrid)
public static class Helper
{
public static T FindAncestor<T>(this DependencyObject current) where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
}

Resources