ScrollViewer ScrollToHorizo​​ntalOffset 奇怪的行为

ScrollViewer ScrollToHorizontalOffset strange behaviour

我的应用程序包含 ScrollViewerStackPanel

XAML:

<Window x:Class="WpfScrollViewer.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="500" Width="800" Loaded="MainWindow_OnLoaded">
    <Window.Resources>
        <Style TargetType="Rectangle">
            <Setter Property="Fill" Value="Blue" />
            <Setter Property="Margin" Value="10" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <ScrollViewer x:Name="MyScrollViewer" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Auto">
            <StackPanel x:Name="MyStackPanel" Orientation="Horizontal" />
        </ScrollViewer>
        <Button Grid.Row="1" Width="50" HorizontalAlignment="Left" Content="left" Click="LeftButton_Click" />
        <Button Grid.Row="1" Width="50" HorizontalAlignment="Right" Content="right" Click="RightButton_Click" />
    </Grid>
</Window>

StackPanel 中,我从后面的代码创建了几个 Rectangle,它们的宽度是根据我的 Window 的宽度计算的。

代码隐藏:

using System.Windows;
using System.Windows.Shapes;

namespace WpfScrollViewer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private double _scrollWidth;
        private double _edgeScrollWidth;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void LeftButton_Click(object sender, RoutedEventArgs e)
        {
            var offset = MyScrollViewer.HorizontalOffset;
            if (offset == MyScrollViewer.ScrollableWidth)
            {
                MyScrollViewer.ScrollToHorizontalOffset(offset - _edgeScrollWidth);
            }
            else
            {
                MyScrollViewer.ScrollToHorizontalOffset(offset - _scrollWidth);
            }
        }

        private void RightButton_Click(object sender, RoutedEventArgs e)
        {
            var offset = MyScrollViewer.HorizontalOffset;
            if (offset == 0)
            {
                MyScrollViewer.ScrollToHorizontalOffset(offset + _edgeScrollWidth);
            }
            else
            {
                MyScrollViewer.ScrollToHorizontalOffset(offset + _scrollWidth);
            }
        }

        private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            _scrollWidth = Width - 60;
            _edgeScrollWidth = Width - 90;
            var itemWidth = Width - 80;

            for (var i = 0; i < 10; i++)
            {
                MyStackPanel.Children.Add(new Rectangle { Width = itemWidth });
            }
        }
    }
}

当我按右边 Button 时,我的矩形看起来像这样: 但我想在两侧看到相等的矩形部分。我想让我的整个矩形居中,但事实并非如此。我真的认为我正确计算了偏移量,所以我真的不知道我哪里错了。
我是不是遗漏了什么或者用 WPFs 独立单位计算有什么问题?

我认为您的问题源于在 Loaded 事件处理程序中使用 Width 属性。 Width这里是Windowclass的宽度属性,属性的值包括[=29]的宽度=]的边框。您真正想要的是 window 或 客户区 内部部分的宽度。没有用于此的 WPF 属性,因此您需要使用 window 的子元素(window 的 Content)的 ActualWidth

此外,我建议通过将 "magic" 数字转换为常量来删除它们。

以下是您的代码的重新调整版本,可以满足您的要求:

const double RectangleMarginThickness = 10;
const double RectangleWidthReduction = 80;  // Rect width is client width less this
const double PaddingToCenter = RectangleWidthReduction/2 - RectangleMarginThickness;

double _rectangleWidthIncludingMargin;

private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
    double clientWidth = (Content as FrameworkElement).ActualWidth;
    var itemWidth = clientWidth - RectangleWidthReduction;
    _rectangleWidthIncludingMargin = itemWidth + (RectangleMarginThickness * 2);

    for (var i = 0; i < 10; i++)
    {
        MyStackPanel.Children.Add(new Rectangle { Width = itemWidth });
    }
}

private void LeftButton_Click(object sender, RoutedEventArgs e)
{
    SetNewHorizontalOffset(childOffset: -1);
}

private void RightButton_Click(object sender, RoutedEventArgs e)
{
    SetNewHorizontalOffset(childOffset: 1);
}

private void SetNewHorizontalOffset(int childOffset)
{
    double offset = MyScrollViewer.HorizontalOffset + PaddingToCenter;

    if (_rectangleWidthIncludingMargin > 0)
    {
        int currentChildIndex;
        if (childOffset < 0)
        {
            currentChildIndex =
                (int) Math.Ceiling(offset / _rectangleWidthIncludingMargin);
        }
        else
        {
            currentChildIndex =
                (int) Math.Floor(offset / _rectangleWidthIncludingMargin);
        }

        int newChildIndex = CoerceToRange(currentChildIndex + childOffset,
            0, MyStackPanel.Children.Count - 1);
        offset = newChildIndex * _rectangleWidthIncludingMargin - PaddingToCenter;
    }

    MyScrollViewer.ScrollToHorizontalOffset(offset);
}

private static int CoerceToRange(int value, int minimum, int maximum)
{
    return Math.Max(minimum, Math.Min(value, maximum));
}