ScrollViewer ScrollToHorizontalOffset 奇怪的行为
ScrollViewer ScrollToHorizontalOffset strange behaviour
我的应用程序包含 ScrollViewer
和 StackPanel
。
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
时,我的矩形看起来像这样:
但我想在两侧看到相等的矩形部分。我想让我的整个矩形居中,但事实并非如此。我真的认为我正确计算了偏移量,所以我真的不知道我哪里错了。
我是不是遗漏了什么或者用 WPF
s 独立单位计算有什么问题?
我认为您的问题源于在 Loaded
事件处理程序中使用 Width
属性。 Width
这里是Window
class的宽度属性,属性的值包括[=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));
}
我的应用程序包含 ScrollViewer
和 StackPanel
。
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
时,我的矩形看起来像这样:
我是不是遗漏了什么或者用 WPF
s 独立单位计算有什么问题?
我认为您的问题源于在 Loaded
事件处理程序中使用 Width
属性。 Width
这里是Window
class的宽度属性,属性的值包括[=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));
}