MenuItem Visibility binding to RelayCommand CanExecute with Parameter - wpf

I'm using Josh Smith RelayCommand class in a WPF MVVM app for creating my commands inside my ViewModel:
For example:
ICommand RemoveAllCommand = new RelayCommand<object>(OnRemoveAll, CanRemoveAll);
I am calling this command from a ContextMenu:
<ContextMenu x:Key="MyContextMenu" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Remove All" Command="{Binding Path=DataContext.RemoveAllCommand,
RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" CommandParameter="{Binding Path=.Header}" />
Everything works fine, except that my MenuItem's is still visible but disabled, I would like to set the Visibility to Collapsed so that my MenuItem doesn't show up when CanExecute in Relay Command returns false.
I tried to set a binding to Visibility property, but I don't know how to bind to my CanRemoveAll(object obj) method with parameter. I also thought about using a DataTrigger but I'm not sure how to do it.
Here is my CanRemoveAll method in the ViewModel:
public bool CanRemoveAll(object param)
{
GoldTreeNodeViewModel gtn = param as GoldTreeNodeViewModel;
return (gtn != null && gtn.Children != null && gtn.Children.Count > 0);
}
From the RelayCommand class:
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
[DebuggerStepThrough]
public Boolean CanExecute(Object parameter)
{
return _canExecute == null ? true : _canExecute((T) parameter);
}
Any help will be highly appreciated,

<ContextMenu x:Key="MyContextMenu" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Remove All" Command="{Binding Path=DataContext.RemoveAllCommand,
RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" CommandParameter="{Binding Path=.Header}"
Visibility="{Binding DataContext.RemoveVisibility,RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}"
/>
private Visibility _removeVisibility;
public Visibility RemoveVisibility
{
get { return _removeVisibility; }
set { _removeVisibility = value; Notify("RemoveVisibility"); }
}
public bool CanRemoveAll(object param)
{
GoldTreeNodeViewModel gtn = param as GoldTreeNodeViewModel;
bool result= (gtn != null && gtn.Children != null && gtn.Children.Count > 0);
if (result)
RemoveVisibility = Visibility.Visible;
else
RemoveVisibility = Visibility.Collapsed;
return result;
}
I think that DataContext you have binded correspond to your ViewModel . I hope this will help.

Related

WPF How to bind a command to the ContextMenu in RichTextBox?

I've created RichTextBox control with Menu at the top side of Window. The MenuItems call Commands - It works perfectly. Then I try to create ContextMenu in the RichTextBox and want to call the same commands like in MenuItems.
So that, I bind the ContextMenu in the same way like MenuItems but it throws the NullReferenceException.
What can be the reason. How should I bind the Command to the ContextMenu??
Below are the parts of my code
MenuItem code:
<MenuItem Name="FontSettings" Header="Font settings" Command="{Binding FontSettingsCommand}" CommandParameter="{Binding ElementName=MainRichTbx}" />
RichTextBox code:
<RichTextBox Name="MainRichTbx" TextBlock.LineHeight="0.1" Margin="5" >
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Font settings" Command="{Binding FontSettingsCommand}" CommandParameter="{Binding ElementName=MainRichTbx}" />
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
That command which I want to execute:
private ICommand _FontSettingsCommand;
public ICommand FontSettingsCommand
{
get
{
if (_FontSettingsCommand == null)
{
_FontSettingsCommand = new RelayCommand(
argument => EditorFormat.SetFont(argument),
argument => true
);
}
return _FontSettingsCommand;
}
}
The method which I call within the Command:
public static void SetFont(object control)
{
FontDialog fontDialog = new FontDialog();
if (fontDialog.ShowDialog() == DialogResult.OK)
{
(control as System.Windows.Controls.RichTextBox).FontFamily = new System.Windows.Media.FontFamily(fontDialog.Font.Name);
(control as System.Windows.Controls.RichTextBox).FontSize = fontDialog.Font.Size;
(control as System.Windows.Controls.RichTextBox).FontStyle = fontDialog.Font.Italic ? FontStyles.Italic : FontStyles.Normal;
(control as System.Windows.Controls.RichTextBox).FontWeight = fontDialog.Font.Bold ? FontWeights.Bold : FontWeights.Regular;
}
}
And The RelayCommand class
class RelayCommand : ICommand
{
private readonly Action<object> _Execute;
private readonly Func<object, bool> _CanExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
_Execute = execute;
_CanExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _CanExecute == null ? true : _CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_CanExecute != null) CommandManager.RequerySuggested += value;
}
remove
{
if (_CanExecute != null) CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_Execute(parameter);
}
}
I have a solution which I think works, binding to the PlacementTarget of the context menu.
<RichTextBox Name="MainRichTbx" TextBlock.LineHeight="0.1" Margin="5" >
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Font settings"
Command="{Binding FontSettingsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
However, the XAML designer underlines the CommandPamameter and shows the tooltip "RelativeSource is not in FindAncestor mode". Nevertheless it seems to work.
Edit
Adding Mode=FindAncestor seems to fix the error message. I don't know if it has any effect on the behavior.
<RichTextBox Name="MainRichTbx" TextBlock.LineHeight="0.1" Margin="5" >
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Font settings"
Command="{Binding FontSettingsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
This is probably because the binding can't find your text box.
This happens because the ContextMenu is not part of the visual tree so can't find your Text Box for the CommandParameter.
The best way around this is to not use the CommandParameter at all, but use a variable in your ViewModel (such as SelectedTextBox).
However, you can get a working (but slightly uglier) solution by naming your ContextMenu and setting its NameScope in the View's constructor:
NameScope.SetNameScope(myContextMenu, this);
This should then work correctly.

WPF - MVVM - TreeView - Commands enabled based on selected item

I have found many pages that bear on this in one way or another, but have still not discovered how to achieve it. Here is my XAML:
<TreeView ItemsSource="{Binding Document}" HorizontalAlignment="Stretch" BorderThickness="0" Background="#FFC2A2A2">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageFile}" Height="16" Width="16"/>
<TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add" Command="{Binding AddCommand}"/>
<MenuItem Header="Delete" Command="{Binding DeleteCommand}"/>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
I implemented the AddCommand and DeleteCommand based roughly on the Search button implementation in this.
Both commands require the SelectedItem from the tree, so I implemented it in the tree MVVM, added a pointer to the tree MVVM to each item MVVM, and maintain it via the IsSelected property in the item MVVM.
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (value != mIsSelected)
{
mIsSelected = value;
this.OnPropertyChanged("IsSelected");
}
if (mIsSelected)
{
mDocViewModel.SelectedItem = this;
}
}
}
(We use mAbc for data members, rather than _abc.)
This all works. However, the context menus have a context. Based on which is selected, the AddCommand may not be valid, and I want that represented as disabled and enabled in the view.
I put my tests for this condition in the CanExecute method of each command. But at run time, CanExecute seems never to be invoked, and both menu item always appear disabled.
Is there a way to get this done? Is there a simple way?
Thanks,
Art
LATER:
Editing my question appears to be the way to make a longer reply. Here, then, is one of the Command classes ... with respect to the CanExecute mentioned afterwards.
#region DeleteCommand
public ICommand DeleteCommand
{
get { return mDeleteCommand; }
}
void DeleteNode()
{
if (mSelectedItem != null)
{
mSelectedItem.Remove();
mSelectedItem = null;
}
}
private class DeleteNodeCommand : RoutedCommand
{
DocumentRulesViewModel mDocumentViewModel;
public DeleteNodeCommand (DocumentRulesViewModel _docViewModel)
{
mDocumentViewModel = _docViewModel;
}
void SelectedItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
public bool CanExecute(object parameter)
{
DesignObjectViewModel current = mDocumentViewModel.SelectedItem;
return (current != null);
}
event EventHandler CanExecuteChanged
{
// I intentionally left these empty because
// this command never raises the event, and
// not using the WeakEvent pattern here can
// cause memory leaks. WeakEvent pattern is
// not simple to implement, so why bother.
add { }
remove { }
}
public void Execute(object parameter)
{
mDocumentViewModel.DeleteNode();
}
public event PropertyChangedEventHandler PropertyChanged;
}
#endregion
I didn't do anything with the event stuff at the bottom, just copied it from an example. And, in that example, the command would always be valid. So maybe the issue lies there.
But I did some prowling for CanExecuteChange, and did not really see what to do with it.
Jim, I guess all I can do it show it all (I'll have to omit the application/model parts, of course.
Main xaml:
<Window x:Class="xDesign.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:xDesign.View"
Title="{StaticResource thisAppName}" Height="350" Width="525">
<DockPanel>
<Menu VerticalAlignment="Top" DockPanel.Dock="Top" BorderThickness="0">
<MenuItem Header="{StaticResource fileMenu}" Name="FileMenu">
<MenuItem Header="{StaticResource newFileMenu}" Click="NewDocumentMenuItem_Click" Name="FileMenuNewDoc"/>
<MenuItem Header="{StaticResource openFileMenu}" Click="OpenDocumentMenuItem_Click" Name="FileMenuOpenDoc" />
<MenuItem Header="{StaticResource closeFileMenu}" Click="CloseDocumentMenuItem_Click" IsEnabled="False" Name="FileMenuCloseDoc" />
<Separator />
<MenuItem Name="FileMenuCheckout" Header="{StaticResource checkoutFileMenu}" Click="FileMenuCheckout_Click"/>
<MenuItem Name="FileMenuCheckin" Header="{StaticResource checkinFileMenu}" Click="FileMenuCheckin_Click" IsEnabled="False"/>
<MenuItem Name="FileMenuDeleteFromServer" Header="{StaticResource deleteFromServerFileMenu}" Click="FileMenuDeleteFromServer_Click" IsEnabled="False"/>
<MenuItem Name="FileMenuLogon" Header="{StaticResource logonFileMenu}" Click="FileMenuLogon_Click"/>
<MenuItem Name="FileMenuLogoff" IsEnabled="False" Header="{StaticResource logoffFileMenu}" Click="FileMenuLogoff_Click"/>
</MenuItem>
<MenuItem Header="{StaticResource editMenu}" IsEnabled="False" Name="EditMenu">
<MenuItem Header="{StaticResource findEditMenu}" Click="FindEditMenuItem_Click"/>
</MenuItem>
<MenuItem Header="{StaticResource viewMenu}" IsEnabled="False" Name="ViewMenu">
<MenuItem Header="{StaticResource expandViewMenu}" Click="ExpandViewMenuItem_Click"/>
<MenuItem Header="{StaticResource collapseViewMenu}" Click="CollapseViewMenuItem_Click"/>
</MenuItem>
</Menu>
<Grid Name="DesignPanel" DockPanel.Dock="Top">
<Grid.ColumnDefinitions >
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions >
<local:DocumentTreeView x:Name="DocTreeView" Grid.Column="0"/>
<GridSplitter Grid.Column="0" HorizontalAlignment="Right" VerticalContentAlignment="Stretch" Width="3" ResizeDirection="Columns" />
<WebBrowser x:Name="objectPreviewBrowser" Grid.Column="1" Margin="6,6,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" OpacityMask="#FF9B8E8E"/>
</Grid>
</DockPanel>
</Window>
Control xaml:
<UserControl x:Class="xDesign.View.DocumentTreeView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TreeView ItemsSource="{Binding Document}" HorizontalAlignment="Stretch" BorderThickness="0" Background="#FFC2A2A2">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageFile}" Height="16" Width="16"/>
<TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add rule" Command="{Binding AddRuleCommand}"/>
<MenuItem Header="Delete" Command="{Binding DeleteCommand}"/>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
</UserControl>
Primary view model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using xDesign.Actions;
using xDesign.API.Model;
namespace xDesign.ViewModel
{
public class DocumentRulesViewModel : INotifyPropertyChanged
{
#region data members
DesignObjectViewModel mRootObject = null;
ObservableCollection<DesignObjectViewModel> mDocument = null;
DesignObjectViewModel mSelectedItem = null;
ICommand mDeleteCommand = null;
ICommand mAddRuleCommand = null;
#endregion
#region consructors
public DocumentRulesViewModel(DocumentObject _rootObject)
{
mRootObject = new DesignObjectViewModel(_rootObject, this);
mDocument = new ObservableCollection<DesignObjectViewModel>
(new DesignObjectViewModel[] { mRootObject });
mRootObject.IsExpanded = true; // We start with the top node expanded
mDeleteCommand = new DeleteNodeCommand(this);
mAddRuleCommand = new AddRuleCommandClass(this);
}
~DocumentRulesViewModel()
{
Close();
}
public void Close()
{
Document = null;
}
#endregion
#region properties
public ObservableCollection<DesignObjectViewModel> Document
{
get { return mDocument; }
set
{
if (value != mDocument)
{
mDocument = value;
this.OnPropertyChanged("Document");
}
}
}
public DesignObjectViewModel SelectedItem
{
get { return mSelectedItem; }
set
{
if (value != mSelectedItem)
{
mSelectedItem = value;
this.OnPropertyChanged("SelectedItem");
}
}
}
public IDesignObject CurrentDesignObject
{
get
{
if (mSelectedItem == null)
{
return null;
}
else
{
return mSelectedItem.DesignObject;
}
}
set
{
DesignObjectViewModel dovm = SearchForNode(value);
if (dovm != null)
{
if (dovm.Parent != null && !dovm.Parent.IsExpanded)
{
dovm.Parent.IsExpanded = true;
}
dovm.IsSelected = true;
}
}
}
#endregion
#region DeleteCommand
public ICommand DeleteCommand
{
get { return mDeleteCommand; }
}
public void DeleteItem ()
{
DesignObjectViewModel node = this.SelectedItem;
node.Remove();
}
private class DeleteNodeCommand : RoutedCommand
{
DocumentRulesViewModel mTree;
public DeleteNodeCommand(DocumentRulesViewModel _tree)
{
mTree = _tree;
}
public bool CanExecute(object parameter)
{
DesignObjectViewModel node = mTree.SelectedItem;
return (node != null);
}
public void Execute(object parameter)
{
mTree.DeleteItem();
}
// allows for constant updating if the event can execute or not.
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void RaiseCanExecuteChanged()
{
// we should not have to reevaluate every can execute.
// but since there are too many places in product code to verify
// we will settle for all or nothing.
CommandManager.InvalidateRequerySuggested();
}
}
#endregion
#region AddRuleCommand
public ICommand AddRuleCommand
{
get { return mAddRuleCommand; }
}
void AddRule()
{
int index = -1; // Where to insert; -1 = inside selected item
if (mSelectedItem.Parent != null)
{
index = mSelectedItem.Parent.Children.IndexOf(mSelectedItem) + 1; // Insert after selected item
}
// Call the application logic
IDesignObject dobj = DocStructureManagement.AddRule(mSelectedItem.DesignObject, ref index);
if (dobj != null)
{
DesignObjectViewModel newItemParent;
if (index == -1)
{
newItemParent = mSelectedItem;
index = 0;
}
else
{
newItemParent = mSelectedItem.Parent;
}
DesignObjectViewModel newItem = new DesignObjectViewModel(dobj, this, newItemParent);
newItemParent.InsertChild(newItem, index);
}
}
private class AddRuleCommandClass : RoutedCommand
{
DocumentRulesViewModel mTree;
public AddRuleCommandClass(DocumentRulesViewModel _tree)
{
mTree = _tree;
}
public bool CanExecute(object parameter)
{
DesignObjectViewModel node = mTree.SelectedItem;
return (node != null && node.DesignObject.CanContainOrPrecede(eDesignNodeType.ContentRule));
}
public void Execute(object parameter)
{
mTree.AddRule();
}
// allows for constant updating if the event can execute or not.
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void RaiseCanExecuteChanged()
{
// we should not have to reevaluate every can execute.
// but since there are too many places in product code to verify
// we will settle for all or nothing.
CommandManager.InvalidateRequerySuggested();
}
}
#endregion
#region Search
private DesignObjectViewModel SearchForNode(IDesignObject _dobj)
{
return SearchNodeForNode(mRootObject, _dobj);
}
private DesignObjectViewModel SearchNodeForNode(DesignObjectViewModel _node, IDesignObject _dobj)
{
if (_node.DesignObject == _dobj)
{
return _node;
}
foreach (DesignObjectViewModel child in _node.Children)
{
DesignObjectViewModel childNode = SearchNodeForNode(child, _dobj);
if (childNode != null)
{
return childNode;
}
}
return null;
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
}
TreeViewItem view model:
using System;
using System.Collections.ObjectModel;
using System.Linq;
using xDesign.API.Model;
using xDesign.Actions;
using System.ComponentModel;
using System.Windows.Input;
namespace xDesign.ViewModel
{
public class DesignObjectViewModel : INotifyPropertyChanged
{
#region data
DocumentRulesViewModel mDocViewModel = null;
IDesignObject mDesignObject = null;
DesignObjectViewModel mParent = null;
ObservableCollection<DesignObjectViewModel> mChildren = null;
bool mIsSelected = false;
bool mIsExpanded = false;
#endregion
#region constructors
public DesignObjectViewModel(IDesignObject _dobj, DocumentRulesViewModel _docViewModel)
: this(_dobj, _docViewModel, null)
{
}
public DesignObjectViewModel(IDesignObject _dobj, DocumentRulesViewModel _docViewModel, DesignObjectViewModel _parent)
{
mDesignObject = _dobj;
mDocViewModel = _docViewModel;
mParent = _parent;
if (_dobj.Type != eDesignNodeType.ContentGroup)
{
mChildren = new ObservableCollection<DesignObjectViewModel>(
(from child in mDesignObject.Children
select new DesignObjectViewModel(child, mDocViewModel, this))
.ToList<DesignObjectViewModel>());
}
else
{
ContentHolder ch = (ContentHolder)_dobj;
mChildren = new ObservableCollection<DesignObjectViewModel>(
(from child in ch.Contents
select new DesignObjectViewModel(child, mDocViewModel, this))
.ToList<DesignObjectViewModel>());
}
}
#endregion
#region properties
public ObservableCollection<DesignObjectViewModel> Children
{
get { return mChildren; }
}
public DesignObjectViewModel Parent
{
get { return mParent; }
}
public String Name
{
get { return mDesignObject.Name; }
}
public IDesignObject DesignObject
{
get { return mDesignObject; }
}
public Type DataType
{
get { return mDesignObject.GetType(); }
}
// Can we use DataType for this, and task the View with finding a corresponding image?
// And do we want to? We could end up with file names that include Model type names.
// Better? Worse? The same?
public String ImageFile
{
get { return GetImageUri(mDesignObject); }
}
public bool IsExpanded
{
get { return mIsExpanded; }
set
{
if (value != mIsExpanded)
{
mIsExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (mIsExpanded && mParent != null)
mParent.IsExpanded = true;
}
}
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (value != mIsSelected)
{
mIsSelected = value;
this.OnPropertyChanged("IsSelected");
if (mIsSelected)
{
mDocViewModel.SelectedItem = this;
}
CommandManager.InvalidateRequerySuggested();
}
}
}
#endregion
#region public methods
public void Remove()
{
DocStructureManagement.DeleteNode(mDesignObject); // Remove from application
if (mParent != null) // Remove from ViewModel
{
mParent.Children.Remove(this);
mParent.OnPropertyChanged("Children");
}
}
public void InsertChild(DesignObjectViewModel _newChild, int _insertIndex)
{
Children.Insert(_insertIndex, _newChild);
this.OnPropertyChanged("Children");
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
internal static string GetImageUri(IDesignObject _dobj)
{
string name = null;
switch (_dobj.Type)
{
case eDesignNodeType.Document:
name = "xDesign.ico";
break;
case eDesignNodeType.ContentRule:
name = "Content Rule.png";
break;
case eDesignNodeType.Section:
name = "section rule.png";
break;
case eDesignNodeType.Table:
name = "Table Rule.bmp";
break;
case eDesignNodeType.Read:
name = "Read Rule.bmp";
break;
case eDesignNodeType.Goto:
name = "Goto Rule.bmp";
break;
case eDesignNodeType.Label:
name = "Label Rule.bmp";
break;
case eDesignNodeType.ContentGroup:
name = "ContentGroup.png";
break;
case eDesignNodeType.Content:
name = "content.png";
break;
case eDesignNodeType.Criteria:
name = "Criteria.bmp";
break;
}
if (name == null)
{
throw new Exception("No image found for " + _dobj.Name);
}
return string.Format(#"C:\DEVPROJECTS\XDMVVM\XDMVVM\Images\{0}", name);
}
}
}
Finally, a code snippet from main window code behind, where I create and connect the main view model.
mDocumentRulesViewModel = new DocumentRulesViewModel(mCurrentDocument);
this.DocTreeView.DataContext = mDocumentRulesViewModel;
Again, I set breakpoints in the CanExecute method of each of the two command classes, and control never stops there.
I created a tiny sample project, similar to yours to solve this. I was able to have the context menu CanExecute behave correctly. If you emulate this style you will be able to solve your problem.
MainWindow.Xaml:
<Window x:Class="CommandChangesInTreeViewContextMenu.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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Command="{Binding AddCommand}">Add Command </Button>
<TreeView Grid.Row="1"
ItemsSource="{Binding MasterList}" HorizontalAlignment="Stretch" BorderThickness="0" Background="#FFC2A2A2">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add" Command="{Binding AddCommand}"/>
<!--<MenuItem Header="Delete" Command="{Binding DeleteCommand}"/>-->
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
<Button Grid.Row="2"
Command="{Binding ClearSelectionsCommand}">Clear Selections </Button>
</Grid>
</Window>
The DataContext ViewModel for the MainWindow.Xaml is TreeViewModel:
public class TreeViewModel : ObservableObject
{
private ObservableCollection<MasterItem> _masterList;
private ICommand _addCommand;
private ICommand _clearSelectionsCommand;
public ObservableCollection<MasterItem> MasterList
{
get { return _masterList; }
set
{
if (_masterList != value)
{
_masterList = value;
OnPropertyChanged("MasterList");
}
}
}
public ICommand AddCommand
{
get
{
if (_addCommand == null)
{
_addCommand = new RelayCommand<object>(Add, CanExecuteAddCommand);
}
return _addCommand;
}
}
public ICommand ClearSelectionsCommand
{
get
{
if (_clearSelectionsCommand == null)
{
_clearSelectionsCommand = new RelayCommand<object>(ClearSelections);
}
return _clearSelectionsCommand;
}
}
public TreeViewModel()
{
MasterList = new ObservableCollection<MasterItem>
{
new MasterItem("sup"), new MasterItem("hi"), new MasterItem("test"), new MasterItem("yo")
};
}
private void Add(object o)
{
// does nothing
}
private void ClearSelections(object o)
{
foreach (var mItem in MasterList)
{
mItem.IsSelected = false;
}
}
private bool CanExecuteAddCommand(object o)
{
return MasterList.Any(mItem => mItem.IsSelected == true);
}
}
The MasterItem class which are the objects in your MasterList:
MasterItem.cs:
public class MasterItem : ObservableObject
{
private string _name;
private bool _isSelected;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
CommandManager.InvalidateRequerySuggested();
}
}
}
public MasterItem(string name)
{
Name = name;
IsSelected = false;
}
}
**Note that When IsSelected is set it will InvalidateRequerySuggested() and work properly. =) **
Supporting Classes, RelayCommand, and ObservableObject
/// <summary>
/// RelayCommand
///
/// General purpose command implementation wrapper. This is an alternative
/// to multiple command classes, it is a single class that encapsulates different
/// business logic using delegates accepted as constructor arguments.
/// </summary>
/// <typeparam name="T"></typeparam>
public class RelayCommand<T> : ICommand
{
private static bool CanExecute(T paramz)
{
return true;
}
private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;
/// <summary>
/// Relay Command
///
/// Stores the Action to be executed in the instance field variable. Also Stores the
/// information about IF it canexecute in the instance field variable. These executing
/// commands can be sent from other methods in other classes. Hence the lambda expressions.
/// Tries to be as generic as possible T type as parameter.
/// </summary>
/// <param name="execute">Holds the method body about what it does when it executes</param>
/// <param name="canExecute">Holds the method body conditions about what needs to happen for the ACTION
/// Execute to execute. If it fails it cannot execute. </param>
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute ?? CanExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute(TranslateParameter(parameter));
}
// allows for constant updating if the event can execute or not.
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute(TranslateParameter(parameter));
}
private T TranslateParameter(object parameter)
{
T value = default(T);
if (parameter != null && typeof(T).IsEnum)
value = (T)Enum.Parse(typeof(T), (string)parameter);
else
value = (T)parameter;
return value;
}
public void RaiseCanExecuteChanged()
{
// we should not have to reevaluate every can execute.
// but since there are too many places in product code to verify
// we will settle for all or nothing.
CommandManager.InvalidateRequerySuggested();
}
}
/// <summary>
/// Class is based on two delegates; one for executing the command and another for returning the validity of the command.
/// The non-generic version is just a special case for the first, in case the command has no parameter.
/// </summary>
public class RelayCommand : RelayCommand<object>
{
public RelayCommand(Action execute, Func<bool> canExecute = null)
: base(obj => execute(),
(canExecute == null ?
null : new Func<object, bool>(obj => canExecute())))
{
}
}
ObservableObject:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T field, T value, Expression<Func<T>> expression)
{
// Allows a comparison for generics. Otherwise could just say x == y ?
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var lambda = (LambdaExpression)expression;
MemberExpression memberExpr;
if (lambda.Body is UnaryExpression)
{
var unaryExpr = (UnaryExpression)lambda.Body;
memberExpr = (MemberExpression)unaryExpr.Operand;
}
else
{
memberExpr = (MemberExpression)lambda.Body;
}
OnPropertyChanged(memberExpr.Member.Name);
return true;
}
return false;
}
}
Note that ObservableObject and RelayCommand are just helpers and not necessary to generating the solution. Mainly look at MainWindow.Xaml, TreeViewModel, and MasterItem. I hope this helps!
Picture of the disabled context menu when IsSelected is set to false for all the MasterItems in MasterList
Example of using a RelayCommand:
in your constructor
public PrimaryViewModel()
{
ICommand bob = new RelayCommand(CommandMethodThatDoesStuff,CanExecuteCommandMethod);
}
private void CommandMethodThatDoesStuff(object o)
{
// do your work
}
private bool CanExecuteCommandMethod(object o)
{
return IsSelected;
}
You are on the right track with the Commands and implementing CanExecute. I have had a similar problem. The commands will not always update their CanExecute immediately. If you want to have all of your commands update, a simple brute force solution is to add a call to CommandManager.InvalidateRequerySuggested().
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (value != mIsSelected)
{
mIsSelected = value;
this.OnPropertyChanged("IsSelected");
}
if (mIsSelected)
{
mDocViewModel.SelectedItem = this;
CommandManager.InvalidateRequerySuggested();
}
}
}
This will call invalidate arrange on all of your commands and force the logic on your commands, CanExecute boolean methods to refresh their state on the UI.
There are two ways to do this, that I can think of.
1 - put the context menu on your HierarchicalDataTemplate. This means the DataContext for the context menu will be the item from the tree. This can be nice because then, for example, the AddCommand is close to where things need to be added. You don't need to track the selected item in this case.
2 - bind the IsEnabled from the MenuItem to an "IsEnabled" property in your VM, and then update it when the selection changes. This is less nice, but can be tidy. If you are only doing single selection, and you already have a property for the selected item in your VM (which you probably should have already) then you can just bind it to something like {Binding SelectedItem.IsEnabled} or something.
ContextMenus aren't part of the visible tree, so binding directly to the view models won't work. The way around it is to use a BindingProxy as explained on this page:
<TreeView ItemsSource="{Binding Items}" >
<TreeView.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}"/>
</TreeView.Resources>
<TreeView.ContextMenu>
<ContextMenu DataContext="{Binding Path=Data, Source={StaticResource Proxy}}">
<MenuItem Header="Add" Command="{Binding AddCommand}"/>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
Alternatively if each tree item has it's own view model then you can add the command handlers to the items themselves and bind relative to the placement target:
<TreeView ItemsSource="{Binding Items}" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add" Command="{Binding AddCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>

Get selected TreeViewItem in MVVM

I want make TreeView with editable nodes. I googled this good, as I think, article:
http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing
But I have a problems. My TreeView formed dinamically, not statically as in the arcticle. Like that
<TreeView Name="_packageTreeView" Margin="5" ItemsSource="{Binding PackageExtendedList}">
<TreeView.InputBindings>
<KeyBinding Key="C" Command="{Binding *TestCommand*}" CommandParameter="{Binding}" />
</TreeView.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type MasterBuisnessLogic:RootDocPackage}" ItemsSource="{Binding Path=Childs}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition>
</ColumnDefinition>
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="/Resources/DocGroup.png"></Image>
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}"></Etb:EditableTextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
PackageExtendedList - List of DocPackageExtended.
So, first question - how can I get TreeViewItem instance in TestCommand? Not instance DocPackageExtended class! I want to get instance selected TreeViewItem like in the article.
And second question - After I get instance TreeViewItem, how can I get EditableTextBlock from the TreeView item's DataTemplate.
added answer
I already tried it. Cause in MVVM ViewModel cannot has any link to View object like TreeView, I make handler in code-behind, like that
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
// Already have TreeViewItem instance without of ItemContainerGenerator help
var tvi = e.OriginalSource as TreeViewItem;
if (tvi == null)
return;
var etb = VisualTreeLib.VisualTreeLib.GetVisualChild<EditableTextBlock>(tvi);
if (etb == null)
return;
// Do what I want
etb.IsEditable = true;
}
Unfortunately, this has no any affect :(
I also tried that approach, but also failed.
in DocPackageExtended type I define property
public bool IsEditable
{
get { return _isEditable; }
set
{
_isEditable = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsEditable"));
}
}
than change in XAML:
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}" *IsEditable="{Binding Path=IsEditable}"*/>
and in ViewModel
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
}
Doesn't work too :(
Any ideas?
This might help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItemFound = GetItem(MyTreeview, MyTreeview.SelectedItem);
ContentPresenter header = treeViewItemFound.Template.FindName("PART_Header", treeViewItemFound) as ContentPresenter;
if (header != null)
{
TextBox myTextBox = (TextBox)header.ContentTemplate.FindName("MyTextBox", header);
}
}
public TreeViewItem GetItem(ItemsControl container, object itemToSelect)
{
foreach (object item in container.Items)
{
if (item == itemToSelect)
{
return (TreeViewItem)container.ItemContainerGenerator.ContainerFromItem(item);
}
else
{
ItemsControl itemContainer = (ItemsControl)container.ItemContainerGenerator.ContainerFromItem(item);
if (itemContainer.Items.Count > 0)
{
TreeViewItem treeViewItemFound = GetItem(itemContainer, itemToSelect);
if (treeViewItemFound != null)
return treeViewItemFound;
}
}
}
return null;
}
First question: Since it seems that you can select multiple entries, you need to filter all selected entries in TestCommand's executed method:
IEnumerable<DocPackageExtended> selectedEntries = PackageExtendedList.Where(d => d.IsSelected);
If multiple selection is disabled, you could bind the TreeView's selected item to a property in your VM and access this property in TestCommand's method.
Second question: You get the dataitem's container through var container = YourTreeViewInstance.ItemContainerGenerator.ContainerFromItem(dataInstance);. Now you have to go through this container with the help of the VisualTreeHelper until it finds a control of type EditableTextBlock. But I wouldn't do this in a ViewModel, rather in a helper-class or with the help of attached properties.
EDIT: You're binding the IsEditable property of the instances in the Childs property of your DocPackageExtended class to your EditableTextBox, but in your TestCommandMethod you're manipulating the IsEditableproperty of a DocPackageExtended instance directly. You could do the following:
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
foreach (RootDocPackage rdp in dpe.Childs)
{
rdp.IsEditable = true;
}
}

WPF MVVM Command With Key Binding not firing

Pressing the delete key is not causing the command below to fire. One can assume DelegateCommand follows interface contracts since other instances of DelegateCommand referenced in the View are properly firing within the ViewModel.
Why?
View:
<ListBox Height="132"
Name="lbFiles"
ItemsSource="{Binding LbItems, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
VerticalAlignment="Center"
SelectedValue="{Binding Path=ListBoxSelection}">
<ListBox.InputBindings>
<KeyBinding Key="Delete"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}
,Path=DataContext.CommandInputFilesDeleteSelected}" />
</ListBox.InputBindings>
ViewModel:
private DelegateCommand commandInputFilesDeleteSelected;
public ICommand CommandInputFilesDeleteSelected {
get {
if (commandInputFilesDeleteSelected == null) {
commandInputFilesDeleteSelected = new DelegateCommand(InputFilesDeleteSelected);
}
return saveCommand;
}
}
private void InputFilesDeleteSelected() { //this never fires :(
LbItems.Remove(ListBoxSelection);
}
private DelegateCommand commandInputFilesDeleteSelected;
public ICommand CommandInputFilesDeleteSelected {
get {
if (commandInputFilesDeleteSelected == null) {
commandInputFilesDeleteSelected = new DelegateCommand(InputFilesDeleteSelected);
}
return saveCommand; //changed from saveCommand to commandInputFilesDeleteSelected whoops
}
}

How to fire event from ComboBox inside StackPanel in WPF and MVVM

I have ComboBox inside StackPanel. I am using MVVM and try to bind 'GotFocus' event Command to Command in ViewModel but when I Click on 'ComboBox', it don't work (It don't call Command in ViewModel) but if I move that 'ComboBox' out of 'StackPanel' it's working properly.
How can I fire event from 'CombBox' inside 'StackPanel' in MVVM?
<StackPanel x:Name="StackPanel" Grid.Column="2" Grid.Row="6">
<ComboBox x:Name="ComboBox" ItemsSource="{Binding Values}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<cmd:EventToCommand Command="{Binding Path=GotFocusCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
ViewModel's code is:
public ViewModelCommand GotFocusCommand { get; set; }
////Change your tag from EventToCommand to InvokeCommandAction
<StackPanel x:Name="StackPanel" Grid.Column="2" Grid.Row="6">
<ComboBox x:Name="ComboBox" ItemsSource="{Binding Values}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<cmd:InvokeCommandAction="{Binding Path=GotFocusCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
////Then, I use my commands this way in my view model:
private ICommand _GotFocusCommand;
public ICommand GotFocusCommand
{
get
{
if (_GotFocusCommand == null)
{
_GotFocusCommand =
new RelayCommand(
param => GotFocusCommand_Executed(),
GotFocusCommand_CanExecute
);
}
return _GotFocusCommand;
}
}
////RelayCommandClass.cs:
public class RelayCommand : ICommand
{
private Action _handler;
public RelayCommand(Action handler)
{
_handler = handler;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
////Finally, you can create an event in your view model:
private void GotFocusCommand_Executed()
{
//DoSomething here
}
private bool GotFocusCommand_CanExecute()
{
return true;
}

Resources