StatusBar ContextMenu Position

StatusBar ContextMenu Position

Anonymous
Not applicable
15,154 Views
23 Replies
Message 1 of 24

StatusBar ContextMenu Position

Anonymous
Not applicable

Hello,

 

I have created a TrayItem that exists in the StatusBar; i use it to notify the user of activity via the the balloon notifications. I know want it as the source of an AboutBox, which I want via a right-click context menu as is traditional.

 

I have the following:

 

Snippet

                     ...
trayItem.MouseDown += TrayItem_MouseDown;                 }                 return trayItem;             }         }         private static void TrayItem_MouseDown(object sender, StatusBarMouseDownEventArgs e)         {             if (e.Button == WinForms.MouseButtons.Right)             {                 WinForms.ContextMenu contextMenu = new WinForms.ContextMenu();                 WinForms.MenuItem about = new WinForms.MenuItem("About...");                 contextMenu.MenuItems.Add(about);                 trayItem.DisplayContextMenu(contextMenu, new Point(e.X, e.Y));             }         }

Which I have found scouring the internet. And it does create and display my context menu with About option.

 

However, it displays it in relation to the origin at top left corner(ish) of the whole status bar, when the StatusBarMouseDownEventArgs X and Y values are in relation to the top left of the TrayItem "window".

 

What am I missing?

 

Secondly, the Autodesk.AutoCad.Windows namespace has it's own menu classes but DisplayContextMenu demands a System.Windows.Forms class, so the context menu will not keep the look and feel of AutoCAD.

 

If anybody knows their way around this area, that would be incredibly stress reducing for me.

 

Thanks.

0 Likes
15,155 Views
23 Replies
Replies (23)
Message 2 of 24

moogalm
Autodesk Support
Autodesk Support

Hi,

 

Can you please explain in detail what is your expected behavior, and also it helps us if you are able to give non-confidential project and steps to reproduce the behaviors.

 

Most of the times  code snippets will not help us to understand the context in which your code executes.

 

May be it will help other experts in this forum to get clue or hint whats going wrong.

0 Likes
Message 3 of 24

Virupaksha_aithal
Autodesk Support
Autodesk Support

Hi,

 

try using "Cursor.Position" 

 

void _ti_MouseDown(object sender, StatusBarMouseDownEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    {
        _ti.DisplayContextMenu(m_ContextMenu, Cursor.Position);
    }
}

full sample code

 

 public class MyCommands
    {
        TrayItem _ti;
        System.Windows.Forms.ContextMenu m_ContextMenu = null;
        System.Windows.Forms.MenuItem _mi = null;

        [CommandMethod("TestTray", CommandFlags.Modal)]
        public void TestTrayMethod()
        {
            if (m_ContextMenu == null)
            {
                m_ContextMenu = new System.Windows.Forms.ContextMenu();
                m_ContextMenu.Name = "MyMenu";

                _mi = new System.Windows.Forms.MenuItem();
                _mi.Click += mi_Click;
                _mi.Name = "TestClick 1";
                _mi.Text = "TestClick 1";
                m_ContextMenu.MenuItems.Add(_mi);

                _mi = new System.Windows.Forms.MenuItem();
                _mi.Click += mi_Click2;
                _mi.Name = "TestClick2";
                _mi.Text = "TestClick2";
                m_ContextMenu.MenuItems.Add(_mi);
                
            }

            Autodesk.AutoCAD.Windows.StatusBar sb = Autodesk.AutoCAD.ApplicationServices.Application.StatusBar;
            TrayItemCollection tic = sb.TrayItems;
            if (_ti == null)
            {
                _ti = new TrayItem();
                _ti.Icon = System.Drawing.SystemIcons.Asterisk;
                _ti.ToolTipText = "Tooltip";
                _ti.MouseDown += _ti_MouseDown;
                tic.Add(_ti);

                _ti.Visible = true;
                sb.Update();
            }
        }

        void _ti_MouseDown(object sender, StatusBarMouseDownEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                _ti.DisplayContextMenu(m_ContextMenu, Cursor.Position);
            }
        }

        void mi_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Test click 1");
        }
        void mi_Click2(object sender, EventArgs e)
        {
            MessageBox.Show("Test click 2");
        }
    }


Virupaksha Aithal KM
Developer Technical Services
Autodesk Developer Network

0 Likes
Message 4 of 24

Anonymous
Not applicable

I thought that was it!

 

However, I then get this (bottom of AutoCAD window top left):

Untitled.png

 

Cursor.Position always seems to be (~500, ~500), but because the Point p parameter of DisplayContextMenu is in relation to the StatusBar window, the menu appears as above.

 

Hopefully this makes it clear as to the problem in my original post.

 

How do I get the position of the cursor within the region of the StatusBar window, when I right click on my tray item.

 

Using StatusBarMouseDownEventArgs e.X and e.Y give the coordinates of the cursor within the TrayItem icon i.e. x and y are between (0,0) and (15, 15). In that regard the context menu appears here:

 

2017-07-28 10_17_06-Autodesk AutoCAD 2017 - [Drawing1.dwg].png

 

Thanks.

0 Likes
Message 5 of 24

ActivistInvestor
Mentor
Mentor

Have you tried looking at the StatusBarItem's PointToClient() and PointToScreen() methods?

0 Likes
Message 6 of 24

Anonymous
Not applicable

Yeh, I did find those methods, however, they then related the point from the event to the AutoCAD window, so the menu would appear at the top of the screen.

0 Likes
Message 7 of 24

Virupaksha_aithal
Autodesk Support
Autodesk Support

Hi,

 

I have reported this behavior to AutoCAD engineering team. 



Virupaksha Aithal KM
Developer Technical Services
Autodesk Developer Network

0 Likes
Message 8 of 24

Anonymous
Not applicable

Further to the above.

 

I've just tried using the Cursor.position point as the parameter to the trayItem.PointToClient method and we are a step closer. This then does pop the context menu up with the mouse point at the top-left hand corner of the context menu, see image below.

 

I'm still interested in how to create a AutoCAD styled context as can be seen by right clicking on any of the standard tray items. I cannot find anything online:

 

AutoCAD: AutoCAD context menu.jpg             Custom: Custom.jpg

 

I am using a WinForms context menu so the style is understandable. Is there some AutoCAD API data methods for this. Is this described in any AutoCAD .NET help text - I've tried to search in the .Net Dev Guide with no luck.

0 Likes
Message 9 of 24

chris.bertschy
Participant
Participant

Hi,

I have been going a bit crazy lately trying to hook up a context menu to a Pane using the same method outlined in this thread.  I doubled back and used the identical code posted here using the tray icon.  All works well with the exception that the click event will not fire for any menu items added to the context menu.  Following microsoft support, I made sure not to add any sub menu items which turn off the the click event, yet no success.  I have implemented many different methods for adding the items.  All with no success.  Neither the Pane nor a Tray Item will allow a click event on the context menu.

 

Currently I have the exact code posted here and get the same result.  I am at a total loss for getting it to work.  I have even implemented a custom item implementing a click event and still can not get it to fire.

 

I am currently building and testing on civil3d 2015.  Could this be known bug?

 

Thank you in advanced.

 

Chris

0 Likes
Message 10 of 24

ActivistInvestor
Mentor
Mentor

@chris.bertschy wrote:

Hi,

I have been going a bit crazy lately trying to hook up a context menu to a Pane using the same method outlined in this thread.  I doubled back and used the identical code posted here using the tray icon.  All works well with the exception that the click event will not fire for any menu items added to the context menu.  Following microsoft support, I made sure not to add any sub menu items which turn off the the click event, yet no success.  I have implemented many different methods for adding the items.  All with no success.  Neither the Pane nor a Tray Item will allow a click event on the context menu.

 

Currently I have the exact code posted here and get the same result.  I am at a total loss for getting it to work.  I have even implemented a custom item implementing a click event and still can not get it to fire.

 

I am currently building and testing on civil3d 2015.  Could this be known bug?

 

Thank you in advanced.

 

Chris


I know you said you're using the exact code posted here, but after looking at it, there doesn't seem to be any problems with that code.

 

So, I don't know if anyone can help you without seeing your exact code attempt.  If you add the menu items to the context menu and set their Click handler, the click event should fire.

0 Likes
Message 11 of 24

chris.bertschy
Participant
Participant

Yes, I agree, I saw it and said the same thing.  It looks like every example I found from many sources.  But no matter how I add a context menu to anything, the click event will not fire.    Below is the code.  It went through a hatchet job with hours of abuse trying many different methods not shown below.  It all works as expected with using either a Pane added to the status bar or the Tray Item.  Currently it is set to use the tray. Other code I have tried include using  StartusBar.MenuItem ; Controls.Context menu instead of windows.form.contextmenu.  Created custom implementation of menuitems with my own events.  All worked but just can't get the context menu to fire.  I was hoping there was some small tidbit of understanding I missed or acknowledgement of a bug with this in civil3d 2015.  

 

    public class LoggerPane
    {
        private Pane _pane; // not using this at the moment
        private Icon iconLoggerON;
        private Icon iconLoggerOff;
        private TrayItem _ti;

        private System.Windows.Forms.ContextMenu _menu = null;

        public LoggerPane()
        {
            pdBaseSettings.Default.LoggerStatusChanged += new LoggerStatusEventHandler(LoggerStatus_Update);

            iconLoggerON = Properties.Resources.LoggerOn;
            iconLoggerOff = Properties.Resources.LoggerOff;

            /* No longer using pane
            _pane = new Pane();
            _pane.Enabled = true;
            _pane.Visible = true;
            _pane.Style = PaneStyles.Normal;
            _pane.Icon = pdBaseSettings.Default.Logger_On ? iconLoggerON: iconLoggerOff;
            _pane.ToolTipText = "Autocad DWG\n Logger";
            _pane.MouseDown += new StatusBarMouseDownEventHandler(callback_MouseDown);
            */

            this.loadContext();

            Autodesk.AutoCAD.Windows.StatusBar sb = Autodesk.AutoCAD.ApplicationServices.Application.StatusBar;
            TrayItemCollection tic = sb.TrayItems;
            
            if (_ti == null)
            {
                _ti = new TrayItem();
                _ti.Icon = pdBaseSettings.Default.Logger_On ? iconLoggerON : iconLoggerOff;
                _ti.ToolTipText = "Autocad DWG\n Logger";
                _ti.MouseDown += new StatusBarMouseDownEventHandler(callback_MouseDown);
                tic.Add(_ti);
                _ti.Visible = true;
                sb.Update();
            }
            
            //App.Application.StatusBar.Panes.Insert(0,_pane);

        }

        public void loadContext()
        {
            //TODO: remove checked initializing to OnPopUp. Better suited for handeling current checked status.

            _menu = new System.Windows.Forms.ContextMenu();

            System.Windows.Forms.MenuItem menu_On = new System.Windows.Forms.MenuItem();
            menu_On.Text = "Logger On";
            menu_On.Checked = pdBaseSettings.Default.Logger_On;
            menu_On.Click += menu_Settings_Click;
            //menu_On.Index = 0;

            //System.Windows.Forms.MenuItem menu_Off = new System.Windows.Forms.MenuItem();
            //menu_Off.Text = "Logger Off";
            //menu_Off.Checked = !pdBaseSettings.Default.Logger_On;
            ////menu_Off.Index = 1;

            //System.Windows.Forms.MenuItem seperator = new System.Windows.Forms.MenuItem();
            //seperator.Text = "_";
            //seperator.Index = 2;

            //System.Windows.Forms.MenuItem menu_settings = new System.Windows.Forms.MenuItem();
            //menu_settings.Text = "Settings";
            //menu_settings.Index = 3;
            //menu_settings.Click += menu_Settings_Click;

            _menu.MenuItems.Add(menu_On);

            //_menu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { menu_On, menu_Off, seperator, menu_settings });

        }

    
        private string getImagePath()
        {
            try
            {
            string _dllPath = typeof(LoggerPane).Assembly.Location;
            string _ResourcePath = Path.Combine(new FileInfo(_dllPath).Directory.FullName , "Images");
            return _ResourcePath;
            }
            catch{ return null;}
        }

        private void LoggerStatus_Update(object sender, EventArgs e, bool newstate)
        {
            _pane.Icon = newstate ? iconLoggerON : iconLoggerOff;
            App.Application.StatusBar.Update();
        }

        void menu_OnoFF_MouseClick (object sender, EventArgs e)
        {
            MessageBox.Show("IT WORKED!!!");
        }

        void menu_Settings_Click (object sender, EventArgs e)
        {
            MessageBox.Show("IT WORKED!!!");
        }


        private void callback_MouseDown(object sender, StatusBarMouseDownEventArgs e)
        {
            try
            {
                if(e.DoubleClick || e.Button == MouseButtons.Right)
                {


                    _ti.DisplayContextMenu(_menu, Cursor.Position);
                    //_pane.DisplayContextMenu(_menu, Cursor.Position);
                }

                else
                {
                    //set the base settings of the logger. The event will trigger the botton change
                    pdBaseSettings.Default.Logger_On = !pdBaseSettings.Default.Logger_On;
                    pdBaseSettings.Default.Save();
                    // refire the database and event triggers. Check if log events exist.  If not, make it, then run the shutdown or turn on.
                    // this ensure the logger events has a chance to clean up any delagate trigger issues
                    if (Logger.logevents == null)
                        Logger.logevents = new LoggerEvents();

                    Logger.logevents.LoggerStatus(pdBaseSettings.Default.Logger_On);

                    LoggerServices.throwLoggerNotify();

                }
            }

            catch
            {
                MessageBox.Show("WHoopsie! \n Somehting is wrong with the Logger Settings");
            }
        }

    }
0 Likes
Message 12 of 24

Anonymous
Not applicable

This is code I use, for invoking an About dialog. This works. I hope it can help.

I assume you've tried without the

new StatusBarMouseDownEventHandler

 and just assigning it a method name as in examples in this thread?

 

Otherwise looks fine. Are you sure the menu isn't appearing somewhere else on screen, because the use of Cursor.Position directly does not invoke the menu at the click site. See where I have used PointToClient function.

 

private static TrayItem TrayItem
{
	get
	{
		if (trayItem == null)
		{
			trayItem = new TrayItem();
			trayItem.Icon = new Icon((Icon)Resources.ResourceManager.GetObject("StatusBarTrayIcon"), 16, 16);
			trayItem.MouseDown += TrayItem_MouseDown;
		}

		return trayItem;
	}
}

public static void WriteStatusBubble(string title, string message, IconType iconType)
{
	TrayItemBubbleWindow bw = new TrayItemBubbleWindow();
	bw.Title = title;
	bw.Text = message;
	bw.IconType = iconType;

	TrayItem.CloseBubbleWindows();
	TrayItem.ShowBubbleWindow(bw);
	Application.StatusBar.Update();
}

public static void InitialiseTrayItem()
{
	Document doc = Application.DocumentManager.MdiActiveDocument;

	TrayItemCollection collection = Application.StatusBar.TrayItems;
	if (!collection.Contains(TrayItem))
	{
		collection.Add(TrayItem);
	}
}

public static void RemoveTrayItem()
{
	TrayItemCollection collection = Application.StatusBar.TrayItems;
	if (!collection.Contains(TrayItem))
	{
		collection.Remove(TrayItem);
	}
}

private static void TrayItem_MouseDown(object sender, StatusBarMouseDownEventArgs e)
{
	if (e.Button == WinForms.MouseButtons.Right)
	{
		WinForms.ContextMenu contextMenu = new WinForms.ContextMenu();

		WinForms.MenuItem about = new WinForms.MenuItem("About...");
		about.Click += About_Click;

		contextMenu.MenuItems.Add(about);

		Point point = trayItem.PointToClient(WinForms.Cursor.Position);
		trayItem.DisplayContextMenu(contextMenu, point);
	}
}

private static void About_Click(object sender, System.EventArgs e)
{
	Application.ShowModalDialog(new AboutBox());
} 

 

Message 13 of 24

chris.bertschy
Participant
Participant

Thank you Ryan,

I was curious if you had trouble with it.  I was not sure if you were still working through the position of the context menu and how you handled it.  It did try removing the '

new StatusBarMouseDownEventHandler

I also tried using a custom handler with a custom menu items class.  Oddly enough, if I call preformclick() on the menu item, it works.  I can even add sub menu's and it does roll out with the sub menu items, but no click response on those as well.  Very strange.  I shudder at having to add a WPF form to the project and use it instead of the context menu. 

 

It would also be nice if the look of the context menu matched that of the 'out of the box' status bar menu's.  

0 Likes
Message 14 of 24

ActivistInvestor
Mentor
Mentor

@chris.bertschy wrote:

Thank you Ryan,

I was curious if you had trouble with it.  I was not sure if you were still working through the position of the context menu and how you handled it.  It did try removing the '

new StatusBarMouseDownEventHandler

I also tried using a custom handler with a custom menu items class.  Oddly enough, if I call preformclick() on the menu item, it works.  I can even add sub menu's and it does roll out with the sub menu items, but no click response on those as well.  Very strange.  I shudder at having to add a WPF form to the project and use it instead of the context menu. 

 

It would also be nice if the look of the context menu matched that of the 'out of the box' status bar menu's.  


What isn't shown in your code, is the execution context. Where is your class's constructor called from?  is it called in the application context or the document context?  You can check DocumentCollection.IsApplicationContext to be sure. It may be that the context menu needs to be created in the application context.

0 Likes
Message 15 of 24

chris.bertschy
Participant
Participant

The project that all of this resides in is over 4,000 lines of code.  But for brevity, it loads the status bar items through the initialization of IExtensionApplication.  So I would definitely say that it is using the application context for loading.  

 

    public class pdApplication : IExtensionApplication
    {
        
        #region IExtensionApplication Members

        void IExtensionApplication.Initialize()
        {
 
#if DATA_LOGGER

            try
            {
                Logger.LoggerStatusBar = new LoggerPane();
               <misc code here>........................................................
             .........................................................
            {

Logger.LoggerStatusBar is a public static member with getter and setter that is back by a private field.  All works, just the click event will not fire.  As if it was turned off at the menuitem component level when subitems are present.

 

 public static class Logger
    {
        private static LoggerPane _LoggerStatusBar;
        public static LoggerPane LoggerStatusBar
        {
            get { return _LoggerStatusBar; }
            set { _LoggerStatusBar = value;}
        }
    
<more code>........................

     }

 

 

0 Likes
Message 16 of 24

ActivistInvestor
Mentor
Mentor

 

Initialize() can be called in either execution context.  It depends on how the assembly is loaded. 


@chris.bertschy wrote:

The project that all of this resides in is over 4,000 lines of code.  But for brevity, it loads the status bar items through the initialization of IExtensionApplication.  So I would definitely say that it is using the application context for loading.  

 

    public class pdApplication : IExtensionApplication
    {
        
        #region IExtensionApplication Members

        void IExtensionApplication.Initialize()
        {
 
#if DATA_LOGGER

            try
            {
                Logger.LoggerStatusBar = new LoggerPane();
               <misc code here>........................................................
             .........................................................
            {

Logger.LoggerStatusBar is a public static member with getter and setter that is back by a private field.  All works, just the click event will not fire.  As if it was turned off at the menuitem component level when subitems are present.

 

 public static class Logger
    {
        private static LoggerPane _LoggerStatusBar;
        public static LoggerPane LoggerStatusBar
        {
            get { return _LoggerStatusBar; }
            set { _LoggerStatusBar = value;}
        }
    
<more code>........................

     }

 

 


 

0 Likes
Message 17 of 24

chris.bertschy
Participant
Participant

The .net DLL autoloads and the IExtensionApplication.Initialize() is run for the session or the life of the DLL being loaded.  At least that is how I have understood it and they way it has been behaving.  I

 

did do something a bit dumb by using a private static field to back the property.  I figured a static auto property would have created a behind the scene private field anyway so I just implemented it.  Probably doesn't need to be static as it is part of the loaded application.

 

I am going to remove it and let change it to a static auto property and see if it helps.

0 Likes
Message 18 of 24

Anonymous
Not applicable

Hey,

 

To confirm, I call InitialiseTrayItem() from the IExtensionApplication.Intiallize() method as you do...

 

Have you tried creating the tray item as a static member? Maybe it's getting destroyed.

 

Have you tried creating the context menu everytime? within the callback method, as it's required?

 

I feel your struggle with AutoCAD API, it can be frustrating.

0 Likes
Message 19 of 24

chris.bertschy
Participant
Participant

Ryan,

Yes, I tried creating the context menu on the fly on the initial Tray item click.  Same result.  I second that it must be destroying the handlers added to the event.  Makes since because it does disappear on click where as if it was disabled, I would imagine it would stay on the screen.  Super frustrating.

 

I lack the debug know how to watch a delegate or system event in action.

0 Likes
Message 20 of 24

ActivistInvestor
Mentor
Mentor

You can try using the following to initialize your application.

 

It is designed to ensure that regardless of how the assembly is loaded, the Initialize() method always runs in the application context.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;

namespace Autodesk.AutoCAD.Runtime
{
   /// <summary>
   /// Provides basic services for initialization and termination
   /// of extension applications.
   /// </summary>
   /// 
   /// <remarks>This class can be used as the base type for classes 
   /// that would otherwise directly implement IExtensionApplication, 
   /// to get control when the containing assembly is loaded, and 
   /// when AutoCAD is about to shut down.
   /// 
   /// It provides these basic services:
   /// 
   ///   1. Defers the call to the Initialize() method to ensure
   ///      that it always executes in the application context,
   ///      and only after AutoCAD has been fully-initialized,
   ///      avoiding the need to defer Ribbon-related operations.
   ///      
   ///   2. Handles exceptions raised during execution of the
   ///      Initialize() method, and displays the exception
   ///      message on the AutoCAD command line.
   ///      
   /// To use this class, derive a class from it and override the
   /// Initialize() method, which is the functional equivalent of 
   /// the IExtensionApplication.Initialize() method. Optionally,
   /// override the Terminate() method to get control when AutoCAD
   /// is about to shut down. 
   /// 
   /// Then apply the [assembly:ExtensionApplication] attribute to 
   /// the assembly containing the derived class, and specify that 
   /// derived class in the attribute.
   /// </remarks>
   
   public abstract class ExtensionApplication : IExtensionApplication
   {
      protected abstract void Initialize();

      protected virtual void Terminate()
      {
      }

      void IExtensionApplication.Initialize()
      {
         Application.Idle += idle;
      }

      void idle(object sender, EventArgs e)
      {
         Application.Idle -= idle;
         try
         {
            this.Initialize();
         }
         catch(System.Exception ex)
         {
            var filename = System.IO.Path.GetFileName(this.GetType().Assembly.Location);
            WriteMessage("\nError loading {0}:\n\n{1}", filename, ex.Message);
            throw ex;
         }
      }

      void IExtensionApplication.Terminate()
      {
         this.Terminate();
      }

      public static void WriteMessage(string fmt, params object[] args)
      {
         Document doc = Application.DocumentManager.MdiActiveDocument;
         if(doc != null)
            doc.Editor.WriteMessage(fmt, args);
      }
   }
}