// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; using System.Runtime.InteropServices; using Windows.Win32.System.Variant; using Windows.Win32.UI.Accessibility; using Windows.Win32.UI.Input.KeyboardAndMouse; namespace System.Windows.Forms; public partial class MonthCalendar { /// <summary> /// Represents an accessible object for buttons in <see cref="MonthCalendar"/> control. /// </summary> internal abstract class CalendarButtonAccessibleObject : MonthCalendarChildAccessibleObject { private readonly MonthCalendarAccessibleObject _monthCalendarAccessibleObject; public CalendarButtonAccessibleObject(MonthCalendarAccessibleObject calendarAccessibleObject) : base(calendarAccessibleObject) { _monthCalendarAccessibleObject = calendarAccessibleObject; } public override string DefaultAction => SR.AccessibleActionClick; internal override bool CanGetDefaultActionInternal => false; public override void DoDefaultAction() => Invoke(); internal override VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID) => propertyID switch { UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_ButtonControlTypeId, _ => base.GetPropertyValue(propertyID) }; internal override void Invoke() => RaiseMouseClick(); internal override bool IsPatternSupported(UIA_PATTERN_ID patternId) => patternId switch { UIA_PATTERN_ID.UIA_InvokePatternId => true, _ => base.IsPatternSupported(patternId) }; public override AccessibleObject Parent => _monthCalendarAccessibleObject; private protected override bool IsInternal => true; private void RaiseMouseClick() { // Make sure that the control is enabled. if (!_monthCalendarAccessibleObject.IsHandleCreated || !_monthCalendarAccessibleObject.IsEnabled || !IsEnabled) { return; } RECT rectangle = Bounds; int x = rectangle.left + (rectangle.Width / 2); int y = rectangle.top + (rectangle.Height / 2); RaiseMouseClick(x, y); } private static void RaiseMouseClick(int x, int y) { BOOL setOldCursorPos = PInvoke.GetPhysicalCursorPos(out Point previousPosition); bool mouseSwapped = PInvokeCore.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_SWAPBUTTON) != 0; SendMouseInput(x, y, MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE); SendMouseInput(0, 0, mouseSwapped ? MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTDOWN : MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN); SendMouseInput(0, 0, mouseSwapped ? MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTUP : MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP); Thread.Sleep(50); // Set back the mouse position where it was. if (setOldCursorPos) { SendMouseInput(previousPosition.X, previousPosition.Y, MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE); } } public override AccessibleRole Role => AccessibleRole.PushButton; private static unsafe void SendMouseInput(int x, int y, MOUSE_EVENT_FLAGS flags) { if ((flags & MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE) != 0) { int vscreenWidth = PInvokeCore.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXVIRTUALSCREEN); int vscreenHeight = PInvokeCore.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN); int vscreenLeft = PInvokeCore.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_XVIRTUALSCREEN); int vscreenTop = PInvokeCore.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_YVIRTUALSCREEN); const int DesktopNormalizedMax = 65536; // Absolute input requires that input is in 'normalized' coords - with the entire // desktop being (0,0)...(65535,65536). Need to convert input x,y coords to this // first. // // In this normalized world, any pixel on the screen corresponds to a block of values // of normalized coords - eg. on a 1024x768 screen, // y pixel 0 corresponds to range 0 to 85.333, // y pixel 1 corresponds to range 85.333 to 170.666, // y pixel 2 corresponds to range 170.666 to 256 - and so on. // Doing basic scaling math - (x-top)*65536/Width - gets us the start of the range. // However, because int math is used, this can end up being rounded into the wrong // pixel. For example, if we wanted pixel 1, we'd get 85.333, but that comes out as // 85 as an int, which falls into pixel 0's range - and that's where the pointer goes. // To avoid this, we add on half-a-"screen pixel"'s worth of normalized coords - to // push us into the middle of any given pixel's range - that's the 65536/(Width*2) // part of the formula. So now pixel 1 maps to 85+42 = 127 - which is comfortably // in the middle of that pixel's block. // The key ting here is that unlike points in coordinate geometry, pixels take up // space, so are often better treated like rectangles - and if you want to target // a particular pixel, target its rectangle's midpoint, not its edge. x = ((x - vscreenLeft) * DesktopNormalizedMax) / vscreenWidth + DesktopNormalizedMax / (vscreenWidth * 2); y = ((y - vscreenTop) * DesktopNormalizedMax) / vscreenHeight + DesktopNormalizedMax / (vscreenHeight * 2); flags |= MOUSE_EVENT_FLAGS.MOUSEEVENTF_VIRTUALDESK; } INPUT mouseInput = default; mouseInput.type = INPUT_TYPE.INPUT_MOUSE; mouseInput.Anonymous.mi.dx = x; mouseInput.Anonymous.mi.dy = y; mouseInput.Anonymous.mi.mouseData = 0; mouseInput.Anonymous.mi.dwFlags = flags; mouseInput.Anonymous.mi.time = 0; mouseInput.Anonymous.mi.dwExtraInfo = UIntPtr.Zero; PInvoke.SendInput(1, &mouseInput, Marshal.SizeOf(mouseInput)); } } } |