Launch window's System Menu on custom window
Asked Answered
H

3

10

I would like to call the ContextMenu when you click on application's icon or right mouse click on the title bar of application.

This is the ContextMenu I mean:

enter image description here

I need it because I made custom control that acts like window.
I need this behavior to complete my control.

EDIT:
Leo Lorenzo Luis asked for me code:

https://skydrive.live.com/?cid=c3392940f5cf5f74&id=C3392940F5CF5F74%21107&authkey=!APd2X3tDxWRfpL4

or:

My MainWindow.xaml:

    <!--<Grid>
        <Border Name="TopBorder" BorderThickness="0.5,0,0,0" BorderBrush="Blue"/>
        <Border Name="RightBorder" BorderThickness="0,0.5,0,0" BorderBrush="Red"/>
        <Border Name="BottomBorder" BorderThickness="0,0,0.5,0" BorderBrush="Green"/>
        <Border Name="LeftBorder" BorderThickness="0,0,0,0.5" BorderBrush="Orange"/>
        <Grid Margin="0.5">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <StatusBar Background="Transparent" MouseDoubleClick="TriggerMaximize" MouseDown="StatusBar_MouseDown">
                    <Image Margin="5,0,0,0" VerticalAlignment="Center" Width="16" Height="16" Source="{Binding Icon, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" RenderOptions.BitmapScalingMode="NearestNeighbor" RenderOptions.EdgeMode="Aliased"/>
                    <Label VerticalAlignment="Center" FontSize="14" Content="{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
                </StatusBar>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="1">
                    <Button x:Name="Minimize" ToolTip="Minimize" Content="0" Style="{DynamicResource TitleBarButton}" Click="TriggerMinimize"/>
                    <Button x:Name="Restore" ToolTip="Restore" Content="2" Style="{DynamicResource TitleBarButton}"  Visibility="Collapsed" Click="TriggerMaximize"/>
                    <Button x:Name="Maximize" ToolTip="Maximize" Content="1" Style="{DynamicResource TitleBarButton}" Click="TriggerMaximize"/>
                    <Button x:Name="Close" ToolTip="Close" Content="r" Style="{DynamicResource TitleBarButton}" Click="TriggerClose"/>
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>-->

    <DockPanel LastChildFill="true">
        <Border Name="TopBorder" DockPanel.Dock="Top" BorderBrush ="#007ACC" BorderThickness="0.5"/>
        <Border Name="RightBorder" DockPanel.Dock="Right" BorderBrush ="#007ACC" BorderThickness="0.5"/>
        <Border Name="BottomBorder" DockPanel.Dock="Bottom" BorderBrush ="#007ACC" BorderThickness="0.5"/>
        <Border Name="LeftBorder" DockPanel.Dock="Left" BorderBrush="#007ACC" BorderThickness="0.5"/>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <StatusBar Background="Transparent" MouseDoubleClick="TriggerMaximize" MouseDown="StatusBar_MouseDown">
                    <Image Margin="5,0,0,0" Width="16" Height="16" Source="{Binding Icon, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" RenderOptions.BitmapScalingMode="NearestNeighbor" RenderOptions.EdgeMode="Aliased"/>
                    <Label VerticalAlignment="Center" FontSize="14" Content="{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>
                </StatusBar>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="1">
                    <Button x:Name="Minimize" ToolTip="Minimize" Content="0" Style="{DynamicResource TitleBarButton}" Click="TriggerMinimize"/>
                    <Button x:Name="Restore" ToolTip="Restore" Content="2" Style="{DynamicResource TitleBarButton}"  Visibility="Collapsed" Click="TriggerMaximize"/>
                    <Button x:Name="Maximize" ToolTip="Maximize" Content="1" Style="{DynamicResource TitleBarButton}" Click="TriggerMaximize"/>
                    <Button x:Name="Close" ToolTip="Close" Content="r" Style="{DynamicResource TitleBarButton}" Click="TriggerClose"/>
                </StackPanel>
            </Grid>
        </Grid>
    </DockPanel>
</Window>

My MainWindow.cs (Code-Behind):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.Windows.Forms;

namespace WpfApplication16
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.SourceInitialized += new EventHandler(win_SourceInitialized);
        }

        private void TriggerMaximize(object sender, MouseButtonEventArgs e)
        {
            TriggerMaximize();
        }

        private void TriggerMaximize(object sender, RoutedEventArgs e)
        {
            TriggerMaximize();
        }

        private void TriggerMaximize()
        {
            if (WindowState == System.Windows.WindowState.Maximized)
            {
                WindowState = System.Windows.WindowState.Normal;
                Restore.Visibility = Visibility.Collapsed;
                Maximize.Visibility = Visibility.Visible;
            }
            else if (WindowState == System.Windows.WindowState.Normal)
            {
                WindowState = System.Windows.WindowState.Maximized;
                Maximize.Visibility = Visibility.Collapsed;
                Restore.Visibility = Visibility.Visible;
            }
        }

        private void Window_LocationChanged(object sender, EventArgs e)
        {
            TriggerBorderChanges();
        }


        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            TriggerBorderChanges();
        }

        private void TriggerBorderChanges()
        {
            TopBorder.BorderThickness = new Thickness(0.5);
            RightBorder.BorderThickness = new Thickness(0.5);
            BottomBorder.BorderThickness = new Thickness(0.5);
            LeftBorder.BorderThickness = new Thickness(0.5);

            if (Top == 0)
            {
                TopBorder.BorderThickness = new Thickness(0);
                BottomBorder.BorderThickness = new Thickness(0);
            }

            if (Left == 0)
            {
                LeftBorder.BorderThickness = new Thickness(0);
            }

            // need to test in dual view -if not needed, remove drawing and windows.forms (from refereance and from the using)
            //Screen currentScreen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
            //if (Left == (currentScreen.WorkArea.Width - Width))
            if (Left == (System.Windows.SystemParameters.WorkArea.Width - 1 - Width))
            {
                RightBorder.BorderThickness = new Thickness(0);
            }
        } 

        private void TriggerClose(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void TriggerMinimize(object sender, RoutedEventArgs e)
        {
            WindowState = System.Windows.WindowState.Minimized;
        }


        private void StatusBar_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
                this.DragMove();
        }


        void win_SourceInitialized(object sender, EventArgs e)
        {
            System.IntPtr handle = (new WindowInteropHelper(this)).Handle;
            HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(WindowProc));
        }

        /// <summary>
        /// POINT aka POINTAPI
        /// </summary>5
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            /// <summary>
            /// x coordinate of point.
            /// </summary>
            public int x;
            /// <summary>
            /// y coordinate of point.
            /// </summary>
            public int y;

            /// <summary>
            /// Construct a point of coordinates (x,y).
            /// </summary>
            public POINT(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MINMAXINFO
        {
            public POINT ptReserved;
            public POINT ptMaxSize;
            public POINT ptMaxPosition;
            public POINT ptMinTrackSize;
            public POINT ptMaxTrackSize;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class MONITORINFO
        {
            /// <summary>
            /// </summary>            
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));

            /// <summary>
            /// </summary>            
            public RECT rcMonitor = new RECT();

            /// <summary>
            /// </summary>            
            public RECT rcWork = new RECT();

            /// <summary>
            /// </summary>            
            public int dwFlags = 0;
        }

        /// <summary> Win32 </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct RECT
        {
            /// <summary> Win32 </summary>
            public int left;
            /// <summary> Win32 </summary>
            public int top;
            /// <summary> Win32 </summary>
            public int right;
            /// <summary> Win32 </summary>
            public int bottom;

            /// <summary> Win32 </summary>
            public static readonly RECT Empty = new RECT();

            /// <summary> Win32 </summary>
            public int Width
            {
                get { return Math.Abs(right - left); }  // Abs needed for BIDI OS
            }

            /// <summary> Win32 </summary>
            public int Height
            {
                get { return bottom - top; }
            }

            /// <summary> Win32 </summary>
            public RECT(int left, int top, int right, int bottom)
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }

            /// <summary> Win32 </summary>
            public RECT(RECT rcSrc)
            {
                this.left = rcSrc.left;
                this.top = rcSrc.top;
                this.right = rcSrc.right;
                this.bottom = rcSrc.bottom;
            }

            /// <summary> Win32 </summary>
            public bool IsEmpty
            {
                get
                {
                    // BUGBUG : On Bidi OS (hebrew arabic) left > right
                    return left >= right || top >= bottom;
                }
            }

            /// <summary> Return a user friendly representation of this struct </summary>
            public override string ToString()
            {
                if (this == RECT.Empty) { return "RECT {Empty}"; }
                return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
            }

            /// <summary> Determine if 2 RECT are equal (deep compare) </summary>
            public override bool Equals(object obj)
            {
                if (!(obj is Rect)) { return false; }
                return (this == (RECT)obj);
            }

            /// <summary>Return the HashCode for this struct (not garanteed to be unique)</summary>
            public override int GetHashCode()
            {
                return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
            }

            /// <summary> Determine if 2 RECT are equal (deep compare)</summary>
            public static bool operator ==(RECT rect1, RECT rect2)
            {
                return (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom);
            }

            /// <summary> Determine if 2 RECT are different(deep compare)</summary>
            public static bool operator !=(RECT rect1, RECT rect2)
            {
                return !(rect1 == rect2);
            }
        }

        [DllImport("user32")]
        internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

        [DllImport("User32")]
        internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

        private static System.IntPtr WindowProc(
                System.IntPtr hwnd,
                int msg,
                System.IntPtr wParam,
                System.IntPtr lParam,
                ref bool handled)
        {
            switch (msg)
            {
                case 0x0024:
                    WmGetMinMaxInfo(hwnd, lParam);
                    handled = true;
                    break;
            }

            return (System.IntPtr)0;
        }

        private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
        {
            MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

            // Adjust the maximized size and position to fit the work area of the correct monitor
            int MONITOR_DEFAULTTONEAREST = 0x00000002;
            System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitor != System.IntPtr.Zero)
            {

                MONITORINFO monitorInfo = new MONITORINFO();
                GetMonitorInfo(monitor, monitorInfo);
                RECT rcWorkArea = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;
                mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
            }

            Marshal.StructureToPtr(mmi, lParam, true);
        }
    }
}

My App.xaml:

<Application
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero" x:Class="WpfApplication16.App"
             StartupUri="MainWindow.xaml">
    <Application.Resources>


        <Style x:Key="TitleBarButton" TargetType="Button">
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Padding" Value="12,7"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="FontFamily" Value="Marlett"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
                            <Grid>
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" TextBlock.FontFamily="{TemplateBinding FontFamily}" TextBlock.FontSize="{TemplateBinding FontSize}" />
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#EFEFF2" />
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Background" Value="#007ACC"/>
                                <Setter Property="Foreground" Value="White"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </Application.Resources>
</Application>

Would appreciate your help.

Hadhramaut answered 16/10, 2013 at 17:38 Comment(3)
In your custom control like the image you shared, can you make the Visual Studio (icon) a button and show the ContextMenu from there when it is clicked?Bluebill
Yes. I just need to know how to call that specific ContextMenu. Do I need to make one myself or I can call directly this one? (my control is based on window control)Hadhramaut
please remove the extra code from your question.. this will help others getting the question quicklyBestir
B
11

The menu that you want to show is system ContextMenu. To work with that you need to import some user32 functions as shown in the code below. I have launched the system menu on button click. You can launch it on any action, right mouse click etc

GetSystemMenu gets the system menu and TrackPopupMenuEx is used to display it. PostMessage is the send system command on menuitem click.

public partial class Window3 : Window
{

     private const int WM_SYSCOMMAND = 0x112;
    uint TPM_LEFTALIGN = 0x0000;
    uint TPM_RETURNCMD = 0x0100;
     const UInt32 MF_ENABLED = 0x00000000;
     const UInt32 MF_GRAYED = 0x00000001;
     internal const UInt32 SC_MAXIMIZE = 0xF030;
     internal const UInt32 SC_RESTORE = 0xF120;

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll")]
    static extern int TrackPopupMenuEx(IntPtr hmenu, uint fuFlags,
      int x, int y, IntPtr hwnd, IntPtr lptpm);

    [DllImport("user32.dll")]
    public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem,
       uint uEnable);

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        WindowInteropHelper helper = new WindowInteropHelper(this);
        IntPtr callingWindow = helper.Handle;
        IntPtr wMenu = GetSystemMenu(callingWindow, false);
        // Display the menu
        if (this.WindowState == System.Windows.WindowState.Maximized)
        {
            EnableMenuItem(wMenu, SC_MAXIMIZE, MF_GRAYED);
        }
        else
        {
            EnableMenuItem(wMenu, SC_MAXIMIZE, MF_ENABLED);
        }

        int command = TrackPopupMenuEx(wMenu, TPM_LEFTALIGN | TPM_RETURNCMD, 100, 100, callingWindow, IntPtr.Zero);
        if (command == 0)
            return;

        PostMessage(callingWindow, WM_SYSCOMMAND, new IntPtr(command), IntPtr.Zero);
    }
Bestir answered 16/10, 2013 at 18:13 Comment(4)
Getting very close to what I want. I have one problem - the maximize item is enabled even when the window is maximized, same about restore (when it is not maximized) - how can I force it to act as it should?Hadhramaut
updated answer for maximize menuitem enable/disable...similarly you will need handle other itemsBestir
Works great, although I hoped it will detect automatically but whatever as long as it works. Thank you very much!Hadhramaut
is there a way to open the menu in the default position?Dharma
B
0

In your custom control like the image you shared, make the Visual Studio (icon) a imagebutton/button and show the ContextMenu from there when it is clicked.

<Button Click="SomeEventHandler">
  <Button.ContextMenu>
     <ContextMenu>
      <!-- DO WHATEVER -->
     </ContextMenu>
  </Button.ContextMenu>
</Button>

Then on the Click handler, just say buttonName.ContextMenu.IsOpen = true

More on how you can achieve this can be found here

There is already a dependency property that you can set to show the context menu. I am not sure what you mean by "Do you need to make one yourself or you can call directly this one"

Edit: I don't understand though why you are recreating the Window behavior instead of inheriting your custom window to the Window class and overwrite what you need to customize.

Bluebill answered 16/10, 2013 at 17:59 Comment(4)
Yes, I can see you didnt understand me properly. I know how to make ContextMenu and call it. I dont want to make my own ContextMenu but to use the window's ContextMenu (with the restore, move, size etc... items) - I am pretty sure there's a way but I dont know how..Hadhramaut
Saw it, 1st I am not sure I know how to inherit. 2nd I overwritten the whole design (made Window based on Windows 8 Design #19335843 - cat take something pre-made because their target is framework 4+ so I had to make my own) + overritten some of the functions (for example: add borders when not maximized)Hadhramaut
Can you show me your custom control? Both the XAML and behind the code if you have one.Bluebill
skydrive.live.com/… also edited my first postHadhramaut
I
0

If you are doing an MVVM application, in the ViewModel you just need to declare the property of the window and your command:

public Window Window { get; set; }
public ICommand MenuCommand { get; set; }

Then a Point property that gets the position of the mouse cursor relative to the screen:

public Point MousePosition
    {
        get
        {
            return Application.Current.MainWindow.PointToScreen(Mouse.GetPosition(Window));
        }
        
    }

And for last in the constructor of your class:

public WindowViewModel(Window window)
{
    MenuCommand = new RelayCommand(() => SystemCommands.ShowSystemMenu(Window, MousePosition));
}

In my RelayCommand class I'm just using the ICommand Interface that executes an action as follow:

public class RelayCommand : ICommand
{
    
    private Action mAction;
    
    public event EventHandler? CanExecuteChanged = (sender, e) => { };
    
    public RelayCommand(Action action)
    {
        mAction = action;
    }
    
    public bool CanExecute(object? parameter)
    {
        return true;
    }

    public void Execute(object? parameter)
    {
        mAction();
    }
}
Indigent answered 26/6, 2022 at 23:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.