【问题标题】:WPF Headered Timeline Control with fixed horizontal scrollbar带有固定水平滚动条的 WPF 标题时间轴控件
【发布时间】:2018-03-19 02:56:43
【问题描述】:

我想创建一个具有以下功能的多时间线控件:

  • 每个时间线都有一个在左侧保持静态的标题区域
  • 可以水平滚动每个时间线的其余部分。
  • 如果控件不适合可用的水平空间,则应在时间线部分下方显示滚动条,而不是标题。
  • 如果控件不适合可用的垂直空间,则应在右侧显示一个滚动条,用于上下滚动标题和时间线。

我最接近此布局的是以下 xaml:

<Grid>
  <HeaderedContentControl>
    <HeaderedContentControl.Template>
      <ControlTemplate TargetType="HeaderedContentControl">
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <ContentPresenter ContentSource="Header" ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}" Content="{TemplateBinding HeaderedContentControl.Header}" />
          <ScrollViewer Grid.Column="1" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{TemplateBinding ContentControl.Content}"
          />
        </Grid>
      </ControlTemplate>
    </HeaderedContentControl.Template>
    <HeaderedContentControl.Header>
      <ItemsControl>
        <Button Height="80" Content="Fixed Header 1" />
        <Button Height="80" Content="Fixed Header 2" />
      </ItemsControl>
    </HeaderedContentControl.Header>
    <ItemsControl>
      <StackPanel Orientation="Horizontal" Height="80">
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
      </StackPanel>
      <StackPanel Orientation="Horizontal" Height="80">
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
        <Button Width="100" Content="thing" />
      </StackPanel>
    </ItemsControl>
  </HeaderedContentControl>
</Grid>

这给了我正确的水平滚动,但没有垂直滚动条:

所以要获得垂直滚动,我只需要用 ScrollViewer 包裹外部 HeaderedContentControl 元素,对吗?

<Grid>
    <ScrollViewer>
        <HeaderedContentControl>
            <HeaderedContentControl.Template>
                <ControlTemplate TargetType="HeaderedContentControl">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <ContentPresenter
                            ContentSource="Header"
                            ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
                            Content="{TemplateBinding HeaderedContentControl.Header}" />
                        <ScrollViewer Grid.Column="1"
                            ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
                            VerticalScrollBarVisibility="Hidden"
                            HorizontalScrollBarVisibility="Auto"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            Content="{TemplateBinding ContentControl.Content}" />
                    </Grid>
                </ControlTemplate>
            </HeaderedContentControl.Template>
            <HeaderedContentControl.Header>
                <ItemsControl>
                    <Button Height="80" Content="Fixed Header 1" />
                    <Button Height="80" Content="Fixed Header 2" />
                </ItemsControl>
            </HeaderedContentControl.Header>
            <ItemsControl>
                <StackPanel Orientation="Horizontal" Height="80">
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" Height="80">
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                </StackPanel>
            </ItemsControl>
        </HeaderedContentControl>
    </ScrollViewer>
</Grid>

结果:

嗯,它几乎是正确的,但是当我降低窗口高度时,我的时间线下方的水平滚动条会被剪裁。我希望它像上一张图​​片一样保持在顶部,这样我就可以随时水平滚动。

有谁知道我如何做到这一点?

【问题讨论】:

  • 也许使用顶级滚动查看器进行所有滚动并摆脱内部滚动查看器?或相反亦然。两个滚动条应该属于同一个滚动查看器,否则外部的滚动条可以滚动内部的滚动条。
  • @EdPlunkett 如果我删除了内部滚动查看器,那么标题列也会滚动,这不是我想要的。

标签: wpf


【解决方案1】:

我找到了一个解决方案(虽然感觉更像是一种变通方法,但你自己来判断)。

我在内部 ScrollViewer 下添加了一个外部 ScrollBar,并完全隐藏了内部 ScrollBar。加载 UserControl 时,我找到属于 ScrollViewer 的水平 ScrollBar,并将所有可视属性绑定到我的外部 ScrollBar。当我的外部 ScrollBar 被移动时,我使用 ScrollToHorizo​​ntalOffset 将值传递给内部 ScrollViewer。我还添加了一个网格拆分器和一些共享的列宽,以便可以调整标题的大小。

这是 xaml:

<Grid Grid.IsSharedSizeScope="True">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" SharedSizeGroup="SharedHeader"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <ScrollViewer Grid.ColumnSpan="2" VerticalScrollBarVisibility="Auto">
        <HeaderedContentControl x:Name="HeaderedControl">
            <HeaderedContentControl.Template>
                <ControlTemplate TargetType="HeaderedContentControl">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" SharedSizeGroup="SharedHeader" />
                            <ColumnDefinition Width="5" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <ContentPresenter
                            ContentSource="Header"
                            ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
                            Content="{TemplateBinding HeaderedContentControl.Header}" />
                        <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
                        <ScrollViewer Grid.Column="2"
                            ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
                            VerticalScrollBarVisibility="Hidden"
                            HorizontalScrollBarVisibility="Hidden"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            Content="{TemplateBinding ContentControl.Content}" />
                    </Grid>
                </ControlTemplate>
            </HeaderedContentControl.Template>
            <HeaderedContentControl.Header>
                <ItemsControl>
                    <Button Height="80" Content="Fixed Header 1" />
                    <Button Height="80" Content="Fixed Header 2" />
                </ItemsControl>
            </HeaderedContentControl.Header>
            <ItemsControl>
                <StackPanel Orientation="Horizontal" Height="80">
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" Height="80">
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                    <Button Width="100" Content="thing" />
                </StackPanel>
            </ItemsControl>
        </HeaderedContentControl>
    </ScrollViewer>
    <ScrollBar Grid.Row="1" Grid.Column="1" x:Name="ExternalScroller" Orientation="Horizontal"/>
</Grid>

下面是代码:

public partial class MainWindow : Window
{
    private ScrollViewer _internalScrollViewer;

    public MainWindow()
    {
        InitializeComponent();

        Loaded += MainWindow_Loaded;    
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // bind the external scrollbar to the internal ScrollViewer horizontal scrollbar
        _internalScrollViewer = GetParentScrollViewer(HeaderedControl.Content as UIElement);
        var hsb = GetHorizontalScrollbar(_internalScrollViewer);

        BindingOperations.SetBinding(ExternalScroller, System.Windows.Controls.Primitives.RangeBase.LargeChangeProperty, new Binding("LargeChange") { Source = hsb, Mode = BindingMode.TwoWay });
        BindingOperations.SetBinding(ExternalScroller, System.Windows.Controls.Primitives.RangeBase.MaximumProperty, new Binding("Maximum") { Source = hsb, Mode = BindingMode.TwoWay });
        BindingOperations.SetBinding(ExternalScroller, System.Windows.Controls.Primitives.RangeBase.MinimumProperty, new Binding("Minimum") { Source = hsb, Mode = BindingMode.TwoWay });
        BindingOperations.SetBinding(ExternalScroller, System.Windows.Controls.Primitives.RangeBase.SmallChangeProperty, new Binding("SmallChange") { Source = hsb, Mode = BindingMode.TwoWay });
        BindingOperations.SetBinding(ExternalScroller, System.Windows.Controls.Primitives.RangeBase.ValueProperty, new Binding("Value") { Source = hsb, Mode = BindingMode.TwoWay });
        BindingOperations.SetBinding(ExternalScroller, System.Windows.Controls.Primitives.ScrollBar.ViewportSizeProperty, new Binding("ViewportSize") { Source = hsb, Mode = BindingMode.TwoWay });

        // forward change events to the internal ScrollViewer
        ExternalScroller.ValueChanged += ExternalScroller_ValueChanged;
        ExternalScroller.Value = hsb.Value;
    }

    private void ExternalScroller_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        _internalScrollViewer.ScrollToHorizontalOffset(e.NewValue);
    }

    private static System.Windows.Controls.Primitives.ScrollBar GetHorizontalScrollbar(ScrollViewer sv)
    {
        return FindChild<System.Windows.Controls.Primitives.ScrollBar>(sv, (sb => sb.Orientation == Orientation.Horizontal));
    }

    private static ScrollViewer GetParentScrollViewer(UIElement uiElement)
    {
        DependencyObject item = VisualTreeHelper.GetParent(uiElement);
        while (item != null)
        {
            string name = "";
            var ctrl = item as Control;
            if (ctrl != null)
                name = ctrl.Name;
            if (item is ScrollViewer)
            {
                return item as ScrollViewer;
            }
            item = VisualTreeHelper.GetParent(item);
        }
        return null;
    }

    private static T FindChild<T>(DependencyObject parent, Func<T, bool> additionalCheck) where T : DependencyObject
    {
        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
        T child;

        for (int index = 0; index < childrenCount; index++)
        {
            child = VisualTreeHelper.GetChild(parent, index) as T;

            if (child != null)
            {
                if (additionalCheck == null)
                {
                    return child;
                }
                else
                {
                    if (additionalCheck(child))
                    {
                        return child;
                    }
                }
            }
        }

        for (int index = 0; index < childrenCount; index++)
        {
            child = FindChild(VisualTreeHelper.GetChild(parent, index), additionalCheck);

            if (child != null)
            {
                return child;
            }
        }

        return null;
    }
}

最后,这是视觉效果:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-26
    • 1970-01-01
    • 2016-10-11
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多