8

I am working on a WPF error logging app, where when a user enters a new connection string, a connection button is created and shown as stacked list on the sidebar.

I want to make a right click event on those connection buttons to show a Button context menu for View, Edit, Delete.

My MainWindow.xaml Sidebar grid is like this,

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="318*" />

        </Grid.ColumnDefinitions>
        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled">
            <StackPanel Name="listConnections" Grid.Column="0" Background="#4682b4" Margin="0,0,0,-0.2" >
            </StackPanel>
        </ScrollViewer>

        </TabControl>
    </Grid> 

And I am calling the Stackpanel listConnections in my MainWindow.xaml.cs like this

public MainWindow()
{
    InitializeComponent();

    GetUserData();
    //Button to create new connection
    listConnections.Children.Add(new NewConnectionButton(this));
    this.Closed += new EventHandler(MainWindow_Close);
}

Right-Click event WPF I tried to follow this link to create the right click event, but it is not working out for me. Can someone please assist me in this please?

Community
  • 1
  • 1
StraightUp
  • 205
  • 1
  • 5
  • 18
  • 3
    Before you get too deep in to learning WPF using code behind, consider learning the MVVM pattern. WPF is *significantly* easier to use with MVVM. It is a pretty big mental shift if you've never used it before though, so be prepared for a learning curve. You will thank yourself later. – Bradley Uffner Apr 21 '17 at 16:43
  • Thank you for the heads up. I am very new to WPF and it is hard to get my head around the .xaml and .xaml cs. But right now I am just stuck on finishing this final WPF project. – StraightUp Apr 21 '17 at 17:00

2 Answers2

8

What I'd do here would be the following:

  • create context menu separately and assign it to every "connection" object on the UI
  • handle MenuItem.Click clicks event for every menu item
  • resolve the clicked item in a list and process it respectively

MVVM and all that is certainly good but this straightforward approach is at least good place to start:

<Window x:Class="WpfApplication7.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"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <!-- Having CommandParameter is crucial here -->
        <ContextMenu x:Key="contextMenu">
            <MenuItem Header="View"
                      Click="View_OnClick"
                      CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
            <MenuItem Header="Edit"
                      Click="Edit_OnClick"
                      CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
            <MenuItem Header="Delete"
                      Click="Delete_OnClick"
                      CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
        </ContextMenu>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="318*" />
        </Grid.ColumnDefinitions>
        <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled">
            <StackPanel Name="listConnections" Grid.Column="0" Background="#4682b4" Margin="0,0,0,-0.2" >
                <Button Click="BtnAdd_OnClick">New Connection</Button>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</Window>

Code-behind:

using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication7
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private static Label FindClickedItem(object sender)
        {
            var mi = sender as MenuItem;
            if (mi == null)
            {
                return null;
            }

            var cm = mi.CommandParameter as ContextMenu;
            if (cm == null)
            {
                return null;
            }

            return cm.PlacementTarget as Label;
        }

        private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
        {
            listConnections.Children.Add(new Label
            {
                Content = "New Connection",
                ContextMenu = (ContextMenu)Resources["contextMenu"]
            });
        }

        private void View_OnClick(object sender, RoutedEventArgs e)
        {
            var clickedItem = FindClickedItem(sender);
            if (clickedItem != null)
            {
                MessageBox.Show(" Viewing: " + clickedItem.Content);
            }
        }

        private void Edit_OnClick(object sender, RoutedEventArgs e)
        {
            var clickedItem = FindClickedItem(sender);
            if (clickedItem != null)
            {
                string newName = "Connection edited on " + DateTime.Now.ToLongTimeString();
                string oldName = Convert.ToString(clickedItem.Content);
                clickedItem.Content = newName;
                MessageBox.Show(string.Format("Changed name from '{0}' to '{1}'", oldName, newName));
            }
        }

        private void Delete_OnClick(object sender, RoutedEventArgs e)
        {
            var clickedItem = FindClickedItem(sender);
            if (clickedItem != null)
            {
                string oldName = Convert.ToString(clickedItem.Content);
                listConnections.Children.Remove(clickedItem);
                MessageBox.Show(string.Format("Removed '{0}'", oldName));
            }
        }
    }
}

This is how it looks like:

about to click edit

after clicking on edit

Hope this helps

alex.b
  • 4,547
  • 1
  • 31
  • 52
  • P.S. kudos to [kenwarner](http://stackoverflow.com/users/55948/kenwarner) for [this answer](http://stackoverflow.com/a/2052782/248406) regarding CommandParameter in MenuItem – alex.b Apr 21 '17 at 17:30
  • I don't see how the contextMenu actually gets displayed on right-click. – Scott Hutchinson Sep 27 '22 at 21:38
5

You should put a context menu inside the resources of the button, like this

<NewConnectionButton.Resources>
    <ContextMenu x:Key="connectionButtonContext"  StaysOpen="true">
        <MenuItem Header="Add" Click="InternalAddButton_Click"/>
        <MenuItem Header="Delete" Click="InternalDeleteButton_Click"/>
        <MenuItem Header="Edit" Click="InternalEditButton_Click"/>
    </ContextMenu>
</NewConnectionButton.Resources>

This code should be inside the NewConnectionButton UserControl. In the UserControl C# code, subscribe to those events and expose them (InternalAddButton_Click, InternalDeleteButton_Click, InternalEditButton_Click) to who uses the Button. Then, subscribe to them in your MainWindow.

eHayik
  • 2,981
  • 1
  • 21
  • 33
aperezfals
  • 1,341
  • 1
  • 10
  • 26