Closing ContextMenu with Templated MenuItems - wpf

I have created a customized Context menu where I changed the appearance of all items.
These Items contain different controls like comboboxes and buttons. Now I want the menu to close if a button was pressed or a combobox item was selected. Currently the menu just remains open.
Can you give me a hint?
This is a simplified code to show what I did:
<ContextMenu StaysOpen="False">
<MenuItem>
<MenuItem.Template>
<ControlTemplate>
<Grid MinWidth="200">
<Button Command="{Binding SomeWorkingCommandBinding}">OK</Button>
</Grid>
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</ContextMenu>
As mentioned, I would like to close the menu when I hit that OK button.
UPDATE
The following button (or any other control) does the trick without the need of Blend SDK:
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(ContextMenu.IsOpen)" Storyboard.Target="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>False</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>

Use the ChangePropertyAction which is part of the Blend SDK to change the IsOpen property of the ContextMenu as soon as the Button is clicked:
<ContextMenu x:Name="MyContextMenu">
<MenuItem>
<MenuItem.Template>
<ControlTemplate>
<Grid MinWidth="200">
<Button Command="{Binding SomeWorkingCommandBinding}" Content="OK">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}" PropertyName="IsOpen" Value="False"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</ContextMenu>
You'll need the following namespaces:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"

Related

How to set DataContext of Button.ContextMenu same as the Button?

<Button Content="{Binding Type}" Name="Ellipsis" Tag="{Binding ElementName=Ellipsis, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu x:Name="MainContextMenu" DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Type}"/>
</ContextMenu>
</Button.ContextMenu>
<Button.Triggers>
<EventTrigger SourceName="Ellipsis" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MainContextMenu" Storyboard.TargetProperty="(ContextMenu.IsOpen)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
As shown above, Button.Content="{Binding Type}" works perfectly but when it goes in its ContextMenu, the DataContext changes which causing MenuItem Header="{Binding Type}" to not work. I researched online and someone said saving the outer DataContext in a tag and use it as the inner DataContext. I tried that in my code but ContextMenu still not reading the correct DataContext. The MenuItem Header should be the same as the Button.Content in this case but it is not. What should I do?
To answer the datacontext part of the question.
One way would be to ensure your contextmenu would have the same datacontext as a button would be to define the contextmenu as a resource.
<Grid.Resources>
<ContextMenu x:Key="MainContextMenu">
<MenuItem Header="{Binding Type}"/>
</ContextMenu>
</Grid.Resources>
<Button ContextMenu="{StaticResource MainContextMenu}"
Because the contextmenu is instantiated in the grid, it inherits the grid's datacontext.
The left click will not work without code though. Not with a button.
Not sure why you'd want left click to show a context menu but maybe you could consider a popup instead of contextmenu.
You could animate isopen using a booleananimation
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="MyPopup" Storyboard.TargetProperty="(Popup.IsOpen)">
<DiscreteBooleanKeyFrame Value="True" />
</BooleanAnimationUsingKeyFrames>
Or you could instead use a togglebutton which has an ischecked boolean:
<ToggleButton Content="{Binding Type}"
x:Name="Ellipsis" />
<Popup IsOpen="{Binding IsChecked, ElementName=Ellipsis, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=Ellipsis}"
DataContext="{Binding DataContext, ElementName=Ellipsis}"
StaysOpen="False">
<ListBox>
<MenuItem Header="{Binding Type}"/>
</ListBox>
</Popup>
OK, probably needs a bit more work but my popup shows up and has the correct datacontext:

Closing Popup after clicking child button using no code-behind

I have a wpf application with a toggle window which opens a popup control. I want to be able to close it after the user clicks it's child button.
My preference would be to do it in the xaml via style triggers. But for some reason I can't set my popup IsOpen property inside of the event trigger. I receive an error saying
A value of type setter cannot be added to a collection or dictionary of type TriggerActionCollection
here's how I have the xaml set up
<ToggleButton x:Name="ShowAvailableOptionsToggleButton"
Content="Add Options" />
<Popup IsOpen="{Binding IsChecked, ElementName=ShowAvailableOptionsToggleButton}">
<StackPanel>
<ListView ItemsSource="{Binding AvailableOptions}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<Button Content="Add"
Name="AddOptionBtn"
Command="{Binding AddOptionCommand}"/>
</StackPanel>
<Popup.Style>
<Style>
<Style.Triggers>
<EventTrigger SourceName="AddOptionBtn" RoutedEvent="Button.Click">
//ERROR HAPPENS HERE
<Setter Property="IsOpen" Value="False"/>
</EventTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
</Popup>
Can someone see what I am doing wrong?
EventTrigger has to be used with animations:
<Popup IsOpen="True">
<Button x:Name="AddOptionBtn"
Content="Add" />
<Popup.Triggers>
<EventTrigger SourceName="AddOptionBtn"
RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.Target="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Popup}}"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0"
Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Popup.Triggers>
</Popup>

Listbox selected item from usercontrol

I have a listbox populate with controls , the control is populated with textboxes and comboboxes. I need to select the underlying listitem when i edit the textbox and select in the combos. Cant seem to find the solutio. Anyone?
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:ComponentEditItem Background="Transparent"/>
</DataTemplate>
</ListBox.ItemTemplate>
You can add an EventTrigger that selects underlying ListBoxItem when one of its controls is focused. Something like this:
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:ComponentEditItem Background="Transparent">
<Controls:ComponentEditItem.Triggers>
<EventTrigger RoutedEvent="GotFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Duration="00:00:00" Storyboard.Target="{Binding Path=., RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}" Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Controls:ComponentEditItem.Triggers>
</Controls:ComponentEditItem>
</DataTemplate>
</ListBox.ItemTemplate>

Button click changes visibility of a DataGrid with a Trigger in WPF

Hi i am trying to find some way to when a button is clicked changes the visibility of other control, like a DataGrid with a Trigger in XAML.
The button only changes the visibility of the DataGrid to Visible, it does other things in Code Behind, but this is something that i think that can be done in a Style with a Trigger.
I tried to find a solution and it seems to be possible to do but i can't understand how.
Thanks in advance.
<Button Content="Button!">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.Target="{x:Reference dataGrid}"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
{x:Reference dataGrid} references a DataGrid with the name dataGrid, alternatively you could just use Storyboard.TargetName. You would normally use the Storyboard.Target property if you do binding or references to resources.
Just a suggestion, but how about, for something more understandable, having a Checkbox enabling/disabling the DataGrid display? This is what I usually do:
<DockPanel LastChildFill="True">
<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" x:Name="DisplayBox"
Content="Display grid" Margin="4" IsChecked="False"/>
<DataGrid Visibility="{Binding ElementName=DisplayBox, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}" />
</DockPanel>
And of course, you'll have to implement the appropriate converter

ControlTemplate.Triggers WPF equivalent in Silverlight 3

I have this controltempalte + trigger stuff in my WPF application.
<ControlTemplate TargetType="me:MyControl" x:Key="fade">
<ContentPresenter - other stuff />
<ControlTemplate.Triggers>
<Trigger Property="IsTransitioned" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation -<stuff>- />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This works cool in WPF, and is very intuitive too for me to write this based on trigger as above.
When i port this to Silverlight (3), i am told i have to use VSM, states and groups etc. since triggers on control template is not supported.
I h ave looked at some samples, and i even atempted to apply teh VSM bits in, in the place of trigger as above, but can't get it to work.
Someone suggested to me that apart from VSM in the xaml, i will have to handle some events etc.
SL3 model is just being painful for me. Please help.
Silverlight 3 introduced interaction triggers which as far as I can tell do what you want but are a little more complex. There's very few examples about them out yet though.
If you're doing this manually you need references to System.Windows.Interactivity and Microsoft.Expression.Interactions (from Blend 3, the class will be in your references tab if you've installed it).
If you add the triggers in Blend then it will add those automatically. This is called Behaviours in Silverlight 3 and you will find these in Blend in the Behaviours section of the Assets tab.
An example of how they work. Note the storyboard sitting in the resource of the second rectangle, I couldn't get it to work inside the ControlStoryboardAction.Storyboard, but it did work if I made the rectangle a ContentControl and put it in the Template. This may be a bug or me missing something :
<UserControl
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"
x:Class="SLTrigger.MainPage"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:im="clr-namespace:Microsoft.Expression.Interactivity.Media;assembly=Microsoft.Expression.Interactions">
<Grid x:Name="LayoutRoot">
<StackPanel>
<Rectangle Margin="5" Fill="Blue" Width="200" Height="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<ic:ChangePropertyAction PropertyName="Fill" Duration="0">
<ic:ChangePropertyAction.Value>
<SolidColorBrush Color="Red"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
<Rectangle Margin="5" x:Name="AnimatedRectangle2" Fill="Blue" Width="200" Height="100">
<Rectangle.Resources>
<Storyboard x:Key="AnimationStoryboard">
<ColorAnimation
Storyboard.TargetName="AnimatedRectangle2"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="Red"
AutoReverse="True"
Duration="0:0:0.5" />
</Storyboard>
</Rectangle.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<im:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource AnimationStoryboard}">
<!--
Doesn't work, but does work inside control templates??
<im:ControlStoryboardAction.Storyboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="AnimatedRectangle2"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="Red"
AutoReverse="True"
Duration="0:0:0.5" />
</Storyboard>
</im:ControlStoryboardAction.Storyboard>
-->
</im:ControlStoryboardAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<Rectangle Margin="5" x:Name="AnimatedRectangle3" Fill="Blue" Width="200" Height="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<im:ControlStoryboardAction ControlStoryboardOption="Play">
<im:ControlStoryboardAction.Storyboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="AnimatedRectangle3"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="Red"
AutoReverse="True"
Duration="0:0:0.5" />
</Storyboard>
</im:ControlStoryboardAction.Storyboard>
</im:ControlStoryboardAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<TextBlock TextAlignment="Center" Text="Click the rectangles" />
</StackPanel>
</Grid>
</UserControl>
The class file has nothing in it:
using System.Windows.Controls;
namespace SLTrigger
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
}

Resources