I want to bind a certain command to a menuItem. The said menu item is part of a ContextMenu that is defined inside an ItemTemplate.
Right now, what I have compiles and runs, but the command is never called. In the past, I had used a similar pattern to hook commands to buttons defined in an ItemTemplate with success.
Anyone has any idea how I could accomplish this?
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf_treeView" x:Name="window" x:Class="Wpf_treeView.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView HorizontalAlignment="Left" Height="299" Margin="10,10,0,0" VerticalAlignment="Top" Width="228" ItemsSource="{Binding DataInfosView}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock DockPanel.Dock="Left" Text="{Binding InfoValue}" TextAlignment="Left" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding InfoValue}" IsEnabled="False"/>
<MenuItem Header="Add child" Command="{Binding AddChildCmd, ElementName=window}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
C#:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace Wpf_treeView
{
public partial class MainWindow : Window
{
private static readonly Random rnd = new Random();
private List<InfoData> m_InfoData = new List<InfoData>();
public ListCollectionView DataInfosView { get; private set; }
public static readonly DependencyProperty AddChildProperty =
DependencyProperty.Register("AddChildCmd",
typeof(ICommand),
typeof(MainWindow));
public ICommand AddChildCmd
{
get { return (ICommand) GetValue(AddChildProperty); }
set { SetValue(AddChildProperty, value); }
}
public MainWindow()
{
AddChildCmd = new RoutedCommand();
CommandManager.RegisterClassCommandBinding(
GetType(),
new CommandBinding(AddChildCmd, AddChild));
m_InfoData.Add(new InfoData(4));
m_InfoData.Add(new InfoData(1));
m_InfoData.Add(new InfoData(5));
m_InfoData[1].Children.Add(new InfoData(3));
m_InfoData[1].Children[0].Children.Add(new InfoData(7));
DataInfosView = new ListCollectionView(m_InfoData);
DataContext = this;
InitializeComponent();
}
private void AddChild(object sender, RoutedEventArgs e)
{
ExecutedRoutedEventArgs args = (ExecutedRoutedEventArgs)e;
InfoData info = (InfoData)args.Parameter;
info.Children.Add(new InfoData(rnd.Next(0, 11)));
}
}
class InfoData : INotifyPropertyChanged
{
private int infoValue;
public int InfoValue
{
get { return infoValue; }
set
{
if (value != infoValue)
{
infoValue = value;
OnPropertyChanged();
}
}
}
public List<InfoData> Children { get; private set; }
public InfoData(int infoValue)
{
InfoValue = infoValue;
Children = new List<InfoData>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(
[CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Alright this should work:
<TextBlock DockPanel.Dock="Left"
Text="{Binding InfoValue}"
TextAlignment="Left"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding InfoValue}"
IsEnabled="False" />
<MenuItem Header="Add child"
Command="{Binding Path=Parent.PlacementTarget.Tag.AddChildCmd, RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
The ContextMenu doesn't exist in the regular Visual Tree, so you aren't able to walk up the tree to get to the main data context. By using the Tag you are able to "pass in" the Main Window's data context to the context menu. For some more information on binding with context menu's see this answer as well as this one as they provide some good explanations as to what is going on
Related
I'm trying to understand binding system in WPF. In my example i need to get access to MainWindow viewmodel from Page in XAML.
I have one solution to implement this. But i want to know more different ways
MainWindow.xaml
<Window x:Class="FunAnkiWPF.MainWindow"
...omitted for brevity
Height="450" Width="800"
DataContext="{Binding ViewModel, RelativeSource={RelativeSource
Self}}">
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
ViewModel = new MainWindowViewModel(this);
InitializeComponent();
}
}
StartPage.xaml (usual page)
StartPage.xaml.cs (One solution that works)
public partial class StartPage : Page
{
public StartPage()
{
InitializeComponent();
DataContext = App.Current.MainWindow.DataContext;
}
}
How to get a direct access to the MainWindow ViewModel property (in XAML and in codebehind)?
How to get access to another datacontext in XAML (like in my StartPage codebehind)?
The short answer to binding from a child control to a property in the parent window's datacontext is relativesource eg:
<TextBlock Text="{Binding Path=DataContext.MainWinVMString,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Here's an example to give you the flavour of what I am suggesting.
The MainWindow markup is a bit quick n dirty.
I would put datatemplates in a resource dictionary merged by app.xaml.
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserViewModel}">
<local:UserUC/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl ItemsSource="{Binding NavigationViewModelTypes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding VMType}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>
Viewmodel for that:
public class MainWindowViewModel : INotifyPropertyChanged
{
public string MainWinVMString { get; set; } = "Hello from MainWindoViewModel";
public ObservableCollection<TypeAndDisplay> NavigationViewModelTypes { get; set; } = new ObservableCollection<TypeAndDisplay>
(
new List<TypeAndDisplay>
{
new TypeAndDisplay{ Name="Log In", VMType= typeof(LoginViewModel) },
new TypeAndDisplay{ Name="User", VMType= typeof(UserViewModel) }
}
);
private object currentViewModel;
public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Relaycommand is from nuget package mvvmlightlibs.
UserUC:
<Grid Background="pink">
<TextBlock Text="This is the User module Control"
VerticalAlignment="Top"
/>
<TextBlock Text="{Binding Path=DataContext.MainWinVMString, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
VerticalAlignment="Bottom"
/>
</Grid>
Full working sample:
https://1drv.ms/u/s!AmPvL3r385QhgqIZUul-ppiIHZ9uyA
I have two list boxes that both hold collections. The current setup is so that when a item is selected in the left listbox, you can click a button to add that selected state the right listbox. There is an add and remove button for the listboxes that are tied to a custom command with the listbox selected item being the command parameter.
I would like to add a double click functionality to each box so that items can be double clicked to add and remove. I should be able to use my current command execute methods to do this, but have not found a solution to implementing this into a listbox, or listboxitem. I would like to follow MVVM as much as possible, but I've already side stepped that a bit with the current execute methods as i'll show below, but any help would be appreciated. I have not had luck finding anything regarding my specific issue.
<ListBox x:Name="List" ItemContainerStyle="{StaticResource ListBoxItem}" DataContext="{StaticResource VM}"
ItemsSource="{Binding Names, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DisplayMemberPath="Name"
Style="{StaticResource ResourceKey=ListBox}"/>
<Button Content=">>" Margin="5" Style="{StaticResource ResourceKey=MultiButton}"
CommandParameter="{Binding ElementName=List}"
Command="{Binding Path=AddSelectedItemCommand}"/>
public void AddSelectedItem(object obj)
{
ListBox ListBox = obj as ListBox;
List<Type> List = new List<Type>();
if (Name == null)
Name = new ObservableCollection<Type>();
if (Name != null)
{
foreach (Type item in ListBox.SelectedItems.Cast<object>().ToList())
{
List.Add(item);
Names.Remove(item);
}
foreach (Type listItem in List)
{
var state = Name.FirstOrDefault(aa => aa.Name == listItem.Name);
if (state == null)
{
Name.Add(listItem);
}
}
}
OnPropertyChanged("Name");
OnPropertyChanged("Names");
}
Firstly I would like to let you know that your View Model should know nothing at all about the View itself, so it should know nothing about ListBoxes.
Objects should only know about they things which they depend upon, and not those which depend upon it. Therefore the ViewModel should only know about the collections of data which it is making available to any client.
In your example, what happens when the control is changed from a ListBox -you will have to change your Command.
So, first things first, you will need to change your view model implementation, what you have currently is not MVVM.
Here is an entire listing which should help you along your way:
<Window x:Class="WpfExample.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:WpfExample"
mc:Ignorable="d"
Title="MainWindow" Height="140" Width="410">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Path=Names, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedName}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyOtherDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the code behind
namespace WpfExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Then there is the ViewModel, which you should notice only modifies the collections which are exposed for the View to consume
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Prism.Commands;
namespace WpfExample
{
public class MyViewModel : INotifyPropertyChanged
{
private string _selectedName;
private string _selectedNameTwo;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> Names { get; }
= new ObservableCollection<string>(new List<string>
{
"Name1",
"Name2",
"Name3",
"Name4",
"Name5"
});
public ObservableCollection<string> NamesTwo { get; } = new ObservableCollection<string>(new List<string>());
public string SelectedName
{
get { return _selectedName; }
set { _selectedName = value; OnPropertyChanged(); }
}
public string SelectedNameTwo
{
get { return _selectedNameTwo; }
set { _selectedNameTwo = value; OnPropertyChanged(); }
}
public ICommand MyOtherDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
NamesTwo.Remove(name);
Names.Add(name);
SelectedNameTwo = "";
});
}
}
public ICommand MyDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
Names.Remove(name);
NamesTwo.Add(name);
SelectedName = "";
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I have used the Prism.Core package for the DelegateCommand object. This is not essential, I just did it for ease
You don't even need the SelectedName and SelectedNameTwo properties if they will not be used whilst processing the ViewModel. I included them for completeness.
.
Edited:
I did not originally notice that this is for a UWP project. I believe the following will work -though it is untested here since I am not set up for UWP on my machine at the moment. I'm not certain of the DoubleClick EventName.
<Page xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" >
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleClick">
<core:InvokeCommandAction Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Page, Mode=FindAncestor} }"
CommandParameter="{Binding .}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Credit to Bill, as the UWP edit pointed me toward a satisfactory solution.
Firstly, I added a NuGet reference to Microsoft.Xaml.Behaviors.Uwp.Managed
Secondly I added the namespaces Bill mentions to the xaml in which my control is located:
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
Then I added some XAML in my control (List View in this example):
<ListView ...>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleTapped">
<core:InvokeCommandAction Command="{Binding NavigateUpCommand, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
...
</ListView>
In my case, this was a templated control - and the "DoubleTapped" event name was used successfully :)
The Command was set up in the best way I know; made available as an ICommand get accessor on the in the control class, which used a stock "RelayCommand" implementation
I have a ListView with a list of name and I want to be able to rename each value by double click or with a button.
I already did this for the doubleclick and it's working using this :
WPF
<ListView Grid.Row="0" x:Name="ListProfileView"
ItemsSource="{Binding ProfilesCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsReadOnly="True" VerticalAlignment="Center">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
<Binding Source="{x:Static classes:BooleanHelper.False}"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction
Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
<Binding Source="{x:Static classes:BooleanHelper.True}"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
c# (MVVM model with ICommand):
private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
get
{
return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
{
if(!(obj is object[] values)) return;
if(!(values[0] is TextBox txtBox) || !(values[1] is bool value)) return;
txtBox.IsReadOnly = value;
if (!value)
{
txtBox.Focus();
}
}));
}
}
But for the button, I don't know how to get the path to the textbox to use the same command.
I tried things like that :
<Button Grid.Column="3" Content="{x:Static dictionnaries:ColorConfigurationDictionnary.rename}"
FontWeight="SemiBold"
Command="{Binding RenameCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding ElementName="ListProfileView" Path="ItemContainerGenerator"/>
<Binding Source="{x:Static classes:BooleanHelper.False}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
But I'm out of idea... Is that possible ?
It seems that there is some sort of misinformation going about so let me describe how MvvM works in the best way I can think of.
Model is where you store your data so let's call that a profile:
namespace Model
{
public class Profile
{
public string Name { get; set; }
}
}
Now what you need is a ViewModel which will provide Information which is manipulated data:
using VM.Commands;
namespace VM
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
ProfilesCollection = new List<Profile>();
for (int i = 0; i < 100; i++)
{
ProfilesCollection.Add(new Profile() {Name = $"Name {i}"});
}
RenameCommand = new TestCommand(renameCommandMethod, (o) => true);
}
void renameCommandMethod(object parameter)// to manipulate the colleciton you use the Commands which you already do but without the need for converters or any UI elements. Makes it much easier to handle.
{
string renameTo = parameter.ToString();
foreach (var profile in ProfilesCollection)
{
profile.Name = renameTo;
}
}
private List<Profile> profilesCollection;
public List<Profile> ProfilesCollection
{
get { return profilesCollection; }
set { profilesCollection = value; OnPropertyChanged(); }
}
private ICommand renameCommand;
public ICommand RenameCommand
{
get { return renameCommand; }
set { renameCommand = value; }
}
And the implementation of the RelayCommand:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace VM.Commands
{
public class TestCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
public TestCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
#region Implementation of ICommand
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
_execute?.Invoke(parameter);
}
public event EventHandler CanExecuteChanged;
#endregion
}
}
Then UI looks like this:
<Window x:Class="SO_app.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:vm="clr-namespace:VM;assembly=VM"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:converter="clr-namespace:SO_app.Converters"
xmlns:validation="clr-namespace:SO_app.Validation"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:SO_app"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:model="clr-namespace:Model;assembly=Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="452.762" Width="525" Closing="Window_Closing">
<Window.Resources>
<CollectionViewSource Source="{Binding ProfilesCollection}" x:Key="profiles"/>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Background>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Width="50" Height="50" Fill="ForestGreen"></Rectangle>
</VisualBrush.Visual>
</VisualBrush>
</Window.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Source={StaticResource profiles}}"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<ToolTip x:Key="Tip">
<TextBlock>
<Run>Some text here</Run>
<LineBreak/>
<Run Text="{Binding Name, StringFormat='Actual Text: {0}'}"/>
</TextBlock>
</ToolTip>
</DataTemplate.Resources>
<TextBlock Text="{Binding Name}" ToolTip="{StaticResource Tip}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Column="1">
<Button Content="Rename" Command="{Binding RenameCommand}" CommandParameter="NewName"></Button>
</StackPanel>
</Grid>
What this gives you is:
* Clean UI without any converters
* Every operation is done in the ViewModel without passing any UI elements.
* In UI you would do stuff like Styles with animation or setting font for text elements. But avoid handling clicks there. It is possible and sometimes it can't be avoided but try to utilise your ViewModel to manipulate the data.
BTW there are no controllers in here.
If you have any questions just ask.
Here is what I did:
that let me change the name of one value only by disabling the readonly of the textboxes in the list view.
I wrote that in the GUI code behind.
private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
get
{
return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
{
if(!(obj is object[] values)) return;
if(!(values[0] is TextBox || values[0] is SetConfiguration) || !(values[1] is bool value)) return;
if (values[0] is TextBox txtBox)
{
txtBox.IsReadOnly = value;
if (!value)
{
txtBox.Focus();
txtBox.SelectAll();
}
}
if (values[0] is SetConfiguration config)
{
var listView = ListProfileView.ItemContainerGenerator.ContainerFromItem(config) as ListViewItem;
var presenter = FindVisualChild<ContentPresenter>(listView);
if(!(presenter.ContentTemplate.FindName("ProfileName", presenter) is TextBox txtBoxItem)) return;
if (!value)
{
txtBoxItem.Focus();
txtBoxItem.SelectAll();
}
txtBoxItem.IsReadOnly = value;
}
}));
}
}
private static TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is TChildItem item)
return item;
var childOfChild = FindVisualChild<TChildItem>(child);
if (childOfChild != null)
return childOfChild;
}
return null;
}
I'm trying to bind Button Command property to ICommand property from a ViewModel (Button placed inside a DockPanel). It worked fine before I set Visibility property of a DockPanel:
<DockPanel Grid.Row="1">
<Button Content="Read" Command="{Binding ButtonBeginReadCommand}" DockPanel.Dock="Right"/>
<Button Content="Write" Command="{Binding ButtonBeginWriteCommand}" DockPanel.Dock="Left"/>
</DockPanel>
But after adding Visibility property to the DockPanel, things go strange (now button is not clickable, but visibility works fine):
<DockPanel Grid.Row="1" Visibility="{Binding IsFilenameCorrect, Converter={StaticResource HiddenIfFalse}}">
<Button Content="Read" Command="{Binding ButtonBeginReadCommand}" DockPanel.Dock="Right"/>
<Button Content="Write" Command="{Binding ButtonBeginWriteCommand}" DockPanel.Dock="Left"/>
</DockPanel>
I also tried to set RelativeSource for a Button Command, but it did not help:
<DockPanel Grid.Row="1" Visibility="{Binding IsFilenameCorrect, Converter={StaticResource HiddenIfFalse}}">
<Button Content="Read" Command="{Binding DataContext.ButtonBeginReadCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" DockPanel.Dock="Right"/>
<Button Content="Write" Command="{Binding DataContext.ButtonBeginWriteCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" DockPanel.Dock="Left"/>
</DockPanel>
DataContext set as:
<Window.DataContext>
<viewModel:MainWindowViewModel/>
</Window.DataContext>
There is part of the MainWindowViewModel class. I used a custom AsyncCommand implementation (can't remember where I found it):
...
public ICommand ButtonBeginReadCommand { get; private set; }
public MainWindowViewModel() {
...
ButtonBeginReadCommand = new AsyncCommand(async () =>
{
await Task.Delay(300);
Monitor.Enter(_locker);
...
Monitor.Exit(_locker);
});
How can I fix this?
Try using the inbuilt BooleanToVisibilityConverter.
I am sharing the sample code. You might have to change the namespace to get it working.
XAML:
<Window x:Class="DockPanel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:DockPanel"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModel:VM/>
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<DockPanel Grid.Row="1" Visibility="{Binding IsFilenameCorrect, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Content="Read" Command="{Binding DataContext.ButtonBeginReadCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" DockPanel.Dock="Right"/>
<Button Content="Write" Command="{Binding DataContext.ButtonBeginWriteCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" DockPanel.Dock="Left"/>
</DockPanel>
</Window>
Code Behind:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace DockPanel
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class VM
{
public bool IsFilenameCorrect { get; set; }
public ICommand ButtonBeginReadCommand { get; set; }
public ICommand ButtonBeginWriteCommand { get; set; }
private object _locker = new object();
public VM()
{
IsFilenameCorrect = true;
ButtonBeginReadCommand = new AsyncCommand(async () =>
{
await Task.Delay(300);
Monitor.Enter(_locker);
MessageBox.Show("Read");
Monitor.Exit(_locker);
});
ButtonBeginWriteCommand = new AsyncCommand(async () =>
{
await Task.Delay(300);
Monitor.Enter(_locker);
MessageBox.Show("Write");
Monitor.Exit(_locker);
});
}
}
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
public abstract class AsyncCommandBase : IAsyncCommand
{
public abstract bool CanExecute(object parameter);
public abstract Task ExecuteAsync(object parameter);
public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
public class AsyncCommand : AsyncCommandBase
{
private readonly Func<Task> _command;
public AsyncCommand(Func<Task> command)
{
_command = command;
}
public override bool CanExecute(object parameter)
{
return true;
}
public override Task ExecuteAsync(object parameter)
{
return _command();
}
}
}
I'm trying to execute a command located on my ViewModel, using a TreeViewItem with a KeyBinding, and a MenuContext.
Currently, using the context menu, the command is invoked on the correct ViewModel instance.
However, when I select a TreeViewItem and press the "C" key, the command is invoked on the "root" ViewModel.
I tried extending KeyBinding class as well ( Keybinding a RelayCommand ) with no luck.
Maybe I'm going to the wrong path : I just want to display the correct MessageBox, if I use the context menu or the key.
Code sample for a WPF project named WpfTest.
MainWindow.xaml
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Child}" DataType="{x:Type vm:ViewModel}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Binding Name}" Command="{Binding SomeCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="vm:MyAttached.InputBindings">
<Setter.Value>
<InputBindingCollection>
<KeyBinding Key="C" Command="{Binding SomeCommand}" CommandParameter="{Binding}"/>
</InputBindingCollection>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Window>
MainWindow.xaml.cs:
namespace WpfTest
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new List<ViewModel>
{
new ViewModel
{
Name = "Parent",
Child = new ObservableCollection<ViewModel>
{
new ViewModel { Name = "Child 1" },
new ViewModel { Name = "Child 2" },
new ViewModel { Name = "Child 3" }
}
}
};
}
}
public class ViewModel
{
public string Name { get; set; }
public ObservableCollection<ViewModel> Child { get; set; }
public ICommand SomeCommand { get; set; }
public ViewModel()
{
this.SomeCommand = new RelayCommand<ViewModel>(OnCommandExecuted);
}
private void OnCommandExecuted(ViewModel parameter)
{
MessageBox.Show("CommandExecuted on " + Name + " with parameter " + parameter.Name);
}
}
public class MyAttached
{
public static readonly DependencyProperty InputBindingsProperty =
DependencyProperty.RegisterAttached("InputBindings", typeof(InputBindingCollection), typeof(MyAttached),
new FrameworkPropertyMetadata(new InputBindingCollection(),
(sender, e) =>
{
var element = sender as UIElement;
if (element == null) return;
element.InputBindings.Clear();
element.InputBindings.AddRange((InputBindingCollection)e.NewValue);
}));
public static InputBindingCollection GetInputBindings(UIElement element)
{
return (InputBindingCollection)element.GetValue(InputBindingsProperty);
}
public static void SetInputBindings(UIElement element, InputBindingCollection inputBindings)
{
element.SetValue(InputBindingsProperty, inputBindings);
}
}
public class RelayCommand<T> : ICommand
{
readonly Action<T> _execute = null;
public RelayCommand(Action<T> execute) { _execute = execute; }
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter) { _execute((T)parameter); }
public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }
}
}
Here is the problem: The Style only creates one InputBindingCollection for all ListViewItems, you have to be very careful with Setter.Values for that reason.
And here is the fix:
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<!-- x:Shared="False" forces the new creation of that object whenever referenced -->
<InputBindingCollection x:Shared="False" x:Key="InputBindings">
<KeyBinding Key="C" Command="{Binding SomeCommand}" CommandParameter="{Binding}" />
</InputBindingCollection>
</TreeView.Resources>
<!-- ... -->
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- ... -->
<Setter Property="vm:MyAttached.InputBindings" Value="{StaticResource InputBindings}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>