Catch mouse events in PowerPoint designer through VSTO

前端 未结 2 946
孤独总比滥情好
孤独总比滥情好 2021-02-10 18:11

I am developing an add-in for PowerPoint (2013) with C# / VSTO. The add-in will work when the user is in design mode, not presentation mode.

How can I c

相关标签:
2条回答
  • 2021-02-10 18:26

    PowerPoint does not expose these events directly, but it is possible to implement your own events by combining global mouse hooks with shape parameters that PowerPoint expose.

    This answer covers the more difficult case with MouseEnter and MouseLeave. To handle other events such as MouseDown and MouseUp, you can make use of the provided PowerPoint event Application_WindowSelectionChange as Steve Rindsberg proposed.

    To get a global mouse hook, you can use excellent Application and Global Mouse and Keyboard Hooks .Net Libary in C# found at http://globalmousekeyhook.codeplex.com/

    You will need to download and reference the library, and then you set up the mouse hook by using this code

    // Initialize the global mouse hook
    _mouseHookManager = new MouseHookListener(new AppHooker());
    _mouseHookManager.Enabled = true;
    
    // Listen to the mouse move event
    _mouseHookManager.MouseMove += MouseHookManager_MouseMove;
    

    The MouseMove event could be set up to handle the mouse events like this (example with only MouseEnter and MouseLeave)

    private List<PPShape> _shapesEntered = new List<PPShape>();       
    private List<PPShape> _shapesOnSlide = new List<PPShape>();
    
    void MouseHookManager_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        // Temporary list holding active shapes (shapes with the mouse cursor within the shape)
        List<PPShape> activeShapes = new List<PPShape>();       
    
        // Loop through all shapes on the slide, and add active shapes to the list
        foreach (PPShapes in _shapesOnSlide)
        {
            if (MouseWithinShape(s, e))
            {
                activeShapes.Add(s);
            }
        }
    
        // Handle shape MouseEnter events
        // Select elements that are active but not currently in the shapesEntered list
        foreach (PPShape s in activeShapes)
        {
            if (!_shapesEntered.Contains(s))
            {
                // Raise your custom MouseEntered event
                s.OnMouseEntered();
    
                // Add the shape to the shapesEntered list
                _shapesEntered.Add(s);
            }
        }
    
        // Handle shape MouseLeave events
        // Remove elements that are in the shapes entered list, but no longer active
        HashSet<long> activeIds = new HashSet<long>(activeShapes.Select(s => s.Id));
        _shapesEntered.RemoveAll(s => {
            if (!activeIds.Contains(s.Id)) {
                // The mouse is no longer over the shape
                // Raise your custom MouseLeave event
                s.OnMouseLeave();
    
                // Remove the shape
                return true;
            }
            else
            {
                return false;
            }
        });
    }
    

    where the _shapesOnSlide is a list holding all the shapes on the current slide, _shapesEntered is a list with the shapes that have been entered but not yet left, and PPShape is a wrapper object for PowerPoint shapes (seen below)

    The MouseWithinShape method tests whether the mouse is currently within a shape on the slide. It does so by translating the shapes X- and Y-coordinates (which PowerPoint exposes in Points unit) to screen coordinates, and the tests whether the mouse is within that bounding box

    /// <summary>
    /// Test whether the mouse is within a shape
    /// </summary>
    /// <param name="shape">The shape to test</param>
    /// <param name="e">MouseEventArgs</param>
    /// <returns>TRUE if the mouse is within the bounding box of the shape; FALSE otherwise</returns>
    private bool MouseWithinShape(PPShape shape, System.Windows.Forms.MouseEventArgs e)
    {
        // Fetch the bounding box of the shape
        RectangleF shapeRect = shape.Rectangle;
    
        // Transform to screen pixels
        Rectangle shapeRectInScreenPixels = PointsToScreenPixels(shapeRect);
    
        // Test whether the mouse is within the bounding box
        return shapeRectInScreenPixels.Contains(e.Location);
    }
    
    /// <summary>
    /// Transforms a RectangleF with PowerPoint points to a Rectangle with screen pixels
    /// </summary>
    /// <param name="shapeRectangle">The Rectangle in PowerPoint point-units</param>
    /// <returns>A Rectangle in screen pixel units</returns>
    private Rectangle PointsToScreenPixels(RectangleF shapeRectangle)
    {
        // Transform the points to screen pixels
        int x1 = pointsToScreenPixelsX(shapeRectangle.X);
        int y1 = pointsToScreenPixelsY(shapeRectangle.Y);
        int x2 = pointsToScreenPixelsX(shapeRectangle.X + shapeRectangle.Width);
        int y2 = pointsToScreenPixelsY(shapeRectangle.Y + shapeRectangle.Height);
    
        // Expand the bounding box with a standard padding
        // NOTE: PowerPoint transforms the mouse cursor when entering shapes before it actually
        // enters the shape. To account for that, add this extra 'padding'
        // Testing reveals that the current value (in PowerPoint 2013) is 6px
        x1 -= 6;
        x2 += 6;
        y1 -= 6;
        y2 += 6;
    
        // Return the rectangle in screen pixel units
        return new Rectangle(x1, y1, x2-x1, y2-y1);
    
    }
    
    /// <summary>
    /// Transforms a PowerPoint point to screen pixels (in X-direction)
    /// </summary>
    /// <param name="point">The value of point to transform in PowerPoint point-units</param>
    /// <returns>The screen position in screen pixel units</returns>
    private int pointsToScreenPixelsX(float point)
    {
        // TODO Handle multiple windows
        // NOTE: PresStatic is a reference to the PowerPoint presentation object
        return PresStatic.Windows[1].PointsToScreenPixelsX(point);
    }
    
    /// <summary>
    /// Transforms a PowerPoint point to screen pixels (in Y-direction)
    /// </summary>
    /// <param name="point">The value of point to transform in PowerPoint point-units</param>
    /// <returns>The screen position in screen pixel units</returns>
    private int pointsToScreenPixelsY(float point)
    {
        // TODO Handle multiple windows
        // NOTE: PresStatic is a reference to the PowerPoint presentation object
        return PresStatic.Windows[1].PointsToScreenPixelsY(point);
    }
    

    Finally we implement a custom PPShape object that exposes the events we want to listen to

    using System.Drawing;
    using Microsoft.Office.Interop.PowerPoint;
    using System;
    
    namespace PowerPointDynamicLink.PPObject
    {
        class PPShape
        {
            #region Fields
            protected Shape shape;
    
            /// <summary>
            /// Get the PowerPoint Shape this object is based on
            /// </summary>
            public Shape Shape
            {
                get { return shape; }
            }
    
            protected long id;
            /// <summary>
            /// Get or set the Id of the Shape
            /// </summary>
            public long Id
            {
                get { return id; }
                set { id = value; }
            }
    
            protected string name;
            /// <summary>
            /// Get or set the name of the Shape
            /// </summary>
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
    
            /// <summary>
            /// Gets the bounding box of the shape in PowerPoint Point-units
            /// </summary>
            public RectangleF Rectangle
            {
                get
                {
                    RectangleF rect = new RectangleF
                    {
                        X = shape.Left,
                        Y = shape.Top,
                        Width = shape.Width,
                        Height = shape.Height
                    };
    
                    return rect;
                }
            }
            #endregion
    
            #region Constructor
            /// <summary>
            /// Creates a new PPShape object
            /// </summary>
            /// <param name="shape">The PowerPoint shape this object is based on</param>
            public PPShape(Shape shape)
            {
                this.shape = shape;
                this.name = shape.Name;
                this.id = shape.Id;
            }
            #endregion
    
            #region Event handling
            #region MouseEntered
            /// <summary>
            /// An event that notifies listeners when the mouse has entered this shape
            /// </summary>
            public event EventHandler MouseEntered = delegate { };
    
            /// <summary>
            /// Raises an event telling listeners that the mouse has entered this shape
            /// </summary>
            internal void OnMouseEntered()
            {
                // Raise the event
                MouseEntered(this, new EventArgs());
            }
            #endregion
    
            #region MouseLeave
            /// <summary>
            /// An event that notifies listeners when the mouse has left this shape
            /// </summary>
            public event EventHandler MouseLeave = delegate { };
    
            /// <summary>
            /// Raises an event telling listeners that the mouse has left this shape
            /// </summary>
            internal void OnMouseLeave()
            {
                // Raise the event
                MouseLeave(this, new EventArgs());
            }
            #endregion
            #endregion
        }
    }
    

    To be completely exhaustive, there are multiple extra elements that should be handled that are not covered here. This includes things such as pausing the mouse hook when the PowerPoint window deactivates, handling multiple PowerPoint windows and multiple screens etc.

    0 讨论(0)
  • 2021-02-10 18:38

    I encountered the same problem a few weeks back. But instead of going deep into the Windows API programming for listening the mouse events, I used Excel.Chart. Unlike PowerPoint.Chart it gives a hell lot of mouse events to work with like

    Chart.MouseUp, Chart.MouseOver, Chart.WindowBeforeDoubleClick, Chart.WindowBeforeRightClick, Chart.DragOver etc.

    Most probably by now you have gone deep into the Windows API programming. Were you successful in listening to the mouse event? If yes, then how you did it?

    Thanks :)

    0 讨论(0)
提交回复
热议问题