Binding Command on ContextMenuItem - c#

I'm having trouble with binding a ContextMenuItem's command to my parent object. I've followed the following examples:
http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda
RelativeSource binding from a ToolTip or ContextMenu
WPF: Binding a ContextMenu to an MVVM Command
And I've got a lot closer, but I still get the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'SearchString' property
not found on 'object' ''MainWindow' (Name='root')'.
BindingExpression:Path=Parent.PlacementTarget.Tag.SearchString; DataItem='MenuItem'
(Name=''); target element is 'MenuItem' (Name=''); target property is 'Command' (type
'ICommand')
The main window class has SearchString defined as:
public partial class MainWindow : Window
{
...
private void SearchString(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
but, obviously, the exception is never getting thrown.
I have the menu defined in a DataTemplate as follows:
<Window x:Class="CodeNaviWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Title="View" Height="865" Width="991"
x:Name="root"
>
<Window.Resources>
<DataTemplate x:Key="fileContentView">
<StackPanel>
<Border BorderThickness="3" BorderBrush="BurlyWood">
<avalonEdit:TextEditor
Width="400"
Height="400"
Document="{Binding Path=Document}"
IsReadOnly="True"
Tag="{Binding ElementName=root}">
<avalonEdit:TextEditor.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Search..." Command="{Binding Path=Parent.PlacementTarget.Tag.SearchString, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</avalonEdit:TextEditor.ContextMenu>
</avalonEdit:TextEditor>
</Border>
</StackPanel>
</DataTemplate>
</Window.Resources>
...
</Window>
Can anyone see where I'm going wrong? If I change the method to be a string property then I don't get any errors, so I'm guessing that I'm somehow telling the XAML to expect a property, rather than a method.

Answering my own question here but hopefully this will prove useful for others. The solution that worked for me was to follow the answers given here: How do I add a custom routed command in WPF?
My MainWindow now looks like the following:
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
...
}
...
private void SearchString(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
public static class Commands
{
public static readonly RoutedUICommand SearchString = new RoutedUICommand("Search String", "SearchString", typeof(MainWindow));
}
}
And the XAML has the following additions:
<Window x:Class="CodeNaviWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CodeNaviWPF"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Title="MyApp" Height="865" Width="991"
x:Name="root"
>
<Window.CommandBindings>
<CommandBinding Command="local:Commands.SearchString" Executed="SearchString" />
</Window.CommandBindings>
<Window.Resources>
<DataTemplate x:Key="fileContentView">
<StackPanel>
<Border BorderThickness="3" BorderBrush="BurlyWood">
<avalonEdit:TextEditor
Width="400"
Height="400"
Document="{Binding Path=Document}"
IsReadOnly="True"
Tag="{Binding ElementName=root}">
<avalonEdit:TextEditor.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Search..." Command="local:Commands.SearchString" />
</ContextMenu>
</avalonEdit:TextEditor.ContextMenu>
</avalonEdit:TextEditor>
</Border>
</StackPanel>
</DataTemplate>
</Window.Resources>
...
</Window>

Related

Nested UserControl event doesn't work with EventTrigger/InvokeCommandAction in MVVM/WPF scenario

I'm working with WPF with Prism (MVVM), and trying to build an Inspector for a few classes. One of those classes is Vector3:
<Grid x:Name="Vector3Root" Background="White">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="X" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding X}" ValueChanged="Vector3ValueChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Y" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Y}" ValueChanged="Vector3ValueChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Z" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Z}" ValueChanged="Vector3ValueChanged"/>
</StackPanel>
</StackPanel>
</Grid>
And its code-behind
namespace SimROV.WPF.Views{
public partial class Vector3View : UserControl
{
public Vector3View()
{
InitializeComponent();
}
public static readonly RoutedEvent SettingConfirmedEvent =
EventManager.RegisterRoutedEvent("SettingConfirmed", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(Vector3View));
public event RoutedEventHandler SettingConfirmed
{
add { AddHandler(SettingConfirmedEvent, value); }
remove { RemoveHandler(SettingConfirmedEvent, value); }
}
public void Vector3ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
RaiseEvent(new RoutedEventArgs(SettingConfirmedEvent));
}
}}
The problem that I'm struggling with is that I can't catch neither of the fired events (ValueChanged or SettingConfirmed) on another UserControl's ViewModel that is using Vector3View:
<UserControl
x:Class="SimROV.WPF.Views.TransformView"
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:views="clr-namespace:SimROV.WPF.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:prism="http://prismlibrary.com/"
mc:Ignorable="d" >
<Grid x:Name="TransformRoot" Background="White">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Position" Margin="5"/>
<!--<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>-->
<views:Vector3View x:Name="PositionVector3">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SettingConfirmed">
<prism:InvokeCommandAction Command="{Binding PositionValueChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</views:Vector3View>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Rotation" Margin="5"/>
<!--<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>-->
<views:Vector3View x:Name="RotationVector3" SettingConfirmed="RotationValueChangedEvent"/>
</StackPanel>
</StackPanel>
</Grid>
At this point I CAN catch SettingConfirmed with RotationValueChangedEvent on code-behind, but since I'm following MVVM pattern, that doesn't work for me, which is why I'm using EventTrigger and InvokeCommandAction to catch those events on TransformViewModel, but those never get fired.
Here it's the TransformViewModel:
namespace SimROV.WPF.ViewModels{
public class TransformViewModel : BindableBase
{
private ICommand _positionCommand;
public ICommand PositionValueChangedCommand => this._positionCommand ?? (this._positionCommand = new DelegateCommand(PositionChanged));
private void PositionChanged()
{
}
public TransformViewModel()
{
}
}}
PositionChanged just never gets fired and I can't understand why at all.
I don't know if this is relevant, but Transform is an element of an ObservableCollection<IComponent> at another ViewModel, which is being presented by a ListView with a ItemContainerStyle, that has a ContentPresenter with a ContentTemplateSelector inside.
Can someone point me out on why this is happening and how to fix it?
Thank you.
Your EventTrigger and InvokeCommandAction should work just fine provided that the DataContext of the Vector3View actually is a TransformViewModel so the binding to the PositionValueChangedCommand property succeeds.

Binding menu item to command

I am trying to bind a command to a view model but it is not triggering
xmlns:ViewModel="clr-namespace:Lister.WPF.ViewModels"
DataContext="{Binding ViewModel:TweetViewModel}" Icon="pack://siteoforigin:,,,/Resources/internet_down_16_hot.png" Loaded="Window_Loaded">
<Grid>
<!--Test-->
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem BorderThickness="5" Header="Status">
<MenuItem BorderThickness="5" Header="Timeline" Command="{Binding RefreshTweetsCommand}"/>
Here is the code from the VM
private RelayCommand _refreshTweetsCommand;
public RelayCommand RefreshTweetsCommand
{
get
{
return _refreshTweetsCommand ??
(_refreshTweetsCommand =
new RelayCommand(RefreshTweetList, LoadTweets));
}
}
DataContext="{Binding ViewModel:TweetViewModel}" This is no way of binding DataContext.
Set your DataContext as follows:
<Window>
<Window.DataContext>
<ViewModel:TweetViewModel/>
</Window.DataContext>
</Window>

MenuItem.IsEnabled is bound to whether there is something selected in Listbox or not, but it doesn't update

I have a MenuItem, which should be enabled only if there is something selected in ListBox. I wrote a converter from object to bool, which returns false, if that object == null, and true otherwise. I bound it to ListBox.SelectedItem with my converter, but it doesn't work. Placing a breakpoint in the converter shows, that it never runs. The MenuItem appears always enabled no matter what.
Here is xaml code of the ListBox and of MenuItem
<ListBox Name="TestsListBox"
HorizontalAlignment="Left" Height="93" VerticalAlignment="Top" Width="128"
Margin="0,5,-1.723,0" ItemsSource="{Binding Path=Tests, Mode=OneWay}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="removeTest"
IsEnabled="{Binding ElementName=TestsListBox, Mode=OneWay,
Path=SelectedItem, Converter={StaticResource ObjectToBool}}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
Here I show how converter is declared as window's resource
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:ClassesLib="clr-namespace:Laba1;assembly=ClassesLib"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="450" Width="525">
<Window.Resources>
<local:ObjectToBoolConverter x:Key="ObjectToBool"/>
</Window.Resources>
And here is the converter class
namespace WpfApplication
{
class ObjectToBoolConverter: IValueConverter
{
// Converts value to boolean. If value is null, returns false.
// Otherwise returns true
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (null == value)
{
return false;
}
return true;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("This is oneway converter, so ConvertBack is not supported");
}
}
}
RelativeSource and Popup
From here you should be able to find out that the reason ElementName binding doesn't work is because the ContextMenu isn't part of the visual tree as other controls are, and therefore can not take part in such binding scenarios. AFAIK, PopUps have a PlacementTarget property that you can bind to and figure out how to use.
This was how I solved it:
VIEW:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ObjectToBoolConverter x:Key="ObjectToBool"/>
<ContextMenu x:Key="contextMenu" DataContext="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Remove" Click="removeTest"
IsEnabled="{Binding Path=., Converter={StaticResource ObjectToBool}}"/>
</ContextMenu>
</Window.Resources>
<Grid>
<ListBox Name="TestsListBox"
HorizontalAlignment="Left" Height="93" VerticalAlignment="Top" Width="128"
Margin="0,5,-1.723,0" ContextMenu="{StaticResource ResourceKey=contextMenu}">
</ListBox>
</Grid>
</Window>
CODE BEHIND
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
List<string> teste = new List<string>();
teste.Add("test1");
teste.Add("test3");
teste.Add("test2");
TestsListBox.ItemsSource = teste;
}
private void removeTest(object sender, RoutedEventArgs e)
{
}
}
}
The converter stayed the same.
Regards,
Looks like ElementName property of Binding doesn't do what I thought it does. Also it sucks very much that XAML just ignores and does nothing about incorrect parameters of Binding: it should raise an error instead.
I added DataContext to my ContextMenu, removed ElementName, and it is working now. This is how I changed the code:
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="Add" Click="addTest"/>
<MenuItem Header="Remove" Click="removeTest"
IsEnabled="{Binding Mode=OneWay,
Path=SelectedItem, Converter={StaticResource ObjectToBool}}"/>
</ContextMenu>
Dtex's comment about a duplicate helped me with this, even though I thought I could use ElementName instead of DataContext.

Combobox binding doesn't work in ItemsControl using MVVM

I have a ComboBox in ItemsControl .I use WPF and MVVM, I have problem to figure out the binding to ComboBox, would someone give me a hand for this. XAML and VM as following:
<Window x:Class="OutageManagement.Views.MarketAssignmentsView"
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="Market Selection"
WindowStartupLocation="CenterOwner"
Width="700" Height="850"
DataContext="{Binding MarketAssignmentsVM, Source={StaticResource Locator}}" >
<Grid>
<ItemsControl ItemsSource="{Binding USMarket}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{Binding MarketName}" Height="28"
HorizontalAlignment="Left" Name="lblUSMarketName"
VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Height="23" HorizontalAlignment="Left"
Name="cbUSUsers" VerticalAlignment="Center" MinWidth="140"
ItemsSource="{Binding RelativeSource={RelativeSource
AncestorType=Window}, Path=UserList}"
DisplayMemberPath="UserName"
SelectedValue="{Binding SelectedUserID}"
SelectedValuePath="UserID"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ViewModel :
public class MarketAssignmentsViewModel : ViewModelBase
{
#region Data
ObservableCollection<NOCUserViewModel> _userList;
ObservableCollection<MarketAssignmentViewModel> _usMarket;
ObservableCollection<MarketAssignmentViewModel> _caMarket;
#endregion
#region Constructor
public MarketAssignmentsViewModel()
{
GetUserList();
GetMarketAssignments();
}
#endregion
#region Properties
public ObservableCollection<NOCUserViewModel> UserList
{
get { return _userList; }
}
public ObservableCollection<MarketAssignmentViewModel> USMarket
{
get { return _usMarket; }
}
public ObservableCollection<MarketAssignmentViewModel> CAMarket
{
get { return _caMarket; }
}
#endregion
.
.
.
}
The problem is that you're trying to access the UserList as a property of the Window, instead of a property of the Window's DataContext...
Modify the ItemsSource like this:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor
AncestorType=Window}, Path=DataContext.UserList}" ... />
I recommend always looking in the Output window when you have binding problems, you probably would have seen something like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'UserList' property not found on 'object' ''MarketAssignmentsView' (Name='')'.

Why does my Custom UserControl's dependency property not work with dynamically binding?

My Custom UserControl's dependency property will bind correctly if the value is statically defined in the XAML calling it, like this:
TextBoxText="myName"
but not if the value is bound dynamically itself:
TextBoxText="{Binding ItemTypeIdCode}"
There is my full Code.
Custom UserControl XAML:
<UserControl
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"
x:Class="TestUserControl.UserControl1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
x:Name="UserControl" Height="22" Width="282">
<Grid x:Name="LayoutRoot">
<TextBlock TextWrapping="Wrap" Text="{Binding MyName, ElementName=LayoutRoot}"/>
</Grid>
Custom UserControl Code:
public static readonly DependencyProperty TextBoxTextProperty =DependencyProperty.Register("TextBoxText", typeof(string), typeof(UserControl1));
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
In my Main Window XAML :
<Grid x:Name="LayoutRoot">
<Button Content="Button" Height="78" Margin="0,0,93,112" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="94" Click="MyButtonClick"/>
<ListBox x:Name="MyListBox" HorizontalAlignment="Left" Margin="8,8,0,112" Width="192">
<ListBox.ItemTemplate>
<DataTemplate>
<local:UserControl1 HorizontalAlignment="Stretch" Margin="286,37,56,0" VerticalAlignment="Top" d:LayoutOverrides="Height" TextBoxText="{Binding MyName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
In my Main Window Code :
private void MyButtonClick(object sender, System.Windows.RoutedEventArgs e)
{
List<string> MyName = new List<string>();
MyName.Add("Name 1");
MyName.Add("Name 2");
MyName.Add("Name 3");
MyListBox.ItemsSource = MyName;
}
This Code Successfully add my Custom UserControl as ListBoxItem in ListBox But Problem it is not display any text which i Binding.
I don`t understand where i am doing wrong.
You set the DataContext of the UserControl to itself, all bindings will then try to find the path on the UserControl, that is why you should not set the DataContext on UserControls.
You should see a binding error in the Output window of Visual Studio saying something like:
System.Windows.Data Error: 40 : BindingExpression path error: 'ItemTypeIdCode' property not found on 'object' ''UserControl1' (Name='UserControl')'. ...

Categories