Binding to existing Command from other location causes Data Error: 2 - c#

I have app, where some controls can 'publish' certain keybinding which in turn appears in context help, for example a button publishes F5 a keybinding:
<Button Name="PublishKeybinding" Content="Publish my keybindings!">
<Button.InputBindings>
<KeyBinding Command="{Binding TestCommand}" Gesture="F5" />
</Button.InputBindings>
</Button>
And there is context help showing all published Commands as clickable list:
<ListView Name="PublicKeybindingsList" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Gesture}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding Command}" Gesture="LeftClick"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
both(published and context) instances of the command work and execute correctly, however there is a binding error logged when PublicKeybindingsList item's binding is being evaluated:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=TestCommand; DataItem='TestVM' (HashCode=47530006); target element is 'KeyBinding' (HashCode=53182860); target property is 'Command' (type 'ICommand')
How can I 'fix' the binding error?
For completeness codebehind used to reproduce the problem:
public class SomeCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
MessageBox.Show("Success!");
}
}
public class TestVM
{
public ICommand TestCommand { get; } = new SomeCommand();
}
public partial class MainWindow : Window
{
public ObservableCollection<KeyBinding> AllCommands { get; } = new ObservableCollection<KeyBinding>();
public MainWindow()
{
InitializeComponent();
PublishKeybinding.DataContext = new TestVM();
PublicKeybindingsList.DataContext = AllCommands;
foreach (var cmd in PublishKeybinding.InputBindings.OfType<KeyBinding>())
{
AllCommands.Add(cmd);
}
}
}
Note that the code sample is "Minimal, Reproducible Example" and displayed values and/or keybindings does not make much sense.

Related

TextBlock is not updating using ViewModel's property

I am working on a small wpf application, i am showing status using a textblock and for achieving this i have bind a property from a view model. I have mapped everything despite this UI is not showing any value. I am pasting code as below.
App.xaml.cs
public LogViewModel LogTextVm { get; set; }
ViewModelLocator.cs
App thisApp = (App)Application.Current;
public ViewModelLocator()
{
thisApp.LogTextVm = new LogViewModel();
}
LogViewModel.cs
public class LogViewModel : INotifyPropertyChanged
{
private string _logText { get; set; }
public string LogText
{
get => _logText;
set
{
if(_logText!=value)
{
_logText = value;
OnPropertyChnaged("LogText");
}
}
}
private void OnPropertyChnaged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainWindow.xaml
xmlns:vm="clr-namespace:MyApplication.ViewModels"
<Window.Resources>
<vm:ViewModelLocator x:Key="Locator"/>
</Window.Resources>
<TextBlock Grid.Column="0" Text="{Binding LogText, Mode=TwoWay}" DataContext="{Binding LogViewModel, Source={StaticResource Locator}}"
FontFamily="segoe UI" FontWeight="SemiBold" FontStyle="Italic" Foreground="White" />
MainWindow.xaml.cs
private App thisApp = (App)Application.Current;
public MainWindow()
{
InitializeComponent();
}
private async void CallGraphButton_Click(object sender, RoutedEventArgs e)
{
thisApp.LogTextVm.LogText = "Status : Loading data ...";
}
Can anyone help me, where i am doing mistake?
You have to assign MainWindow.DataContext with the same LogViewModel instance that is created by ViewModelLocator
DataContext="{Binding LogViewModel, Source={StaticResource Locator}}" can it create a new instance of the ViewModelLocator (and therefore thus new LogViewModel) ?
Please put the breakpoint at the thisApp.LogTextVm = new LogViewModel(); line and check is it executed twice?
UPD VS output shows binding error for your code
System.Windows.Data Error: 40 : BindingExpression path error: 'LogViewModel' property not found on 'object' ''ViewModelLocator' (HashCode=37320431)'. BindingExpression:Path=LogViewModel; DataItem='ViewModelLocator' (HashCode=37320431); target element is 'StackPanel' (Name=''); target property is 'DataContext' (type 'Object')
You are binding to the wrong names unfortunately. To fix the binding I had to change the code as below:
ViewModelLocator
public class ViewModelLocator
{
public ViewModelLocator()
{
App thisApp = (App)Application.Current;
LogTextVm = new LogViewModel();
thisApp.LogTextVm = LogTextVm;
}
public LogViewModel LogTextVm { get; set; }
}
MainWindow.xaml
<Grid>
<StackPanel DataContext="{Binding LogTextVm, Source={StaticResource Locator}}">
<TextBlock Text="label:" />
<TextBlock Text="{Binding LogText, Mode=TwoWay}" />
<Button Content="click" Click="Button_Click" />
</StackPanel>
</Grid>
DataContext="{Binding LogTextVm, Source={StaticResource Locator}}"> creates ViewModelLocator instance and binds StackPanel.DataContext to the ViewModelLocator.LogTextVm property.
The <TextBlock Text="{Binding LogText, Mode=TwoWay}" /> binds to the LogText for the current DataContext (which is LogTextVm property value)

WPF MVVM Listview : Is Leftdoubleclick need write event?

I wrote update function, I want to when I double-click a data in the listview, data will be shown in a textbox. I search and find many solutions,
I have a example:
`<ListView ItemsSource="{Binding listHocVien}"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}">
<ListView.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" />
</ListView.InputBindings>
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>`
But I when I run the app and click data, I just need one click, not double-click.
I have to find the solution on the internet and didn't see anyone said to write an event for LeftDoubleClick.
So, did we need to write the event to LeftDoubleClick? If yes, can anyone show me examples.
Thank for all your help.
You could use behaviours:
How to add System.Windows.Interactivity to project?.
This way you could create a double click command and bind it to your view model class. In the execute of your command you could set the property of the textbox to the desired text
After you've added in your project you should reference the namespace in the xaml code. If you reference it as i then your code to add the behaviour to the list view should be as follows:
In your xaml:
<TextBox Text ="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
<ListView>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding YourCommand}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
In your View Model:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
public class SampleViewModel : INotifyPropertyChanged {
private string _Text;
public event PropertyChangedEventHandler PropertyChanged;
public string Text {
get { return _Text; }
set {
if (_Text != value) {
_Text = value;
RaisePropertyChanged();
}
}
}
public ICommand YourCommand { get; set; }
public SampleViewModel() {
YourCommand = new RelayCommand<TType>(YourCommandExecute); // that TType is the type of your elements in the listview
}
// Here I will assume that your TType has a property named Description
private void YourCommandExecute(TType selectedElement) {
Text = selectedItem.Description;
}
public void RaisePropertyChanged([CallerMemberName] propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay Command Implementation
// Simple Implementation of Generic Relay Command:
public class RelayCommand<T> : ICommand
{
private Action<T> execute;
private Func<T,bool> canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<T> execute,Func<T,bool> canExecute=null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute((T)parameter);
}
public void Execute(object parameter)
{
execute((T)parameter);
}
}

MouseBinding in TexBox within ItemsControl does not work WPF C#

I have my MainWindow which has an ItemsControl for my EngineersUserControl (Engineers_UC) along with other controls. My Engineers_UC consists of a few TextBoxes for which I want to add MouseBinding with the aim of being able to left click on a TextBox and another method in my ViewModel to be executed. I have read that the issue might be that the elements of ItemsControl are not focusable but I haven't found a solution. Any ideas ?
MainWindow:
<Grid>
<StackPanel>
<UserControl:Ribbon_UC Loaded="Ribbon_UC_Loaded" Margin="0,0,0,70"/>
<UserControl:Calendar_UC/>
<ItemsControl ItemsSource="{Binding Engineer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl:Engineers_UC />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Engineers_UC:
<TextBox Name="EngineerName" IsReadOnly="True" Style="{StaticResource StyleTrigger} Text ="{Binding FULLNAME}">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EngineerCommand}" CommandParameter="{Binding ElementName=EngineerName}" MouseAction="{Binding EngineerCommand.MouseGesture}"/>
</TextBox.InputBindings>
</TextBox>
EngineerCommand:
void RelayCommands()
{
EngineerCommand = new SimpleDelegateCommand(x => EngineerFunction(x))
{
MouseGesture = MouseAction.LeftClick
};
}
void EngineerFunction (object _engineername)
{
EngineerNameClicked = (_engineername as TextBox).Text;
}
public class SimpleDelegateCommand : ICommand
{
public Key GestureKey { get; set; }
public ModifierKeys GestureModifier { get; set; }
public MouseAction MouseGesture { get; set; }
Action<object> _executeDelegate;
public SimpleDelegateCommand(Action<object> executeDelegate)
{
_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
If the EngineerCommand command is defined in the same view model class as the collection to which you bind the ItemsSource property of the ItemsControl to (Engineer), you should use a RelativeSource for your binding(s) in the ItemTemplate:
<MouseBinding Command="{Binding DataContext.EngineerCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" ... />

Binding Exceed IntegerUpDown value to a CommandParameter

I am using Exceed IntegerUpDown control in my .xaml file. I want to bind IntegerUpDown value as a CommandParameter of a button.
I do not have any code behind files and this is a custom control xaml file. So i want to achieve this by only using xaml systax.
<DockPanel>
<xctk:IntegerUpDown x:Name="ExtraExpressionValue" Increment="1" FormatString="N0" AllowSpin="True" Width="70" Watermark="Numeric" AllowTextInput="False" Minimum="0" Value="999"/>
<Button Style="{StaticResource ContextMenuButton}" Margin="5,0,0,0" Content="Add" Command="{Binding SetExtaExpressionValueCommand}" CommandParameter="{Binding ElementName=ExtraExpressionValue,Path=Value}"/>
</DockPanel>
Above is my xaml code. this return 0 to command method.
My command class is as follows,
public class DesignItemCommands
{
private ICommand setExtaExpressionValueCommand;
public ICommand SetExtaExpressionValueCommand => setExtaExpressionValueCommand ?? (setExtaExpressionValueCommand = new CommandHandler(SetExtaExpressionValue, canExecute));
private bool canExecute;
public DesignItemCommands()
{
canExecute = true;
}
private void SetExtaExpressionValue(object parameter)
{
//I need parameter here..
}
}
Couldn't find a way on the requirement. Just posting here to help someone later on this issue.
I used a ViewModel Variable to bind IntegerUpDown control value.
<DockPanel>
<xctk:IntegerUpDown Increment="1" Value="{Binding ExtraExpressionValue}"/>
<Button Content="Add" Command="{Binding SetExtaExpressionValueCommand}"/>
</DockPanel>
My ViewModel is as follows,
public class DesignItemCommands
{
private ICommand setExtaExpressionValueCommand;
public ICommand SetExtaExpressionValueCommand => setExtaExpressionValueCommand ?? (setExtaExpressionValueCommand = new CommandHandler(SetExtaExpressionValue, canExecute));
private bool canExecute;
public int ExtraExpressionValue { get; set; }
public DesignItemCommands()
{
canExecute = true;
ExtraExpressionValue = 1;
}
private void SetExtaExpressionValue(object parameter)
{
//I can use value here using variable ExtraExpressionValue
}
}
Hope this helps someone later.

How to bind a command to StackPanel or Grid tap event?

I have seen some answers regarding WP8 or others, however it seems that there is no triggers in WP8.1 (Or I am missing something?)
I have a datatemplate bound from the code (it is a hub datatemplate, and I have a mix of static and dynamic hubsections, therefore this datatemplate needs to be set from the code).
This datatemplate is defined in a separate xaml file, it includes a listbox (or listview) with another datatemplate defined for the items.
I need to bind a command on the item's tap or listbox selectionchanged (or something equivalent). However, the tap event defined in the template is not called, therefore I thought of binding a command on an UI element, but these seems not to support Commands neither interactivity triggers.
Any clue on how to handle that? :)
On the example below I don't get the event Item_Tapped nor ListBox_SelectionChanged, I would anyway prefer to bind one of these to a command in the viewmodel.
<DataTemplate x:Key="HubSectionTemplate">
<Grid>
<ListBox ItemsSource="{Binding MyNodes}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64" Tapped="Item_Tapped" >
<TextBlock Text="{Binding MyText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
This is how it is used from code:
HubSection hs = new HubSection()
{
ContentTemplate = Application.Current.Resources[HUBSECTION_TEMPLATE] as DataTemplate,
DataContext = model,
Tag = model.UniqueId,
};
Hub.Sections.Insert(firstSectIdx + 1, hs);
public class Model
{
public Guid UniqueId {get;set;}
public List<ItemModel> MyNodes {get;set;}
}
public class ItemModel
{
public string MyText {get;set;}
}
PS: The ItemModel is defined in another assembly and therefore should not be edited (the command should be in the Model class if possible)
--- EDIT ---
In order to simplify the problem, I use the following models:
public class Model
{
public Guid UniqueId {get;set;}
public List<ItemModel> MyNodes {get;set;}
public ICommand MyCommand {get;set;}
}
public class ItemModel
{
Model _Model;
public ItemModel(Model m) {_Model = m; }
public string MyText {get;set;}
public ICommand MyCommand { get { return _Model.MyCommand; }}
}
And my (temporary) solution is to use a button in the itemtemplate:
<ListView.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch" Command="{Binding TapCommand}" Height="64">
<TextBlock Text="{Binding MyText}" />
</Button>
</DataTemplate>
</ListView.ItemTemplate>
You can use Behaviors SDK.
In Visual Studio go to 'Tools -> Extension and updates' and install Behaviors SDK (XAML). Then reference it in your project using Add reference dialog.
After that add following namespaces to your page:
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
Now you can register events like tap on your stack panel using following syntax:
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64">
<TextBlock Text="{Binding MyText}" />
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding YourCommand}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
</DataTemplate>
However this code only works if your Command is defined in your ItemModel class. If you want to bind to the parent element Command, you can try something like this (not tested):
{Binding ElementName=LayoutRoot, Path=DataContext.ParentCommand}
But I would preferer having command on your ItemModel class
Edit: Solution without Behaviors SDK:
If you are using ListView (or something inherited from ListViewBase) you can use ItemClick event. To make it more reusable and Mvvm friendly you can implement your DependencyProperty like this:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
{
control.ItemClick += OnItemClick;
}
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
{
command.Execute(e.ClickedItem);
}
}
}
Then your ListView will look like this:
<ListView
IsItemClickEnabled="True"
helpers:ItemClickCommand.Command="{Binding YourCommand}"
ItemsSource="{Binding MyNodes}"
ItemTemplate="{StaticResource YourDataTemplate}" />
In this case your child item is passed to your command as a parameter, so it should also solve your problem with your Command defined in parent model.

Categories

Resources