65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (10投票s)

2009年6月17日

CPOL

2分钟阅读

viewsIcon

85370

downloadIcon

3667

如何在 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 控制或其他东西,由你选择。 这里唯一重要的一点是,ScrollViewerHorizontalScrollBarVisibility/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 属性设置一个介于 01 之间的值,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:  }

就这样,我们现在有一个漂亮的滚动设计表面。 这是演示应用程序的屏幕截图,用户可以使用鼠标(必须按下鼠标按钮)愉快地滚动。

37349/image-thumb.png

这里是演示应用程序的链接无摩擦)。

这里是演示应用程序的链接有摩擦)。

© . All rights reserved.