using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using Tango.Core.EventArguments; using Tango.DragAndDrop; namespace Tango.Touch.Controls { public class LightTouchScrollViewer : ContentControl { private const double _min_scroll_delta = 20; private Border _border_viewport; private Grid _grid_content; private Point _mouse_down_location; private List _last_manipulation_deltas; private const double touch_inertia_coefficiant = 10; private const double bounce_offset_max = 20; private Thumb _thumb; private Border _border_thumb; private StylusDevice _moveTouchDevice; private FrameworkElement _down_element; private bool _is_dragging_off_view; private DispatcherTimer _dragging_timer; private DateTime _current_move_time; private double _current_move_delta; private double _last_move_delta; private const double _min_move_delta = 10; private const double _delta_measure_interval_milli = 10; private const double _friction_factor_per_second = 0.9; private DispatcherTimer _move_timer; private double _last_scroll_position; private const double SCROLL_DELTA = 30; #region Events public delegate void ScrollingEventHandler(object sender, DoubleValueChangedEventArgs e); public static readonly RoutedEvent ScrollingEvent = EventManager.RegisterRoutedEvent("Scrolling", RoutingStrategy.Bubble, typeof(ScrollingEventHandler), typeof(LightTouchScrollViewer)); /// /// Occurs when the scrolling position has changed. /// public event ScrollingEventHandler Scrolling { add { AddHandler(ScrollingEvent, value); } remove { RemoveHandler(ScrollingEvent, value); } } #endregion #region Properties public Thickness InternalMargin { get { return (Thickness)GetValue(InternalMarginProperty); } set { SetValue(InternalMarginProperty, value); } } public static readonly DependencyProperty InternalMarginProperty = DependencyProperty.Register("InternalMargin", typeof(Thickness), typeof(LightTouchScrollViewer), new PropertyMetadata(default(Thickness), (d, e) => (d as LightTouchScrollViewer).RaiseScrollEvent())); public Border GetViewportBorder() { return _border_viewport; } public Grid GetContentGrid() { return _grid_content; } /// /// Gets or sets the scroll bar margins. /// public Thickness ScrollBarMargin { get { return (Thickness)GetValue(ScrollBarMarginProperty); } set { SetValue(ScrollBarMarginProperty, value); } } public static readonly DependencyProperty ScrollBarMarginProperty = DependencyProperty.Register("ScrollBarMargin", typeof(Thickness), typeof(LightTouchScrollViewer), new PropertyMetadata(new Thickness())); /// /// Gets or sets a value indicating whether the data grid is currently scrolling. /// public bool IsScrolling { get { return (bool)GetValue(IsScrollingProperty); } set { SetValue(IsScrollingProperty, value); } } public static readonly DependencyProperty IsScrollingProperty = DependencyProperty.Register("IsScrolling", typeof(bool), typeof(LightTouchScrollViewer), new PropertyMetadata(false)); /// /// Gets or sets a value indicating whether the last mouse or touch up event was after scrolling. /// public bool IsAfterScrolling { get { return (bool)GetValue(IsAfterScrollingProperty); } set { SetValue(IsAfterScrollingProperty, value); } } public static readonly DependencyProperty IsAfterScrollingProperty = DependencyProperty.Register("IsAfterScrolling", typeof(bool), typeof(LightTouchScrollViewer), new PropertyMetadata(false)); /// /// Gets or sets a value indicating whether this instance is mouse touch down. /// public bool IsMouseTouchDown { get { return (bool)GetValue(IsMouseTouchDownProperty); } set { SetValue(IsMouseTouchDownProperty, value); } } public static readonly DependencyProperty IsMouseTouchDownProperty = DependencyProperty.Register("IsMouseTouchDown", typeof(bool), typeof(LightTouchScrollViewer), new PropertyMetadata(false)); /// /// Gets or sets the width of the scroll bar. /// /// /// The width of the scroll bar. /// public double ScrollBarWidth { get { return (double)GetValue(ScrollBarWidthProperty); } set { SetValue(ScrollBarWidthProperty, value); } } public static readonly DependencyProperty ScrollBarWidthProperty = DependencyProperty.Register("ScrollBarWidth", typeof(double), typeof(LightTouchScrollViewer), new PropertyMetadata(5.0)); /// /// Gets or sets the scroll bar background. /// public Brush ScrollBarBackground { get { return (Brush)GetValue(ScrollBarBackgroundProperty); } set { SetValue(ScrollBarBackgroundProperty, value); } } public static readonly DependencyProperty ScrollBarBackgroundProperty = DependencyProperty.Register("ScrollBarBackground", typeof(Brush), typeof(LightTouchScrollViewer), new PropertyMetadata(null)); /// /// Gets or sets the scroll bar foreground. /// public Brush ScrollBarForeground { get { return (Brush)GetValue(ScrollBarForegroundProperty); } set { SetValue(ScrollBarForegroundProperty, value); } } public static readonly DependencyProperty ScrollBarForegroundProperty = DependencyProperty.Register("ScrollBarForeground", typeof(Brush), typeof(LightTouchScrollViewer), new PropertyMetadata(null)); /// /// Gets or sets the scroll bar corner radius. /// public CornerRadius ScrollBarCornerRadius { get { return (CornerRadius)GetValue(ScrollBarCornerRadiusProperty); } set { SetValue(ScrollBarCornerRadiusProperty, value); } } public static readonly DependencyProperty ScrollBarCornerRadiusProperty = DependencyProperty.Register("ScrollBarCornerRadius", typeof(CornerRadius), typeof(LightTouchScrollViewer), new PropertyMetadata(default(CornerRadius))); /// /// Gets or sets the size of the top arc. /// public Size TopArcSize { get { return (Size)GetValue(TopArcSizeProperty); } set { SetValue(TopArcSizeProperty, value); } } public static readonly DependencyProperty TopArcSizeProperty = DependencyProperty.Register("TopArcSize", typeof(Size), typeof(LightTouchScrollViewer), new PropertyMetadata(default(Size))); /// /// Gets or sets the size of the bottom arc. /// public Size BottomArcSize { get { return (Size)GetValue(BottomArcSizeProperty); } set { SetValue(BottomArcSizeProperty, value); } } public static readonly DependencyProperty BottomArcSizeProperty = DependencyProperty.Register("BottomArcSize", typeof(Size), typeof(LightTouchScrollViewer), new PropertyMetadata(default(Size))); public bool DisableScrolling { get { return (bool)GetValue(DisableScrollingProperty); } set { SetValue(DisableScrollingProperty, value); } } public static readonly DependencyProperty DisableScrollingProperty = DependencyProperty.Register("DisableScrolling", typeof(bool), typeof(LightTouchScrollViewer), new PropertyMetadata(false)); /// /// Gets or sets the scroll bar visibility. /// public Visibility ScrollBarVisibility { get { return (Visibility)GetValue(ScrollBarVisibilityProperty); } set { SetValue(ScrollBarVisibilityProperty, value); } } public static readonly DependencyProperty ScrollBarVisibilityProperty = DependencyProperty.Register("ScrollBarVisibility", typeof(Visibility), typeof(LightTouchScrollViewer), new PropertyMetadata(Visibility.Visible)); #endregion #region Constructors public LightTouchScrollViewer() { _last_manipulation_deltas = new List(); } static LightTouchScrollViewer() { DefaultStyleKeyProperty.OverrideMetadata(typeof(LightTouchScrollViewer), new FrameworkPropertyMetadata(typeof(LightTouchScrollViewer))); } #endregion #region Apply Template public override void OnApplyTemplate() { base.OnApplyTemplate(); _border_viewport = GetTemplateChild("PART_Border") as Border; _grid_content = GetTemplateChild("PART_Grid_Content") as Grid; _thumb = GetTemplateChild("PART_Thumb") as Thumb; _border_thumb = GetTemplateChild("PART_Thumb_Border") as Border; ContentPresenter presenter = GetTemplateChild("PART_Content_Presenter") as ContentPresenter; _border_viewport.RegisterForMouseOrTouchDown(OnMouseTouchDown); _border_viewport.RegisterForMouseOrStylusMove(OnMouseTouchMove); _border_viewport.RegisterForPreviewMouseOrTouchUp(OnMouseTouchUp); //_border_viewport.ManipulationDelta += _grid_rows_ManipulationDelta; //_border_viewport.ManipulationCompleted += _grid_rows_ManipulationCompleted; _thumb.DragDelta += _thumb_DragDelta; _thumb.DragStarted += _thumb_DragStarted; _thumb.DragCompleted += _thumb_DragCompleted; this.Bind(InternalMarginProperty, _grid_content, Grid.MarginProperty, System.Windows.Data.BindingMode.OneWay); } private void _thumb_DragCompleted(object sender, DragCompletedEventArgs e) { IsScrolling = false; } private void _thumb_DragStarted(object sender, DragStartedEventArgs e) { IsScrolling = true; } private void _thumb_DragDelta(object sender, DragDeltaEventArgs e) { var step = CalculateScrollbarThumbStep(); double content_margin = _grid_content.Margin.Top + -(step * e.VerticalChange); _grid_content.BeginAnimation(Grid.MarginProperty, null); Canvas.SetTop(_border_thumb, Canvas.GetTop(_border_thumb) + e.VerticalChange); _grid_content.Margin = new Thickness(0, content_margin, 0, 0); if (content_margin > 0) { Canvas.SetTop(_border_thumb, 0); _grid_content.Margin = new Thickness(0, 0, 0, 0); } else if (content_margin - _border_viewport.ActualHeight < -_grid_content.ActualHeight) { Canvas.SetTop(_border_thumb, _border_viewport.ActualHeight - _border_thumb.ActualHeight); _grid_content.Margin = new Thickness(0, -(_grid_content.ActualHeight - _border_viewport.ActualHeight), 0, 0); } } #endregion #region Touch Manipulation Handlers /// /// Handles the ManipulationCompleted event of the _grid_rows control. /// /// The source of the event. /// The instance containing the event data. //private void _grid_rows_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) //{ // //_border_viewport.IsManipulationEnabled = false; // ReleaseMouseTouchCapture(); // e.Cancel(); // if (!IsAfterScrolling) return; // if (_last_manipulation_deltas.Count == 0) return; // var last_10 = _last_manipulation_deltas.TakeLast(10); // double last_10_avg = last_10.TakeLast(10).Average(); // double max_delta = last_10.First(x => Math.Abs(x) == last_10.Max(y => Math.Abs(y))); // if (Math.Abs(last_10_avg) > 5) // { // var to = (_grid_content.Margin.Top + (max_delta * touch_inertia_coefficiant)); // if (to > 0) // { // to = Math.Min(to, bounce_offset_max); // } // else // { // to = Math.Max(to, bounce_offset_max + -(_grid_content.ActualHeight - _border_viewport.ActualHeight)); // } // bool bounced = false; // ThicknessAnimation ani = new ThicknessAnimation(); // ani.Duration = TimeSpan.FromSeconds(1); // ani.To = new Thickness(0, to, 0, 0); // ani.CurrentTimeInvalidated += (_, __) => // { // if (!bounced) // { // if (_grid_content.Margin.Top > 0 || (_grid_content.Margin.Top - _border_viewport.ActualHeight < -_grid_content.ActualHeight)) // { // bounced = true; // SnapContentToBounds(); // } // } // SetThumbPosition(); // }; // ani.DecelerationRatio = 1.0; // _grid_content.BeginAnimation(Grid.MarginProperty, ani); // _last_manipulation_deltas.Clear(); // } //} /// /// Handles the ManipulationDelta event of the _grid_rows control. /// /// The source of the event. /// The instance containing the event data. //private void _grid_rows_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) //{ // _last_manipulation_deltas.Add(e.DeltaManipulation.Translation.Y); //} #endregion #region Attached Properties #region Prevent Scroll /// /// The prevent scroll property /// public static readonly DependencyProperty PreventScrollProperty = DependencyProperty.RegisterAttached("PreventScroll", typeof(bool), typeof(LightTouchScrollViewer), new FrameworkPropertyMetadata(false)); /// /// Sets the PreventScroll attached property. /// /// The element. /// if set to true [value]. public static void SetPreventScroll(FrameworkElement element, bool value) { element.SetValue(PreventScrollProperty, value); } /// /// Gets the PreventScroll attached property. /// /// The element. /// public static bool GetPreventScroll(FrameworkElement element) { if (element != null) { return (bool)element.GetValue(PreventScrollProperty); } else { return false; } } #endregion #endregion #region Touch / Mouse Handlers /// /// Called when the mouse touch has been down /// /// The sender. /// The instance containing the event data. protected virtual void OnMouseTouchDown(object sender, MouseOrTouchEventArgs e) { _current_move_delta = 0; if (DisableScrolling) return; IsAfterScrolling = false; if (_move_timer != null) { Debug.WriteLine("INERTIA STOPPED!"); _move_timer.Stop(); } _down_element = e.OriginalSource as FrameworkElement; if (e.OriginalSource.GetType() == typeof(DragThumb)) { return; } var element = e.OriginalSource as FrameworkElement; if (element != null) { if (element.FindAncestor() != this) { return; } if (GetPreventScroll(element)) return; var parentPreventScroll = element.FindAncestor((x) => GetPreventScroll(x as FrameworkElement)); if (parentPreventScroll != null) return; } _mouse_down_location = new Point(e.Location.X, e.Location.Y - _grid_content.Margin.Top); IsMouseTouchDown = true; _last_move_delta = e.Location.Y; _current_move_time = DateTime.Now; } /// /// Called when the mouse touch has been move /// /// The sender. /// The instance containing the event data. protected virtual void OnMouseTouchMove(object sender, MouseOrTouchEventArgs e) { if (DisableScrolling) return; if (_down_element is DragThumb) { if (e.Location.Y > ActualHeight - 100) { //Start animating down. if (!_is_dragging_off_view) { _is_dragging_off_view = true; _dragging_timer = new DispatcherTimer(); _dragging_timer.Interval = TimeSpan.FromMilliseconds(30); _dragging_timer.Tick += (x, y) => { ScrollToPosition(GetScrollPosition() + 15); }; _dragging_timer.Start(); } } else if (e.Location.Y < 100) { //Start animating up. if (!_is_dragging_off_view) { _is_dragging_off_view = true; _dragging_timer = new DispatcherTimer(); _dragging_timer.Interval = TimeSpan.FromMilliseconds(30); _dragging_timer.Tick += (x, y) => { ScrollToPosition(GetScrollPosition() - 15); }; _dragging_timer.Start(); } } else { if (_is_dragging_off_view) { _is_dragging_off_view = false; _dragging_timer.Stop(); } } } var a = _mouse_down_location.Y + _grid_content.Margin.Top; if (IsMouseTouchDown && (Math.Abs((e.Location.Y - a)) > _min_scroll_delta) || IsScrolling) { if (!IsScrolling) { //_border_viewport.IsManipulationEnabled = true; IsScrolling = true; Mouse.Capture(_border_viewport); if (e.StylusDevice != null) { _moveTouchDevice = e.StylusDevice; e.StylusDevice.Capture(_border_viewport); } } double position_y = e.Location.Y - _mouse_down_location.Y; double bottom_offset = -(_grid_content.ActualHeight - (_border_viewport.ActualHeight + -position_y)); if (_grid_content.ActualHeight > _border_viewport.ActualHeight) { _grid_content.BeginAnimation(Grid.MarginProperty, null); _grid_content.Margin = new Thickness(0, position_y, 0, 0); if (_grid_content.Margin.Top > 0) { _grid_content.Margin = new Thickness(0, 0, 0, 0); } if (_grid_content.Margin.Top < -(_grid_content.ActualHeight - _border_viewport.ActualHeight)) { _grid_content.Margin = new Thickness(0, -(_grid_content.ActualHeight - _border_viewport.ActualHeight), 0, 0); } } if (position_y > 0) { BeginAnimation(TopArcSizeProperty, null); TopArcSize = new Size(250, Math.Min(position_y / 15, 100)); } else if (bottom_offset > 0) { BeginAnimation(BottomArcSizeProperty, null); BottomArcSize = new Size(250, Math.Min(bottom_offset / 15, 100)); } SetThumbPosition(); DateTime curDate = DateTime.Now; if (curDate >= _current_move_time.AddMilliseconds(_delta_measure_interval_milli)) { if (e.Location.Y - _last_move_delta != 0) { _current_move_delta = e.Location.Y - _last_move_delta; } _last_move_delta = e.Location.Y; _current_move_time = curDate; } } } /// /// Called when the mouse touch has been up /// /// The sender. /// The instance containing the event data. protected virtual void OnMouseTouchUp(object sender, MouseOrTouchEventArgs e) { if (DisableScrolling) { IsMouseTouchDown = false; ReleaseMouseTouchCapture(); return; } if (_is_dragging_off_view) { _is_dragging_off_view = false; _dragging_timer.Stop(); } if (IsMouseTouchDown) { IsMouseTouchDown = false; if (IsScrolling) { double max_move_delta = 120; double strengh = Math.Abs(_current_move_delta) / max_move_delta; Debug.WriteLine((DateTime.Now - _current_move_time).TotalMilliseconds); Debug.WriteLine("MOVE DELTA: " + _current_move_delta); //Emulate inertia if (Math.Abs(_current_move_delta) > _min_move_delta && DateTime.Now <= _current_move_time.AddMilliseconds(100)) { double pixels_per_second = _current_move_delta * (_delta_measure_interval_milli * 10); Debug.WriteLine("INERTIA STARTED!"); DateTime cur_time = DateTime.Now; Debug.WriteLine("FROM: " + _grid_content.Margin.Top + " TO: " + (_grid_content.Margin.Top + (pixels_per_second) / 100)); _move_timer = new DispatcherTimer(); _move_timer.Interval = TimeSpan.FromMilliseconds(10); _move_timer.Tick += (x, y) => { double to = _grid_content.Margin.Top + (pixels_per_second) / 100; _grid_content.Margin = new Thickness(0, to, 0, 0); if (_grid_content.Margin.Top > 0) { _grid_content.Margin = new Thickness(0, 0, 0, 0); } if (_grid_content.Margin.Top < -(_grid_content.ActualHeight - _border_viewport.ActualHeight)) { _grid_content.Margin = new Thickness(0, -(_grid_content.ActualHeight - _border_viewport.ActualHeight), 0, 0); } if (_grid_content.Margin.Top > bounce_offset_max || (_grid_content.Margin.Top - _border_viewport.ActualHeight < -_grid_content.ActualHeight)) { _move_timer.Stop(); SnapContentToBounds(); SetThumbPosition(); Debug.WriteLine("INERTIA STOPPED BY SNAPPING!"); return; } if (DateTime.Now > cur_time.AddMilliseconds(300 * strengh)) { pixels_per_second *= _friction_factor_per_second; } SetThumbPosition(); if (pixels_per_second > -2 && pixels_per_second < 2) { _move_timer.Stop(); _move_timer = null; Debug.WriteLine("INERTIA STOPPED!"); } }; _move_timer.Start(); } IsAfterScrolling = true; ReleaseMouseTouchCapture(); e.Handled = true; SnapContentToBounds(); } else { IsAfterScrolling = false; } IsScrolling = false; } } /// /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// /// The that contains the event data. protected override void OnMouseWheel( MouseWheelEventArgs e) { base.OnMouseWheel(e); if (DisableScrolling) { return; } base.OnMouseWheel(e); if (!e.Handled ) { if (e.Delta != 0) { var scrollPos = GetScrollPosition(); if (e.Delta > 0) { scrollPos -= SCROLL_DELTA; } else if(e.Delta < 0) { scrollPos += SCROLL_DELTA; } ScrollToPosition(scrollPos); SetThumbPosition(); } } e.Handled = false; } #endregion #region Private Methods private double CalculateScrollbarThumbStep() { var scrollTrackSpace = _grid_content.ActualHeight - _border_viewport.ActualHeight; var scrollThumbSpace = _border_viewport.ActualHeight - _thumb.ActualHeight; var scrollJump = scrollTrackSpace / scrollThumbSpace; return scrollJump; } private void SetThumbPosition() { double step = CalculateScrollbarThumbStep(); if (step != 0) { double content_margin = _grid_content.Margin.Top; double thumb_top = -(content_margin / step); Canvas.SetTop(_border_thumb, thumb_top); if (thumb_top < 0) { Canvas.SetTop(_border_thumb, 0); } else if (thumb_top + _border_thumb.ActualHeight > _border_viewport.ActualHeight) { Canvas.SetTop(_border_thumb, _border_viewport.ActualHeight - _border_thumb.ActualHeight); } } } private void ReleaseMouseTouchCapture() { Mouse.Capture(null); _border_viewport.ReleaseMouseCapture(); _border_viewport.ReleaseAllTouchCaptures(); if (_moveTouchDevice != null) { _border_viewport.ReleaseStylusCapture(); _moveTouchDevice = null; } } public Rect GetViewPortRect() { return new Rect(0, -_grid_content.Margin.Top, _border_viewport.ActualWidth, _border_viewport.ActualHeight); } private Rect GetElementRect(FrameworkElement element) { if (element != null) { return new Rect(element.TranslatePoint(new Point(0, 0), _grid_content), new Size(element.ActualWidth, element.ActualHeight)); } return new Rect(); } #endregion #region Public Methods public void ScrollToElement(FrameworkElement element) { if (element != null) { var e = this.FindVisualChildren().SingleOrDefault(x => x == element); if (e != null) { var location = e.TranslatePoint(new Point(0, 0), _grid_content); Rect elementRect = new Rect(location, new Size(e.ActualWidth, e.ActualHeight)); Rect viewPortRect = new Rect(0, 0, _border_viewport.ActualWidth, _border_viewport.ActualHeight); ScrollToPosition((location.Y - (viewPortRect.Height / 2)) + (elementRect.Height / 2)); } } } public void ScrollToTop() { ScrollToPosition(0); } public void ScrollToPosition(double y) { if (_grid_content != null) { if (_grid_content.ActualHeight > _border_viewport.ActualHeight) { if (y < 0) { y = 0; } if (-y - _border_viewport.ActualHeight < -_grid_content.ActualHeight) { y = (_grid_content.ActualHeight - _border_viewport.ActualHeight); } _grid_content.BeginAnimation(Grid.MarginProperty, null); _grid_content.Margin = new Thickness(0, -y, 0, 0); } SnapContentToBounds(); } } public double GetScrollPosition() { double position = -_grid_content.Margin.Top; if (position < 0) { position = 0; } if (position > (_grid_content.ActualHeight - _border_viewport.ActualHeight)) { position = (_grid_content.ActualHeight - _border_viewport.ActualHeight); } return position; } /// /// Snaps the content if it's out of bounds. /// public void SnapContentToBounds() { ThicknessAnimation ani = new ThicknessAnimation(); ani.Duration = TimeSpan.FromSeconds(0.2); ani.AccelerationRatio = 1; SizeAnimation sizeAni = new SizeAnimation(); sizeAni.Duration = TimeSpan.FromSeconds(0.5); sizeAni.AccelerationRatio = 1; sizeAni.To = new Size(250, 0); BeginAnimation(TopArcSizeProperty, sizeAni); BeginAnimation(BottomArcSizeProperty, sizeAni); if (_grid_content.Margin.Top > 0 || _grid_content.ActualHeight < _border_viewport.ActualHeight) { //ani.To = new Thickness(0); _grid_content.Margin = new Thickness(0); //_grid_content.BeginAnimation(Grid.MarginProperty, ani); } else if (_grid_content.Margin.Top - _border_viewport.ActualHeight < -_grid_content.ActualHeight) { //ani.To = new Thickness(0, -(_grid_content.ActualHeight - _border_viewport.ActualHeight), 0, 0); _grid_content.Margin = new Thickness(0, -(_grid_content.ActualHeight - _border_viewport.ActualHeight), 0, 0); //_grid_content.BeginAnimation(Grid.MarginProperty, ani); } } /// /// Gets the most visible element. /// /// public FrameworkElement GetMostVisibleElement() { var elements = _grid_content.FindVisualChildren().ToList(); if (elements.Count > 0) { var viewPort = GetViewPortRect(); var mostVisible = elements.OrderBy(x => Rect.Intersect(viewPort, GetElementRect(x)).Height).Last(); return mostVisible; } return null; } public Point GetElementPosition(FrameworkElement element) { return element.TranslatePoint(new Point(0, 0), this); } public T GetMostVisibleElementDataContext() where T : class { var elements = _grid_content.FindVisualChildren().Where(x => x.DataContext is T).ToList(); if (elements.Count > 0) { var viewPort = GetViewPortRect(); var mostVisible = elements.OrderBy(x => Rect.Intersect(viewPort, GetElementRect(x)).Height).Last(); return mostVisible.DataContext as T; } return null; } #endregion #region Virtual Methods /// /// Raises the scroll event. /// protected virtual void RaiseScrollEvent() { double position = GetScrollPosition(); if (_last_scroll_position != position) { _last_scroll_position = position; DoubleValueChangedEventArgs e = new DoubleValueChangedEventArgs(ScrollingEvent, this, position); RaiseEvent(e); } } #endregion } }