Skip to content

Instantly share code, notes, and snippets.

@ryanvs
Last active January 10, 2023 16:35
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • 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/>
@tuxy42
Copy link

tuxy42 commented Sep 9, 2015

Really great! I tested some other stuff like this.. yours is the best, it is themes compatible, and use the best and nicer way to do the trick. Thank you again!

@Aduciicba
Copy link

Aduciicba commented Dec 27, 2016

It works like a charm! Thank you:)

@RickStrahl
Copy link

RickStrahl commented Jan 30, 2017

Nice - this worked great for me!

To programmatically attach in code:

var ctxm = new ContextMenu();
btnDrop.ContextMenu = ctxm;
var behaviors = Interaction.GetBehaviors(btnDrop);
behaviors.Add(new DropDownButtonBehavior());

@funkel1989
Copy link

Not sure if this is still being monitors or not but if it is I love your example and plan to use the idea in my own work. One thing I have not been able to figure out is how to add context to the menu items. How do i tell it to do what the menu item says when the user clicks on it? Is there a way to bind menu items to code or do i somehow reference the heading in my code?

@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