《ListBox》———何如实现ListBox下拉刷新和到底部自动加载

一、下拉刷新
下拉刷新实现思路:
1、定义一个PullDownToRefreshPanel容器控件。为它添加3种状态模板,分别是PullingDownTemplate,ReadyToReleaseTemplate
     和RefreshingTemplate,顾名思义分别是显示下拉状态模板,显示松开刷新状态模板和正在刷新中的状态模板。
2、定义自己的ListBox让它继承系统的ListBox,并重写它的Style,把ScrollViewer的ManipulationMode属性设为Conrtrol(必需),
     只有这样才能和我们的定义的PullDownToRefreshPanel兼容。ManipulationMode属性系统默认是System;区别就是,System的
     滑动效果更好。这里的ListBox的Style定义可以参考安装的SDK目录里面的系统定义,路径大致是:c->Program Files(x86)->
     Microsoft SDKs->Windows Phone->v7.1->Design->System.Windows.xaml.

View Code
  1 <Style TargetType="rlb:CustListBox">
  2 
  3 <Setter Property="Background" Value="Transparent" />
  4 
  5 <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}" />
  6 
  7 <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
  8 
  9 <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
 10 
 11 <Setter Property="BorderThickness" Value="0" />
 12 
 13 <Setter Property="BorderBrush" Value="Transparent" />
 14 
 15 <Setter Property="Padding" Value="0" />
 16 
 17 <Setter Property="Template">
 18 
 19 <Setter.Value>
 20 
 21 <ControlTemplate TargetType="rlb:CustListBox">
 22 
 23 <Grid>
 24 
 25 <Grid.ColumnDefinitions>
 26 
 27 <ColumnDefinition Width="*" />
 28 
 29 <ColumnDefinition Width="Auto" />
 30 
 31 </Grid.ColumnDefinitions>
 32 
 33 <!-- ScrollViewer的ManipulationMode属性必须设为Conrtrol才能和PullDownToRefreshPanel兼容 -->
 34 
 35 <!-- ScrollViewer的ManipulationMode属性默认是System它能提供更好的滚动效果 -->
 36 
 37 <ScrollViewer x:Name="ScrollViewer" Grid.ColumnSpan="2" ManipulationMode="Control" Foreground="{TemplateBinding Foreground}" Background="{TemplateBinding Background}"
 38 
 39 BorderBrush="Transparent" BorderThickness="0" Padding="{TemplateBinding Padding}">
 40 
 41 <ItemsPresenter />
 42 
 43 </ScrollViewer>
 44 
 45 <!-- The DragInterceptor sits on top of the item DragHandles and intercepts drag events
 46 
 47 so that the capture is not lost when the item container is removed from the panel.
 48 
 49 Its width must be equal to the width of the item DragHandles. -->
 50 
 51 <Canvas x:Name="DragInterceptor" Grid.Column="1" Margin="{TemplateBinding Padding}" Background="Transparent" VerticalAlignment="Stretch" Width="52">
 52 
 53 <Image x:Name="DragIndicator" Visibility="Collapsed">
 54 
 55 <Image.RenderTransform>
 56 
 57 <TranslateTransform />
 58 
 59 </Image.RenderTransform>
 60 
 61 </Image>
 62 
 63 </Canvas>
 64 
 65 <Canvas x:Name="RearrangeCanvas" Grid.ColumnSpan="2" Margin="{TemplateBinding Padding}" Background="Transparent" Visibility="Collapsed" />
 66 
 67 </Grid>
 68 
 69 </ControlTemplate>
 70 
 71 </Setter.Value>
 72 
 73 </Setter>
 74 
 75 </Style>
 76 
 77 
 78 
 79 <Style TargetType="rlb:PullDownToRefreshPanel">
 80 
 81 <Setter Property="Background" Value="Transparent" />
 82 
 83 <Setter Property="PullingDownTemplate">
 84 
 85 <Setter.Value>
 86 
 87 <DataTemplate>
 88 
 89 <TextBlock Margin="0,16,0,0" Style="{StaticResource PhoneTextGroupHeaderStyle}" TextAlignment="Center" FontStyle="Italic" Text="Pull down to refresh." />
 90 
 91 </DataTemplate>
 92 
 93 </Setter.Value>
 94 
 95 </Setter>
 96 
 97 <Setter Property="ReadyToReleaseTemplate">
 98 
 99 <Setter.Value>
100 
101 <DataTemplate>
102 
103 <TextBlock Margin="0,16,0,0" Style="{StaticResource PhoneTextGroupHeaderStyle}" TextAlignment="Center" FontWeight="Bold" Text="Release to refresh!" />
104 
105 </DataTemplate>
106 
107 </Setter.Value>
108 
109 </Setter>
110 
111 <Setter Property="RefreshingTemplate">
112 
113 <Setter.Value>
114 
115 <DataTemplate>
116 
117 <ProgressBar Margin="0,4,0,4" IsIndeterminate="True" />
118 
119 </DataTemplate>
120 
121 </Setter.Value>
122 
123 </Setter>
124 
125 <Setter Property="Template">
126 
127 <Setter.Value>
128 
129 <ControlTemplate TargetType="rlb:PullDownToRefreshPanel">
130 
131 <StackPanel x:Name="PullDownContainer" HorizontalAlignment="Stretch">
132 
133 <StackPanel.Resources>
134 
135 <rlb:NegativeValueConverter x:Key="NegativeValueConverter" />
136 
137 </StackPanel.Resources>
138 
139 <StackPanel x:Name="PullingDownPanel" Background="{TemplateBinding Background}" Height="{TemplateBinding PullDistance}" Opacity="{TemplateBinding PullFraction}"
140 
141 Margin="{Binding PullDistance, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource NegativeValueConverter}, ConverterParameter=Bottom}" Visibility="Collapsed">
142 
143 <ContentPresenter ContentTemplate="{TemplateBinding PullingDownTemplate}" />
144 
145 </StackPanel>
146 
147 <StackPanel x:Name="ReadyToReleasePanel" Background="{TemplateBinding Background}" Height="{TemplateBinding PullDistance}"
148 
149 Margin="{Binding PullDistance, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource NegativeValueConverter}, ConverterParameter=Bottom}" Visibility="Collapsed">
150 
151 <ContentPresenter ContentTemplate="{TemplateBinding ReadyToReleaseTemplate}" />
152 
153 </StackPanel>
154 
155 <StackPanel x:Name="RefreshingPanel" Background="{TemplateBinding Background}" Visibility="Collapsed">
156 
157 <ContentPresenter ContentTemplate="{TemplateBinding RefreshingTemplate}" />
158 
159 </StackPanel>
160 
161 <VisualStateManager.VisualStateGroups>
162 
163 <VisualStateGroup x:Name="ActivityStates">
164 
165 <VisualState x:Name="Inactive" />
166 
167 <VisualState x:Name="PullingDown">
168 
169 <Storyboard>
170 
171 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PullingDownPanel" Storyboard.TargetProperty="Visibility">
172 
173 <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
174 
175 </ObjectAnimationUsingKeyFrames>
176 
177 </Storyboard>
178 
179 </VisualState>
180 
181 <VisualState x:Name="ReadyToRelease">
182 
183 <Storyboard>
184 
185 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ReadyToReleasePanel" Storyboard.TargetProperty="Visibility">
186 
187 <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
188 
189 </ObjectAnimationUsingKeyFrames>
190 
191 </Storyboard>
192 
193 </VisualState>
194 
195 <VisualState x:Name="Refreshing">
196 
197 <Storyboard>
198 
199 <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RefreshingPanel" Storyboard.TargetProperty="Visibility">
200 
201 <DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
202 
203 </ObjectAnimationUsingKeyFrames>
204 
205 </Storyboard>
206 
207 </VisualState>
208 
209 </VisualStateGroup>
210 
211 </VisualStateManager.VisualStateGroups>
212 
213 </StackPanel>
214 
215 </ControlTemplate>
216 
217 </Setter.Value>
218 
219 </Setter>
220 
221 </Style>

3、编写PullDownToRefreshPanel控件,主要是把PullDownToRefreshPanel和ScrollViewer联系起来,通过PullDwonReflesh距离滚动条的位置
     来实现下拉刷新功能. 具体的实现,代码里有很好的注释。相信难不到你

View Code
  1 /// <summary>
  2 
  3 /// 给滚得条添加下拉刷新机制
  4 
  5 /// </summary>
  6 
  7 /// <remarks>
  8 
  9 /// 使用PullDwonReflesh距离滚动条的位置来实现下拉刷新功能. 
 10 
 11 /// 包含PullDwonReflesh的容器,必须直接或间接的包含滚动条。
 12 
 13 /// 例如:一个StackPanel包含一个PullDownToRefreshPanel 和一个ListBox,
 14 
 15 /// 而ListBox内部包含一个ScrollViewer来实现子项的显示。
 16 
 17 /// </remarks>
 18 
 19 [TemplateVisualState(Name = PullDownToRefreshPanel.InactiveVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
 20 
 21 [TemplateVisualState(Name = PullDownToRefreshPanel.PullingDownVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
 22 
 23 [TemplateVisualState(Name = PullDownToRefreshPanel.ReadyToReleaseVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
 24 
 25 [TemplateVisualState(Name = PullDownToRefreshPanel.RefreshingVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
 26 
 27 public class PullDownToRefreshPanel : Control
 28 
 29 {
 30 
 31 #region Visual state name constants
 32 
 33 //滚动中
 34 
 35 private const string ActivityVisualStateGroup = "ActivityStates";
 36 
 37 //无操作
 38 
 39 private const string InactiveVisualState = "Inactive";
 40 
 41 //下拉
 42 
 43 private const string PullingDownVisualState = "PullingDown";
 44 
 45 //松手刷新
 46 
 47 private const string ReadyToReleaseVisualState = "ReadyToRelease";
 48 
 49 //刷新中
 50 
 51 private const string RefreshingVisualState = "Refreshing";
 52 
 53 
 54 
 55 #endregion
 56 
 57 
 58 
 59 /// <summary>
 60 
 61 /// 用于绑定下拉文字显示,松手刷新的滚动条
 62 
 63 /// </summary>
 64 
 65 private ScrollViewer targetScrollViewer;
 66 
 67 
 68 
 69 /// <summary>
 70 
 71 /// 构造函数
 72 
 73 /// </summary>
 74 
 75 public PullDownToRefreshPanel()
 76 
 77 {
 78 
 79 this.DefaultStyleKey = typeof(PullDownToRefreshPanel);
 80 
 81 this.LayoutUpdated += this.PullDownToRefreshPanel_LayoutUpdated;
 82 
 83 }
 84 
 85 
 86 
 87 /// <summary>
 88 
 89 /// 当滚动条下拉到指定程度(PullThreshold)时引发这个事件,并显示松手刷新
 90 
 91 /// 如果开始刷新的时候事件要把IsRefreshing设置为true
 92 
 93 /// 当刷新完成时把IsRefreshing设回false.
 94 
 95 /// </summary>
 96 
 97 public event EventHandler RefreshRequested;
 98 
 99 
100 
101 #region IsRefreshing DependencyProperty 是否正在刷新
102 
103 
104 
105 public static readonly DependencyProperty IsRefreshingProperty = DependencyProperty.Register
106 
107 (
108 
109 "IsRefreshing", typeof(bool), typeof(PullDownToRefreshPanel),
110 
111 new PropertyMetadata(false, (d, e) => 
112 
113 {
114 
115 ((PullDownToRefreshPanel)d).OnIsRefreshingChanged(e); 
116 
117 })
118 
119 );
120 
121 
122 
123 public bool IsRefreshing
124 
125 {
126 
127 get
128 
129 {
130 
131 return (bool)this.GetValue(PullDownToRefreshPanel.IsRefreshingProperty);
132 
133 }
134 
135 set
136 
137 {
138 
139 this.SetValue(PullDownToRefreshPanel.IsRefreshingProperty, value);
140 
141 }
142 
143 }
144 
145 
146 
147 protected void OnIsRefreshingChanged(DependencyPropertyChangedEventArgs e)
148 
149 {
150 
151 string activityState = (bool)e.NewValue ? PullDownToRefreshPanel.RefreshingVisualState : PullDownToRefreshPanel.InactiveVisualState;
152 
153 VisualStateManager.GoToState(this, activityState, false);
154 
155 }
156 
157 
158 
159 #endregion
160 
161 
162 
163 #region PullThreshold DependencyProperty 设置下拉距离
164 
165 
166 
167 public static readonly DependencyProperty PullThresholdProperty = DependencyProperty.Register(
168 
169 "PullThreshold", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(100.0));
170 
171 
172 
173 /// <summary>
174 
175 /// 得到或设置一个滚动条从顶部下拉的最小距离,当达到这个距离松手时会触发刷新事件
176 
177 /// 默认距离是100. 推荐最大值不要超过125.
178 
179 /// </summary>
180 
181 public double PullThreshold
182 
183 {
184 
185 get
186 
187 {
188 
189 return (double)this.GetValue(PullDownToRefreshPanel.PullThresholdProperty);
190 
191 }
192 
193 set
194 
195 {
196 
197 this.SetValue(PullDownToRefreshPanel.PullThresholdProperty, value);
198 
199 }
200 
201 }
202 
203 
204 
205 #endregion
206 
207 
208 
209 #region PullDistance DependencyProperty 滚动条拉下的距离
210 
211 
212 
213 public static readonly DependencyProperty PullDistanceProperty = DependencyProperty.Register(
214 
215 "PullDistance", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(0.0));
216 
217 
218 
219 /// <summary>
220 
221 /// 得到滚动条已经拉下的距离。
222 
223 /// 用它绑定图形距离
224 
225 /// </summary>
226 
227 public double PullDistance
228 
229 {
230 
231 get
232 
233 {
234 
235 return (double)this.GetValue(PullDownToRefreshPanel.PullDistanceProperty);
236 
237 }
238 
239 private set
240 
241 {
242 
243 this.SetValue(PullDownToRefreshPanel.PullDistanceProperty, value);
244 
245 }
246 
247 }
248 
249 
250 
251 #endregion 
252 
253 
254 
255 #region PullFraction DependencyProperty 相对PullDistance拉下距离分值
256 
257 
258 
259 public static readonly DependencyProperty PullFractionProperty = DependencyProperty.Register(
260 
261 "PullFraction", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(0.0));
262 
263 
264 
265 /// <summary>
266 
267 /// 得到滚动条从顶部拉下的距离分数。即拉下PullThreshold的几分之几,取值范围(0.0 - 1.0)
268 
269 /// </summary>
270 
271 public double PullFraction
272 
273 {
274 
275 get
276 
277 {
278 
279 return (double)this.GetValue(PullDownToRefreshPanel.PullFractionProperty);
280 
281 }
282 
283 private set
284 
285 {
286 
287 this.SetValue(PullDownToRefreshPanel.PullFractionProperty, value);
288 
289 }
290 
291 }
292 
293 
294 
295 #endregion
296 
297 
298 
299 #region PullingDownTemplate DependencyProperty 提示下拉刷新的模板
300 
301 
302 
303 public static readonly DependencyProperty PullingDownTemplateProperty = DependencyProperty.Register(
304 
305 "PullingDownTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);
306 
307 
308 
309 /// <summary>
310 
311 /// Gets or sets a template that is progressively revealed has the ScrollViewer is pulled down.
312 
313 /// </summary>
314 
315 public DataTemplate PullingDownTemplate
316 
317 {
318 
319 get
320 
321 {
322 
323 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.PullingDownTemplateProperty);
324 
325 }
326 
327 set
328 
329 {
330 
331 this.SetValue(PullDownToRefreshPanel.PullingDownTemplateProperty, value);
332 
333 }
334 
335 }
336 
337 
338 
339 #endregion
340 
341 
342 
343 #region ReadyToReleaseTemplate DependencyProperty 提示松开刷新的模板
344 
345 
346 
347 public static readonly DependencyProperty ReadyToReleaseTemplateProperty = DependencyProperty.Register(
348 
349 "ReadyToReleaseTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);
350 
351 
352 
353 /// <summary>
354 
355 /// Gets or sets the template that is displayed after the ScrollViewer is pulled down past
356 
357 /// the PullThreshold.
358 
359 /// </summary>
360 
361 public DataTemplate ReadyToReleaseTemplate
362 
363 {
364 
365 get
366 
367 {
368 
369 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.ReadyToReleaseTemplateProperty);
370 
371 }
372 
373 set
374 
375 {
376 
377 this.SetValue(PullDownToRefreshPanel.ReadyToReleaseTemplateProperty, value);
378 
379 }
380 
381 }
382 
383 
384 
385 #endregion
386 
387 
388 
389 #region RefreshingTemplate DependencyProperty 提示正在刷新的模板
390 
391 
392 
393 public static readonly DependencyProperty RefreshingTemplateProperty = DependencyProperty.Register(
394 
395 "RefreshingTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);
396 
397 
398 
399 /// <summary>
400 
401 /// Gets or sets the template that is displayed while the ScrollViewer is refreshing.
402 
403 /// </summary>
404 
405 public DataTemplate RefreshingTemplate
406 
407 {
408 
409 get
410 
411 {
412 
413 return (DataTemplate)this.GetValue(PullDownToRefreshPanel.RefreshingTemplateProperty);
414 
415 }
416 
417 set
418 
419 {
420 
421 this.SetValue(PullDownToRefreshPanel.RefreshingTemplateProperty, value);
422 
423 }
424 
425 }
426 
427 
428 
429 #endregion
430 
431 
432 
433 #region Initial setup 初始化设置
434 
435 
436 
437 private void PullDownToRefreshPanel_LayoutUpdated(object sender, EventArgs e)
438 
439 {
440 
441 if (this.targetScrollViewer == null)
442 
443 {
444 
445 //找到要绑定的目标滚动条
446 
447 this.targetScrollViewer = FindVisualElement<ScrollViewer>(VisualTreeHelper.GetParent(this));
448 
449 App._ScrollViewer = targetScrollViewer;
450 
451 if (this.targetScrollViewer != null)
452 
453 {
454 
455 this.targetScrollViewer.MouseMove += this.targetScrollViewer_MouseMove;
456 
457 this.targetScrollViewer.MouseLeftButtonUp += this.targetScrollViewer_MouseLeftButtonUp;
458 
459 
460 
461 //滚得条的类型必须是控件类型
462 
463 if (this.targetScrollViewer.ManipulationMode != ManipulationMode.Control)
464 
465 {
466 
467 throw new InvalidOperationException("PullDownToRefreshPanel requires the ScrollViewer " +
468 
469 "to have ManipulationMode=Control. (ListBoxes may require re-templating.");
470 
471 }
472 
473 }
474 
475 }
476 
477 }
478 
479 
480 
481 #endregion
482 
483 
484 
485 #region Pull-down detection 下拉检测
486 
487 
488 
489 /// <summary>
490 
491 /// 当滚动条被拖拽时,显示动画和对应的控件模型
492 
493 /// </summary>
494 
495 private void targetScrollViewer_MouseMove(object sender, MouseEventArgs e)
496 
497 {
498 
499 UIElement scrollContent = (UIElement)this.targetScrollViewer.Content;
500 
501 CompositeTransform ct = scrollContent.RenderTransform as CompositeTransform;
502 
503 if (ct != null && !this.IsRefreshing)
504 
505 {
506 
507 string activityState;
508 
509 if (ct.TranslateY > this.PullThreshold)
510 
511 {
512 
513 this.PullDistance = ct.TranslateY;
514 
515 this.PullFraction = 1.0;
516 
517 activityState = PullDownToRefreshPanel.ReadyToReleaseVisualState;
518 
519 }
520 
521 else if (ct.TranslateY > 0)
522 
523 {
524 
525 this.PullDistance = ct.TranslateY;
526 
527 double threshold = this.PullThreshold;
528 
529 this.PullFraction = threshold == 0.0 ? 1.0 : Math.Min(1.0, ct.TranslateY / threshold);
530 
531 activityState = PullDownToRefreshPanel.PullingDownVisualState;
532 
533 }
534 
535 else
536 
537 {
538 
539 this.PullDistance = 0;
540 
541 this.PullFraction = 0;
542 
543 activityState = PullDownToRefreshPanel.InactiveVisualState;
544 
545 }
546 
547 VisualStateManager.GoToState(this, activityState, false);
548 
549 }
550 
551 }
552 
553 
554 
555 /// <summary>
556 
557 /// 当松手时检查是否要刷新
558 
559 /// </summary>
560 
561 private void targetScrollViewer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
562 
563 {
564 
565 UIElement scrollContent = (UIElement)this.targetScrollViewer.Content;
566 
567 CompositeTransform ct = scrollContent.RenderTransform as CompositeTransform;
568 
569 if (ct != null && !this.IsRefreshing)
570 
571 {
572 
573 VisualStateManager.GoToState(this, PullDownToRefreshPanel.InactiveVisualState, false);
574 
575 this.PullDistance = 0;
576 
577 this.PullFraction = 0;
578 
579 
580 
581 if (ct.TranslateY >= this.PullThreshold)
582 
583 {
584 
585 EventHandler handler = this.RefreshRequested;
586 
587 if (handler != null)
588 
589 {
590 
591 handler(this, EventArgs.Empty);
592 
593 }
594 
595 }
596 
597 }
598 
599 }
600 
601 
602 
603 #endregion
604 
605 
606 
607 #region Utility methods 查找第一个符合要求的元素方法
608 
609 
610 
611 /// <summary>
612 
613 /// 查找容器内所有元素,并返回第一个是T类型的元素
614 
615 /// </summary>
616 
617 /// <typeparam name="T">要搜索的元素类型</typeparam>
618 
619 /// <param name="initial">要搜索的容器</param>
620 
621 /// <returns>返回要找的元素,没找到返回null</returns>
622 
623 private static T FindVisualElement<T>(DependencyObject container) where T : DependencyObject
624 
625 {
626 
627 Queue<DependencyObject> childQueue = new Queue<DependencyObject>();
628 
629 childQueue.Enqueue(container);
630 
631 
632 
633 while (childQueue.Count > 0)
634 
635 {
636 
637 DependencyObject current = childQueue.Dequeue();
638 
639 
640 
641 System.Diagnostics.Debug.WriteLine(current.GetType().ToString());
642 
643 
644 
645 T result = current as T;
646 
647 if (result != null && result != container)
648 
649 {
650 
651 return result;
652 
653 }
654 
655 
656 
657 int childCount = VisualTreeHelper.GetChildrenCount(current);
658 
659 for (int childIndex = 0; childIndex < childCount; childIndex++)
660 
661 {
662 
663 childQueue.Enqueue(VisualTreeHelper.GetChild(current, childIndex));
664 
665 }
666 
667 }
668 
669 
670 
671 return null;
672 
673 }
674 
675 
676 
677 #endregion
678 
679 }

4、实现自定义ListBox->CustListBox,代码很简单直接上。

View Code
 1 [TemplatePart(Name = CustListBox.ScrollViewerPart, Type = typeof(ScrollViewer))]
 2 
 3 public class CustListBox : ListBox
 4 
 5 {
 6 
 7 public const string ScrollViewerPart = "ScrollViewer";
 8 
 9 
10 
11 public CustListBox()
12 
13 {
14 
15 this.DefaultStyleKey = typeof(CustListBox);
16 
17 }
18 
19 
20 
21 #region AutoScrollMargin DependencyProperty
22 
23 
24 
25 public static readonly DependencyProperty AutoScrollMarginProperty = DependencyProperty.Register(
26 
27 "AutoScrollMargin", typeof(int), typeof(CustListBox), new PropertyMetadata(32));
28 
29 
30 
31 public double AutoScrollMargin
32 
33 {
34 
35 get
36 
37 {
38 
39 return (int)this.GetValue(CustListBox.AutoScrollMarginProperty);
40 
41 }
42 
43 set
44 
45 {
46 
47 this.SetValue(CustListBox.AutoScrollMarginProperty, value);
48 
49 }
50 
51 }
52 
53 
54 
55 #endregion
56 
57 }

5、使用要求   
     包含PullDwonReflesh的容器,必须直接或间接的包含滚动条。
     例如:一个StackPanel包含一个PullDownToRefreshPanel 和一个ListBox,而ListBox内部包含一个ScrollViewer来实现子项的显示。
6、为了使用起来更方便,我把CustListBox和PullDownToRefreshPanel 又封装了一层
     xaml

View Code
 1 <UserControl x:Class="PullDwonReflesh.Themes.CustListbox"
 2 
 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4 
 5 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 6 
 7 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 8 
 9 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
10 
11 mc:Ignorable="d"
12 
13 FontFamily="{StaticResource PhoneFontFamilyNormal}"
14 
15 FontSize="{StaticResource PhoneFontSizeNormal}"
16 
17 Foreground="{StaticResource PhoneForegroundBrush}"
18 
19 d:DesignHeight="480" d:DesignWidth="480" xmlns:my="clr-namespace:PullDwonReflesh"
20 
21 
22 
23 xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
24 
25 x:Name="this">
26 
27 
28 
29 <Grid x:Name="LayoutRoot">
30 
31 <Grid.RowDefinitions>
32 
33 <RowDefinition Height="Auto" />
34 
35 <RowDefinition Height="*" />
36 
37 </Grid.RowDefinitions>
38 
39 <my:PullDownToRefreshPanel x:Name="refreshPanel" RefreshRequested="refreshPanel_RefreshRequested" Grid.Row="0" />
40 
41 <my:CustListBox x:Name="custListBox" Grid.Row="1" Margin="12,0,12,12" toolkit:TiltEffect.IsTiltEnabled="True"
42 
43 ItemsSource="{Binding ElementName=this, Path=ItemsSource}" 
44 
45 ItemTemplate="{Binding ElementName=this,Path=ItemTemplate}"
46 
47 SelectionChanged="CustListBox_SelectionChanged" >
48 
49 <!--<ListBox.ItemsPanel>
50 
51 <ItemsPanelTemplate>
52 
53 <toolkit:WrapPanel/>
54 
55 </ItemsPanelTemplate>
56 
57 </ListBox.ItemsPanel>
58 
59 <ListBox.ItemContainerStyle>
60 
61 <Style TargetType="ListBoxItem">
62 
63 <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
64 
65 </Style>
66 
67 </ListBox.ItemContainerStyle>-->
68 
69 </my:CustListBox>
70 
71 </Grid>
72 
73 </UserControl>
View Code
  1 using System;
  2 
  3 using System.Collections.Generic;
  4 
  5 using System.Linq;
  6 
  7 using System.Net;
  8 
  9 using System.Windows;
 10 
 11 using System.Windows.Controls;
 12 
 13 using System.Windows.Documents;
 14 
 15 using System.Windows.Input;
 16 
 17 using System.Windows.Media;
 18 
 19 using System.Windows.Media.Animation;
 20 
 21 using System.Windows.Shapes;
 22 
 23 using System.Collections;
 24 
 25 
 26 
 27 namespace PullDwonReflesh.Themes
 28 
 29 {
 30 
 31 public partial class CustListbox : UserControl
 32 
 33 {
 34 
 35 public event SelectionChangedEventHandler SelectionChanged;
 36 
 37 public event EventHandler RefreshRequested;
 38 
 39 
 40 
 41 public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustListbox), new PropertyMetadata(""));
 42 
 43 public IEnumerable ItemsSource
 44 
 45 {
 46 
 47 get
 48 
 49 {
 50 
 51 return (IEnumerable)base.GetValue(ItemsSourceProperty);
 52 
 53 }
 54 
 55 set
 56 
 57 {
 58 
 59 base.SetValue(ItemsSourceProperty, value);
 60 
 61 }
 62 
 63 }
 64 
 65 
 66 
 67 private DataTemplate _ItemTemplate = null;
 68 
 69 public DataTemplate ItemTemplate
 70 
 71 {
 72 
 73 get
 74 
 75 {
 76 
 77 return _ItemTemplate;
 78 
 79 }
 80 
 81 set
 82 
 83 {
 84 
 85 _ItemTemplate = value;
 86 
 87 custListBox.ItemTemplate = value;
 88 
 89 }
 90 
 91 }
 92 
 93 
 94 
 95 public static readonly DependencyProperty IsRefreshingProperty = DependencyProperty.Register("IsRefreshing", typeof(bool), typeof(CustListbox), new PropertyMetadata(false, (d, e) => ((CustListbox)d).OnIsRefreshingChanged(e)));
 96 
 97 public bool IsRefreshing
 98 
 99 {
100 
101 get
102 
103 {
104 
105 return (bool)this.GetValue(CustListbox.IsRefreshingProperty);
106 
107 }
108 
109 set
110 
111 {
112 
113 this.SetValue(CustListbox.IsRefreshingProperty, value);
114 
115 }
116 
117 }
118 
119 
120 
121 protected void OnIsRefreshingChanged(DependencyPropertyChangedEventArgs e)
122 
123 {
124 
125 this.refreshPanel.IsRefreshing = (bool)e.NewValue;
126 
127 }
128 
129 
130 
131 public CustListbox()
132 
133 {
134 
135 InitializeComponent();
136 
137 }
138 
139 
140 
141 private void CustListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
142 
143 {
144 
145 if (SelectionChanged != null)
146 
147 {
148 
149 SelectionChanged(sender, e);
150 
151 }
152 
153 }
154 
155 
156 
157 private void refreshPanel_RefreshRequested(object sender, EventArgs e)
158 
159 {
160 
161 if (RefreshRequested != null)
162 
163 {
164 
165 RefreshRequested(sender, e);
166 
167 }
168 
169 }
170 
171 }
172 
173 }

到此,下拉刷新已经完成,可以拿出来单独使用。


二、ListBox滚动到底部自动加载
这个实现起来就更简单,思路
1、检测ListBox中的ScrollViewer控件状态
2、若状态不为滚动中:根据ScrollViewer的ExtentHeight与VerticalOffset,判断是否到底,并执行请求加载数据。

首先获取ScrollViewer,这个上面实现下拉的中已经得到了,这里在App定义一个变量把下拉时获得的ScrollViewer保存起来,并在这里作为目标滚动条。
然后编写获得状态函数,如下

View Code
 1 private VisualStateGroup FindVisualState(FrameworkElement element, string name)
 2 
 3 {
 4 
 5 if (element == null)
 6 
 7 return null;
 8 
 9 
10 
11 IList groups = VisualStateManager.GetVisualStateGroups(element);
12 
13 foreach (VisualStateGroup group in groups)
14 
15 {
16 
17 if (group.Name == name)
18 
19 return group;
20 
21 }
22 
23 
24 
25 return null;
26 
27 }

接下来根据状态的改变做相应的加载功能,代码如下

 1 void visualStateGroup_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
 2 
 3 {
 4 
 5 var visualState = e.NewState.Name;
 6 
 7 if (visualState == "NotScrolling")
 8 
 9 {
10 
11 var v1 = _ScrollViewer.ExtentHeight - _ScrollViewer.VerticalOffset;
12 
13 var v2 = _ScrollViewer.ViewportHeight * 1.5;
14 
15 
16 
17 if (v1 <= v2 && !custListBox.IsRefreshing)
18 
19 {
20 
21 AddString(index, 20);
22 
23 visualState += "_End";
24 
25 }
26 
27 }
28 
29 }

最后,在页面的loaded里把这些代码串联起来就OK了。如下:

 1 private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
 2 
 3 {
 4 
 5 if (_IsHookedScrollEvent)
 6 
 7 return;
 8 
 9 _ScrollViewer = App._ScrollViewer;
10 
11 if (_ScrollViewer != null)
12 
13 {
14 
15 _IsHookedScrollEvent = true;
16 
17 FrameworkElement element = VisualTreeHelper.GetChild(_ScrollViewer, 0) as FrameworkElement;
18 
19 if (element != null)
20 
21 {
22 
23 VisualStateGroup visualStateGroup = FindVisualState(element, "ScrollStates");
24 
25 visualStateGroup.CurrentStateChanged += new EventHandler<VisualStateChangedEventArgs>(visualStateGroup_CurrentStateChanged);
26 
27 }
28 
29 }
30 
31 }

本文参考:http://www.hugwp.com/thread-2058-1.html
和Jason Ginchereau的博客

源码:猛击下载

本文版权归作者和卤面网所有,
转载请标明原始出处

原文地址:https://www.cnblogs.com/qq278360339/p/2530532.html