WPF ContextMenu Dictionary<Key, List<Value>> databinding - wpf

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>

Related

WPF MVVM ListBox MultiSelect

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.

Binding a Command to HierarchicalDataTemplate MenuItem

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>

WPF Binding ContextMenu MenuItem's ItemsSource

I'm trying to bind a single MenuItem's ItemsSource to a ReadOnlyCollection<string>located in the ViewModel. I've read that the ContextMenu is not under the main Visual tree so i can't bind it directly, but any method i try doesn't work. I have the code snippet please let me know what am i doing wrong.
<Window>
…
<DockPanel>
<!-- Task bar Icon -->
<tb:TaskbarIcon x:Name="AppNotifyIcon"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ToolTipText="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
<MenuItem Header="Technologies" ItemsSource="{Binding to the ReadOnlyCollection of string in ViewModel}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding <!--Command in ViewModel-->, RelativeSource={RelativeSource AncestorType=Window}}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/> <!—Binding to the menuItem Header item -->
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
…
</DockPanel>
I am trying to bind the second MenuItem's ItemsSource and inside it's ItemContainerStyle i want to bind the command and the commandParameter.
**Update: ** i'm using hardcodet's TaskbarIcon for wpf, if it matters.
Thanks
Try check this out:
1. XAML Code:
<DataGrid x:Name="SelectDataGrid"
ItemsSource="{Binding Persons}" HorizontalAlignment="Left" CellEditEnding="SelectDataGrid_OnCellEditEnding"
VerticalAlignment="Top" AutoGenerateColumns="False" Loaded="SelectDataGrid_OnLoaded">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Technologies" ItemsSource="{Binding MenuItems}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="Header" Value="{Binding Content}"/>
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=DataContext}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding HelloCommand}"></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn></DataGrid>
2. DataContext of the context menu is the same as datagrid and window.
3. Inside the DataContext put the next code:
private void Init()
{
MenuItems = new ObservableCollection<MenuItemObject>(new List<MenuItemObject>
{
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "A"},
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "B"},
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "C"},
new MenuItemObject {Command = new RelayCommand<object>(Execute), Content = "D"},
});
}
public ObservableCollection<MenuItemObject> MenuItems { get; set; }
private void Execute(object o)
{
}
4. MenuItemsObject model code:
public class MenuItemObject:BaseObservableObject
{
private ICommand _command;
private string _content;
public ICommand Command
{
get { return _command; }
set
{
_command = value;
OnPropertyChanged();
}
}
public string Content
{
get { return _content; }
set
{
_content = value;
OnPropertyChanged();
}
}
}
5. MVVM parts implementation:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
public class RelayCommand<T> : ICommand
{
readonly Action<T> _execute;
readonly Func<T, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public void RefreshCommand()
{
var cec = CanExecuteChanged;
if (cec != null)
cec(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
if (_canExecute == null) return true;
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
}
public class RelayCommand : RelayCommand<object>
{
public RelayCommand(Action execute, Func<bool> canExecute = null)
: base(_ => execute(),
_ => canExecute == null || canExecute())
{
}
}
Call to Init method to generate toy menu item's collection DataContext.
An Execute is the method called when some menu item is pressed.
That is all. I'will be glad to help if there will be problems with the code.
Regards,
Ok, I have found the problem thanks to Ilan's suggestion in the comments of using snoop utility.
I saw that in the visual tree, the ContextMenu didn't have its PlacementTarget to point to its parent, the TaskbarIcon (Weird..), but it had an Attached Property called TaskbarIcon.ParentTaskbarIcon from the TaskbarIcon, so i binded the ContextMenu's DataContext to the TaskbarIcon.ParentTaskbarIcon.Tag and that fixed it all.
<Window>
...
<DockPanel>
<!-- Task bar Icon -->
<tb:TaskbarIcon x:Name="AppNotifyIcon"
IconSource="pack://application:,,,/Icons/HwServerIcon.ico"
Tag="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ToolTipText="{Binding Tag, RelativeSource={RelativeSource Self}}"><!--{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}-->
<tb:TaskbarIcon.ContextMenu>
<ContextMenu DataContext="{Binding Path=(tb:TaskbarIcon.ParentTaskbarIcon).Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
<MenuItem Header="Technologies" ItemsSource="{Binding TechnologiesNames}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding DataContext.OpenTechnology, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
So, the TaskbarIcon's Tag is pointing the Window's DataContext and the ContextMenu's DataContext is pointing the Taskbar's attached property ParentTaskbarIcon.Tag and from now every binding is performed like it was under the window in the visual tree.
For a context menu in a ListBox I add my DataContext to the parent control's tag, and find it in a relative source binding to the placement target. There are many questions on SO regarding this though, and some of those may address more specific instances.
<ListBox ItemsSource="{Binding ItemList}"
SelectedItem="{Binding SelectedItem}"
Tag="{Binding}">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Delete"
Command="{Binding Path=DeleteCommand}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
So for your example specifically:
<tb:TaskbarIcon x:Name="AppNotifyIcon"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
ToolTipText="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.MainTitle}"
Tag="{Binding}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconOpen}" Click="MenuItem_Open_Click"/>
<MenuItem Header="Technologies" ItemsSource="{Binding TechnologyList}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding VmCommand}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Binding Source={StaticResource LocalizedStrings}, Path=Strings.NotifyIconExit}" Click="MenuItem_Exit_Click"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
Of course your bindings will probably vary, but from here you should at least have the DataContext set, and go from there.

Multibinding Command Parameters

So I have a context menu for each individual listview item and the listview is bound to a user list. The context menu has a sub-menu that is bound to an observable collection of user statuses. I want to be able to pass the user id from the list view AND the new status ID from the context menu to my update command parameter. I just looked in to MultiBindings and believe this may be a good, long term solution that I can use elsewhere. Here is some code:
ListView from User View:
<ListView Background="Transparent" ItemsSource="{Binding UserList}" SelectionMode="Single">
<ListView.Resources>
<ContextMenu x:Key="Menu" DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Name="UserID" Header="{Binding UserID}"/>
<Separator></Separator>
<MenuItem Header="Status" ItemsSource="{Binding DataContext.UserStatus, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
DisplayMemberPath="Name" Name="StatusID">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.UpdateDriverStatus, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource MultiBindConverter}">
<Binding ElementName="DriverID"></Binding>
<Binding ElementName="StatusID"></Binding>
</MultiBinding>
</MenuItem.CommandParameter> </MenuItem>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource Menu}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding UserName}" >
</TextBlock>
</ItemContainerTemplate>
</ListView.ItemTemplate>
</ListView>
User VM:
public class UsersPanelVM : ViewModelBase, INotifyPropertyChanged
{
public ObservableCollection<UserPanelItem> UserList { get; set; }
public ObservableCollection<UserStatusList> UserStatus { get; set; }
private readonly IUserService _userService;
public IUserService UserService { get { return this._userService; } }
public UsersPanelVM(IUserService userService)
{
this._userService = userService;
var model = this.UserService.GetUsers();
this.UserList = model.Users;
var statusmodel = this.UserService.GetUserStatus();
this.UserStatus = statusmodel.UserStatus;
this.UpdateUserStatus = new RelayCommand<UserStatusList>((s) => UpdateStatus(1,s));
}
//The 1, above, is hard coded to test the method call, but ideally that should be the selected UserID
private void UpdateStatus(int ID, UserStatusList s)
{
}
public RelayCommand<UserStatusList> UpdateUserStatus { get; private set; }
}
I'm pretty sure I am 100% lost at this point.
This is not necessary as i already mentioned in your other question because you have all the information except the new status in your object model.
Move the command and the UpdateStatus method into the UserPanelItem's class, which also should hold your ID, then you just need to change the command to:
new RelayCommand(param => UpdateStatus(ID, (UserStatusList)param))
If you really want to do it this way: You again set the CommandParameter of the parent MenuItem whose Command will never even be used, move it to the CommandParameter-Setter's Value in the container style, i.e.
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding ...>
....

Change TreeViewItem Template when IsSelected and two types using in TreeView

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>

Resources