WPF包装面板和滚动

28

我有一个简单的WrapPanel,其中包含许多宽度较大的控件。当我调整Window的宽度时,一切都按预期工作。如果有足够的空间,控件将在单行上横跨,否则将折叠到下一行。

然而,我需要的是,如果所有控件基本上垂直堆叠(因为没有更多的水平空间),并且WindowWidth被进一步减小,那么会出现一个水平滚动条,以便我可以滚动并查看整个控件。以下是我的XAML代码。我尝试将WrapPanel包装在ScrollViewer中,但无法实现我的目标。

<Window x:Class="WpfQuotes.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="Auto" Width="600" Foreground="White">
    <WrapPanel>
      <Button Width="250">1</Button>
      <Button Width="250">2</Button>
      <Button Width="250">3</Button>
    </WrapPanel>
</Window>
所以,如果你将上述窗口的Width减小到最小,你将看不到按钮的文本。我希望出现水平滚动条,这样我就可以滚动查看文本,但不会干扰常规换行功能。
谢谢。
更新: 我按照Paul下面的建议进行了操作,水平滚动条如预期般出现了。然而,我还想要垂直滚动可用,所以我设置了VerticalScrollBarVisibility="Auto"。问题是,如果我调整窗口大小以使垂直滚动条出现,则水平滚动条也总是出现,即使它不需要(有足够的水平空间来看到整个控件)。似乎垂直滚动条的出现正在影响滚动查看器的宽度。有没有办法纠正这种情况,使得水平滚动条只有在实际需要时才出现?
以下是我的XAML和我添加在CustomWrapPanel中的唯一代码:
<Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cwp="clr-namespace:CustomWrapPanelExample"
        Title="Window1" Height="Auto" Width="300" Foreground="White" Name="mainPanel">
  <ScrollViewer x:Name="MyScrollViewer" HorizontalScrollBarVisibility="Auto"
                VerticalScrollBarVisibility="Auto">
    <cwp:CustomWrapPanel Width="{Binding ElementName=MyScrollViewer, Path=ActualWidth}">
      <Button Width="250">1</Button>
      <Button Width="250">2</Button>
      <Button Width="250">3</Button>
      <Button Width="250">4</Button>
      <Button Width="250">5</Button>
      <Button Width="250">6</Button>
      <Button Width="250">7</Button>
      <Button Width="250">8</Button>
      <Button Width="250">9</Button>
    </cwp:CustomWrapPanel>
  </ScrollViewer>
</Window>

CustomWrapPanel 中唯一被覆盖的东西:

protected override Size MeasureOverride(Size availableSize)
{
  double maxChildWidth = 0;
  if (Children.Count > 0)
  {
    foreach (UIElement el in Children)
    {
      if (el.DesiredSize.Width > maxChildWidth)
      {
        maxChildWidth = el.DesiredSize.Width;
      }
    }
  }      
  MinWidth = maxChildWidth;
  return base.MeasureOverride(availableSize);
}

1
你能贴出带有 ScrollViewer 的 XAML 吗? - hackerhasid
3个回答

63

使用 WrapPanel 的话需要注意:它会沿一个方向占用所有可用的空间,并在另一个方向上根据需要扩展。例如,如果你将其放置在窗口内,它会尽可能地占用水平空间,然后根据需要向下扩展。这就是为什么垂直滚动条能够正常工作的原因。父容器告诉子控件:“这是我的宽度,但你可以在垂直方向上自由扩展”。如果你将它更改为水平滚动条,那么滚动视图控件基本上会告诉你:“这是你的高度,但你可以在水平方向上任意扩展”。在这种情况下,WrapPanel 不会折行,因为没有水平约束。

一种潜在的解决方案是将 WrapPanel 的折行方向从水平改为垂直(这可能不是理想或预期的行为):

    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
        <WrapPanel Orientation="Vertical">
            <Button Width="250">1</Button>
            <Button Width="250">2</Button>
            <Button Width="250">3</Button>
        </WrapPanel>
    </ScrollViewer>
为了得到你期望的行为,你需要做一些更接近这样的事情:
    <ScrollViewer x:Name="MyScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
        <WrapPanel MinWidth="250" Width="{Binding ElementName=MyScrollViewer, Path=ViewportWidth}">
            <Button Width="250">1</Button>
            <Button Width="250">2</Button>
            <Button Width="250">3</Button>
        </WrapPanel>
    </ScrollViewer>

然而,这个第二种解决方案只适用于您已经知道子元素的宽度的情况下。理想情况下,您希望将最大宽度设置为最大子项的实际宽度,但是要做到这一点,您必须创建一个自定义控件来继承Wrap Panel并编写代码来进行检查。


1
谢谢Paul。我按照你的描述进行了更改,现在工作得好多了。然而,我发现一个小问题,与垂直滚动有关。当Wrap Panel的项目在垂直方向上没有完全可见时,我也想要出现一个垂直滚动条。我编辑了原始帖子来展示我所做的更改和我遇到的问题。 - Flack
啊...稍微试了一下,你只需要将Wrap Panel上的绑定路径从ActualWidth更改为ViewportWidth即可。当垂直滚动条添加时,它会使视口宽度变小,但实际控件大小保持不变。这应该可以解决你看到的奇怪水平滚动条错误,并且我会更新我的帖子。 - Paul Rohde
这可以在C#代码中使用,如下所示:ScrollViewer sv = new ScrollViewer(); WrapPanel wp = new WrapPanel(); sv.VerticalScrollBarVisibility = ScrollBarVisibility.Visible; sv.Content = wp;同时,绑定一个事件,当窗口大小改变时,按钮大小相对于当前窗口大小在WrapPanel中改变。谢谢。 - Slobodan Stanković

6

这是我对此问题的解决方案:

    <Grid Width="475">
        <ItemsControl ItemsSource="{Binding Items}" 
                          Height="450" Width="475" >

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:HorizontalListItemControl />
                </DataTemplate>
            </ItemsControl.ItemTemplate>

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer>
                        <ItemsPresenter />
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>

        </ItemsControl>
    </Grid>
我来试着解释一下:
我使用了一个ItemsControl,它的ItemsSource绑定到我的Items集合。 在其中,我定义了一个WrapPanel作为ItemsPanelTemplate。这是使换行工作完成的关键。
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>


但现在没有滚动,是吗?
为了解决这个问题,我定义了一个ItemsPresenter嵌套在ScrollViewer中作为控件模板:

            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer>
                        <ItemsPresenter />
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>


现在,您可以滚动页面了。



希望我有所帮助。


0
     public bool CheckUIElementInBounary(UIElement element, Rect r)
            {
                bool inbound = false;
                Point p1 = element.PointToScreen(new Point(0, 0));
                Point p2 = element.PointToScreen(new Point(0, element.RenderSize.Height));
                Point p3 = element.PointToScreen(new Point(element.RenderSize.Width, 0));
                Point p4 = element.PointToScreen(new Point(element.RenderSize.Width, element.RenderSize.Height));
                if (CheckPoint(p1, r) || CheckPoint(p2, r) || CheckPoint(p3, r) || CheckPoint(p4, r))
                {
                    inbound = true;
                }
                return inbound;
            }
            public bool CheckPoint(Point p, Rect bounday)
            {
                bool inbound = false;
                if (p.X >= bounday.Left && p.X <= bounday.Right && p.Y <= bounday.Top && p.Y <= bounday.Bottom)
                {
                    inbound = true;
                }
                return inbound;
            }

===================
void mainViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            foreach (var item in this.mainContent.Items)
            {
                Button btn = item as Button;
                Point p1 = mainViewer.PointToScreen(new Point(0, 0));
                Point p2 = mainViewer.PointToScreen(new Point(mainViewer.ActualWidth, mainViewer.ActualHeight));
                Rect bounds = new Rect(p1, p2);
                if (!CheckUIElementInBounary(btn, bounds))
                {
                    this.Title = btn.Content.ToString();
                    mainContent.ScrollIntoView(btn);
                    break;
                }
            }

        }

1
你能否添加一些解释说明? - Werner Henze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接