Search code examples
c#ms-wordvstonetoffice

Getting Range coordinates only for Ranges on the screen


I am currently using the following method to find the coordinates of a Range within a document:

private Rectangle GetRangeCoordinates(Window w, Range r)
{
    int left = 0;
    int top = 0;
    int width = 0;
    int height = 0;

    w.GetPoint(out left, out top, out width, out height, r);

    return new Rectangle(left, top, width, height);
}

This works really well unless the Range is off the screen by a fairly large margin (quite a few pages), in which case I get the following exception:

System.Runtime.InteropServices.COMException (0x800A1066): Command failed at Microsoft.Office.Interop.Word.Window.GetPoint(Int32& ScreenPixelsLeft, Int32& ScreenPixelsTop, Int32& ScreenPixelsWidth, Int32& ScreenPixelsHeight, Object obj) at [ProjectName].[TaskpaneName].GetRangeCoordinates(Window w, Range r) in [...somePath...][TaskpaneName].cs:line 66

Is there a way to figure out if a Range is on screen or not, so that I can only call this method when it is?


Solution

  • This is how I did this.

    I created a few extension methods for Application and Range:

    public static class ApplicationExensions
    {
        // more (rather than less)
        // does not do headers and footers
        public static Range GetCurrentlyVisibleRange(this Application application)
        {
            try
            {
                Window activeWindow = application.ActiveWindow;
                var left = application.PointsToPixels(activeWindow.Left);
                var top = application.PointsToPixels(activeWindow.Top);
                var width = application.PointsToPixels(activeWindow.Width);
                var height = application.PointsToPixels(activeWindow.Height);
                var usableWidth = application.PointsToPixels(activeWindow.UsableWidth);
                var usableHeight = application.PointsToPixels(activeWindow.UsableHeight);
    
                var startRangeX = left;// + (width - usableWidth);
                var startRangeY = top;// + (height - usableHeight);
    
                var endRangeX = startRangeX + width;//usableWidth;
                var endRangeY = startRangeY + height;//usableHeight;
    
                Range start = (Range) activeWindow.RangeFromPoint((int) startRangeX, (int) startRangeY);
                Range end = (Range) activeWindow.RangeFromPoint((int) endRangeX, (int) endRangeY);
    
                Range r = application.ActiveDocument.Range(start.Start, end.Start);
    
                return r;
            }
            catch (COMException)
            {
                return null;
            }
        }
    }
    
    public static class RangeExtensions
    {
        public static bool Intersects(this Range a, Range b)
        {
            return a.Start <= b.End && b.Start <= a.End;
        }
    
        public static Rectangle? GetCoordinates(this Range range)
        {
            try
            {
                Application application = range.Application;
                Window window = application.ActiveWindow;
    
                int left = 0;
                int top = 0;
                int width = 0;
                int height = 0;
    
                window.GetPoint(out left, out top, out width, out height, range);
    
                return new Rectangle(left, top, width, height);
            }
            catch (COMException e)
            {
                return null;
            }
        }
    }
    

    And then I used them like this:

    Range currentlyVisibleRange = application.GetCurrentlyVisibleRange();
    
    if (currentlyVisibleRange.Intersects(rng)){
        var coords = rng.GetCoordinates();
    }