|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Description: ScrollBar Proxy
using System;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Runtime.InteropServices;
using System.Windows;
using MS.Win32;
namespace MS.Internal.AutomationProxies
{
// Win32 Proxy Scrollbar implementation.
// This code works for vertical and horizontal scroll bars
// both as part of the none client area or as a stand alone
// scroll bar control.
class WindowsScrollBar: ProxyHwnd, IRangeValueProvider
{
// ------------------------------------------------------
//
// Constructors
//
// ------------------------------------------------------
#region Constructors
internal WindowsScrollBar (IntPtr hwnd, ProxyFragment parent, int item, int sbFlag)
: base( hwnd, parent, item)
{
_sbFlag = sbFlag;
// Never do any non client area cliping when dealing with the scroll bar and
// scroll bar bits
_fNonClientAreaElement = true;
// Control Type
_cControlType = ControlType.ScrollBar;
// Only Focusable if it is a stand alone scroll bar
_fIsKeyboardFocusable = IsStandAlone();
if (!IsStandAlone())
{
_sAutomationId = sbFlag == NativeMethods.SB_VERT ? "Vertical ScrollBar" : "Horizontal ScrollBar"; // This string is a non-localizable string
}
// support for events
_createOnEvent = new WinEventTracker.ProxyRaiseEvents (RaiseEvents);
}
#endregion
#region Proxy Create
// Static Create method called by UIAutomation to create this proxy.
// returns null if unsuccessful
internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild, int idObject)
{
return Create(hwnd, idChild);
}
private static IRawElementProviderSimple Create(IntPtr hwnd, int idChild)
{
// Something is wrong if idChild is not zero
ArgumentOutOfRangeException.ThrowIfNotEqual(idChild, 0);
return new WindowsScrollBar(hwnd, null, idChild, NativeMethods.SB_CTL);
}
// Static create method called by the event tracker system.
// WinEvents are raised only when a notification has been set for a
// specific item. Create the item first and check for details afterward.
internal static void RaiseEvents (IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
{
WindowsScrollBar wtv = new WindowsScrollBar (hwnd, null, -1, NativeMethods.SB_CTL);
if (idChild == 0 && eventId == NativeMethods.EventObjectStateChange && idProp == ValuePattern.IsReadOnlyProperty)
{
// UIA works differently than MSAA. Events are set for elements rather than hwnd
// Scroll bar are processed the same way whatever they are part of the non client area
// or stand alone hwnd. OBJID_HSCROLL and OBJID_VSCROLL are mapped to OBJID_WINDOW
// so they behave the same for NC and hwnd SB.
// Parameters are setup so that the dispatch will send the proper notification and
// recursively will send notifications to all of the children
if (idObject == NativeMethods.OBJID_HSCROLL || idObject == NativeMethods.OBJID_VSCROLL)
{
idObject = NativeMethods.OBJID_WINDOW;
}
}
if (idChild == 0)
{
wtv.DispatchEvents (eventId, idProp, idObject, idChild);
}
else
{
// raise events for the children
ProxySimple scrollBarBit = WindowsScrollBarBits.CreateFromChildId(hwnd, wtv, idChild, NativeMethods.SB_CTL);
if (scrollBarBit != null)
{
scrollBarBit.DispatchEvents(eventId, idProp, idObject, idChild);
}
}
}
#endregion Proxy Create
//------------------------------------------------------
//
// Patterns Implementation
//
//------------------------------------------------------
#region ProxySimple Interface
// Returns a pattern interface if supported.
internal override object GetPatternProvider (AutomationPattern iid)
{
return (iid == RangeValuePattern.Pattern && WindowScroll.Scrollable (_hwnd, _sbFlag) && HasValuePattern (_hwnd, _sbFlag)) ? this : null;
}
// Gets the bounding rectangle for this element
internal override Rect BoundingRectangle
{
get
{
//
// If its scrollbar then get the default Win32Rect
//
if (_sbFlag == NativeMethods.SB_CTL)
{
return base.BoundingRectangle;
}
NativeMethods.ScrollBarInfo sbi = new NativeMethods.ScrollBarInfo ();
sbi.cbSize = Marshal.SizeOf (sbi.GetType ());
int idObject = _sbFlag == NativeMethods.SB_VERT ? NativeMethods.OBJID_VSCROLL : _sbFlag == NativeMethods.SB_HORZ ? NativeMethods.OBJID_HSCROLL : NativeMethods.OBJID_CLIENT;
if (!Misc.GetScrollBarInfo(_hwnd, idObject, ref sbi))
{
return Rect.Empty;
}
// When the scroll bar is for a listbox within a combo and it is hidden, then
// GetScrollBarInfo returns true but the rectangle is boggus!
// 32 bits * 32 bits > 64 values
long area = (sbi.rcScrollBar.right - sbi.rcScrollBar.left) * (sbi.rcScrollBar.bottom - sbi.rcScrollBar.top);
if (area <= 0 || area > 1000 * 1000)
{
// Ridiculous value assume error
return Rect.Empty;
}
if (!IsStandAlone())
{
//
// Builds prior to Vista 5359 failed to correctly account for RTL scrollbar layouts.
//
if ((Environment.OSVersion.Version.Major < 6) && (Misc.IsLayoutRTL(_parent._hwnd)))
{
// Right to left mirroring style
Rect rcParent = _parent.BoundingRectangle;
int width = sbi.rcScrollBar.right - sbi.rcScrollBar.left;
if (_sbFlag == NativeMethods.SB_VERT)
{
int offset = (int)rcParent.Right - sbi.rcScrollBar.right;
sbi.rcScrollBar.left = (int)rcParent.Left + offset;
sbi.rcScrollBar.right = sbi.rcScrollBar.left + width;
}
else
{
int offset = sbi.rcScrollBar.left - (int)rcParent.Left;
sbi.rcScrollBar.right = (int)rcParent.Right - offset;
sbi.rcScrollBar.left = sbi.rcScrollBar.right - width;
}
}
}
// Don't need to normalize, OSVer conditional block converts to absolute coordinates.
return sbi.rcScrollBar.ToRect(false);
}
}
// Process all the Logical and Raw Element Properties
internal override object GetElementProperty (AutomationProperty idProp)
{
if (idProp == AutomationElement.IsControlElementProperty)
{
return SafeNativeMethods.IsWindowVisible(_hwnd);
}
else if (idProp == AutomationElement.IsKeyboardFocusableProperty)
{
// When a scroll bar is embedded into a control, treat it as a piece of the control
// and not as a control, i.e. ignore the WS_TABSTOP style.
if (_parent != null)
{
// If it's visible and enabled it might be focusable
if (SafeNativeMethods.IsWindowVisible(_hwnd) &&
(bool)GetElementProperty(AutomationElement.IsEnabledProperty))
{
return _fIsKeyboardFocusable;
}
else
{
return false;
}
}
}
else if (idProp == AutomationElement.IsEnabledProperty)
{
// When the scroll bar is not stand-alone, _hwnd
// is the handle of the containing window.
return IsEnabled() && Misc.IsEnabled(_hwnd);
}
else if (idProp == AutomationElement.OrientationProperty)
{
return IsScrollBarVertical(_hwnd, _sbFlag) ? OrientationType.Vertical : OrientationType.Horizontal;
}
else if (idProp == AutomationElement.AutomationIdProperty)
{
// If this scroll bar is a sub-component of a control, do not let the base (ProxyHwnd) process the
// AutomationIdProperty, since it may pick up the AutomationID for the whole control.
if (!IsStandAlone())
{
return _sAutomationId;
}
}
return base.GetElementProperty (idProp);
}
// Returns the Run Time Id.
// The default behavior in ProxySimple is to check if the element is a ProxyHwnd.
// In that case it return a run time it the form [1,hwnd]. If a scroll bar is part
// of the non client area, the WindowsScrollBar is a ProxyHwnd but it is not an hwnd.
// Overide the default implementation in ProxySimple removing the check for ProxyHwnd
internal override int[] GetRuntimeId()
{
if (_fSubTree)
{
// add the id for this level at the end of the chain
return new int[] { AutomationInteropProvider.AppendRuntimeId, _item };
}
else
{
// UIA handles runtimeIDs for the HWND part of the object for us
return null;
}
}
//Gets the localized name
internal override string LocalizedName
{
get
{
if (_item == -1)
{
return null;
}
return IsScrollBarVertical(_hwnd, _sbFlag)
? SR.LocalizedNameWindowsVerticalScrollBar
: SR.LocalizedNameWindowsHorizontalScrollBar;
}
}
#endregion
#region ProxyFragment Interface
// Returns the next sibling element in the raw hierarchy.
// Peripheral controls have always negative values.
// Returns null if no next child.
internal override ProxySimple GetNextSibling (ProxySimple child)
{
ScrollBarItem item = (ScrollBarItem) child._item;
if (item != ScrollBarItem.DownArrow)
{
// skip the Large increment/decrement if there is no thumb
if (item == ScrollBarItem.UpArrow && !IsScrollBarWithThumb (_hwnd, _sbFlag))
{
item = ScrollBarItem.DownArrow - 1;
}
return CreateScrollBitsItem ((ScrollBarItem) ((int) item + 1));
}
return null;
}
// Returns the previous sibling element in the raw hierarchy.
// Peripheral controls have always negative values.
// Returns null is no previous.
internal override ProxySimple GetPreviousSibling (ProxySimple child)
{
ScrollBarItem item = (ScrollBarItem) child._item;
if (item != ScrollBarItem.UpArrow)
{
// skip the Large increment/decrement if there is no thumb
if (item == ScrollBarItem.DownArrow && !IsScrollBarWithThumb (_hwnd, _sbFlag))
{
item = ScrollBarItem.UpArrow + 1;
}
return CreateScrollBitsItem ((ScrollBarItem) ((int) item - 1));
}
return null;
}
// Returns the first child element in the raw hierarchy.
internal override ProxySimple GetFirstChild ()
{
return CreateScrollBitsItem (ScrollBarItem.UpArrow);
}
// Returns the last child element in the raw hierarchy.
internal override ProxySimple GetLastChild ()
{
return CreateScrollBitsItem (ScrollBarItem.DownArrow);
}
// Returns a Proxy element corresponding to the specified screen coordinates.
internal override ProxySimple ElementProviderFromPoint (int x, int y)
{
for (ScrollBarItem item = ScrollBarItem.UpArrow; (int) item <= (int) ScrollBarItem.DownArrow; item = (ScrollBarItem) ((int) item + 1))
{
NativeMethods.Win32Rect rc = new NativeMethods.Win32Rect(WindowsScrollBarBits.GetBoundingRectangle(_hwnd, this, item, _sbFlag));
if (Misc.PtInRect(ref rc, x, y))
{
return new WindowsScrollBarBits (_hwnd, this, (int) item, _sbFlag);
}
}
return this;
}
#endregion
#region RangeValue Pattern
// Change the position of the scroll bar
void IRangeValueProvider.SetValue (double val)
{
SetScrollValue((int)val);
}
// Request to get the value that this UI element is representing in a native format
double IRangeValueProvider.Value
{
get
{
return (double)GetScrollValue (ScrollBarInfo.CurrentPosition);
}
}
bool IRangeValueProvider.IsReadOnly
{
get
{
return !Misc.IsEnabled(_hwnd) || !HasValuePattern(_hwnd, _sbFlag);
}
}
double IRangeValueProvider.Maximum
{
get
{
return (double)GetScrollValue (ScrollBarInfo.MaximumPosition);
}
}
double IRangeValueProvider.Minimum
{
get
{
return (double)GetScrollValue (ScrollBarInfo.MinimumPosition);
}
}
double IRangeValueProvider.SmallChange
{
get
{
return (double)GetScrollValue(ScrollBarInfo.SmallChange);
}
}
double IRangeValueProvider.LargeChange
{
get
{
return (double)GetScrollValue(ScrollBarInfo.LargeChange);
}
}
#endregion RangeValue Pattern
// ------------------------------------------------------
//
// Internal Methods
//
// ------------------------------------------------------
#region Internal Methods
static internal bool HasVerticalScrollBar (IntPtr hwnd)
{
return Misc.IsBitSet(Misc.GetWindowStyle(hwnd), NativeMethods.WS_VSCROLL);
}
static internal bool HasHorizontalScrollBar (IntPtr hwnd)
{
return Misc.IsBitSet(Misc.GetWindowStyle(hwnd), NativeMethods.WS_HSCROLL);
}
internal static bool IsScrollBarVertical(IntPtr hwnd, int sbFlag)
{
if (sbFlag == NativeMethods.SB_CTL)
return Misc.IsBitSet(Misc.GetWindowStyle(hwnd), NativeMethods.SBS_VERT);
else
return sbFlag == NativeMethods.SB_VERT;
}
// Check if a scroll bar is in a disabled state
internal static bool IsScrollBarWithThumb (IntPtr hwnd, int sbFlag)
{
NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo ();
si.cbSize = Marshal.SizeOf (si.GetType ());
si.fMask = NativeMethods.SIF_RANGE | NativeMethods.SIF_PAGE;
if (!Misc.GetScrollInfo(hwnd, sbFlag, ref si))
{
return false;
}
// Check for the min / max value
if (si.nMax != si.nMin && si.nPage != si.nMax - si.nMin + 1)
{
// The scroll bar is enabled, check if we have a thumb
int idObject = sbFlag == NativeMethods.SB_VERT ? NativeMethods.OBJID_VSCROLL : sbFlag == NativeMethods.SB_HORZ ? NativeMethods.OBJID_HSCROLL : NativeMethods.OBJID_CLIENT;
NativeMethods.ScrollBarInfo sbi = new NativeMethods.ScrollBarInfo ();
sbi.cbSize = Marshal.SizeOf (sbi.GetType ());
// check that the 2 buttons can hold in the scroll bar
if (Misc.GetScrollBarInfo(hwnd, idObject, ref sbi))
{
// When the scroll bar is for a listbox within a combo and it is hidden, then
// GetScrollBarInfo returns true but the rectangle is boggus!
// 32 bits * 32 bits > 64 values
long area = (sbi.rcScrollBar.right - sbi.rcScrollBar.left) * (sbi.rcScrollBar.bottom - sbi.rcScrollBar.top);
if (area > 0 && area < 1000 * 1000)
{
NativeMethods.SIZE sizeArrow;
using (ThemePart themePart = new ThemePart(hwnd, "SCROLLBAR"))
{
sizeArrow = themePart.Size((int)ThemePart.SCROLLBARPARTS.SBP_ARROWBTN, 0);
}
bool fThumbVisible = false;
if (IsScrollBarVertical(hwnd, sbFlag))
{
fThumbVisible = (sbi.rcScrollBar.bottom - sbi.rcScrollBar.top >= 5 * sizeArrow.cy / 2);
}
else
{
fThumbVisible = (sbi.rcScrollBar.right - sbi.rcScrollBar.left >= 5 * sizeArrow.cx / 2);
}
return fThumbVisible;
}
}
}
return false;
}
#endregion
//------------------------------------------------------
//
// Internal Fields
//
//------------------------------------------------------
#region Internal Fields
// ------------------------------------------------------
//
// Internal Types Declaration
//
// ------------------------------------------------------
internal enum ScrollBarItem
{
UpArrow = 0,
LargeDecrement = 1,
Thumb = 2,
LargeIncrement = 3,
DownArrow = 4
}
#endregion
// ------------------------------------------------------
//
// Private Methods
//
// ------------------------------------------------------
#region Private Methods
// Create a new proxy for one of the scroll bit items
private ProxySimple CreateScrollBitsItem (ScrollBarItem index)
{
// For Scrollbars as standalone controls, make sure that the buttons are not invisible. (office scroll bars)
// Checking is done from the return value of SetScrolBarInfo.
NativeMethods.ScrollBarInfo sbi = new NativeMethods.ScrollBarInfo ();
sbi.cbSize = Marshal.SizeOf (sbi.GetType ());
if (_sbFlag != NativeMethods.SB_CTL || Misc.GetScrollBarInfo(_hwnd, NativeMethods.OBJID_CLIENT, ref sbi))
{
return new WindowsScrollBarBits (_hwnd, this, (int) index, _sbFlag);
}
return null;
}
private int GetScrollMaxValue(NativeMethods.ScrollInfo si)
{
// NOTE:
// Proportional scrollbars have a few key values: min, max, page, and current.
//
// Min and max represent the endpoints of the scrollbar; page is the side of the thumb,
// and current is the position of the leading edge of the thumb (top for a vert scrollbar).
//
// Because of this arrangment, current can't be any value between min and max, it's actually
// confied to min...(max-page)+1. That +1 is a quirk of the scrollbar's internal logic.
//
// For example, in an edit in notepad, these might be:
// min = 0
// max = 33 (~total lines in file)
// current = { any value from 0 .. 12 inclusive }
// page = 22
//
// Most controls just let the scrollbar do all the proportional logic: they pass the incoming
// values from the scroll messages (eg as a reuslt of dragging with a mouse or from UIA's SetValue)
// straight down to the scrollbar APIs.
//
// RichEdit is different: it does its own extra 'validation', and limits the current value to
// (max-page) - without that +1.
//
// The end result of this is that it's not possible using UIA to scroll a richedit to the max value:
// the richedit is exposing (implicitly through the scrollbar APIs) a max value one higher than the
// max value that it will actually allow.
string classname = Misc.GetClassName(_hwnd);
if (classname.ToLower(System.Globalization.CultureInfo.InvariantCulture).Contains("richedit"))
{
return si.nMax - si.nPage;
}
else
{
return (si.nMax - si.nPage) + (si.nPage > 0 ? 1 : 0);
}
}
private int GetScrollValue (ScrollBarInfo info)
{
NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo ();
si.fMask = NativeMethods.SIF_ALL;
si.cbSize = Marshal.SizeOf (si.GetType ());
if (!Misc.GetScrollInfo(_hwnd, _sbFlag, ref si))
{
return 0;
}
switch (info)
{
case ScrollBarInfo.CurrentPosition:
//
// Builds prior to Vista 5359 failed to correctly account for RTL scrollbar layouts.
//
if ((Environment.OSVersion.Version.Major < 6) && (_sbFlag == NativeMethods.SB_HORZ) && (Misc.IsControlRTL(_parent._hwnd)))
{
return GetScrollMaxValue(si) - si.nPos;
}
return si.nPos;
case ScrollBarInfo.MaximumPosition:
return GetScrollMaxValue(si);
case ScrollBarInfo.MinimumPosition:
return si.nMin;
case ScrollBarInfo.PageSize:
return si.nPage;
case ScrollBarInfo.TrackPosition:
return si.nTrackPos;
case ScrollBarInfo.LargeChange:
return si.nPage;
case ScrollBarInfo.SmallChange:
return 1;
}
return 0;
}
private void SetScrollValue (int val)
{
// Check if the window is disabled
if (!SafeNativeMethods.IsWindowEnabled (_hwnd))
{
throw new ElementNotEnabledException();
}
NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo ();
si.fMask = NativeMethods.SIF_ALL;
si.cbSize = Marshal.SizeOf (si.GetType ());
if (!Misc.GetScrollInfo(_hwnd, _sbFlag, ref si))
{
throw new InvalidOperationException(SR.OperationCannotBePerformed);
}
// No move, exit
if (val == si.nPos)
{
return;
}
// NOTE:
// Proportional scrollbars have a few key values: min, max, page, and current.
//
// Min and max represent the endpoints of the scrollbar; page is the side of the thumb,
// and current is the position of the leading edge of the thumb (top for a vert scrollbar).
//
// Because of this arrangment, current can't be any value between min and max, it's actually
// confied to min...(max-page)+1. That +1 is a quirk of the scrollbar's internal logic.
//
// For example, in an edit in notepad, these might be:
// min = 0
// max = 33 (~total lines in file)
// current = { any value from 0 .. 12 inclusive }
// page = 22
//
// Most controls just let the scrollbar do all the proportional logic: they pass the incoming
// values from the scroll messages (eg as a reuslt of dragging with a mouse or from UIA's SetValue)
// straight down to the scrollbar APIs.
//
// RichEdit is different: it does its own extra 'validation', and limits the current value to
// (max-page) - without that +1.
//
// The end result of this is that it's not possible using UIA to scroll a richedit to the max value:
// the richedit is exposing (implicitly through the scrollbar APIs) a max value one higher than the
// max value that it will actually allow.
int max;
string classname = Misc.GetClassName(_hwnd);
if (classname.ToLower(System.Globalization.CultureInfo.InvariantCulture).Contains("richedit"))
{
max = si.nMax - si.nPage;
}
else
{
max = (si.nMax - si.nPage) + (si.nPage > 0 ? 1 : 0);
}
// Explicit comparisions are made to the MaxPercentage and MinPercentage,
// so that we dont miss out the fractions
if (val > max )
{
throw new ArgumentOutOfRangeException("value", val, SR.RangeValueMax);
}
else if (val < si.nMin)
{
throw new ArgumentOutOfRangeException("value", val, SR.RangeValueMin);
}
if (_sbFlag == NativeMethods.SB_CTL)
{
Misc.SetScrollPos(_hwnd, _sbFlag, val, true);
}
else
{
// Determine the msg from the style.
int msg =
IsScrollBarVertical(_hwnd, _sbFlag) ? NativeMethods.WM_VSCROLL : NativeMethods.WM_HSCROLL;
// An application is generally programmed to process either the
// SB_THUMBTRACK or SB_THUMBPOSITION request code.
int wParam = NativeMethods.Util.MAKELONG ((short) NativeMethods.SB_THUMBPOSITION, (short) val);
Misc.ProxySendMessage(_hwnd, msg, (IntPtr)wParam, IntPtr.Zero);
wParam = NativeMethods.Util.MAKELONG ((short) NativeMethods.SB_THUMBTRACK, (short) val);
Misc.ProxySendMessage(_hwnd, msg, (IntPtr)wParam, IntPtr.Zero);
}
}
// Check if a scroll bar implements the Value Pattern
private static bool HasValuePattern (IntPtr hwnd, int sbFlag)
{
// The scroll bar is enabled, check if we have a thumb
int idObject = sbFlag == NativeMethods.SB_VERT ? NativeMethods.OBJID_VSCROLL : sbFlag == NativeMethods.SB_HORZ ? NativeMethods.OBJID_HSCROLL : NativeMethods.OBJID_CLIENT;
NativeMethods.ScrollBarInfo sbi = new NativeMethods.ScrollBarInfo ();
sbi.cbSize = Marshal.SizeOf (sbi.GetType ());
// Scroll bars implements the Value pattern if
// 1) they are owner drawn (in which case GetScrollBarInfo fails)
// 2) they have a thumb (not completely squished)
if (Misc.GetScrollBarInfo(hwnd, idObject, ref sbi))
{
return IsScrollBarWithThumb (hwnd, sbFlag);
}
return true;
}
private bool IsEnabled()
{
NativeMethods.ScrollBarInfo sbi = new NativeMethods.ScrollBarInfo();
sbi.cbSize = Marshal.SizeOf(sbi.GetType());
int idObject = NativeMethods.OBJID_CLIENT;
if (_sbFlag == NativeMethods.SB_VERT)
{
idObject = NativeMethods.OBJID_VSCROLL;
}
else if (_sbFlag == NativeMethods.SB_HORZ)
{
idObject = NativeMethods.OBJID_HSCROLL;
}
if (!Misc.GetScrollBarInfo(_hwnd, idObject, ref sbi))
{
return false;
}
return !Misc.IsBitSet(sbi.scrollBarInfo, NativeMethods.STATE_SYSTEM_UNAVAILABLE);
}
private bool IsStandAlone()
{
return _parent == null;
}
#endregion
// ------------------------------------------------------
//
// Protected Fields
//
// ------------------------------------------------------
#region Protected Fields
// Cached value for the scroll bar style
protected int _sbFlag = NativeMethods.SB_CTL;
#endregion
// ------------------------------------------------------
//
// Private Fields
//
// ------------------------------------------------------
#region Private Fields
private enum ScrollBarInfo
{
CurrentPosition,
MaximumPosition,
MinimumPosition,
PageSize,
TrackPosition,
LargeChange,
SmallChange
};
#endregion
}
}
|