WPF ScrollViewer+WrapPanel滚动条

滚动条内容自适应

<ScrollViewer x:Name="scrList" VerticalScrollBarVisibility="Auto">
  <WrapPanel x:Name="wplContent" Width="{Binding ElementName=scrList, Path=Width, Mode=OneWay}">
  </WrapPanel>
 </ScrollViewer>

控件滚动到顶端

TreeView

/// <summary>
/// 控件滚动到顶端
/// </summary>
/// <param name="element"></param>
public static void ScrollToTop(TreeView element)
{
    var lvap = new TreeViewAutomationPeer(element);
    var svap = lvap.GetPattern(PatternInterface.Scroll) as ScrollViewerAutomationPeer;
    if (svap != null && svap.Owner is ScrollViewer scroll)
    {
        scroll.ScrollToTop();
    }
}

ListBox

/// <summary>
/// 控件滚动到顶端
/// </summary>
/// <param name="element"></param>
public static void ScrollToTop(ListBox element)
{
    var scroll = GetVisualChild<ScrollViewer>(element);
    if (scroll != null)
    {
        scroll.ScrollToHome();
    }
}

其中,GetVisualChild是获取可视子元素。

滚动加载数据

ScrollViewerHelper

ScrollViewerHelper参考Mah.Metro的实现。

SimpleCommand

using System;
using System.Windows.Input;

public class SimpleCommand : ICommand
{
    public SimpleCommand(Func<object, bool> canExecute = null, Action<object> execute = null)
    {
        CanExecuteDelegate = canExecute;
        ExecuteDelegate = execute;
    }
    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
    public Func<object, bool> CanExecuteDelegate { get; set; }

    public Action<object> ExecuteDelegate { get; set; }

    public bool CanExecute(object parameter)
    {
        var canExecute = CanExecuteDelegate;
        return canExecute == null || canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        ExecuteDelegate?.Invoke(parameter);
    }
}

xaml

<ListBox x:Name="ListBoxInfo" 
     ScrollViewer.VerticalScrollBarVisibility="Auto" ItemTemplate="{DynamicResource ListItem}" ItemsSource="{Binding ListBoxSource}" FocusVisualStyle="{x:Null}"
     utl:ScrollViewerHelper.EndOfVerticalScrollReachedCommand="{Binding EndOfScrollReachedCmdWithParameter, Mode=OneWay}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <ContentPresenter/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

cs

/// <summary>
/// 滚动条到底响应事件
/// </summary>
public ICommand EndOfScrollReachedCmdWithParameter { get; set; }

private List<DemoDto> _listDtos;
/// <summary>
/// 滚动加载每页大小(默认显示的泊位数:12)
/// </summary>
private readonly int _rollLoadPagerSize = 12;
/// <summary>
/// 滚动加载页数,默认1页
/// </summary>
private int _rollLoadPagerIndex = 1;

public ctor()
{
    InitializeComponent();
    
    this.EndOfScrollReachedCmdWithParameter = new SimpleCommand(o => true, EndOfScrollReached);
}

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    ScrollViewerHelper.SetEndOfVerticalScrollReachedCommand(ListBoxInfo, null);
    ScrollViewerHelper.SetEndOfVerticalScrollReachedCommand(ListBoxInfo, EndOfScrollReachedCmdWithParameter);
    _rollLoadPagerIndex = 1;
}

/// <summary>
/// 滚动轴到底时响应方法
/// </summary>
/// <param name="parameter"></param>
private void EndOfScrollReached(object parameter)
{
    if (_listDtos == null)
    {
        return;
    }
    int sum = _listDtos.Count;
    if (_rollLoadPagerSize * _rollLoadPagerIndex >= sum)    // 已加载所有
    {
        return;
    }
    // 加载下一页
    int newIndx = _rollLoadPagerIndex + 1;
    int start = _rollLoadPagerSize * _rollLoadPagerIndex;
    if (_rollLoadPagerSize * newIndx >= sum) // 要加载的是最后一页
    {
        PartRollLoad(_spacePreviewDtos, start, sum - start);
    }
    else    // 整页加载
    {
        PartRollLoad(_spacePreviewDtos, start, _rollLoadPagerSize);
    }

    // 更新索引
    _rollLoadPagerIndex = newIndx;
}

/// <summary>
/// 滚动加载任务
/// </summary>
/// <param name="src">数据源</param>
/// <param name="startIndex">加载的起始位置</param>
/// <param name="count">加载数量</param>
/// <returns></returns>
private void PartRollLoad(List<DemoDto> src, int startIndex, int count)
{
    for (int i = 0; i < count; i++)
    {
        var dto = src[startIndex + i];
        var item = new DtoItemEntity()
        {
            Guid = dto.Guid,
        };
        ListBoxSource.Add(item);
        // 长时间任务
        LongTimeTaskAsync(item, dto).ConfigureAwait(false);
    }
}

滚动加载数据2

<ListBox x:Name="ListBoxInfo" 
     ScrollViewer.ScrollChanged="ListBoxInfo_ScrollChanged"
     ScrollViewer.VerticalScrollBarVisibility="Visible" 
/>

private void ListBoxInfo_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    if (e.OriginalSource is ScrollViewer scrollViewer)
    {
        if (e.VerticalOffset != 0 && e.VerticalOffset == scrollViewer.ScrollableHeight)
        {
            EndOfScrollReached(null);
        }
    }
}

ScrollViewer滚动到指定控件处

// 获取要定位之前 ScrollViewer 目前的滚动位置
var currentScrollPosition = ScrollViewer.VerticalOffset;
var point = new Point(0, currentScrollPosition);

// 计算出目标位置并滚动
var targetPosition = TargetControl.TransformToVisual(ScrollViewer).Transform(point);
ScrollViewer.ScrollToVerticalOffset(targetPosition.Y);

MVVM

/// <summary>
/// 在 ScrollViewer 中定位到指定的控件
/// 说明:目前支持的是垂直滚动
/// </summary>
public class ScrollToControlAction : TriggerAction<FrameworkElement>
{
    public static readonly DependencyProperty ScrollViewerProperty =
    DependencyProperty.Register("ScrollViewer", typeof(ScrollViewer), typeof(ScrollToControlAction), new PropertyMetadata(null));

    public static readonly DependencyProperty TargetControlProperty =
    DependencyProperty.Register("TargetControl", typeof(FrameworkElement), typeof(ScrollToControlAction), new PropertyMetadata(null));

    /// <summary>
    /// 目标 ScrollViewer
    /// </summary>
    public ScrollViewer ScrollViewer
    {
        get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
        set { SetValue(ScrollViewerProperty, value); }
    }

    /// <summary>
    /// 要定位的到的控件
    /// </summary>
    public FrameworkElement TargetControl
    {
        get { return (FrameworkElement)GetValue(TargetControlProperty); }
        set { SetValue(TargetControlProperty, value); }
    }

    protected override void Invoke(object parameter)
    {
        if (TargetControl == null || ScrollViewer == null)
        {
            throw new ArgumentNullException($"{ScrollViewer} or {TargetControl} cannot be null");
        }

        // 检查指定的控件是否在指定的 ScrollViewer 中
        // TODO: 这里只是指定离它最近的 ScrollViewer,并没有继续向上找
        var container = TargetControl.FindParent<ScrollViewer>();
        if (container == null || container != ScrollViewer)
        {
            throw new Exception("The TargetControl is not in the target ScrollViewer");
        }

        // 获取要定位之前 ScrollViewer 目前的滚动位置
        var currentScrollPosition = ScrollViewer.VerticalOffset;
        var point = new Point(0, currentScrollPosition);

        // 计算出目标位置并滚动
        var targetPosition = TargetControl.TransformToVisual(ScrollViewer).Transform(point);
        ScrollViewer.ScrollToVerticalOffset(targetPosition.Y);
    }
}
<Button>
 <i:Interaction.Triggers>
  <i:EventTrigger EventName="Click">
  <local:ScrollToControlAction ScrollViewer="{Binding ElementName=s}" TargetControl="{Binding ElementName=txtSectionC}" />
  </i:EventTrigger>
 </i:Interaction.Triggers>
</Button>

ScrollViewerHelper

posted @ 2019-11-12 20:20  wesson2019  阅读(1896)  评论(0)    收藏  举报