I Have created a list box containing list of items and i need to bind them on selection
changed(Select and deselect).
ABCD.xalm
<ListBox Grid.Column="2" Grid.ColumnSpan="9" Height="30" Margin="0 0 5 0" Foreground="{StaticResource AcresTheme}" SelectedItem="{Binding Path=UpdateSimulationItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding SmulationTypes, NotifyOnSourceUpdated=True}"
Background="{Binding }"
MinHeight="65" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Foreground="{StaticResource AcresTheme}"
Content="{Binding Item}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ABCD.cs (View Model)
public List<string> SimulationTypesList { get; set; } = new List<string>();
private ObservableCollection<SimulationType> _simulationTypes = new ObservableCollection<SimulationType>();
public ObservableCollection<Items> SimulationTypes
{
get
{
return _simulationTypes;
}
set
{
_simulationTypes = value;
OnPropertyChanged("SimulationTypes");
}
}
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Logic for getting the selected item
_updateSimulationItem = value;
OnPropertyChanged("UpdateSimulationItem");
}
}
public ABCD()
{
SimulationTypes.Add(new SimulationType() { Item = "Simulation 1", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 2", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 3", IsSelected = false });
}
Items.cs
public class Items: ViewModelBase
{
private string item;
public string Item
{
get { return item; }
set
{
item = value;
this.OnPropertyChanged("Item");
}
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
I did try the solution given in https://stackoverflow.com/a/34632944/12020323 This worked fine
for deleting a single item or selecting a single item.
When we select the second item it does not trigger the property change.
Error somewhere not in this code.
You may be confused about VM instances.
This is often the case for beginners.
There may be something wrong with the implementation of SimulationType. You didn't show it.
Here's a complete example of your code demonstrating that multiselect binding works correctly.
using Simplified;
using System;
using System.Collections.ObjectModel;
namespace Core2022.SO.ChaithanyaS
{
public class Item : ViewModelBase
{
private string _title = string.Empty;
public string Title
{
get => _title;
set => Set(ref _title, value ?? string.Empty);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => Set(ref _isSelected, value);
}
}
public class SimulationType : Item
{
private int _count;
public int Count { get => _count; set => Set(ref _count, value); }
}
public class ItemsViewModel : ViewModelBase
{
private static readonly Random random = new Random();
private Item? _selectedItem;
public ObservableCollection<Item> Items { get; } =
new ObservableCollection<Item>()
{
new Item() {Title = "First" },
new SimulationType() { Title = "Yield Simulation", Count = random.Next(5, 15) },
new Item() {Title = "Second" },
new SimulationType() { Title = "HLR Simulation", Count = random.Next(5, 15) },
new SimulationType() { Title = "UnCorr HLR Simulation", Count = random.Next(5, 15)},
new Item() {Title = "Third" }
};
public Item? SelectedItem { get => _selectedItem; set => Set(ref _selectedItem, value); }
}
}
<Window x:Class="Core2022.SO.ChaithanyaS.ItemsWindow"
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:Core2022.SO.ChaithanyaS"
mc:Ignorable="d"
Title="ItemsWindow" Height="450" Width="800">
<Window.DataContext>
<local:ItemsViewModel/>
</Window.DataContext>
<UniformGrid Columns="2">
<ListBox Margin="10"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<CheckBox Foreground="Red"
Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<CheckBox Foreground="Green"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</CheckBox>
</DataTemplate>
</FrameworkElement.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ItemsControl ItemsSource="{Binding Items}" Margin="10"
BorderBrush="Green" BorderThickness="1"
Padding="10">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Foreground="Red"
Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<TextBlock Foreground="Green">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataTemplate.Resources>
<Label x:Name="cc" Content="{Binding}" Margin="1" BorderBrush="Gray" BorderThickness="1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="cc" Property="Background" Value="LightPink"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
</Window>
Perhaps you mean that in multi-selection mode, the SelectedItem property always has only the item that was selected first, until it is deselected?
Unfortunately, the implementation of such a task is not easy.
It's better to make a custom AP property and then use it in XAML.
public static class MuliSelectorHelper
{
/// <summary>Returns the value of the IsSelectedItemLast attached property for <paramref name="multiSelector"/>.</summary>
/// <param name="multiSelector"><see cref="DependencyObject"/> whose property value will be returned.</param>
/// <returns><see cref="bool"/> property value.</returns>
public static bool GetIsSelectedItemLast(DependencyObject multiSelector)
{
return (bool)multiSelector.GetValue(IsSelectedItemLastProperty);
}
/// <summary>Sets the value of the IsSelectedItemLast attached property for <paramref name="multiSelector"/>.</summary>
/// <param name="multiSelector"><see cref="MultiSelector"/> whose property value will be returned.</param>
/// <param name="value"><see cref="bool"/> value for property.</param>
public static void SetIsSelectedItemLast(DependencyObject multiSelector, bool value)
{
multiSelector.SetValue(IsSelectedItemLastProperty, value);
}
/// <summary><see cref="DependencyProperty"/> for methods <see cref="GetIsSelectedItemLast(MultiSelector)"/>
/// and <see cref="SetIsSelectedItemLast(MultiSelector, bool)"/>.</summary>
public static readonly DependencyProperty IsSelectedItemLastProperty =
DependencyProperty.RegisterAttached(
nameof(GetIsSelectedItemLast).Substring(3),
typeof(bool),
typeof(MuliSelectorHelper),
new PropertyMetadata(false, OnIsSelectedItemLastChanged));
private static void OnIsSelectedItemLastChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not Selector selector || selector.GetValue(ListBox.SelectedItemsProperty) is not IList list)
{
throw new NotImplementedException("Implemented only types that derive from Selector and that use the ListBox.SelectedItems dependency property.");
}
if (Equals(e.NewValue, true))
{
selector.SelectionChanged += OnSelectionChanged;
}
else
{
selector.SelectionChanged -= OnSelectionChanged;
}
}
private static async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 0)
return;
Selector selector = (Selector)sender;
IList selectedItems = (IList)selector.GetValue(ListBox.SelectedItemsProperty);
if (selectedItems.Count != e.AddedItems.Count && !Equals(selectedItems[0], e.AddedItems[0]))
{
selector.SelectionChanged -= OnSelectionChanged;
int beginIndex = selectedItems.Count - e.AddedItems.Count;
var selectedItemsArray = new object[selectedItems.Count];
selectedItems.CopyTo(selectedItemsArray, 0);
selectedItems.Clear();
await selector.Dispatcher.BeginInvoke(() =>
{
for (int i = selectedItemsArray.Length-1; i >= beginIndex; i--)
{
selectedItems.Add(selectedItemsArray[i]);
}
for (int i = 0; i < beginIndex; i++)
{
selectedItems.Add(selectedItemsArray[i]);
}
});
selector.SelectionChanged += OnSelectionChanged;
}
}
}
<UniformGrid Columns="2">
<ListBox Margin="10"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple"
local:MuliSelectorHelper.IsSelectedItemLast="true">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<CheckBox Foreground="Red"
Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<CheckBox Foreground="Green"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</CheckBox>
</DataTemplate>
</FrameworkElement.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ContentControl Content="{Binding SelectedItem}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Foreground="Red"
Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<TextBlock Foreground="Green">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataTemplate.Resources>
<Label x:Name="cc" Content="{Binding}" Margin="1" BorderBrush="Gray" BorderThickness="1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="cc" Property="Background" Value="LightPink"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</UniformGrid>
Thank you #EldHasp, for the time taken to respond to my question. Very greatfull to the solution provided.
Currently I am intrested in having the complete code in ViewModel, I found the mistake that I had done in the ViewModel.
Old Code:
private ObservableCollection<SimulationType> _simulationTypes = new ObservableCollection<SimulationType>();
public ObservableCollection<Items> SimulationTypes
{
get
{
return _simulationTypes;
}
set
{
_simulationTypes = value;
OnPropertyChanged("SimulationTypes");
}
}
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Logic for getting the selected item
_updateSimulationItem = value;
OnPropertyChanged("UpdateSimulationItem");
}
}
_updateSimulationItem = value; Binds the first selected item to UpdateSimulationItem and propertychange will trigger only when that perticular item is changed.
For example:
SimulationTypes.Add(new SimulationType() { Item = "Simulation 1", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 2", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 3", IsSelected = false });
In this three items, if I select Simulation 1 then the UpdateSimulationItem will bind to Simulation 1 and the propertychange will narrow down to one item i.e. Simulation 1. Now if we click on Simulation 2 the peopertychange will not trigger as the UpdateSimulationItem is bound to only Simulation 1 item changes.
The change that I Made.
Updated code:
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Removed unnecessary code and the assignment of value to _updateSimulationItem
OnPropertyChanged("UpdateSimulationItem");
}
}
As we have binded the SimulationTypes to ItemSource in the ABC.XAML as shown below
<ListBox Foreground="{StaticResource AcresTheme}"
SelectedItem="{Binding Path=UpdateSimulationItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding SimulationTypes, NotifyOnSourceUpdated=True}"
MinHeight="65" SelectionMode="Multiple">
when i click on the checkbox that is present in the view, it will automatically updat the SimulationTypes as i have bound the checkbox to IsSelected.
<CheckBox Foreground="{StaticResource AcresTheme}"
Content="{Binding Item}"
IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
#EldHasp the code changes that we have to do in your code is to remove the assignment to _selectedItem in the setter property and just keep the OnPropertychange(nameOf(SelectedItem)).
public Item? SelectedItem { get => _selectedItem; set => Set(ref _selectedItem, value); }
The bold text was making the SelectedItem to bind to one item, which was restricting the trigger when other item was selected.
Once Again Thank you #EldHasp for taking out your time on this.
Related
I am trying to bind a VM method as a command in MenuItem. Though menu is displays correctly the function never get called.I expecting the MenuCommand Method to be get called from the command binding.
Xaml
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubMenu}">
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding MenuCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
ViewModel
public class MenuViewModel : ViewModelBase
{
public ObservableCollection<Menu> Menu { get; set; }
public RelayCommand MenuCommand { get; set; }
public void Load()
{
Menu = new ObservableCollection<Menu> {
new Menu
{
Name = "File",
SubMenu = new List<Menu>
{
new Menu { Name = "New" },
new Menu { Name = "Open" },
new Menu { Name = "Save" }
}
}};
MenuCommand = new RelayCommand(MenuExecution);
}
public void MenuExecution(object item)
{
MessageBox.Show("Hello");
}
}
Thanks #GK & #Mark I am able to bind the command successfully by below Xaml
<Menu ItemsSource="{Binding Menu}" >
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Command" Value="{Binding ElementName=level1Lister, Path=DataContext.MenuCommand}" />
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=SubMenu}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
I need to present a WPF GridView where one column is a Combobox, The user can select one value from the list or enter a new value so I set the IsComboBoxEditable to true but the problem is that if the user types a value that is not in the ItemsSource the Text is blank when the Combobox looses the focus.
Note : I don't want, when a new value is typed , this value to be
added to the ItemsSource. I only need to save it's string value in row
that bounded to it.
I also need DropDownOpened event, to populate it's ItemsSource.
Here is my code:
<telerik:GridViewDataColumn Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<telerik:RadComboBox IsEditable="True" ItemsSource="{Binding Descriptions}" Text="{Binding Description1,Mode=TwoWay}" DropDownOpened="descriptionRadComboBox_DropDownOpened"/>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
</telerik:GridViewDataColumn>
Description1 is string property, and Descriptions is List of string that populate in runtime.(When DropDownOpened Event occurred)
Like you mentioned, your goal is, simply, to "Editable ComboBox".
(And, of course, you don't want to add new Item to ItemsSource)
<telerik:GridViewDataColumn UniqueName="description1" Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description1}"></TextBlock>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox Name="SLStandardDescriptionsRadComboBox" IsEditable="True"
ItemsSource="{Binding DataContext.SLStandardDescriptions, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
DisplayMemberPath="SLStandardDescriptionTitle" DropDownOpened="Description_DropDownOpened">
</telerik:RadComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:GridViewDataColumn>
Codebehinde :
private void RadGridView_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == "description1")
{
RadComboBox combo = e.Cell.ChildrenOfType<RadComboBox>().FirstOrDefault();
if (combo != null)
{
List<Description> comboItems = combo.ItemsSource as List<Description>;
string textEntered = e.Cell.ChildrenOfType<RadComboBox>().First().Text;
bool result = comboItems.Contains(comboItems.Where(x => x.DescriptionTitle == textEntered).FirstOrDefault());
if (!result)
{
comboItems.Add(new Description { DescriptionTitle = textEntered });
combo.SelectedItem = new Description { DescriptionTitle = textEntered };
}
if (_viewModel.AccDocumentItem != null)
{
if (e.Cell.Column.UniqueName == "description1")
_viewModel.AccDocumentItem.Description1 = textEntered;
}
}
}
}
Here is the solution for .net DataGrid control:
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Title}" ></DataGridTextColumn>
<DataGridComboBoxColumn SelectedValueBinding="{Binding ComboItem.ID}" DisplayMemberPath="ComboTitle" SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="IsEditable" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Surely you can do this using Telerik DataGrid control as well.
And here is my ViewModel:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
ComboItems = new ObservableCollection<ComboItem>()
{
new ComboItem(){ID=1,ComboTitle="ComboItem1"},
new ComboItem(){ID=2,ComboTitle="ComboItem2"},
new ComboItem(){ID=3,ComboTitle="ComboItem3"}
};
Items = new ObservableCollection<Item>()
{
new Item(){ID=1,Title="Item1",ComboItem=ComboItems[0]},
new Item(){ID=2,Title="Item2",ComboItem=ComboItems[1]},
new Item(){ID=3,Title="Item3",ComboItem=ComboItems[2]}
};
}
public ObservableCollection<Item> Items { get; set; }
public ObservableCollection<ComboItem> ComboItems { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public ComboItem ComboItem { get; set; }
}
public class ComboItem
{
public int ID { get; set; }
public string ComboTitle { get; set; }
}
I found a nice solution for SelectAll Checkboxes in a datagrid using XAML only:
<DataGrid x:Name="TestGrid" Tag="false">
<DataGrid.Resources>
<DataTemplate x:Key="HeaderCheckbox">
<CheckBox Name="SelectAll" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=Tag, Mode=TwoWay}" />
</DataTemplate>
<DataTemplate x:Key="ItemCheckbox">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=Tag, Mode=OneWay}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn HeaderTemplate="{StaticResource HeaderCheckbox}" CellTemplate="{StaticResource ItemCheckbox}" />
<DataGridTextColumn Binding="{Binding FirstName}" />
</DataGrid.Columns>
</DataGrid>
Source: Complete XAML Solution For SelectAll In Datagrid
But it's my question... In above example, the ItemCheckbox is bound to tag property of Datagrid, then how do I to bind the ItemCheckbox to my data field inclusive?
Easiest solution I can see it to use properties in your view model instead of Tag. First in class that holds items create SelectAll property that will update accordingly all items when changed:
public class MyItemCollection : INotifyPropertyChanged
{
private readonly ObservableCollection<MyItem> _items;
public ICollection<MyItem> Items { get { return _items; } }
private bool _selectAll;
public bool SelectAll
{
get { return _selectAll; }
set
{
if (_selectAll != value)
{
_selectAll = value;
OnPropertyChanged("SelectAll");
foreach (var item in _items) item.IsSelected = value;
}
}
}
}
Then add IsSelected property to your item. It will be updated either by SelectAll property or CheckBox in DataGrid
public class MyItem : INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
}
and then update your binding to point to new properties:
<DataGrid x:Name="TestGrid" ItemsSource="{Binding Items}">
<DataGrid.Resources>
<DataTemplate x:Key="HeaderCheckbox">
<CheckBox Name="SelectAll" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.SelectAll}" />
</DataTemplate>
<DataTemplate x:Key="ItemCheckbox">
<CheckBox IsChecked="{Binding Path=IsSelected}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
In my TreeView I use two differnt classes for binding. For example, I have a Group what can have ChildGroup and can have Items.
Example code of this classes:
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public class Group
{
public Group(string name)
{
Name = name;
items = new ObservableCollection<Item>();
groups = new ObservableCollection<Group>();
}
public string Name { get;
set;
}
private ObservableCollection<Item> items;
private ObservableCollection<Group> groups;
public ObservableCollection<Item> Items
{
get { return items; }
}
public ObservableCollection<Group> Groups
{
get { return groups; }
}
public IEnumerable<object> AllItems
{
get
{
foreach (var group in groups)
{
yield return group;
}
foreach (var item in items)
{
yield return item;
}
}
}
}
public class Item
{
public Item(string name)
{
ItemName = name;
}
public string ItemName
{
get;
set;
}
}
}
To bind it to TreeView I use following template
<Grid>
<TreeView Name="treeView">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication1:Group}"
ItemsSource="{Binding AllItems}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type WpfApplication1:Item}">
<TextBlock Text="{Binding ItemName}" FontStyle="Italic"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
It is easy.
The problem is that I need to change ItemTemplate when Is selected. And I need to change only then Item class selected.
I can do it if only one class use for binding. It also easy using Style and Trigger, like this:
<TreeView Name="treeView1" Grid.Column="1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication1:Group}"
ItemsSource="{Binding AllItems}"
x:Key="groupTemplate">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication1:Group}"
ItemsSource="{Binding AllItems}"
x:Key="selectedGroupTemplate">
<TextBlock Text="{Binding Name}" FontStyle="Italic" FontWeight="Bold" FontSize="14"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate" Value="{StaticResource groupTemplate}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="HeaderTemplate" Value="{StaticResource selectedGroupTemplate}"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
But I have a trouble for multiclass binding.
How can I change SelectedItem template then multiclass binding using? Any ideas?
My code behind sample:
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
private ObservableCollection<Group> _groups;
public ObservableCollection<Group> Groups
{
get { return _groups; }
}
public Window2()
{
InitializeComponent();
InitGroups();
treeView.ItemsSource = _groups;
treeView1.ItemsSource = _groups;
}
private void InitGroups()
{
_groups = new ObservableCollection<Group>();
Group group1 = new Group("Group1");
group1.Groups.Add(new Group("Group1.1"));
group1.Groups.Add(new Group("Group1.2"));
group1.Groups.Add(new Group("Group1.3"));
group1.Items.Add(new Item("Item1.1"));
group1.Items.Add(new Item("Item1.2"));
group1.Groups[1].Items.Add(new Item("Item1.2.1"));
group1.Groups[1].Items.Add(new Item("Item1.2.2"));
_groups.Add(group1);
Group group2 = new Group("Group2");
group2.Groups.Add(new Group("Group2.1"));
group2.Groups.Add(new Group("Group2.2"));
group2.Items.Add(new Item("Item2.1"));
group2.Groups[0].Items.Add(new Item("Item2.1.1"));
group2.Groups[0].Items.Add(new Item("Item2.1.1"));
_groups.Add(group2);
}
}
}
Result
Now I think to use TreeView.HeaderTemplateSelector, but may be exists way to use only xaml.
Thanks.
There are a number of ways to acheive your desired result. If you are sure that your DataTemplate will only be used in TreeViewItem objects, then the easiest is simply to bind directly to the TreeViewItem.IsSelected property and then react to the change in your DataTemplate:
<DataTemplate DataType="{x:Type WpfApplication1:Item}">
<TextBlock Text="{Binding ItemName}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, FallbackValue=False}"
Value="True">
<Setter Property="TextBlock.FontStyle" Value="Italic" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
Assume the following class definitions.
public enum ContentType { Playlist, Audio, Video, Picture }
public interface IDataProvider
{
string Name
{
get;
}
}
public class ProviderList : List<IDataProvider>
{
}
public class MainViewModel
{
public Dictionary<ContentType, ProviderList> ProvidersDictionary;
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
this.ProvidersDictionary = new Dictionary<ContentType, ProviderList>();
ProviderList providerList = new ProviderList();
providerList.Add(new DataProvider());
this.ProvidersDictionary.Add(ContentType.Audio, providerList);
providerList = new ProviderList(providerList);
providerList.Add(new DataProvider());
this.ProvidersDictionary.Add(ContentType.Video, providerList);
}
}
}
So, this ProvidersDictionary property is bound to Window context menu as follows:
<Window.ContextMenu>
<ContextMenu ItemsSource="{Binding ProvidersDictionary}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value}">
<TextBlock Margin="1" Text="{Binding Key}" Height="20"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Window.ContextMenu>
The question is: how to make ICommand databinding for the DataProvider menu item click and to retrieve data type (enum type) and data provider (IDataProvider interface) within the command' Execute method.
Update
I want to have some command class to be bound to MenuItems like:
class DataProviderMenuSelectCommand : ICommand
{
#region ICommand Members
public void Execute(object parameter)
{
ContentTypeProviderPair contentProviderPair = parameter as ContentTypeProviderPair;
if (contentProviderPair != null)
{
// contentProviderPair.Type property - ContentType
// contentProviderPair.Provider property - IProvider
}
}
}
MainViewModel will expose instance of this command class as a property.
The problem has been solved:
<UserControl x:Class="DataProvidersView"
...
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.DataProviderSwitchCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type vw:DataProvidersView}}}" />
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding>
<Binding Path="DataContext.Key"
RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" />
<Binding />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</UserControl>