Skip to content

Instantly share code, notes, and snippets.

@ryanvs
Last active January 10, 2023 16:35
Show Gist options
  • Save ryanvs/8059757 to your computer and use it in GitHub Desktop.
Save ryanvs/8059757 to your computer and use it in GitHub Desktop.
WPF Behavior for buttons to show a drop down context menu when the button is pressed. Uses Microsoft.Xaml.Behaviors (older code uses System.Windows.Interactivity) for Blend behavior. Possible solution to -- http://stackoverflow.com/questions/8958946/how-to-open-a-popup-menu-when-a-button-is-clicked
// This is free and unencumbered software released into the public domain.
// For more information, please refer to <http://unlicense.org/>
using System;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
// Newer code should use Microsoft.Xaml.Behaviors
using Microsoft.Xaml.Behaviors;
// Older code uses System.Windows.Interactivity
//using System.Windows.Interactivity;
namespace DropDownButtonExample
{
public class DropDownButtonBehavior : Behavior<Button>
{
private long attachedCount;
private bool isContextMenuOpen;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(Button.ClickEvent, new RoutedEventHandler(AssociatedObject_Click), true);
}
void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
{
Button source = sender as Button;
if (source != null && source.ContextMenu != null)
{
// Only open the ContextMenu when it is not already open. If it is already open,
// when the button is pressed the ContextMenu will lose focus and automatically close.
if (!isContextMenuOpen)
{
source.ContextMenu.AddHandler(ContextMenu.ClosedEvent, new RoutedEventHandler(ContextMenu_Closed), true);
Interlocked.Increment(ref attachedCount);
// If there is a drop-down assigned to this button, then position and display it
source.ContextMenu.PlacementTarget = source;
source.ContextMenu.Placement = PlacementMode.Bottom;
source.ContextMenu.IsOpen = true;
isContextMenuOpen = true;
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(Button.ClickEvent, new RoutedEventHandler(AssociatedObject_Click));
}
void ContextMenu_Closed(object sender, RoutedEventArgs e)
{
isContextMenuOpen = false;
var contextMenu = sender as ContextMenu;
if (contextMenu != null)
{
contextMenu.RemoveHandler(ContextMenu.ClosedEvent, new RoutedEventHandler(ContextMenu_Closed));
Interlocked.Decrement(ref attachedCount);
}
}
}
}
<UserControl
x:Class="DropDownButtonExample"
x:Name="ThisControl"
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:behaviors="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:DropDownButtonExample"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="600"
>
<Grid>
<ToolBar Grid.Row="0" ToolBarTray.IsLocked="True" SnapsToDevicePixels="True">
<Button>
<behaviors:Interaction.Behaviors>
<local:DropDownButtonBehavior/>
</behaviors:Interaction.Behaviors>
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Source="/DropDownButtonExample;component/Assets/add.png" SnapsToDevicePixels="True" Height="16" Width="16" />
<TextBlock Text="Add"/>
<Separator Margin="2,0">
<Separator.LayoutTransform>
<TransformGroup>
<TransformGroup.Children>
<TransformCollection>
<RotateTransform Angle="90"/>
</TransformCollection>
</TransformGroup.Children>
</TransformGroup>
</Separator.LayoutTransform>
</Separator>
<Path Margin="2" VerticalAlignment="Center" Width="6" Fill="#FF527DB5" Stretch="Uniform" HorizontalAlignment="Right" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "/>
</StackPanel>
</Button.Content>
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Attribute"/>
<MenuItem Header="Setting"/>
<Separator/>
<MenuItem Header="Property"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</ToolBar>
</Grid>
</UserControl>
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@ryanvs
Copy link
Author

ryanvs commented Sep 12, 2017

@funkel1989 I didn't see your comment until now. Normally I would use ICommand for each of the menu items to bind against the view model. So example code for the menu would be:

                    <ContextMenu>
                        <MenuItem Header="Attribute" Command="{Binding AttributeCommand}"/>
                        <MenuItem Header="Setting" Command="{Binding SettingCommand}"/>
                        <Separator/>
                        <MenuItem Header="Property" Command="{Binding PropertyCommand}"/>
                    </ContextMenu>

Occasionally there are issues with the context menu and visual tree so you can use a BindingProxy (google WPF BindingProxy for more info). In that case, the example code is:

                    <ContextMenu>
                        <MenuItem Header="Attribute" Command="{Binding Data.AttributeCommand, Source={StaticResource proxy}}"/>
                        <MenuItem Header="Setting" Command="{Binding Data.SettingCommand, Source={StaticResource proxy}}"/>
                        <Separator/>
                        <MenuItem Header="Property" Command="{Binding Data.PropertyCommand, Source={StaticResource proxy}}"/>
                    </ContextMenu>
<!-- or -->
                    <ContextMenu DataContext="{Binding Data, Source={StaticResource proxy}}">
                        <MenuItem Header="Attribute" Command="{Binding AttributeCommand}"/>
                        <MenuItem Header="Setting" Command="{Binding SettingCommand}"/>
                        <Separator/>
                        <MenuItem Header="Property" Command="{Binding PropertyCommand}"/>
                    </ContextMenu>

@RedX2501
Copy link

You should attach a licence to this gist to make clear when we are allowed to reuse this code as is.

@ryanvs
Copy link
Author

ryanvs commented May 20, 2020

@RedX2501 Good point. I was debating on MIT but this code is basic that I chose unlicensed instead.

@Kafka9
Copy link

Kafka9 commented Aug 16, 2021

Hello,

I am very new to this stuff. Can you please tell me how and where to attach this menu code to my button? I want this menu to open when I press the button on the Home Window.

Right now, I have a Home Window with a button.

My HomeWindow Code looks like this:

public partial class HomeWindow : Window
{
public HomeWindow()
{
InitializeComponent();
}

    private void Home_Button_Click(object sender, RoutedEventArgs e)
    {
        

      
    }
}

For some reason my design window for "ThisControl.xaml" is not showing anything.

My "ThisControl.xaml.cs" file looks like this:

public partial class ThisControl : UserControl
{
public ThisControl()
{
InitializeComponent();
}

    private void InitializeComponent()
    {
        
    }
}

And I have the "DropDownButtonBehavior.cs" code from above. And I also have the "ThisControl.XAML" code from above.

Thanks a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment