Exemplo n.º 1
0
        internal void GetVisibleRangePoints(out int start, out int end)
        {
            start = 0;
            end   = 0;

            NativeMethods.Win32Rect rect = new NativeMethods.Win32Rect();

            if (Misc.GetClientRect(_hwnd, ref rect) && !rect.IsEmpty)
            {
                NativeMethods.SIZE size;
                string             s = new string('E', 1);
                GetTextExtentPoint32(s, out size);

                NativeMethods.Win32Point ptStart = new NativeMethods.Win32Point((int)(rect.left + size.cx / 4), (int)(rect.top + size.cy / 4));
                NativeMethods.Win32Point ptEnd   = new NativeMethods.Win32Point((int)(rect.right - size.cx / 8), (int)(rect.bottom - size.cy / 4));

                start = CharFromPosEx(ptStart);
                end   = CharFromPosEx(ptEnd);

                if (start > 0)
                {
                    Point pt = PosFromChar(start);
                    if (pt.X < rect.left)
                    {
                        start++;
                    }
                }
            }
            else
            {
                // multi-line edit controls are handled differently than single-line edit controls.

                if (IsMultiline)
                {
                    // get the line number of the first visible line and start the range at
                    // the beginning of that line.
                    int firstLine = GetFirstVisibleLine();
                    start = LineIndex(firstLine);

                    // calculate the line number of the first line scrolled off the bottom and
                    // end the range at the beginning of that line.
                    end = LineIndex(firstLine + LinesPerPage());
                }
                else
                {
                    // single-line edit control

                    // the problem is that using a variable-width font the number of characters visible
                    // depends on the text that is in the edit control.  so we can't just divide the
                    // width of the edit control by the width of a character.

                    // so instead we do a binary search of the characters from the first visible character
                    // to the end of the text to find the visibility boundary.
                    Rect r     = GetRect();
                    int  limit = GetTextLength();
                    start = GetFirstVisibleChar();

                    int lo = start; // known visible
                    int hi = limit; // known non-visible
                    while (lo + 1 < hi)
                    {
                        int mid = (lo + hi) / 2;

                        Point pt = PosFromChar(mid);
                        if (pt.X >= r.Left && pt.X < r.Right)
                        {
                            lo = mid;
                        }
                        else
                        {
                            hi = mid;
                        }
                    }

                    // trim off one character unless the range is empty or reaches the end.
                    end = hi > start && hi < limit ? hi - 1 : hi;
                }
            }
        }
Exemplo n.º 2
0
 public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, [In, Out] ref NativeMethods.Win32Rect rect, int cPoints);
            //------------------------------------------------------
            //
            //  Private Methods
            //
            //------------------------------------------------------

            #region Private Methods

            private NativeMethods.Win32Rect BoundingRect()
            {
                NativeMethods.Win32Rect itemRect = new NativeMethods.Win32Rect();
                Misc.ProxySendMessage(_hwnd, NativeMethods.LB_GETITEMRECT, new IntPtr(_item), ref itemRect);
                return(Misc.MapWindowPoints(_hwnd, IntPtr.Zero, ref itemRect, 2) ? itemRect : NativeMethods.Win32Rect.Empty);
            }
Exemplo n.º 4
0
 // send an EM_GETRECT message to find out the bounding rectangle
 internal Rect GetRect()
 {
     NativeMethods.Win32Rect rect = new NativeMethods.Win32Rect();
     Misc.ProxySendMessage(WindowHandle, NativeMethods.EM_GETRECT, IntPtr.Zero, ref rect);
     return(new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top));
 }
            // Gets the bounding rectangle for this element
            private NativeMethods.Win32Rect BoundingRect()
            {
                // get index
                int index = OrderToIndex(_hwnd, _item);

                NativeMethods.Win32Rect rectW32 = NativeMethods.Win32Rect.Empty;

                bool result;

                unsafe
                {
                    result = XSendMessage.XSend(_hwnd, NativeMethods.HDM_GETITEMRECT, new IntPtr(index), new IntPtr(&rectW32), Marshal.SizeOf(rectW32.GetType()), XSendMessage.ErrorValue.Zero);
                }

                if (result)
                {
                    if (!Misc.MapWindowPoints(_hwnd, IntPtr.Zero, ref rectW32, 2))
                    {
                        return(NativeMethods.Win32Rect.Empty);
                    }

                    // Remove the space that is used to
                    if (!IsFilter())
                    {
                        // From the source code for the SysHeader control.
                        // This is the divider slop area. Selecting this area with the mouse does not select
                        // the header, it perpares the header/column to be resized.
                        int cxBorder = 8 * UnsafeNativeMethods.GetSystemMetrics(NativeMethods.SM_CXBORDER);

                        if (Misc.IsLayoutRTL(_hwnd))
                        {
                            // Right to left mirroring style

                            // adjust the left margin
                            rectW32.left += cxBorder;
                            if (rectW32.left > rectW32.right)
                            {
                                rectW32.left = rectW32.right;
                            }

                            // adjust the right margin
                            if (_item > 0)
                            {
                                rectW32.right -= cxBorder;
                                if (rectW32.right < rectW32.left)
                                {
                                    rectW32.right = rectW32.left;
                                }
                            }
                        }
                        else
                        {
                            // adjust the left margin
                            if (_item > 0)
                            {
                                rectW32.left += cxBorder;
                                if (rectW32.left > rectW32.right)
                                {
                                    rectW32.left = rectW32.right;
                                }
                            }

                            // adjust the right margin
                            rectW32.right -= cxBorder;
                            if (rectW32.right < rectW32.left)
                            {
                                rectW32.right = rectW32.left;
                            }
                        }
                    }
                    return(rectW32);
                }

                return(NativeMethods.Win32Rect.Empty);
            }
Exemplo n.º 6
0
        // Process all the Element Properties
        internal override object GetElementProperty(AutomationProperty idProp)
        {
            // if the hwnd is a winform, then return the Winform id otherwise let
            // UIAutomation do the job
            if (idProp == AutomationElement.AutomationIdProperty)
            {
                // Winforms have a special way to obtain the id
                if (WindowsFormsHelper.IsWindowsFormsControl(_hwnd, ref _windowsForms))
                {
                    string sPersistentID = WindowsFormsHelper.WindowsFormsID(_hwnd);
                    return(string.IsNullOrEmpty(sPersistentID) ? null : sPersistentID);
                }
            }
            else if (idProp == AutomationElement.NameProperty)
            {
                string name;
                // If this is a winforms control and the AccessibleName is set, use it.
                if (WindowsFormsHelper.IsWindowsFormsControl(_hwnd, ref _windowsForms))
                {
                    name = GetAccessibleName(NativeMethods.CHILD_SELF);

                    if (!string.IsNullOrEmpty(name))
                    {
                        return(name);
                    }
                }

                // Only hwnd's can be labeled.
                name = LocalizedName;

                // PerSharp/PreFast will flag this as a warning 6507/56507: Prefer 'string.IsNullOrEmpty(name)' over checks for null and/or emptiness.
                // It is valid to set LocalizedName to an empty string.  LocalizedName being an
                // empty string will prevent the SendMessage(WM_GETTEXT) call.
#pragma warning suppress 6507
                if (name == null && GetParent() == null)
                {
                    if (_fControlHasLabel)
                    {
                        IntPtr label = Misc.GetLabelhwnd(_hwnd);
                        name = Misc.GetControlName(label, true);
                        if (!string.IsNullOrEmpty(name))
                        {
                            _controlLabel = label;
                        }
                    }
                    else
                    {
                        name = Misc.ProxyGetText(_hwnd);
                    }
                }


                // If name is still null, and we have an IAccessible, try it:
                // this picks up names on HWNDs set through Dynamic Annotation
                // (eg. on the richedits in Windows Mail), and holds us over till
                // we add DA support to UIA properly.
                if (String.IsNullOrEmpty(name))
                {
                    name = GetAccessibleName(NativeMethods.CHILD_SELF);
                }
                return(name);
            }
            // Only hwnd's can be labeled.
            else if (idProp == AutomationElement.LabeledByProperty && _fControlHasLabel)
            {
                // This is called to make sure that _controlLabel gets set.
                object name = GetElementProperty(AutomationElement.NameProperty);

                // If a control has a LocalizedName, the _controlLabel will not get set.
                // So look for it now.
                if (_controlLabel == IntPtr.Zero && name != null && GetParent() == null)
                {
                    _controlLabel = Misc.GetLabelhwnd(_hwnd);
                }

                // If we have a cached _controlLabel that means that the name property we just got
                // was retreived from the label of the control and not its text or something else.  If this
                // is the case expose it as the label.
                if (_controlLabel != IntPtr.Zero)
                {
                    return(AutomationInteropProvider.HostProviderFromHandle(_controlLabel));
                }
            }
            else if (idProp == AutomationElement.IsOffscreenProperty)
            {
                if (!SafeNativeMethods.IsWindowVisible(_hwnd))
                {
                    return(true);
                }

                IntPtr hwndParent = Misc.GetParent(_hwnd);
                // Check if rect is within rect of parent. Don't do this for top-level windows,
                // however, since the win32 desktop hwnd claims to have a rect only as large as the
                // primary monitor, making hwnds on other monitors seem clipped.
                if (hwndParent != IntPtr.Zero && hwndParent != UnsafeNativeMethods.GetDesktopWindow())
                {
                    NativeMethods.Win32Rect parentRect = NativeMethods.Win32Rect.Empty;
                    if (Misc.GetClientRectInScreenCoordinates(hwndParent, ref parentRect) && !parentRect.IsEmpty)
                    {
                        Rect itemRect = BoundingRectangle;

                        if (!itemRect.IsEmpty && !Misc.IsItemVisible(ref parentRect, ref itemRect))
                        {
                            return(true);
                        }
                    }
                }
            }

            return(base.GetElementProperty(idProp));
        }
Exemplo n.º 7
0
        // Same as clicking on an hyperlink
        void IInvokeProvider.Invoke()
        {
            // Check that button can be clicked.
            //
            // @



            // Make sure that the control is enabled
            if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
            {
                throw new ElementNotEnabledException();
            }

            if (!SafeNativeMethods.IsWindowVisible(_hwnd))
            {
                throw new InvalidOperationException(SR.Get(SRID.OperationCannotBePerformed));
            }

            //
            // Get the bounding rect for the window.
            //
            NativeMethods.Win32Rect BoundingRect = NativeMethods.Win32Rect.Empty;
            if (!Misc.GetWindowRect(_hwnd, ref BoundingRect))
            {
                return;
            }

            //
            // All we really need here are the height and the width,
            // so we don't even need to translate between screen
            // and client coordinates.
            //
            int width  = BoundingRect.right - BoundingRect.left;
            int height = BoundingRect.bottom - BoundingRect.top;

            //
            // Determine the point to click.
            //
            // @



            for (int Resolution = 10; Resolution > 0; --Resolution)
            {
                for (int x = 1; x <= width; x += Resolution)
                {
                    for (int y = 1; y <= height; y += Resolution)
                    {
                        //
                        // Send the link an LM_HITTEST message.
                        //
                        // Allocate a local hit test info struct.
                        UnsafeNativeMethods.LHITTESTINFO HitTestInfo = new UnsafeNativeMethods.LHITTESTINFO();

                        // Fill in the coordinates that we want to check.
                        HitTestInfo.pt.x = x;
                        HitTestInfo.pt.y = y;

                        // Fill in index and state info.
                        HitTestInfo.item.mask      = NativeMethods.LIF_ITEMINDEX | NativeMethods.LIF_STATE;
                        HitTestInfo.item.iLink     = 0;
                        HitTestInfo.item.stateMask = NativeMethods.LIS_ENABLED;
                        HitTestInfo.item.state     = 0;

                        bool bGetItemResult;
                        unsafe
                        {
                            // Send the LM_HITTEST message.
                            bGetItemResult = XSendMessage.XSend(_hwnd, NativeMethods.LM_HITTEST, IntPtr.Zero, new IntPtr(&HitTestInfo), Marshal.SizeOf(HitTestInfo.GetType()));
                        }

                        if (bGetItemResult == true && HitTestInfo.item.iLink == _item)
                        {
                            //
                            // N.B. [SEdmison]:
                            // This multiplication is essentially just
                            // a left shift by one word's width; in
                            // Win32 I'd just use my trusty MAKELONG macro,
                            // but C# doesn't give me that option.
                            //
                            Misc.ProxySendMessage(_hwnd, NativeMethods.WM_LBUTTONDOWN, IntPtr.Zero, NativeMethods.Util.MAKELPARAM(x, y));
                            Misc.ProxySendMessage(_hwnd, NativeMethods.WM_LBUTTONUP, IntPtr.Zero, NativeMethods.Util.MAKELPARAM(x, y));
                            return;
                        }
                    }
                }
            }
        }
Exemplo n.º 8
0
            // ------------------------------------------------------
            //
            // Internal Methods
            //
            // ------------------------------------------------------

            #region Internal Methods

            // Returns the bounding rectangle of the control.
            internal static Rect GetBoundingRectangle(IntPtr hwnd, WindowsSlider.SItem item, bool fHorizontal)
            {
                NativeMethods.Win32Rect rcChannel = new NativeMethods.Win32Rect();
                rcChannel.left = rcChannel.right = rcChannel.top = rcChannel.bottom = 1000;

                unsafe
                {
                    XSendMessage.XSend(hwnd, NativeMethods.TBM_GETCHANNELRECT, IntPtr.Zero, new IntPtr(&rcChannel), Marshal.SizeOf(rcChannel.GetType()), XSendMessage.ErrorValue.NoCheck);
                }
                if (!Misc.MapWindowPoints(hwnd, IntPtr.Zero, ref rcChannel, 2))
                {
                    return(Rect.Empty);
                }

                NativeMethods.Win32Rect rcThumb = new NativeMethods.Win32Rect();
                rcThumb.left = rcThumb.right = rcThumb.top = rcThumb.bottom = 1000;

                unsafe
                {
                    XSendMessage.XSend(hwnd, NativeMethods.TBM_GETTHUMBRECT, IntPtr.Zero, new IntPtr(&rcThumb), Marshal.SizeOf(rcThumb.GetType()), XSendMessage.ErrorValue.NoCheck);
                }
                if (!Misc.MapWindowPoints(hwnd, IntPtr.Zero, ref rcThumb, 2))
                {
                    return(Rect.Empty);
                }

                if (fHorizontal)
                {
                    // When WS_EX_RTLREADING is set swap the increment and decrement bars.
                    if (Misc.IsLayoutRTL(hwnd))
                    {
                        if (item == SItem.LargeDecrement)
                        {
                            item = SItem.LargeIncrement;
                        }
                        else if (item == SItem.LargeIncrement)
                        {
                            item = SItem.LargeDecrement;
                        }
                    }

                    switch (item)
                    {
                    case WindowsSlider.SItem.LargeDecrement:
                        return(new Rect(rcChannel.left, rcChannel.top, rcThumb.left - rcChannel.left, rcChannel.bottom - rcChannel.top));

                    case WindowsSlider.SItem.Thumb:
                        return(new Rect(rcThumb.left, rcThumb.top, rcThumb.right - rcThumb.left, rcThumb.bottom - rcThumb.top));

                    case WindowsSlider.SItem.LargeIncrement:
                        return(new Rect(rcThumb.right, rcChannel.top, rcChannel.right - rcThumb.right, rcChannel.bottom - rcChannel.top));
                    }
                }
                else
                {
                    int dx = rcChannel.bottom - rcChannel.top;
                    int dy = rcChannel.right - rcChannel.left;

                    switch (item)
                    {
                    case WindowsSlider.SItem.LargeDecrement:
                        return(new Rect(rcChannel.left, rcChannel.top, dx, rcThumb.top - rcChannel.top));

                    case WindowsSlider.SItem.Thumb:
                        return(new Rect(rcThumb.left, rcThumb.top, rcThumb.right - rcThumb.left, rcThumb.bottom - rcThumb.top));

                    case WindowsSlider.SItem.LargeIncrement:
                        return(new Rect(rcChannel.left, rcThumb.bottom, dx, dy));
                    }
                }

                return(Rect.Empty);
            }
        // ------------------------------------------------------
        //
        // Internal Methods
        //
        // ------------------------------------------------------

        #region Internal Methods

        // Static implementation for the bounding rectangle. This is used by
        // ElementProviderFromPoint to avoid to have to create for a simple
        // boundary check
        // param "item", ID for the scrollbar bit
        // param "sbFlag", SBS_ WindowLong equivallent flag
        static internal Rect GetHorizontalScrollbarBitBoundingRectangle(IntPtr hwnd, WindowsScrollBar.ScrollBarItem item, NativeMethods.ScrollBarInfo sbi)
        {
            // Horizontal Scrollbar
            NativeMethods.Win32Rect rc = new NativeMethods.Win32Rect(sbi.xyThumbTop, sbi.rcScrollBar.top, sbi.xyThumbBottom, sbi.rcScrollBar.bottom);
            if (!Misc.MapWindowPoints(hwnd, IntPtr.Zero, ref rc, 2))
            {
                return(Rect.Empty);
            }

            // Since the scrollbar position is already mapped, restore them back
            rc.top    = sbi.rcScrollBar.top;
            rc.bottom = sbi.rcScrollBar.bottom;

            NativeMethods.SIZE sizeArrow;

            using (ThemePart themePart = new ThemePart(hwnd, "SCROLLBAR"))
            {
                sizeArrow = themePart.Size((int)ThemePart.SCROLLBARPARTS.SBP_ARROWBTN, 0);
            }

            // check that the 2 buttons can hold in the scroll bar
            bool fThumbVisible = sbi.rcScrollBar.right - sbi.rcScrollBar.left >= 5 * sizeArrow.cx / 2;

            if (sbi.rcScrollBar.right - sbi.rcScrollBar.left < 2 * sizeArrow.cx)
            {
                // the scroll bar is tiny, need to shrink the button
                sizeArrow.cx = (sbi.rcScrollBar.right - sbi.rcScrollBar.left) / 2;
            }

            //
            // Builds prior to Vista 5359 failed to correctly account for RTL scrollbar layouts.
            //
            if ((Environment.OSVersion.Version.Major < 6) && (Misc.IsLayoutRTL(hwnd)))
            {
                if (item == WindowsScrollBar.ScrollBarItem.UpArrow)
                {
                    item = WindowsScrollBar.ScrollBarItem.DownArrow;
                }
                else if (item == WindowsScrollBar.ScrollBarItem.DownArrow)
                {
                    item = WindowsScrollBar.ScrollBarItem.UpArrow;
                }
                else if (item == WindowsScrollBar.ScrollBarItem.LargeIncrement)
                {
                    item = WindowsScrollBar.ScrollBarItem.LargeDecrement;
                }
                else if (item == WindowsScrollBar.ScrollBarItem.LargeDecrement)
                {
                    item = WindowsScrollBar.ScrollBarItem.LargeIncrement;
                }
            }

            switch (item)
            {
            case WindowsScrollBar.ScrollBarItem.UpArrow:
                rc.left  = sbi.rcScrollBar.left;
                rc.right = sbi.rcScrollBar.left + sizeArrow.cx;
                break;

            case WindowsScrollBar.ScrollBarItem.LargeIncrement:
                if (fThumbVisible)
                {
                    rc.left  = rc.right;
                    rc.right = sbi.rcScrollBar.right - sizeArrow.cx;
                }
                else
                {
                    rc.left = rc.right = sbi.rcScrollBar.left + sizeArrow.cx;
                }
                break;

            case WindowsScrollBar.ScrollBarItem.Thumb:
                if (!fThumbVisible)
                {
                    rc.left = rc.right = sbi.rcScrollBar.left + sizeArrow.cx;
                }
                break;

            case WindowsScrollBar.ScrollBarItem.LargeDecrement:
                if (fThumbVisible)
                {
                    rc.right = rc.left;
                    rc.left  = sbi.rcScrollBar.left + sizeArrow.cx;
                }
                else
                {
                    rc.left = rc.right = sbi.rcScrollBar.left + sizeArrow.cx;
                }
                break;

            case WindowsScrollBar.ScrollBarItem.DownArrow:
                rc.left  = sbi.rcScrollBar.right - sizeArrow.cx;
                rc.right = sbi.rcScrollBar.right;
                break;
            }

            // Don't need to normalize, OSVer conditional block converts to absolute coordinates.
            return(rc.ToRect(false));
        }
        // ------------------------------------------------------
        //
        // Internal Methods
        //
        // ------------------------------------------------------

        #region Internal Methods

        // Static implementation for the bounding rectangle. This is used by
        // ElementProviderFromPoint to avoid to have to create for a simple
        // boundary check
        // param "item", ID for the scrollbar bit
        // param "sbFlag", SBS_ WindowLong equivallent flag
        static internal Rect GetVerticalScrollbarBitBoundingRectangle(IntPtr hwnd, WindowsScrollBar.ScrollBarItem item, NativeMethods.ScrollBarInfo sbi)
        {
            NativeMethods.Win32Rect rc = new NativeMethods.Win32Rect(sbi.rcScrollBar.left, sbi.xyThumbTop, sbi.rcScrollBar.right, sbi.xyThumbBottom);
            if (!Misc.MapWindowPoints(hwnd, IntPtr.Zero, ref rc, 2))
            {
                return(Rect.Empty);
            }

            // Vertical Scrollbar
            // Since the scrollbar position is already mapped, restore them back
            rc.left  = sbi.rcScrollBar.left;
            rc.right = sbi.rcScrollBar.right;

            NativeMethods.SIZE sizeArrow;

            using (ThemePart themePart = new ThemePart(hwnd, "SCROLLBAR"))
            {
                sizeArrow = themePart.Size((int)ThemePart.SCROLLBARPARTS.SBP_ARROWBTN, 0);
            }

            // check that the 2 buttons can hold in the scroll bar
            bool fThumbVisible = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top >= 5 * sizeArrow.cy / 2;

            if (sbi.rcScrollBar.bottom - sbi.rcScrollBar.top < 2 * sizeArrow.cy)
            {
                // the scroll bar is tiny, need to shrink the button
                sizeArrow.cy = (sbi.rcScrollBar.bottom - sbi.rcScrollBar.top) / 2;
            }

            switch (item)
            {
            case WindowsScrollBar.ScrollBarItem.UpArrow:
                rc.top    = sbi.rcScrollBar.top;
                rc.bottom = sbi.rcScrollBar.top + sizeArrow.cy;
                break;

            case WindowsScrollBar.ScrollBarItem.LargeIncrement:
                if (fThumbVisible)
                {
                    rc.top    = rc.bottom;
                    rc.bottom = sbi.rcScrollBar.bottom - sizeArrow.cy;
                }
                else
                {
                    rc.top = rc.bottom = sbi.rcScrollBar.top + sizeArrow.cy;
                }
                break;

            case WindowsScrollBar.ScrollBarItem.Thumb:
                if (!fThumbVisible)
                {
                    rc.top = rc.bottom = sbi.rcScrollBar.top + sizeArrow.cy;
                }
                break;

            case WindowsScrollBar.ScrollBarItem.LargeDecrement:
                if (fThumbVisible)
                {
                    rc.bottom = rc.top;
                    rc.top    = sbi.rcScrollBar.top + sizeArrow.cy;
                }
                else
                {
                    rc.top = rc.bottom = sbi.rcScrollBar.top + sizeArrow.cy;
                }
                break;

            case WindowsScrollBar.ScrollBarItem.DownArrow:
                rc.top    = sbi.rcScrollBar.bottom - sizeArrow.cy;
                rc.bottom = sbi.rcScrollBar.bottom;
                break;
            }

            // Don't need to normalize, OSVer conditional block converts to absolute coordinates.
            return(rc.ToRect(false));
        }
        // Same as clicking on an hyperlink
        void IInvokeProvider.Invoke()
        {
            // Check that button can be clicked.
            //
            // This state could change anytime.
            //

            // Make sure that the control is enabled
            if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
            {
                throw new ElementNotEnabledException();
            }

            if (!SafeNativeMethods.IsWindowVisible(_hwnd))
            {
                throw new InvalidOperationException(SR.Get(SRID.OperationCannotBePerformed));
            }

            //
            // Get the bounding rect for the window.
            //
            NativeMethods.Win32Rect BoundingRect = NativeMethods.Win32Rect.Empty;
            if (!Misc.GetWindowRect(_hwnd, ref BoundingRect))
            {
                return;
            }

            //
            // All we really need here are the height and the width,
            // so we don't even need to translate between screen
            // and client coordinates.
            //
            int width  = BoundingRect.right - BoundingRect.left;
            int height = BoundingRect.bottom - BoundingRect.top;

            //
            // Determine the point to click.
            //
            // We do this by scanning over the window's client
            // region hit-testing until we find a point that
            // corresponds to our link.  We start out by scanning
            // at a resolution of 10x10 pixels, starting from the
            // window's (1,1) coordinate, then reducing the resolution
            // if needed.  Thus the idea here is to scan quickly if
            // possible, but more thoroughly if absolutely necessary.
            //
            // Note:
            // I intentionally started scanning points starting from
            // (1,1) rather than from (0,0) just in case clicking the
            // border of a control is different than clicking inside
            // the border.
            //
            for (int Resolution = 10; Resolution > 0; --Resolution)
            {
                for (int x = 1; x <= width; x += Resolution)
                {
                    for (int y = 1; y <= height; y += Resolution)
                    {
                        //
                        // Send the link an LM_HITTEST message.
                        //
                        // Allocate a local hit test info struct.
                        UnsafeNativeMethods.LHITTESTINFO HitTestInfo = new UnsafeNativeMethods.LHITTESTINFO();

                        // Fill in the coordinates that we want to check.
                        HitTestInfo.pt.x = x;
                        HitTestInfo.pt.y = y;

                        // Fill in index and state info.
                        HitTestInfo.item.mask      = NativeMethods.LIF_ITEMINDEX | NativeMethods.LIF_STATE;
                        HitTestInfo.item.iLink     = 0;
                        HitTestInfo.item.stateMask = NativeMethods.LIS_ENABLED;
                        HitTestInfo.item.state     = 0;

                        bool bGetItemResult;
                        unsafe
                        {
                            // Send the LM_HITTEST message.
                            bGetItemResult = XSendMessage.XSend(_hwnd, NativeMethods.LM_HITTEST, IntPtr.Zero, new IntPtr(&HitTestInfo), Marshal.SizeOf(HitTestInfo.GetType()));
                        }

                        if (bGetItemResult == true && HitTestInfo.item.iLink == _item)
                        {
                            //
                            // N.B. [SEdmison]:
                            // This multiplication is essentially just
                            // a left shift by one word's width; in
                            // Win32 I'd just use my trusty MAKELONG macro,
                            // but C# doesn't give me that option.
                            //
                            Misc.ProxySendMessage(_hwnd, NativeMethods.WM_LBUTTONDOWN, IntPtr.Zero, NativeMethods.Util.MAKELPARAM(x, y));
                            Misc.ProxySendMessage(_hwnd, NativeMethods.WM_LBUTTONUP, IntPtr.Zero, NativeMethods.Util.MAKELPARAM(x, y));
                            return;
                        }
                    }
                }
            }
        }