I'm trying to create a WPF custom control called "DataTextBox". Everything works fine except for the context menu of this control. Indeed, I would like to add an item in the DataTextBox's Context Menu. To do this, I have added a MenuItem in my DataTextBox style defined in generic.xaml:
<Style TargetType="{x:Type controls:DataTextBox}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{DynamicResource Components_TextBoxCut}" Command="ApplicationCommands.Cut" />
<MenuItem Header="{DynamicResource Components_TextBoxCopy}" Command="ApplicationCommands.Copy" />
<MenuItem Header="{DynamicResource Components_TextBoxPaste}" Command="ApplicationCommands.Paste" />
<MenuItem x:Name="menuItemInsertChecksum" Header="{DynamicResource Components_DataTextBoxInsertChecksum}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:DataTextBox}}, Path=CalculateChecksumCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
...
</Setter.Value>
</Setter>
</Style>
I have also added a command in the DataTextBox code-behind
public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
public ICommand CalculateChecksumCommand { get; private set; }
This command is initialized in the DataTextBox constructor :
public DataTextBox() : base()
{
CalculateChecksumCommand = new RelayCommand(() => CalculateChecksum(), () => CanCalculateChecksum());
}
The issue I have is that the Command binding of my last MenuItem does not work because the "CalculateChecksumCommand" is not found. This means that the "CalculateChecksum()" method is never called.
I would appreciate any help on that subject. Thank you.
EDIT : The Dependency Property declaration should be :
public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
public ICommand CalculateChecksumCommand
{
get { return (ICommand)GetValue(CalculateChecksumCommandProperty); }
private set { SetValue(CalculateChecksumCommandProperty, value); }
}
The window that hosts a control and defines a style for it which binds one menu item of its context menu to a command of it :
XAML
<Window x:Class="WpfApplication2.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:wpfApplication2="clr-namespace:WpfApplication2"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<Style TargetType="wpfApplication2:UserControl1" x:Shared="False">
<Style.Setters>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.(wpfApplication2:UserControl1.MyCommand)}" Header="Hello" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Grid.Resources>
<wpfApplication2:UserControl1 />
</Grid>
</Window>
Code
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class DelegateCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute) : this(execute, s => true)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
}
Control :
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
private DelegateCommand _myCommand;
public UserControl1()
{
InitializeComponent();
MyCommand = new DelegateCommand(Execute);
}
public DelegateCommand MyCommand
{
get { return _myCommand; }
set
{
_myCommand = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void Execute(object o)
{
MessageBox.Show("Hello");
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
With comments from Aybe and nkoniishvt, I think that I'm able to answer my own question.
Objective: create a command in a CustomControl (not UserControl) and use it in the xaml part of this CustomControl
(As nkoniishvt said, commands are typically used in ViewModel and not in UI components. However, I have not found any similar solutions.)
CustomControl code-behind:
using GalaSoft.MvvmLight.Command;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public class CustomControl1 : TextBox
{
public CustomControl1()
: base()
{
MyCommand = new RelayCommand(() => Execute(), () => CanExecute());
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public static DependencyProperty MyCommandProperty = DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(CustomControl1));
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
private set { SetValue(MyCommandProperty, value); }
}
private void Execute()
{
//Do stuff
}
private bool CanExecute()
{
return true;
}
}
}
CustomControl appearance defined in Themes/Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Cut" Command="ApplicationCommands.Cut" />
<MenuItem Header="Copy" Command="ApplicationCommands.Copy" />
<MenuItem Header="Past" Command="ApplicationCommands.Paste" />
<MenuItem Header="Execute MyCommand in CustomControl1"
Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.TemplatedParent.MyCommand}" />
<!--In this case, PlacementTarget is "txtBox"
This is why we have to find the templated parent of the PlacementTarget because MyCommand is defined in the CustomControl1 code-behind-->
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid>
<!--Some UI elements-->
<TextBox Name="txtBox" ContextMenu="{TemplateBinding ContextMenu}" />
<!--Others UI elements-->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Example of use of this CustomControl in MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CustomControl1 />
</Grid>
</Window>
Do not forget to add resources in App.xaml:
<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
By running the application, we can see that MyCommand is properly bound. This means that the Execute() method is called when the user click on the fourth MenuItem in the ContextMenu.
If you see areas for improvement, thank you to let me know.
Hoping it will help someone.
Have you tried to implement a CustomRoutedCommand?
This works for my CustomControl:
public static RoutedCommand CustomCommand = new RoutedCommand();
CommandBinding CustomCommandBinding = new CommandBinding(CustomCommand, ExecutedCustomCommand, CanExecuteCustomCommand);
this.CommandBindings.Add(CustomCommandBinding);
customControl.Command = CustomCommand;
KeyGesture kg = new KeyGesture(Key.F, ModifierKeys.Control);
InputBinding ib = new InputBinding(CustomCommand, kg);
this.InputBindings.Add(ib);
private void ExecutedCustomCommand(object sender, ExecutedRoutedEventArgs e)
{
//what to do;
MessageBox.Show("Custom Command Executed");
}
private void CanExecuteCustomCommand(object sender, CanExecuteRoutedEventArgs e)
{
Control target = e.Source as Control;
if (target != null)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
another interesting example
Related
So I have this checkbox in WPF.
<CheckBox
Name="folder_browser" Checked="{}" Unchecked="{}"
Content="Folder browser" Foreground="Black"
Grid.Row="5" Grid.Column="0"
Visibility="{Binding Visibility}">
</CheckBox>
I have to bind its checked and unchecked in such a way that with check, a certain button is visible and when unchecked, a different button. Both are separate buttons. The location of both is same on UI so I am getting confused.
Use a single Button and set its Content by a DataTrigger in a Button Style:
<Button Click="BrowseButtonClicked">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Content" Value="File..."/>
<Style.Triggers>
<DataTrigger
Binding="{Binding IsChecked, ElementName=folder_browser}"
Value="True">
<Setter Property="Content" Value="Folder..."/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
In the Click handler, perform different actions according to the check state of the CheckBox:
private void BrowseButtonClicked(object sender, RoutedEventArgs e)
{
if (folder_browser.IsChecked == true)
{
//...
}
else
{
//...
}
}
whenever your view elements cannot have their property directly bind to source, then you need to implement a viewmodel as intermediate translator.
I made a simple demo WPF application for you. To repeate what I have done, create a new WPF application project named "CheckboxAndButtonVisibilityDemo", in MainWindow.xaml, copy following code:
<Window x:Name="window" x:Class="CheckboxAndButtonVisibilityDemo.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:CheckboxAndButtonVisibilityDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<CheckBox IsChecked="{Binding ViewModel.CheckBoxChecked, ElementName=window, Mode=TwoWay}"></CheckBox>
<Button Visibility="{Binding ViewModel.ButtonAVisibility, ElementName=window, Mode=OneWay}">ButtonA</Button>
<Button Visibility="{Binding ViewModel.ButtonBVisibility, ElementName=window, Mode=OneWay}">ButtonB</Button>
</StackPanel>
</Window>
Copy following code into MainWindow.xaml.cs
using System.Windows;
namespace CheckboxAndButtonVisibilityDemo {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
public ViewModel ViewModel { get; } = new ViewModel();
}
}
Add a new file, ViewModel.cs, and copy following code into it:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace CheckboxAndButtonVisibilityDemo {
public class ViewModel : INotifyPropertyChanged {
private bool checkBoxChecked;
public bool CheckBoxChecked {
get { return checkBoxChecked; }
set {
checkBoxChecked = value;
if (checkBoxChecked) {
ButtonAVisibility = Visibility.Hidden;
ButtonBVisibility = Visibility.Visible;
} else {
ButtonAVisibility = Visibility.Visible;
ButtonBVisibility = Visibility.Hidden;
}
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Visibility buttonAVisibility = Visibility.Visible;
public Visibility ButtonAVisibility {
get { return buttonAVisibility; }
private set {
buttonAVisibility = value;
NotifyPropertyChanged();
}
}
private Visibility buttonBVisibility = Visibility.Hidden;
public Visibility ButtonBVisibility {
get { return buttonBVisibility; }
private set {
buttonBVisibility = value;
NotifyPropertyChanged();
}
}
}
}
Now its done, run and test it.
I have a window which contains a TabControl and I would like to have TabItems generated based on user actions. Inside the TabItems I would like to display a UserControl which uses a ViewModel.
I can get everything to display properly, however when the UserControl's ViewModel is updated, the changes are not reflected in the TabItem.
Consider the following simplified example:
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModel
{
public MainWindowViewModel()
{
MyControls = new ObservableCollection<MyControlViewModel>();
}
private ObservableCollection<MyControlViewModel> _myControls;
public ObservableCollection<MyControlViewModel> MyControls
{
get { return _myControls; }
set
{
_myControls = value;
RaisePropertyChangedEvent(nameof(MyControls));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private static MainWindowViewModel _vm;
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
_vm = DataContext as MainWindowViewModel;
var myItem1 = new MyItem("Item1");
var myItem2 = new MyItem("Item2");
var myControlVm = new MyControlViewModel(new ObservableCollection<MyItem> { myItem1, myItem2 });
_vm.MyControls.Add(myControlVm);
}
}
MainWindow.xaml
<Window x:Class="TabControlTest.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:TabControlTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding MyControls}">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MyControlViewModel}">
<local:MyControl />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MyControlViewModel.cs
public class MyControlViewModel : ViewModel
{
public MyControlViewModel(ObservableCollection<MyItem> items)
{
MyItems = items;
}
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set
{
_myItems = value;
RaisePropertyChangedEvent(nameof(MyItems));
}
}
}
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var vm = DataContext as MyControlViewModel;
foreach (var item in vm.MyItems)
{
item.ShouldBold = !item.ShouldBold;
}
}
}
MyControl.xaml
<UserControl x:Class="TabControlTest.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TabControlTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ListBox ItemsSource="{Binding MyItems}" MouseDoubleClick="ListBox_MouseDoubleClick">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Label}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShouldBold}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
</Grid>
</UserControl>
MyItem.cs
public class MyItem : ViewModel
{
public MyItem(string label)
{
Label = label;
}
private string _label;
public string Label
{
get { return _label; }
set
{
_label = value;
RaisePropertyChangedEvent(nameof(Label));
}
}
private bool _shouldBold;
public bool ShouldBold
{
get { return _shouldBold; }
set
{
_shouldBold = value;
RaisePropertyChangedEvent(nameof(ShouldBold));
}
}
}
When I double-click on the MyControl in the MainWindow I would expect the items to be displayed in bold via the ListBox_MouseDoubleClick method, however the style is not applying. When I step through the ListBox_MouseDoubleClick method, I can see that the ShouldBold is being updated correctly, but again the style is not applying.
Is there another way I should structure this or something else I need to do to have the style apply? Any help is appreciated!
Edit
Here's my ViewModel.cs
public class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Your ViewModel class should implement INotifyPropertyChanged:
public class ViewModel : INotifyPropertyChanged
...
This question already has answers here:
XPath : Bind to last item of collection
(2 answers)
Closed 7 years ago.
In my ListBox.ItemTemplate i have a TextBlock and a Remove button, the button must be enabled only if it's the last element o the listbox.
Created a quick example for you. since you are want to use Button Control you can use Command to enable or disable your button.
View:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ParentViewModel />
</Window.DataContext>
<ListBox Width="400" Height="auto" ItemsSource="{Binding MyList,Mode=TwoWay}" >
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" >
<Setter.Value>
<DataTemplate>
<Button Content="{Binding}" Padding="15,5" Margin="10,4"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBox}},Path=DataContext.RemoveButtonCommand}"
CommandParameter="{Binding}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Style>
</ListBox>
View Model:
public class ParentViewModel : INotifyPropertyChanged
{
public ParentViewModel()
{
MyList = new List<string>();
MyList.Add("1");
MyList.Add("2");
MyList.Add("3");
MyList.Add("4");
MyList.Add("5");
MyList.Add("6");
RemoveButtonCommand = new RelayCommand(param => this.RemoveCommandAction(param), param => this.CanExecuteRemoveButtonCommand(param));
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private List<string> myList;
public List<string> MyList
{
get { return myList; }
set
{
myList = value;
OnPropertyChanged("MyList");
}
}
public RelayCommand RemoveButtonCommand { get; set; }
public void RemoveCommandAction(object param)
{
MyList.Remove((string)param);
MyList = new List<string>(MyList);
}
public bool CanExecuteRemoveButtonCommand(object param)
{
return MyList[MyList.Count - 1] == ((string)param);
}
}
RelayCommand:
Copy from here
I'm Just checking if the content of the Button is the last element of the list in CanExecute logic.so I'm passing that in CommandParameter binding. You can surely use the Index of listboxitem or just pass the whole item and compare that. it's the easiest way of doing this.
Hi I have use the following Code snippet to create custom control for generating multiple Menuitems and it is the code snippet of example to explain my working scenorio
Generic XAML
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Menu ItemsSource="{Binding ItemsSource,RelativeSource={RelativeSource TemplatedParent}}">
<Menu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The following code snippet i have created the DependencyProperty in CustomControl
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public object ItemsSource
{
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(CustomControl1), new PropertyMetadata(0));
}
And also i have intizialize this custom control in my xaml part like below code snippet
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:CustomLib="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Button Content="Add" Click="Button_Click"></Button>
<CustomLib:CustomControl1 Name="CustomControl" ItemsSource="{Binding ListItems}" Grid.Row="1"/>
</Grid>
</Window>
I have created the ListItems in MainWindows.xaml.cs file and also define the DataContext as this.DataContex as this like below code snippet
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
CreateListItems();
}
private void CreateListItems()
{
this.ListItems = new List<MenuModel>();
this.ListItems.Add(new MenuModel() { Header = "Test1" });
this.ListItems.Add(new MenuModel() { Header = "Test2" });
this.ListItems.Add(new MenuModel() { Header = "Test3" });
}
private List<MenuModel> listItems;
public List<MenuModel> ListItems
{
get
{
return listItems;
}
set
{
listItems = value;
RaisePropertyChanged("ListItems");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.ListItems.Add(new MenuModel() { Header = "Test4" });
this.CustomControl.ItemsSource = this.ListItems;
}
}
public class MenuModel
{
public string Header
{
get;
set;
}
}
}
While add button click i have change the ItemsSource but it is not updated. Can you please help me how to achieve this
I have an user control (ex: UserCtrlClass)with a tree view inside it
I have View model (ex: OBJViewModel) class for represent the actual items/data display on the tree view
Next I have a Tree View Model (ex: TreeViewModel), which has a list of OBJViewModel objects
Now in the code behind file of the user control, I have instantiated the tree view model class and set as the data context of the user control class
I need a context sensitive menu, which i need to display only when I right click on a specific item in the tree, so I have handled the right click event of the user control class and did the work there
But the commands are not working, The commands are derived from I command and instantiated in TreeViewModel class. i tried to debug my Command.execute was never hit! Any help would be appreciated as I am being a newbie to .net and wpf
TreeViewModel class
<UserControl Name="PFDBUserCtrl" x:Class="BFSimMaster.BFSMTreeview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BFSimMaster.ViewModel"
xmlns:cmd="clr-namespace:BFSimMaster.Commands"
mc:Ignorable="d"
d:DesignHeight="66" d:DesignWidth="300">
<UserControl.Resources>
<!--cmd:ActivateProjectCmd x:Key="CMDActivateProject"/-->
<!--cmd:DeActivateProjectCmd x:Key="CMDDeActivateProject"/-->
</UserControl.Resources>
<DockPanel>
<!-- PF Object Browser TREE -->
<TreeView Name="PFDataBrowser" ItemsSource="{Binding LevelOnePFObjects}" >
<TreeView.Resources>
<ContextMenu x:Key ="ProjectMenu" StaysOpen="true" >
<!-- Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}-->
<!--MenuItem Header="Activate" Command="{Binding Source={StaticResource CMDActivateProject}}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/-->
<MenuItem Header="Activate" Command="{Binding DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<MenuItem Header="Deactivate" Command="{Binding Source=TVViewModel, Path=CMDDeActivateProject}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<!-- This Style binds a TreeViewItem to a PFObject View Model.-->
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseRightButtonDown" Handler="OnRightButtonDown"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
Code behind class
using System;
namespace BFSimMaster
{
public partial class BFSMTreeview : UserControl
{
readonly TreeViewItemViewModel mViewModelPFObjBrowserTree;
public BFSMTreeview()
{
InitializeComponent();
WApplication appPF = PFAPIUtils.APIInstance.GetApplication();
WDataObject User = appPF.GetCurrentUser();
// Get raw objects - tree data from a PF database.
//BFPFDataObject userdb = new BFPFDataObject(User,false,"*.IntPrj");
BFPFDataObject userdb = new BFPFDataObject(User, true);
// Create UI-friendly wrappers around the
// raw data objects (i.e. the view-model).
mViewModelPFObjBrowserTree = new TreeViewItemViewModel(userdb);
// Let the UI bind to the view-model.
base.DataContext = mViewModelPFObjBrowserTree;
}
public TreeViewItemViewModel TVViewModel
{
get { return mViewModelPFObjBrowserTree; }
}
private void OnRightButtonDown(object sender, MouseButtonEventArgs e)
{
//MessageBox.Show("Right Clicked on tree view");
if (sender is TreeViewItem)
{
e.Handled = true;
(sender as TreeViewItem).IsSelected = true;
string strObjectType = ((sender as TreeViewItem).Header as PFObjectViewModel).PFDataObject.mThisPFObject.GetClassName().GetString();
switch (strObjectType)
{
case "IntPrj":
(sender as TreeViewItem).ContextMenu = PFDataBrowser.Resources["ProjectMenu"] as System.Windows.Controls.ContextMenu;
(sender as TreeViewItem).ContextMenu.PlacementTarget = (sender as TreeViewItem);
break;
case "Folder":
(sender as TreeViewItem).ContextMenu = PFDataBrowser.Resources["ProjectMenu"] as System.Windows.Controls.ContextMenu;
break;
}
}
}
}
}
the TreeViewModel Class
using System;
namespace BFSimMaster.ViewModel
{
public class TreeViewItemViewModel
{
#region Data
readonly ReadOnlyCollection<PFObjectViewModel> mLevelOnePFObjects;
readonly PFObjectViewModel mRootOfPFObjects;
#endregion // Data
#region Constructor
public TreeViewItemViewModel(BFPFDataObject rootOfPFObjectsA)
{
this.CMDActivateProject = new ActivateProjectCmd();
this.CMDDeActivateProject = new DeActivateProjectCmd();
mRootOfPFObjects = new PFObjectViewModel(rootOfPFObjectsA);
mLevelOnePFObjects = new ReadOnlyCollection<PFObjectViewModel>(
new PFObjectViewModel[]
{
mRootOfPFObjects
});
}
#endregion // Constructor
public ICommand CMDActivateProject { get; set; }
public ICommand CMDDeActivateProject { get; set; }
public ReadOnlyCollection<PFObjectViewModel> LevelOnePFObjects
{
get { return mLevelOnePFObjects; }
}
}
}
A ContextMenu isn't part of the logical tree, so this binding "{Binding DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" wont work, because it simply has no ancestor of type UserControl. If you dont set the DataContext of the PlacementTarget manuelly, you could try
"{Binding PlacementTarget.DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
I not 100% sure about the following, but I think this will just work, if youre using the ContextMenu property:
<Treeview>
<Treeview.ContextMenu>
<ContextMenu>
...
<ContextMenu>
<Treeview.ContextMenu>
...
The adventage here is, that you dont have to handle the rightbuttondown event in code-behind, it automatically opens if you right click on your treeview.
I've solved this problem by introducing VM class for MenuItem and setting context menu with ExtendedContextMenu.Items={Binding ContextMenu} attached property. MenuResourcesDictionary is ResourceDictionary.xaml with back-side .cs file (shown below).
To use it for your code you need to add IEnumerable<MenuItemVM> ContextMenu property on your tree model and put commands there (e.g. pass them to MenuItemVM constructor). And in the item style add <Setter Property="ExtendedContextMenu.Items" Value="{Binding DataContext.ContextMenu}" />
public static class ExtendedContextMenu
{
private static readonly StyleSelector _styleSelector = new ContextMenuItemStyleSelector();
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.RegisterAttached("Items",
typeof(IEnumerable<MenuItemVM>),
typeof(ExtendedContextMenu),
new FrameworkPropertyMetadata(default(IEnumerable<MenuItemVM>), ItemsChanged));
public static void SetItems(DependencyObject element, IEnumerable<MenuItemVM> value)
{
element.SetValue(ItemsProperty, value);
}
public static IEnumerable<MenuItemVM> GetItems(DependencyObject element)
{
return (IEnumerable<MenuItemVM>)element.GetValue(ItemsProperty);
}
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (FrameworkElement)d;
var items = (IEnumerable<MenuItemVM>)e.NewValue;
var contextMenu = new ContextMenu();
contextMenu.ItemContainerStyleSelector = _styleSelector;
contextMenu.ItemsSource = items;
return contextMenu;
}
private static void AdjustContextMenuVisibility(ContextMenu menu)
{
menu.Visibility = menu.HasItems ? Visibility.Visible : Visibility.Hidden;
}
}
public class ContextMenuItemStyleSelector : StyleSelector
{
private static readonly MenuResourcesDictionary _resources = new MenuResourcesDictionary();
private static readonly Style _menuItemStyle = (Style)_resources[MenuResourcesDictionary.MenuItemStyleResourceKey];
private static readonly Style _separatorStyle = (Style)_resources[MenuResourcesDictionary.SeparatorStyleResourceKey];
public override Style SelectStyle(object item, DependencyObject container)
{
if (item == MenuItemVM.Separator)
return _separatorStyle;
return _menuItemStyle;
}
}
public sealed partial class MenuResourcesDictionary
{
public const string MenuItemStyleResourceKey = "MenuItemStyleResourceKey";
public const string DynamicMenuItemStyleResourceKey = "DynamicMenuItemStyleResourceKey";
public const string SeparatorStyleResourceKey = "SeparatorStyleResourceKey";
public const string LoadingStyleResourceKey = "LoadingMenuItemStyleResourceKey";
public MenuResourcesDictionary()
{
InitializeComponent();
}
}
Here is XAML styles:
<Style x:Key="{x:Static local:MenuResourcesDictionary.MenuItemStyleResourceKey}">
<Setter Property="MenuItem.Header" Value="{Binding Path=(menuVMs:MenuItemVM.Text)}" />
<Setter Property="MenuItem.Command" Value="{Binding Path=(menuVMs:MenuItemVM.Command)}" />
</Style>
<Style x:Key="{x:Static local:MenuResourcesDictionary.SeparatorStyleResourceKey}" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MenuItemVM class is more-or-less straighforward, so I don't write it here.
I Found the Answer for TreeView Context menu with and with out DataTemplate here:
TreeView ContextMenu MVVM Binding
MVVM binding command to contextmenu item