using an event to command trigger inside a style - c#

I am trying to use the Mvvm Light toolkit to bind an event to a command from within a style.
I currently have the style:
<Style
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:controls='clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls'
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:ig='http://schemas.infragistics.com/xaml'
xmlns:i='http://schemas.microsoft.com/expression/2010/interactivity'
xmlns:Command='clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4'
TargetType='ig:EventSpan'>
<Setter Property='Template'>
<Setter.Value>
<ControlTemplate TargetType='ig:EventSpan'>
<Grid Margin='0,2,0,2'>
<i:Interaction.Triggers>
<i:EventTrigger EventName='MouseEnter'>
<Command:EventToCommand Command='{Binding EventSpan_MouseEnter1}' CommandParameter='{Binding RelativeSource={RelativeSource AncestorType={x:Type ig:EventSpan}}}'/>
</i:EventTrigger>
<i:EventTrigger EventName='MouseLeave'>
<Command:EventToCommand Command='{Binding EventSpan_MouseLeave1}' CommandParameter='{Binding RelativeSource={RelativeSource AncestorType={x:Type ig:EventSpan}}}'/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Rectangle RadiusX='0' RadiusY='0' Fill='{TemplateBinding Fill}' Stroke='{TemplateBinding Stroke}' StrokeThickness='0' Height='0' Margin='0, 0, 0, 0' VerticalAlignment='Top' />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and an example of the code behind is:
private RelayCommand<string> _eventSpan_MouseEnter;
public RelayCommand<string> EventSpan_MouseEnter1
{
get
{
return _eventSpan_MouseEnter
?? (_eventSpan_MouseEnter = new RelayCommand<string>(
sender =>
{
MouseOverLayer = sender;
}));
}
}
But this command never fires and I cannot work out why?
PS. The reason that the libraries are referenced in the styles is because this style is loaded dynamically from code behind. It is legacy code that I am currently trying to convert to mvvm.

So There were two problems with the way I had bound before. Firstly the main reason was that the style had no datacontext. And apparently didnt inherit the datacontext from the template implementing the style. and also that the command parameter was incorrect.
CommandParameter='{Binding RelativeSource={RelativeSource AncestorType={x:Type ig:EventSpan}}}'
was binding to a relaycommand expecting a string parameter and so crashed. Therefore the correct commandparameter was
CommandParameter='{Binding Path=EventEntry.Series.Tag, RelativeSource={RelativeSource AncestorType={x:Type ig:EventSpan}}}'

Related

Getting access to listviews (selected) items from context menu

In my WPF MVVM (using Prism) application I have a listview. In this listview I have bound a Ctrl-C key input to a copy function with an IList parameter. (see CopyChannelChangeNotificationToClipboardCommand)
This works well.
Now I want to add a context menu that has a MenuItem with "Copy Ctrl-C" functionality.
What I do not understand is how to bind the CommandParameter from the MenuItem so that it provides the ListView's Selected items? The idea is to use the same function as the input bound key, i.e. get the IList from the listview to be attached as a command parameter.
I've tried to read quite a few posts here, tried even more but all result in the objList parameter to be null.
If anyone has suggestions on how to accomplish this I'd be grateful.
My view (partial)
<ListView Grid.Row="1" ItemTemplate="{StaticResource ChannelChangeDataTemplateKey}" ItemsSource="{Binding ChannelChangeNotifications}" lb:ListBoxBehavior.ScrollOnNewItem="true">
<ListView.InputBindings>
<KeyBinding Key="C" Modifiers="Control" Command="{Binding CopyChannelChangeNotificationToClipboardCommand}" CommandParameter="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
</ListView.InputBindings>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.UICopySelectedItems}" Command="{Binding CopySelectedItemsClickCommand}" CommandParameter="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
and my view model code
public Prism.Commands.DelegateCommand<IList> CopyChannelChangeNotificationToClipboardCommand => new Prism.Commands.DelegateCommand<IList>(CopyChannelChangeNotificationToClipboard);
public Prism.Commands.DelegateCommand<IList> CopySelectedItemsClickCommand => new Prism.Commands.DelegateCommand<IList>(CopyChannelChangeNotificationToClipboard);
private void CopyChannelChangeNotificationToClipboard(IList objList)
{
if (objList != null)
{
// Copy selected list view objects to clipboard
// Works well when coming from CopyChannelChangeNotificationToClipboardCommand
// but not when coming from CopySelectedItemsClickCommand
// since objList is always null
}
}

WPF DataGrid Remove Column

I have created a DataGrid in WPF from a DataTable with AutoGenerateColumns = true. In the VM I have a command property for adding and deleting columns which manipulates the underlying DataTable.
When I call AddColumn from the main XAML window via Command="{Binding AddColumn}" it works as expected but when I call the RemoveColumn from a resource file with a context menu it calls the command property (im able to step through the code) but does not update the Grid.
<MenuItem Header="Delete" Command="{Binding Source={StaticResource AppViewModel}, Path=DeleteColumn}" CommandParameter="{Binding}" />
I now updated both commands to only set the DataTable to null, ie Dt = null and for the AddColumn this works as expected and removed the Grid and columns but for the RemoveColumn it does nothing... I also see no errors in the output window in regard to binding and when stepping through the code the property is called. I also tried to set the column to invisible which also did not work.
UPDATE
I call the DeleteColumn from the following (simplified) code in a resources file.
<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Style.Triggers>
<DataTrigger Binding="{Binding Content, ConverterParameter=*, Converter={StaticResource AfterDashConverter}, RelativeSource={RelativeSource self}}" Value="Green">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding Source={StaticResource AppViewModel}, Path=DeleteColumn}" />
</ContextMenu>
</Grid.ContextMenu>
<DataGridColumnHeader x:Name="PART_FillerColumnHeader" IsHitTestVisible="False"/>
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The commands are very simple, the AddColumn works as its directly in my main view and the DeleteColumn does not as its in the above resource file. I have checked the commands fire properly.
AddColumn = new RelayCommand(_ =>
{
Dt = null;
}, true);
DeleteColumn = new RelayCommand(column =>
{
Dt = null;
}, true);
the main issue was my resource was creating a new viewmodel so it was not calling / impacting the VM instance that my view was dependent on. To fix this I needed to set the DataContext of my ContextMenu parent control (which was a grid) as follows:
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyType}}}
If you just call the following it will create a new instance of your ViewModel and use that:
<MenuItem Header="Delete" Command="{Binding Source={StaticResource AppViewModel}, Path=DeleteColumn}" />

How to get parent event inside ControlTemplate?

I want to invoke a Command from view model inside radio button ControlTemplate. And do it when event Checked is raised
I tried this:
<ControlTemplate x:Key="RequestTypeControlTemplate" TargetType="RadioButton">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0,0,2">
<ContentPresenter Margin="10,0,10,0">
<i:Interaction.Triggers>
<!-- Checked event from parent -->
<i:EventTrigger EventName="Checked">
<!-- Command from view model and binding to tag of radio button as arg -->
<i:InvokeCommandAction Command="{Binding DataContext.OnTypeChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentPresenter>
</Border>
</ControlTemplate>
But I think event goes from ContentPresenter, not from RadioButton. How do I get the event from RadioButton?
EDIT:
<RadioButton Content="All requests"
ContentTemplate="{StaticResource RadioButtonDataTemplate}"
GroupName="Filter"
IsChecked="True"
Style="{StaticResource RadioButtonStyle}"
Template="{StaticResource RequestTypeControlTemplate}"
Tag="{x:Static local:ContentType.Any}"/>
In viewModel
public ICommand OnTypeChangedCommand
{
get
{
return m_TypeChangedCommand ?? (m_TypeChangedCommand = new DelegateCommand<ContentType>(OnTypeChanged));
}
}
public void OnTypeChanged(ContentType type)
{
Debug.WriteLine(type);
}
You can't do that because the Routed event is only handled from the element (bubble) or to the element (tunnel)
What you can use is a DataTrigger on IsChecked property .
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding IsChecked,RelativeSource={RelativeSource AncestorType=RadioButton}}" Value=True>
<i:InvokeCommandAction Command="{Binding DataContext.OnTypeChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding Tag, RelativeSource={RelativeSource AncestorType=RadioButton}}}" />
</ei:DataTrigger>
</i:Interaction.Triggers>

XAML Is there a way to bind an Event to a Method using Style?

I am using style to define the template of a predefined control. The control is part of a Framework which I cannot modify. This control has a DataGrid and for that I want to add a MouseDoubleClick event. The event handler (HandleGridDoubleClick) is already specified in the framework.
Is there a way to bind to this method ? I understand it would have been easy if the Framework provided a Command for that.
<Style
TargetType="xyz:FileMessageControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="xyz:FileMessageControl">
<Grid>
.....
<DataGrid MouseDoubleClick = HandleGridDoubleClick>
.....
</DataGrid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Try something like this (you need to use Interaction dll)
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<action:ExecuteCommandAction Command="{Binding DataContext.HandleGridDoubleClick}" />
</i:EventTrigger>
</i:Interaction.Triggers>

wpf eventsetter handler binding in style

I have a style, and I want to bind a command to the EventSetter's Handler with RelativeSource. The command is in the viewModel.
<Style x:Key="ItemTextBlockEventSetterStyle" TargetType="{x:Type TextBlock}">
<EventSetter Event="MouseLeftButtonDown"
Handler="{Binding TextBlockMouseLeftButtonDownCommand,
RelativeSource={RelativeSource Self}}"/>
</Style>
The problem is that I get an error, because something is wrong with this (maybe it's not possible to do this in such easy way)
I've googled a lot before, and I found the AttachedCommandBehaviour, but I think it doesn't work with style.
Could you give some hints on how to solve this problem?
Update 13/10/2011
I found this in the MVVM Light Toolkit EventToCommand example program:
<Button Background="{Binding Brushes.Brush1}"
Margin="10"
Style="{StaticResource ButtonStyle}"
Content="Simple Command"
Grid.Row="1"
ToolTipService.ToolTip="Click to activate command">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding SimpleCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<cmd:EventToCommand Command="{Binding ResetCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
But here, the binding isn't in the style. How can I put this EventToCommand to the style of the button?
Right now you are binding the MouseLeftButtonDown Event to TextBlock.TextBlockMouseLeftButtonDownCommand. TextBlockMouseLeftButtonDownCommand is not a valid property for a TextBlock, nor does it sound like it's an Event Handler.
I use the AttachedCommandBehavior all the time in styles for hooking up a Command to an Event. The syntax usually looks like this (note the DataContextin the Command Binding):
<Style x:Key="ItemTextBlockEventSetterStyle" TargetType="{x:Type TextBlock}">
<Setter Property="local:CommandBehavior.Event" Value="MouseLeftButtonDown" />
<Setter Property="local:CommandBehavior.Command"
Value="{Binding DataContext.TextBlockMouseLeftButtonDownCommand,
RelativeSource={RelativeSource Self}}" />
</Style>
The alternative is to hook the EventSetter up to an event in the code-behind, and process the command from there:
<Style x:Key="ItemTextBlockEventSetterStyle" TargetType="{x:Type TextBlock}">
<EventSetter Event="MouseLeftButtonDown"
Handler="TextBlockMouseLeftButtonDown"/>
</Style>
Event handler in code behind...
void TextBlockMouseLeftButtonDown(object sender, MouseEventArgs e)
{
var tb = sender as TextBlock;
if (tb != null)
{
MyViewModel vm = tb.DataContext as MyViewModel;
if (vm != null && TextBlockMouseLeftButtonDownCommand != null
&& TextBlockMouseLeftButtonDownCommand.CanExecute(null))
{
vm.TextBlockMouseLeftButtonDownCommand.Execute(null)
}
}
}
As you are using MVVM, I suggest you Galasoft MVVM Light Toolkit EventToCommand
My answer on this question does the trick without any external tool kits/libraries. However, it does not use RelativeSource, and it is not 100% MVVM. It requires one line of code in a code-behind event handler.

Categories