// ------------------------------------------------------ // // Private Methods // // ------------------------------------------------------ // Retrieve the scrollbar position in the [0..100]% range static private double GetScrollInfo(IntPtr hwnd, int sbFlag) { // check if there is a scrollbar NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo(); si.fMask = NativeMethods.SIF_ALL; si.cbSize = Marshal.SizeOf(si.GetType()); if (Misc.GetScrollInfo(hwnd, sbFlag, ref si)) { if (si.nMax != si.nMin && si.nPage != si.nMax - si.nMin + 1) { int delta; // 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")) { delta = (si.nPage > 0) ? si.nPage : 0; } else { delta = (si.nPage > 0) ? si.nPage - 1 : 0; } return(100.0 * (si.nPos - si.nMin) / ((si.nMax - delta) - si.nMin)); } } return((double)ScrollPattern.NoScroll); }
// ------------------------------------------------------ // // Private Methods // // ------------------------------------------------------ #region Private Methods // Scroll by a given amount private void Scroll(ScrollAmount amount, int style) { IntPtr parentHwnd = _sbFlag == NativeMethods.SB_CTL ? Misc.GetWindowParent(_hwnd) : _hwnd; int wParam = 0; switch (amount) { case ScrollAmount.LargeDecrement: wParam = NativeMethods.SB_PAGEUP; break; case ScrollAmount.SmallDecrement: wParam = NativeMethods.SB_LINEUP; break; case ScrollAmount.LargeIncrement: wParam = NativeMethods.SB_PAGEDOWN; break; case ScrollAmount.SmallIncrement: wParam = NativeMethods.SB_LINEDOWN; break; } 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; } // If the scrollbar is at the maximum position and the user passes // pagedown or linedown, just return if ((si.nPos == si.nMax) && (wParam == NativeMethods.SB_PAGEDOWN || wParam == NativeMethods.SB_LINEDOWN)) { return; } // If the scrollbar is at the minimum position and the user passes // pageup or lineup, just return if ((si.nPos == si.nMin) && (wParam == NativeMethods.SB_PAGEUP || wParam == NativeMethods.SB_LINEUP)) { return; } int msg = (style == NativeMethods.SBS_HORZ) ? NativeMethods.WM_HSCROLL : NativeMethods.WM_VSCROLL; Misc.ProxySendMessage(parentHwnd, msg, (IntPtr)wParam, (IntPtr)(parentHwnd == _hwnd ? IntPtr.Zero : _hwnd)); }
// 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); }
// Finds if a control can be scrolled static internal bool Scrollable(IntPtr hwnd, int sbFlag) { int style = Misc.GetWindowStyle(hwnd); if ((sbFlag == NativeMethods.SB_HORZ && !Misc.IsBitSet(style, NativeMethods.WS_HSCROLL)) || (sbFlag == NativeMethods.SB_VERT && !Misc.IsBitSet(style, NativeMethods.WS_VSCROLL))) { return(false); } if (!Misc.IsEnabled(hwnd)) { return(false); } // Check if the scroll info shows the scroll bar as enabled. bool scrollBarEnabled = false; NativeMethods.ScrollBarInfo sbi = new NativeMethods.ScrollBarInfo(); sbi.cbSize = Marshal.SizeOf(sbi.GetType()); int scrollBarObjectId = (sbFlag == NativeMethods.SB_VERT) ? NativeMethods.OBJID_VSCROLL : NativeMethods.OBJID_HSCROLL; if (Misc.GetScrollBarInfo(hwnd, scrollBarObjectId, ref sbi)) { scrollBarEnabled = !Misc.IsBitSet(sbi.scrollBarInfo, NativeMethods.STATE_SYSTEM_UNAVAILABLE); } if (!scrollBarEnabled) { return(false); } // Get scroll range NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo(); si.cbSize = Marshal.SizeOf(si.GetType()); si.fMask = NativeMethods.SIF_ALL; if (!Misc.GetScrollInfo(hwnd, sbFlag, ref si)) { return(false); } return(si.nMax != si.nMin && si.nPage != si.nMax - si.nMin + 1); }
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); }
// View Size static private double ScrollViewSize(IntPtr hwnd, int sbFlag) { // Get scroll range and page size 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) || (si.nMax == si.nMin)) { return(100.0); } else { // "+1" because nPage can be 0 to nMax-nMin+1 int nPage = si.nPage > 0 ? si.nPage : 1; return((100.0 * nPage) / (si.nMax + 1 - si.nMin)); } }
// ------------------------------------------------------ // // 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 GetBoundingRectangle(IntPtr hwnd, ProxyFragment parent, WindowsScrollBar.ScrollBarItem item, int sbFlag) { NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo(); si.cbSize = Marshal.SizeOf(si.GetType()); si.fMask = NativeMethods.SIF_RANGE; // If the scroll bar is disabled, we cannot have a thumb and large Increment/Decrement) bool fDisableScrollBar = !WindowsScrollBar.IsScrollBarWithThumb(hwnd, sbFlag); if (fDisableScrollBar && (item == WindowsScrollBar.ScrollBarItem.LargeDecrement || item == WindowsScrollBar.ScrollBarItem.Thumb || item == WindowsScrollBar.ScrollBarItem.LargeDecrement)) { return(Rect.Empty); } // If fails assume that the hwnd is invalid if (!Misc.GetScrollInfo(hwnd, sbFlag, ref si)) { return(Rect.Empty); } 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()); if (!Misc.GetScrollBarInfo(hwnd, idObject, ref sbi)) { return(Rect.Empty); } if (parent != null && parent._parent != null) { // // Builds prior to Vista 5359 failed to correctly account for RTL scrollbar layouts. // if ((Environment.OSVersion.Version.Major < 6) && (Misc.IsLayoutRTL(parent._parent._hwnd))) { // Right to left mirroring style Rect rcParent = parent._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; } } } // 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 // // Note that this test must come after the rectangle has been normalized for RTL or it will fail // 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 (WindowsScrollBar.IsScrollBarVertical(hwnd, sbFlag)) { return(GetVerticalScrollbarBitBoundingRectangle(hwnd, item, sbi)); } else { return(GetHorizontalScrollbarBitBoundingRectangle(hwnd, item, sbi)); } }
// Scroll control by a given amount static private bool ScrollCursor(IntPtr hwnd, ScrollAmount amount, int sbFlag, bool fForceResults) { // Check Param if (amount == ScrollAmount.NoAmount) { return(true); } if (!Scrollable(hwnd, sbFlag)) { return(false); } // Get Max & min NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo(); si.fMask = NativeMethods.SIF_ALL; si.cbSize = Marshal.SizeOf(si.GetType()); // if no scroll bar return false // on Win 6.0 success is false // on other system check through the scroll info is a scroll bar is there if ((!Misc.GetScrollInfo(hwnd, sbFlag, ref si) || !((si.nMax != si.nMin && si.nPage != si.nMax - si.nMin + 1)))) { return(false); } // Get Action to perform int nAction; if (sbFlag == NativeMethods.SB_HORZ) { switch (amount) { case ScrollAmount.SmallDecrement: nAction = NativeMethods.SB_LINELEFT; break; case ScrollAmount.LargeDecrement: nAction = NativeMethods.SB_PAGELEFT; break; case ScrollAmount.SmallIncrement: nAction = NativeMethods.SB_LINERIGHT; break; case ScrollAmount.LargeIncrement: nAction = NativeMethods.SB_PAGERIGHT; break; default: return(false); } } else { switch (amount) { case ScrollAmount.SmallDecrement: nAction = NativeMethods.SB_LINEUP; break; case ScrollAmount.LargeDecrement: nAction = NativeMethods.SB_PAGEUP; break; case ScrollAmount.SmallIncrement: nAction = NativeMethods.SB_LINEDOWN; break; case ScrollAmount.LargeIncrement: nAction = NativeMethods.SB_PAGEDOWN; break; default: return(false); } } // Set position int wParam = NativeMethods.Util.MAKELONG(nAction, 0); int message = sbFlag == NativeMethods.SB_HORZ ? NativeMethods.WM_HSCROLL : NativeMethods.WM_VSCROLL; int result = Misc.ProxySendMessageInt(hwnd, message, (IntPtr)wParam, IntPtr.Zero); return(result == 0 || fForceResults); }
// Request to scroll a control horizontally or vertically by a specified amount. static private bool SetScrollPercent(IntPtr hwnd, double fScrollPos, int sbFlag, out bool forceResults) { forceResults = false; // Check param if ((int)fScrollPos == (int)ScrollPattern.NoScroll) { return(true); } if (!Scrollable(hwnd, sbFlag)) { return(false); } if (fScrollPos < 0 || fScrollPos > 100) { throw new ArgumentOutOfRangeException(sbFlag == NativeMethods.SB_HORZ ? "horizontalPercent" : "verticalPercent", SR.Get(SRID.ScrollBarOutOfRange)); } // Get Max & min NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo(); si.fMask = NativeMethods.SIF_ALL; si.cbSize = Marshal.SizeOf(si.GetType()); // if no scroll bar return false // on Win 6.0 success is false // on other system check through the scroll info is a scroll bar is there if (!Misc.GetScrollInfo(hwnd, sbFlag, ref si) || !((si.nMax != si.nMin && si.nPage != si.nMax - si.nMin + 1))) { return(false); } // Set position int delta = (si.nPage > 0) ? si.nPage - 1 : 0; int newPos = (int)Math.Round(((si.nMax - delta) - si.nMin) * fScrollPos / 100.0 + si.nMin); // No move, exit if (newPos == si.nPos) { return(true); } si.nPos = newPos; forceResults = true; int message = sbFlag == NativeMethods.SB_HORZ ? NativeMethods.WM_HSCROLL : NativeMethods.WM_VSCROLL; int wParam = NativeMethods.Util.MAKELONG(NativeMethods.SB_THUMBPOSITION, si.nPos); bool fRet = Misc.ProxySendMessageInt(hwnd, message, (IntPtr)wParam, IntPtr.Zero) == 0; if (fRet && Misc.GetScrollInfo(hwnd, sbFlag, ref si) && si.nPos != newPos) { // Microsoft treeview has some problems. The first is that the SendMessage with WM_HSCROLL/WM_VSCROLL // with SB_THUMBPOSITION is not moving the scroll position. The second problem is that SetScrollInfo() // lose the theming for the scroll bars and it really does not move the scroll position. The // scrollbars change but it does not scroll the treeview control. int prevPos = newPos; ScrollAmount prevAmount = si.nPos > newPos ? ScrollAmount.SmallDecrement : ScrollAmount.SmallIncrement; do { ScrollAmount amount = si.nPos > newPos ? ScrollAmount.SmallDecrement : ScrollAmount.SmallIncrement; // If we were moving in one direction and overshoot, break to prevent getting into infant loop. // If ScrollCursor() can not set the new position, also break to prevent infant loop. if (prevAmount != amount || prevPos == si.nPos) { break; } prevPos = si.nPos; fRet = ScrollCursor(hwnd, amount, sbFlag, forceResults); } while (fRet && Misc.GetScrollInfo(hwnd, sbFlag, ref si) && si.nPos != newPos); } return(fRet); }
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.Get(SRID.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.Get(SRID.RangeValueMax)); } else if (val < si.nMin) { throw new ArgumentOutOfRangeException("value", val, SR.Get(SRID.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); } }
//------------------------------------------------------ // // Patterns Implementation // //------------------------------------------------------ #region RangeValue Pattern void IRangeValueProvider.SetValue(double 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)) { return; } int pos = (int)val; // Throw if val is greater than the maximum or less than the minimum. // See remarks for WindowsScrollBar.GetScrollValue(ScrollBarInfo.MaximumPosition) // regarding this calculation of the allowed maximum. if (pos > si.nMax - si.nPage + (si.nPage > 0 ? 1 : 0)) { throw new ArgumentOutOfRangeException("value", val, SR.Get(SRID.RangeValueMax)); } else if (pos < si.nMin) { throw new ArgumentOutOfRangeException("value", val, SR.Get(SRID.RangeValueMin)); } // LVM_SCROLL does not work in mode Report, use SetScrollPos instead bool isVerticalScroll = IsScrollBarVertical(_hwnd, _sbFlag); if (isVerticalScroll && WindowsListView.InReportView(_hwnd)) { Misc.SetScrollPos(_hwnd, _sbFlag, pos, true); return; } // get the "full size" of the list-view int size = WindowsListView.ApproximateViewRect(_hwnd); // delta between current and user-requested position in pixels // since the cPelsAll contains the dimension in pels for all items + the 2 pels of the border // the operation below does a trunc on purpose int dx = 0, dy = 0; if (!isVerticalScroll) { int cPelsAll = NativeMethods.Util.LOWORD(size); dx = (int)((pos - si.nPos) * ((double)cPelsAll / (si.nMax + 1 - si.nMin))); } else { int cPelsAll = NativeMethods.Util.HIWORD(size); dy = (int)((pos - si.nPos) * ((double)cPelsAll / (si.nMax + 1 - si.nMin))); } if (WindowsListView.Scroll(_hwnd, (IntPtr)dx, (IntPtr)dy)) { // Check the result again, on occasion the result will be different than the value // we previously got. It's unsure why this is, but for now we'll just reissue the // scroll command with the new delta. if (!Misc.GetScrollInfo(_hwnd, _sbFlag, ref si)) { return; } if (si.nPos != pos) { if (!isVerticalScroll) { int cPelsAll = NativeMethods.Util.LOWORD(size); dx = (pos - si.nPos) * (cPelsAll / (si.nMax + 1 - si.nMin)); } else { int cPelsAll = NativeMethods.Util.HIWORD(size); dy = (pos - si.nPos) * (cPelsAll / (si.nMax + 1 - si.nMin)); } WindowsListView.Scroll(_hwnd, (IntPtr)dx, (IntPtr)dy); } } }