在 WPF 中创建可滚动控件表面






4.44/5 (10投票s)
如何在 WPF 中创建可滚动控件表面。
你是否遇到过这样的需求:需要用户能够滚动一个大型对象,例如一个图表? 我确实遇到过,并且我刚刚开始做一个业余项目,其中就需要这样的功能。 我们可能都知道 WPF 有一个 ScrollViewer
控制,它允许用户使用滚动条滚动,这很好,但它看起来不太美观。 我希望用户根本意识不到存在一个滚动区域,我希望他们只是使用鼠标来平移大型区域。
为此,我开始四处寻找,并整理了一个小型的演示项目来说明这一点。 它不是很复杂,但它能很好地完成任务。
最终,你仍然使用 WPF 的原生 ScrollViewer
,但隐藏它的 ScrollBars
,并响应鼠标事件。 现在我响应了人们添加一些摩擦力的请求(实际上是我以前的团队领导完成的,因为这是他的领域),所以我们有两个版本,两个版本的 XAML 都是相同的。
让我们看看代码吧?
1: <Window x:Class="ScrollableArea.Window1" 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4: Title="Window1" Height="300" Width="300"> 5: <Window.Resources> 6: 7: <!– scroll viewer Style –> 8: <Style x:Key="ScrollViewerStyle" 9: TargetType="{x:Type ScrollViewer}"> 10: <Setter Property="HorizontalScrollBarVisibility" 11: Value="Hidden" /> 12: <Setter Property="VerticalScrollBarVisibility" 13: Value="Hidden" /> 14: </Style> 15: 16: </Window.Resources> 17: 18: <ScrollViewer x:Name="ScrollViewer" 19: Style="{StaticResource ScrollViewerStyle}"> 20: <ItemsControl x:Name="itemsControl" 21: VerticalAlignment="Center"/> 22: </ScrollViewer> 23: 24: </Window>
可以看到,有一个 ScrollViewer
包含一个 ItemsControl
,但 ItemsControl
可以替换为 Diagram
控制或其他东西,由你选择。 这里唯一重要的一点是,ScrollViewer
的 HorizontalScrollBarVisibility
/VerticalScrollBarVisibility
设置为 Hidden
,这样用户就看不到它们了。
无摩擦版本
接下来,我们需要响应 Mouse
事件。 如下所示:
1: protected override void OnPreviewMouseDown(MouseButtonEventArgs e) 2: { 3: if (ScrollViewer.IsMouseOver) 4: { 5: // Save starting point, used later when determining 6: //how much to scroll. 7: scrollStartPoint = e.GetPosition(this); 8: scrollStartOffset.X = ScrollViewer.HorizontalOffset; 9: scrollStartOffset.Y = ScrollViewer.VerticalOffset; 10: 11: // Update the cursor if can scroll or not. 12: this.Cursor = (ScrollViewer.ExtentWidth > 13: ScrollViewer.ViewportWidth) || 14: (ScrollViewer.ExtentHeight > 15: ScrollViewer.ViewportHeight) ? 16: Cursors.ScrollAll : Cursors.Arrow; 17: 18: this.CaptureMouse(); 19: } 20: 21: base.OnPreviewMouseDown(e); 22: } 23: 24: 25: protected override void OnPreviewMouseMove(MouseEventArgs e) 26: { 27: if (this.IsMouseCaptured) 28: { 29: // Get the new scroll position. 30: Point point = e.GetPosition(this); 31: 32: // Determine the new amount to scroll. 33: Point delta = new Point( 34: (point.X > this.scrollStartPoint.X) ? 35: -(point.X - this.scrollStartPoint.X) : 36: (this.scrollStartPoint.X - point.X), 37: 38: (point.Y > this.scrollStartPoint.Y) ? 39: -(point.Y - this.scrollStartPoint.Y) : 40: (this.scrollStartPoint.Y - point.Y)); 41: 42: // Scroll to the new position. 43: ScrollViewer.ScrollToHorizontalOffset( 44: this.scrollStartOffset.X + delta.X); 45: ScrollViewer.ScrollToVerticalOffset( 46: this.scrollStartOffset.Y + delta.Y); 47: } 48: 49: base.OnPreviewMouseMove(e); 50: } 51: 52: 53: 54: protected override void OnPreviewMouseUp( 55: MouseButtonEventArgs e) 56: { 57: if (this.IsMouseCaptured) 58: { 59: this.Cursor = Cursors.Arrow; 60: this.ReleaseMouseCapture(); 61: } 62: 63: base.OnPreviewMouseUp(e); 64: }
有摩擦版本
使用 Friction
属性设置一个介于 0
和 1
之间的值,0
表示没有摩擦力,1
表示完全摩擦力,这意味着面板将不会“自动滚动
”。
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Windows; 6: using System.Windows.Controls; 7: using System.Windows.Data; 8: using System.Windows.Documents; 9: using System.Windows.Input; 10: using System.Windows.Media; 11: using System.Windows.Media.Imaging; 12: using System.Windows.Navigation; 13: using System.Windows.Shapes; 14: using System.Windows.Threading; 15: using System.Diagnostics; 16: 17: namespace ScrollableArea 18: { 19: /// <summary> 20: /// Demonstrates how to make a scrollable (via the mouse) area that 21: /// would be useful for storing a large object, such as diagram or 22: /// something like that 23: /// </summary> 24: public partial class Window1 : Window 25: { 26: #region Data 27: // Used when manually scrolling. 28: private Point scrollTarget; 29: private Point scrollStartPoint; 30: private Point scrollStartOffset; 31: private Point previousPoint; 32: private Vector velocity; 33: private double friction; 34: private DispatcherTimer animationTimer = new DispatcherTimer(); 35: #endregion 36: 37: #region Ctor 38: 39: public Window1() 40: { 41: InitializeComponent(); 42: this.LoadStuff(); 43: 44: friction = 0.95; 45: 46: animationTimer.Interval = new TimeSpan(0, 0, 0, 0, 20); 47: animationTimer.Tick += new EventHandler(HandleWorldTimerTick); 48: animationTimer.Start(); 49: } 50: #endregion 51: 52: #region Load DUMMY Items 53: void LoadStuff() 54: { 55: //this could be any large object, imagine a diagram… 56: //though for this example I'm just using loads 57: //of Rectangles 58: itemsControl.Items.Add(CreateStackPanel(Brushes.Salmon)); 59: itemsControl.Items.Add(CreateStackPanel(Brushes.Goldenrod)); 60: itemsControl.Items.Add(CreateStackPanel(Brushes.Green)); 61: itemsControl.Items.Add(CreateStackPanel(Brushes.Yellow)); 62: itemsControl.Items.Add(CreateStackPanel(Brushes.Purple)); 63: itemsControl.Items.Add(CreateStackPanel(Brushes.SeaShell)); 64: itemsControl.Items.Add(CreateStackPanel(Brushes.SlateBlue)); 65: itemsControl.Items.Add(CreateStackPanel(Brushes.Tomato)); 66: itemsControl.Items.Add(CreateStackPanel(Brushes.Violet)); 67: itemsControl.Items.Add(CreateStackPanel(Brushes.Plum)); 68: itemsControl.Items.Add(CreateStackPanel(Brushes.PapayaWhip)); 69: itemsControl.Items.Add(CreateStackPanel(Brushes.Pink)); 70: itemsControl.Items.Add(CreateStackPanel(Brushes.Snow)); 71: itemsControl.Items.Add(CreateStackPanel(Brushes.YellowGreen)); 72: itemsControl.Items.Add(CreateStackPanel(Brushes.Tan)); 73: 74: } 75: 76: private StackPanel CreateStackPanel(SolidColorBrush color) 77: { 78: 79: StackPanel sp = new StackPanel(); 80: sp.Orientation = Orientation.Horizontal; 81: 82: for (int i = 0; i < 50; i++) 83: { 84: Rectangle rect = new Rectangle(); 85: rect.Width = 100; 86: rect.Height = 100; 87: rect.Margin = new Thickness(5); 88: rect.Fill = i % 2 == 0 ? Brushes.Black : color; 89: sp.Children.Add(rect); 90: } 91: return sp; 92: } 93: #endregion 94: 95: #region Friction Stuff 96: private void HandleWorldTimerTick(object sender, EventArgs e) 97: { 98: if (IsMouseCaptured) 99: { 100: Point currentPoint = Mouse.GetPosition(this); 101: velocity = previousPoint - currentPoint; 102: previousPoint = currentPoint; 103: } 104: else 105: { 106: if (velocity.Length > 1) 107: { 108: ScrollViewer.ScrollToHorizontalOffset(scrollTarget.X); 109: ScrollViewer.ScrollToVerticalOffset(scrollTarget.Y); 110: scrollTarget.X += velocity.X; 111: scrollTarget.Y += velocity.Y; 112: velocity *= friction; 113: } 114: } 115: } 116: 117: public double Friction 118: { 119: get { return 1.0 - friction; } 120: set { friction = Math.Min(Math.Max(1.0 - value, 0), 1.0); } 121: } 122: #endregion 123: 124: #region Mouse Events 125: protected override void OnPreviewMouseDown(MouseButtonEventArgs e) 126: { 127: if (ScrollViewer.IsMouseOver) 128: { 129: // Save starting point, used later when determining how much to scroll. 130: scrollStartPoint = e.GetPosition(this); 131: scrollStartOffset.X = ScrollViewer.HorizontalOffset; 132: scrollStartOffset.Y = ScrollViewer.VerticalOffset; 133: 134: // Update the cursor if can scroll or not. 135: this.Cursor = (ScrollViewer.ExtentWidth > ScrollViewer.ViewportWidth) || 136: (ScrollViewer.ExtentHeight > ScrollViewer.ViewportHeight) ? 137: Cursors.ScrollAll : Cursors.Arrow; 138: 139: this.CaptureMouse(); 140: } 141: 142: base.OnPreviewMouseDown(e); 143: } 144: 145: 146: protected override void OnPreviewMouseMove(MouseEventArgs e) 147: { 148: if (this.IsMouseCaptured) 149: { 150: Point currentPoint = e.GetPosition(this); 151: 152: // Determine the new amount to scroll. 153: Point delta = new Point(scrollStartPoint.X - 154: currentPoint.X, scrollStartPoint.Y - currentPoint.Y); 155: 156: scrollTarget.X = scrollStartOffset.X + delta.X; 157: scrollTarget.Y = scrollStartOffset.Y + delta.Y; 158: 159: // Scroll to the new position. 160: ScrollViewer.ScrollToHorizontalOffset(scrollTarget.X); 161: ScrollViewer.ScrollToVerticalOffset(scrollTarget.Y); 162: } 163: 164: base.OnPreviewMouseMove(e); 165: } 166: 167: protected override void OnPreviewMouseUp(MouseButtonEventArgs e) 168: { 169: if (this.IsMouseCaptured) 170: { 171: this.Cursor = Cursors.Arrow; 172: this.ReleaseMouseCapture(); 173: } 174: 175: base.OnPreviewMouseUp(e); 176: } 177: #endregion 178: 179: 180: 181: } 182: }
就这样,我们现在有一个漂亮的滚动设计表面。 这是演示应用程序的屏幕截图,用户可以使用鼠标(必须按下鼠标按钮)愉快地滚动。
这里是演示应用程序的链接(无摩擦
)。
这里是演示应用程序的链接(有摩擦
)。