using System; using System.Collections.Generic; using System.Text; using System.Linq; using CPF.Input; using System.ComponentModel; namespace CPF.Controls { /// /// 表示可包含其他可视元素的可滚动区域。 /// [Description("表示可包含其他可视元素的可滚动区域")] public class ScrollViewer : ContentControl { ///// ///// 获取或设置一个值,该值指示是否允许滚动支持 IScrollInfo 接口的元素。 ///// //[PropertyMetadata(true)] //public bool CanContentScroll //{ // get { return GetValue(); } // set { SetValue(value); } //} /// /// 获取或设置一个值,该值指示是否应显示水平 ScrollBar。 /// [PropertyMetadata(ScrollBarVisibility.Auto)] public ScrollBarVisibility HorizontalScrollBarVisibility { get { return GetValue(); } set { SetValue(value); } } /// /// 获取或设置一个值,该值指示是否应显示垂直 ScrollBar。 /// [PropertyMetadata(ScrollBarVisibility.Auto)] public ScrollBarVisibility VerticalScrollBarVisibility { get { return GetValue(); } set { SetValue(value); } } /// /// 水平 ScrollBar 是否可见。 /// public Visibility ComputedHorizontalScrollBarVisibility { get { return GetValue(); } private set { SetValue(value); } } /// /// 垂直 ScrollBar 是否可见。 /// public Visibility ComputedVerticalScrollBarVisibility { get { return GetValue(); } private set { SetValue(value); } } ///// ///// 获取可见内容的垂直偏移量。 ///// //public double ContentVerticalOffset //{ // get { return GetValue(); } // set { SetValue(value); } //} ///// ///// 获取可见内容的水平偏移量。 ///// //public double ContentHorizontalOffset //{ // get { return GetValue(); } // set { SetValue(value); } //} /// /// 获取包含盘区垂直大小的值 /// [NotCpfProperty] public float ExtentHeight { get { if (scrollInfo == null) { return 0; } return scrollInfo.ExtentHeight; } } /// /// 获取包含盘区水平大小的值 /// [NotCpfProperty] public float ExtentWidth { get { if (scrollInfo == null) { return 0; } return scrollInfo.ExtentWidth; } } /// /// ViewportWidth contains the horizontal size of the scrolling viewport. /// /// /// ExtentWidth is only an output property; it can effectively be set by specifying /// Width on this element. /// [NotCpfProperty] public float ViewportWidth { get { if (scrollInfo == null) { return 0; } return scrollInfo.ViewportWidth; } } /// /// ViewportHeight contains the vertical size of the scrolling viewport. /// /// /// ViewportHeight is only an output property; it can effectively be set by specifying /// Height on this element. /// [NotCpfProperty] public float ViewportHeight { get { if (scrollInfo == null) { return 0; } return scrollInfo.ViewportHeight; } } IScrollInfo scrollInfo; /// /// Scroll content by one line to the top. /// public void LineUp() { if (scrollInfo != null) { scrollInfo.LineUp(); } } /// /// Scroll content by one line to the bottom. /// public void LineDown() { if (scrollInfo != null) { scrollInfo.LineDown(); } } /// /// Scroll content by one line to the left. /// public void LineLeft() { if (scrollInfo != null) { scrollInfo.LineLeft(); } } /// /// Scroll content by one line to the right. /// public void LineRight() { if (scrollInfo != null) { scrollInfo.LineRight(); } } /// /// Scroll content by one page to the top. /// public void PageUp() { if (scrollInfo != null) { scrollInfo.PageUp(); } } /// /// Scroll content by one page to the bottom. /// public void PageDown() { if (scrollInfo != null) { scrollInfo.PageDown(); } } /// /// Scroll content by one page to the left. /// public void PageLeft() { if (scrollInfo != null) { scrollInfo.PageLeft(); } } /// /// Scroll content by one page to the right. /// public void PageRight() { if (scrollInfo != null) { scrollInfo.PageRight(); } } ///// ///// 0 and ExtentWidth less ViewportWidth" ///// //public void SetHorizontalOffset(float offset) //{ // if (scrollInfo != null) // { // scrollInfo.SetHorizontalOffset(offset); // } //} ///// ///// Set the VerticalOffset to the passed value. ///// An implementation may coerce this value into a valid range, typically inclusively between 0 and less . ///// //public void SetVerticalOffset(float offset) //{ // if (scrollInfo != null) // { // scrollInfo.SetVerticalOffset(offset); // } //} protected override void InitializeComponent() { //var col = new ColumnDefinition { }; //var row = new RowDefinition { }; Children.Add(new Grid { Width = "100%", Height = "100%", ColumnDefinitions = { new ColumnDefinition { }, new ColumnDefinition { Width = "auto" } }, RowDefinitions = { new RowDefinition { }, new RowDefinition { Height = "auto" } }, Children = { {new Border{Name="contentPresenter",Width="100%",Height="100%", BorderStroke="0", PresenterFor=this, } }, {new ScrollBar{ Width=20, Height="100%",Cursor=Cursors.Arrow, IncreaseLargeChanged=PageDown, DecreaseLargeChanged=PageUp, IncreaseSmallChange=LineDown, DecreaseSmallChange=LineUp,Bindings={ { nameof(ScrollBar.Maximum),nameof(VerticalMaximum),this,BindingMode.TwoWay }, { nameof(ScrollBar.Value),nameof(VerticalOffset),this,BindingMode.TwoWay}, { nameof(ScrollBar.ViewportSize),nameof(VerticalViewportSize),2 }, { nameof(Visibility),nameof(ComputedVerticalScrollBarVisibility),2}, { nameof(IsEnabled),nameof(VerticalScrollBarVisibility),this,BindingMode.OneWay,a=>((ScrollBarVisibility)a)!=ScrollBarVisibility.Disabled }, //{ nameof(Visibility),nameof(Width),col,BindingMode.OneWayToSource,null,a=>((Visibility)a)==Visibility.Visible?(GridLength)((FloatField)Binding.Current.Owner.GetValue(nameof(ScrollBar.Width))).Value:(GridLength)0} } },1,0 }, {new ScrollBar{ Orientation=Orientation.Horizontal,Width="100%", Height=20,Cursor=Cursors.Arrow, IncreaseLargeChanged=PageRight, DecreaseLargeChanged=PageLeft, IncreaseSmallChange=LineRight, DecreaseSmallChange=LineLeft,Bindings={ { nameof(ScrollBar.Maximum),nameof(HorizontalMaximum),this,BindingMode.TwoWay }, { nameof(ScrollBar.Value),nameof(HorizontalOffset),this,BindingMode.TwoWay}, { nameof(ScrollBar.ViewportSize),nameof(HorizontalViewportSize),2 }, { nameof(Visibility),nameof(ComputedHorizontalScrollBarVisibility),2}, { nameof(IsEnabled),nameof(HorizontalScrollBarVisibility),this,BindingMode.OneWay,a=>((ScrollBarVisibility)a)!=ScrollBarVisibility.Disabled }, //{ nameof(Visibility),nameof(Height),row,BindingMode.OneWayToSource,null,a=>((Visibility)a)==Visibility.Visible?(GridLength)((FloatField)Binding.Current.Owner.GetValue(nameof(ScrollBar.Height))).Value:(GridLength)0 } } },0,1 }, } }); } protected float VerticalViewportSize { get { return GetValue(); } set { SetValue(value); } } protected float HorizontalViewportSize { get { return GetValue(); } set { SetValue(value); } } /// /// 垂直偏移,0到ExtentHeight-ViewportHeight /// public float VerticalOffset { get { return GetValue(); } set { SetValue(value); } } /// /// 垂直最大值 /// [Browsable(false)] public float VerticalMaximum { get { return GetValue(); } private set { SetValue(value); } } /// /// 水平最大值 /// [Browsable(false)] public float HorizontalMaximum { get { return GetValue(); } private set { SetValue(value); } } /// /// 水平偏移,0到ExtentWidth-ViewportWidth /// public float HorizontalOffset { get { return GetValue(); } set { SetValue(value); } } protected override void OnMouseWheel(MouseWheelEventArgs e) { base.OnMouseWheel(e); if (!e.Handled) { if (scrollInfo != null) { if (e.IsTouch) { if (scrollInfo.CanHorizontallyScroll) { scrollInfo.SetHorizontalOffset(HorizontalOffset - e.Delta.X); e.Handled = true; } if (scrollInfo.CanVerticallyScroll) { scrollInfo.SetVerticalOffset(VerticalOffset - e.Delta.Y); e.Handled = true; } } else { if (scrollInfo.CanVerticallyScroll) { if (e.Delta.Y < 0) { scrollInfo.MouseWheelUp(); } else if (e.Delta.Y > 0) { scrollInfo.MouseWheelDown(); } e.Handled = true; } if (scrollInfo.CanHorizontallyScroll) { if (e.Delta.X < 0) { scrollInfo.MouseWheelLeft(); } else if (e.Delta.X > 0) { scrollInfo.MouseWheelRight(); } e.Handled = true; } } } } } protected override void OnSetContentElement(UIElement contentPresenter, UIElement oldValue, UIElement newValue) { if (oldValue != null) { if (scrollInfo != null) { scrollInfo.ScrollChanged -= Scroll_ScrollChanged; scrollInfo.ScrollOwner = null; } contentPresenter.Children.Remove(oldValue); } scrollInfo = null; if (newValue != null) { scrollInfo = newValue as IScrollInfo; if (scrollInfo == null) { scrollInfo = new ScrollContentPresenter { Child = newValue, Width = "100%", Height = "100%" }; } scrollInfo.ScrollChanged += Scroll_ScrollChanged; scrollInfo.ScrollOwner = this; var n = scrollInfo as UIElement; n.ClipToBounds = true; contentPresenter.Children.Add(n); Scroll_ScrollChanged(null, null); } } protected override void OnLayoutUpdated() { base.OnLayoutUpdated(); Scroll_ScrollChanged(null, null); } private void Scroll_ScrollChanged(object sender, EventArgs e) { if (scrollInfo != null) { HorizontalMaximum = Math.Max(0, scrollInfo.ExtentWidth - scrollInfo.ViewportWidth); VerticalMaximum = Math.Max(0, scrollInfo.ExtentHeight - scrollInfo.ViewportHeight); HorizontalOffset = scrollInfo.HorizontalOffset; VerticalOffset = scrollInfo.VerticalOffset; VerticalViewportSize = scrollInfo.ViewportHeight; HorizontalViewportSize = scrollInfo.ViewportWidth; //var len = (float)(scrollInfo.ViewportHeight / scrollInfo.ExtentHeight); //if (scrollInfo.ExtentHeight == 0) //{ // len = 1; //} //len = Math.Min(1, len); //VerticalThumbLength = new FloatField(len, Unit.Percent); //len = (float)(scrollInfo.ViewportWidth / scrollInfo.ExtentWidth); //if (scrollInfo.ExtentWidth == 0) //{ // len = 1; //} //len = Math.Min(1, len); //HorizontalThumbLength = new FloatField(len, Unit.Percent); switch (HorizontalScrollBarVisibility) { case ScrollBarVisibility.Auto: ComputedHorizontalScrollBarVisibility = scrollInfo.CanHorizontallyScroll ? Visibility.Visible : Visibility.Collapsed; break; case ScrollBarVisibility.Hidden: ComputedHorizontalScrollBarVisibility = Visibility.Collapsed; break; case ScrollBarVisibility.Disabled: case ScrollBarVisibility.Visible: ComputedHorizontalScrollBarVisibility = Visibility.Visible; break; } switch (VerticalScrollBarVisibility) { case ScrollBarVisibility.Auto: ComputedVerticalScrollBarVisibility = scrollInfo.CanVerticallyScroll ? Visibility.Visible : Visibility.Collapsed; break; case ScrollBarVisibility.Hidden: ComputedVerticalScrollBarVisibility = Visibility.Collapsed; break; case ScrollBarVisibility.Disabled: case ScrollBarVisibility.Visible: ComputedVerticalScrollBarVisibility = Visibility.Visible; break; } OnScroll(); } else { switch (HorizontalScrollBarVisibility) { case ScrollBarVisibility.Auto: ComputedHorizontalScrollBarVisibility = Visibility.Collapsed; break; case ScrollBarVisibility.Hidden: ComputedHorizontalScrollBarVisibility = Visibility.Collapsed; break; case ScrollBarVisibility.Disabled: case ScrollBarVisibility.Visible: ComputedHorizontalScrollBarVisibility = Visibility.Visible; break; } switch (VerticalScrollBarVisibility) { case ScrollBarVisibility.Auto: ComputedVerticalScrollBarVisibility = Visibility.Collapsed; break; case ScrollBarVisibility.Hidden: ComputedVerticalScrollBarVisibility = Visibility.Collapsed; break; case ScrollBarVisibility.Disabled: case ScrollBarVisibility.Visible: ComputedVerticalScrollBarVisibility = Visibility.Visible; break; } } } protected virtual void OnScroll() { } [PropertyChanged(nameof(VerticalOffset))] void OnVerticalOffset(object newValue, object oldValue, PropertyMetadataAttribute attribute) { if (scrollInfo != null) { scrollInfo.SetVerticalOffset((float)newValue); } } [PropertyChanged(nameof(HorizontalOffset))] void OnHorizontalOffset(object newValue, object oldValue, PropertyMetadataAttribute attribute) { if (scrollInfo != null) { scrollInfo.SetHorizontalOffset((float)newValue); } } //protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue, PropertyMetadataAttribute propertyMetadata) //{ // if (propertyName == nameof(VerticalOffset)) // { // if (scrollInfo != null) // { // scrollInfo.SetVerticalOffset((float)newValue); // } // } // else if (propertyName == nameof(HorizontalOffset)) // { // if (scrollInfo != null) // { // scrollInfo.SetHorizontalOffset((float)newValue); // } // } // base.OnPropertyChanged(propertyName, oldValue, newValue, propertyMetadata); //} } public enum ScrollBarVisibility { /// /// No scrollbars and no scrolling in this dimension. /// Disabled = 0, /// /// The scrollbar should be visible only if there is more content than fits in the viewport. /// Auto, /// /// The scrollbar should never be visible. No space should ever be reserved for the scrollbar. /// Hidden, /// /// The scrollbar should always be visible. Space should always be reserved for the scrollbar. /// Visible, } }