In my View I have a Menu Control which is binded to my ViewModel's Property becauase I want to populate it dynamically.I created a seperated class for my Menu .
Here is my My menu class:
public class MenuItemViewModel : ViewModelBase
{
internal MenuItemViewModel()
{
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
if (_menuText == value)
return;
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
private ObservableCollection<MenuItemViewModel> _children;
public ObservableCollection<MenuItemViewModel> Children
{
get { return _children; }
set
{
_children = value;
RaisePropertyChanged("Children");
}
}
}
and In my MainViewModel I created an Collection property of my MenuItemViewModel
here is my MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadMainMenu();
}
#region Menu
private ObservableCollection<MenuItemViewModel> _topMenuItems;
public ObservableCollection<MenuItemViewModel> TopMenuItems
{
get { return _topMenuItems; }
set
{
if (_topMenuItems == value)
return;
_topMenuItems = value;
RaisePropertyChanged("TopMenuItems");
}
}
public void LoadMainMenu()
{
IList<MenuItemViewModel> fileMenuItems = PopulateFileMenuEntries();
_topMenuItems.Add(new MenuItemViewModel() { MenuText = "_File", Children = new ObservableCollection<MenuItemViewModel>(fileMenuItems) });
}
private IList<MenuItemViewModel> PopulateFileMenuEntries()
{
List<MenuItemViewModel> fileMenuItems = new List<MenuItemViewModel>();
fileMenuItems.Add(new MenuItemViewModel() { MenuText = "Open _Recent" });
return fileMenuItems;
}
}
here is my XAML:
<Window.Resources>
<WpfApplication3_ViewModel:MainViewModel x:Key="MainViewModelDataSource"
d:IsDataSource="True" />
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModelDataSource}">
<Menu
ItemsSource="{Binding TopMenuItems}"
Margin="12,0,50,237">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding MenuText}" />
<Setter Property="ItemsSource"
Value="{Binding Children}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }"
Value="{x:Null}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>
</Grid>
When the application run it throw an exception "Exception has been thrown by the target of an invocation."
What is wrong with my code
I have had similar problem and I tracked it down to an uninitialised variable. Your _topMenuItems in your Constructor should be
new ObservableCollection<MenuItemViewModel>()
or
ObservableCollection<MenuItemViewModel> _topMenuItems = new ObservableCollection<MenuItemViewModel>();
Related
I'm updating a small app to MVVM. It uses a DataGrid and the context menu displayed depend upon whether a row is selected or not. My problem is when I right click on an unselected row the row is first selected so the Selected Menu is displayed. The left mouse button is coded to toggle the selection. If I toggle the selection off then click the right mouse button the No Selection Menu is displayed. How can I get the right mouse click to show the no selection menu on an unselected row without having to explicitly deselect the row?
I have created a small example to illustrate the problem.
I'm using Fody to handle property changes; Microsoft MVVM Toolkit.
XAML:
<Window.Resources>
<ContextMenu x:Key="PopupNoSelection">
<MenuItem Name="NewItem" Header="No Item Selection" Command="{Binding NoSelectionClick}" />
</ContextMenu>
<ContextMenu x:Key="PopupSelection">
<MenuItem Name="AmmendItem" Header="Item Selection" Command="{Binding SelectionClick}" />
</ContextMenu>
</Window.Resources>
<Grid>
<DataGrid x:Name="dgToggle" AutoGenerateColumns="False"
CanUserSortColumns="False" CanUserAddRows="False" SelectionMode="Single"
SelectedItem="{Binding ToggleSelected, Mode=TwoWay}" SelectionUnit="FullRow"
ItemsSource="{Binding ToggleRows}">
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItems.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="ContextMenu" Value="{StaticResource ResourceKey=PopupNoSelection}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=SelectedItems.Count, RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="ContextMenu" Value="{StaticResource ResourceKey=PopupSelection}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Header="Rubbish" Binding="{Binding Rubbish, Mode=OneWay}" />
</DataGrid.Columns>
<!-- Toggle Selected item & set Selected Item Colour -->
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DoSelectedRow" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LimeGreen" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
XAML.cs
public partial class DataGridToggleView : Window
{
public DataGridToggleView()
{
InitializeComponent();
}
public void DoSelectedRow(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing)
{
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null)
{
row.IsSelected = !row.IsSelected;
e.Handled = true;
}
}
}
public static Parent FindVisualParent<Parent>(DependencyObject child) where Parent : DependencyObject
{
DependencyObject parentObject = child;
while (!((parentObject is System.Windows.Media.Visual)
|| (parentObject is System.Windows.Media.Media3D.Visual3D)))
{
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
parentObject = (parentObject as FrameworkContentElement).Parent;
}
}
parentObject = VisualTreeHelper.GetParent(parentObject);
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
return FindVisualParent<Parent>(parentObject);
}
}
}
Model:
public class DataGridToggleModel
{
public string Rubbish { get; set; }
}
ViewModel
public class DataGridToggleViewModel : ObservableObject
{
public ObservableCollection<DataGridToggleModel> ToggleRows { get; set; }
public List<DataGridToggleModel> ToggleList { get; set; }
private DataGridToggleModel _toggleSelected;
public DataGridToggleModel ToggleSelected
{
get { return _toggleSelected; }
set
{
_toggleSelected = value;
}
}
public ICommand MouseLeftClick { get; set; }
public ICommand NoSelectionClick { get; set; }
public ICommand SelectionClick { get; set; }
public DataGridToggleViewModel()
{
MouseLeftClick = new RelayCommand<object>(MouseLeftCommand);
NoSelectionClick = new RelayCommand(NoSelectionCommand);
SelectionClick = new RelayCommand(SelectionCommand);
}
private void MouseLeftCommand( object obj)
{
////MessageBox.Show($"Row Clicked {ToggleSelected.Rubbish}");
//DataGridRow dataGridRow = ToggleSelected as DataGridRow;
//ToggleSelected.IsSelected = !ToggleSelected.IsSelected;
}
private void NoSelectionCommand()
{
MessageBox.Show("No Selection Menu");
}
private void SelectionCommand()
{
MessageBox.Show("Selected Menu");
}
public void PopulateDataGrid()
{
ToggleList = new List<DataGridToggleModel>();
ToggleList.Add(new DataGridToggleModel { Rubbish = "Waste paper" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Empty tins" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Junk mail" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Empty boxes" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Newpapers & Magazines" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Old clothes" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Packaging" });
ToggleRows = new ObservableCollection<DataGridToggleModel>(ToggleList);
}
}
Any help would be appreciated - thanks.
I'm trying to display a treeview in my application but somehow I can't get the binding to work and application displays a blank treeview. Here's the source code.
MainWindow.xaml
<Window x:Class="TreeViewTest.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:TreeViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<ListBox x:Name="listbox" MinWidth="200" MinHeight="300">
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Regions}"
MinWidth="200" MinHeight="300">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Region}" ItemsSource="{Binding Customers}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:CustomerDataHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Customer}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:CustomerDataHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</ListBox>
<Button Content="?" Click="Button_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ObservableCollection<Region> Regions { get; }
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Region Region in this.Regions)
foreach (Customer Customer in Region.Customers)
if (CustomerDataHelper.GetIsChecked(Customer) == true)
crew += Customer.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Selected customers: " + crew;
}
}
MainWindowViewModel.cs
class MainWindowViewModel
{
public MainWindowViewModel()
{
Regions = new ObservableCollection<Region>();
Regions.Add(new Region() { Name = "North", Customers = new List<Customer>() { new Customer() { Name = "N1" }, new Customer() { Name = "N2" } } });
Regions.Add(new Region() { Name = "East", Customers = new List<Customer>() { new Customer() { Name = "E1" }, new Customer() { Name = "E2" } } });
Regions.Add(new Region() { Name = "South", Customers = new List<Customer>() { new Customer() { Name = "S1" } } });
foreach (Region Region in this.Regions)
foreach (Customer Customer in Region.Customers)
Customer.SetValue(CustomerDataHelper.ParentProperty, Region);
}
public ObservableCollection<Region> Regions { get; }
}
Customers.cs
public class Region : DependencyObject
{
public string Name { get; set; }
public List<Customer> Customers { get; set; }
}
public class Customer : DependencyObject
{
public string Name { get; set; }
}
CustomerDataHelper.cs
public class CustomerDataHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CustomerDataHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(CustomerDataHelper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(CustomerDataHelper.IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(CustomerDataHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(CustomerDataHelper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(CustomerDataHelper.ParentProperty);
}
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Region && ((bool?)e.NewValue).HasValue)
foreach (Customer p in (d as Region).Customers)
CustomerDataHelper.SetIsChecked(p, (bool?)e.NewValue);
if (d is Customer)
{
int checkedCount = ((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as Region).Customers.Where(x => CustomerDataHelper.GetIsChecked(x) == true).Count();
int uncheckedCount = ((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as Region).Customers.Where(x => CustomerDataHelper.GetIsChecked(x) == false).Count();
if (uncheckedCount > 0 && checkedCount > 0)
{
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, null);
return;
}
if (checkedCount > 0)
{
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, true);
return;
}
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, false);
}
}
}
If I add the view model ctor code in the code behind ctor and run, it works and I can see the tree view items displayed. Can't seem to figure out what I'm missing.
I've created a DockingManager from AvalonDock content in my project and my request is quite simple: when I add a document to my LayoutDocumentPaneGroup I want it to be Active, Selected, and not only add at the end of the LayoutDocumentPaneGroup still activate on the first document.
I tried to implement an IsActive property to my documentView class but it doesn't work.
My dockingmanager in xaml file is defined as below:
<dock:DockingManager DataContext="{Binding DockManagerViewModel}" DocumentsSource="{Binding Documents}" AnchorablesSource="{Binding Anchorables}">
<dock:DockingManager.Resources>
<!-- add views for specific ViewModels -->
<DataTemplate DataType="{x:Type vmdock:SampleDockWindowViewModel}">
<uscontrol:SampleDockWindowView />
</DataTemplate>
</dock:DockingManager.Resources>
<dock:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type dockctrl:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}" />
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}" />
<Setter Property="CanClose" Value="{Binding Model.CanClose}" />
</Style>
</dock:DockingManager.LayoutItemContainerStyle>
<dock:LayoutRoot>
<dock:LayoutPanel Orientation="Vertical">
<dock:LayoutDocumentPaneGroup>
<dock:LayoutDocumentPane />
</dock:LayoutDocumentPaneGroup>
<dock:LayoutAnchorablePaneGroup>
<dock:LayoutAnchorablePane />
</dock:LayoutAnchorablePaneGroup>
</dock:LayoutPanel>
</dock:LayoutRoot>
</dock:DockingManager>
My documentView is defined with the class as below:
public abstract class DockWindowViewModel : BaseViewModel
{
#region Properties
#region CloseCommand
private ICommand _CloseCommand;
public ICommand CloseCommand
{
get
{
if (_CloseCommand == null)
_CloseCommand = new RelayCommand(call => Close());
return _CloseCommand;
}
}
#endregion
#region IsClosed
private bool _IsClosed;
public bool IsClosed
{
get { return _IsClosed; }
set
{
if (_IsClosed != value)
{
_IsClosed = value;
OnPropertyChanged(nameof(IsClosed));
}
}
}
#endregion
#region CanClose
private bool _CanClose;
public bool CanClose
{
get { return _CanClose; }
set
{
if (_CanClose != value)
{
_CanClose = value;
OnPropertyChanged(nameof(CanClose));
}
}
}
#endregion
#region Title
private string _Title;
public string Title
{
get { return _Title; }
set
{
if (_Title != value)
{
_Title = value;
OnPropertyChanged(nameof(Title));
}
}
}
#endregion
#endregion
public DockWindowViewModel()
{
CanClose = true;
IsClosed = false;
}
public void Close()
{
IsClosed = true;
}
Finally found ! I post the result because I think I'll not be alone...
First, I add a new property to my document view definition:
#region IsSelected
private bool _isSelected = false;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
}
#endregion
But I had also to implement it as a property in my XAML code, as below:
<dock:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type dockctrl:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}" />
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}" />
<Setter Property="CanClose" Value="{Binding Model.CanClose}" />
**<Setter Property="IsSelected" Value="{Binding Model.IsSelected}" />**
</Style>
</dock:DockingManager.LayoutItemContainerStyle>
Supposed it works the same for all properties of LayoutContent defined there: LayoutDocument defined by AvalonDock
EDIT :
Need to add also "Mode=TwoWay" to programmatically update when the selected content changed, like this:
<Setter Property="IsSelected" Value="{Binding Model.IsSelected, Mode=TwoWay}" />
As I already mentioned in my comment on the accepted answer, this didn't work for me, so I moved on trying. My case may differ, though. I'm using Stylet and my DockManager.DocumentsSource is bound to the Items property of a Conductor<MyViewModelBase>.Collection.OneActive Documents in the ViewModel. In this case I needed to do two things to activate the added document:
Bind DockingManager.ActiveContent to Documents.ActiveItem
After Documents.Items.Add(an_instance_of_my_viewmodel); activate the new ViewModel with Documents.ActivateItem(vm);
I have a simple object Action that has a property Code. Depending on its Code, I want to select different DataTemplates a it is also possible for the user to change the Code through a ComboBox.
public class Action : INotifyPropertyChanged
{
public Action()
{
Parameters = new List<Parameter>();
}
public int ActionID { get; set; }
public int StepID { get; set; }
public int Code { get; set; }
[NotMapped]
public List<Parameter> Parameters { get; set; }
}
So I was looking at this answer: https://stackoverflow.com/a/18000310/2877820
I tried the implement the solution like this:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var action = (ASI.RecipeManagement.Data.Action) item;
if (action == null) return null;
PropertyChangedEventHandler lambda = null;
lambda = (o, args) =>
{
if (args.PropertyName == "Code")
{
action.PropertyChanged -= lambda;
var cp = (ContentPresenter)container;
cp.ContentTemplateSelector = null;
cp.ContentTemplateSelector = this;
}
};
action.PropertyChanged += lambda;
if (action.Code == 0)
return NoParamTemplate;
if (action.Code == 1)
return OneParamTemplate;
if (action.Code == 2)
{
if (action.Parameters[0].Type == ParameterInputTypes.List)
{
return ComboBoxParamTemplate;
}
return TwoParamTemplate;
}
return null;
}
Sadly it does not seem to work for me. Can anybody help me out? What am I doing wrong right here?
A DataTemplateSelector does't respond to property change notifications. As a workaround, you could use a ContentControl with DataTriggers in the ItemTemplate, .e.g.:
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NoParamTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Code}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource OneParamTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Code}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TwoParamTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
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