开发者

WPF wrap panel and scrolling

I have a simple WrapPanel which contains a number of wide controls. When I resize the Width of the Window everything works as expected. The controls will go across on a single line if there is enough space or wrap down to the next line when there isn't.

However, what I need to happen is that if all of the controls are basically stacked vertically (since there is no more horizontal space) and the Width of the Window is decreased even more, a horizontal scroll bar appears so that I can scroll and see the entire control if I want to. Below is my xaml. I tried wrapping the WrapPanel in a ScrollViewer but I couldn't achieve my goal.

<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>

So if you reduce the Width of the above Window to its minimum, you will not be able to see the text of the buttons. I would like a horizontal scroll bar appear so that I can scroll to see the text but not interfere with the usual wrapping functionality.

Thanks.

Update: I have followed Paul's suggestion below and the horizontal scrollbar appears as expected now. However, I also wanted vertical scrolling available so I set VerticalScrollBarVisibility="Auto". The thing is, if I resize the window so that a vertical scroll bar appears, the horizontal one also always appears, even if it is not needed (there is enough horizontal space to see the entire control). It seems like the vertical scrollbar appearing is messing with the width of the scrollviewer. Is there a way to 开发者_开发问答correct this so that the horizontal scrollbar doesn't appear unless it is actually needed?

Below is my xaml and the only code I added in the 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>

The only thing overridden in 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);
}


Here's the thing, if your going to use a wrap panel, it does two things, it will take up as much available space in one direction, and expand as needed in the other. For instance, if you place it inside of a window like you have it, it takes up as much horizontal space as it can, and then expands as needed downward, that's why a vertical scroll bar will work, the parent container says "this is how wide I am, but you can make yourself as big as you want vertically", if you change it to a horizontal scroll bar, the scroll viewer is essentially saying "this is how tall you can be, but you can be as wide as you want" in this case the wrap panel doesn't wrap because there is no horizontal constraints.

One potential solution is to change the direction the wrap panel wraps from horizontal to vertical like this (Which is probably not the ideal or expected behavior):

    <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>

In order to get the behavior your expecting, you'll have to do something closer to this:

    <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>

However, this second solution only works if you already know the width of your child elements, ideally you want your max width to be set to the actual width of the largest child item, but in order to do that you'd have to create a custom control that derives from wrap panel and write the code yourself to check for that.


This is my solution for this:

    <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>



I'll try to explain:
I used an ItemsControl, its ItemsSource was bound to my Items collection. Inside it, I defined a WrapPanel as the ItemsPanelTemplate. This is what makes the wrapping job done.

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


But now, there is no scrolling, right?
To solve this, I defined an ItemsPresenter inside a ScrollViewer as the ControlTemplate:

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


And now you can scroll.



Hope I helped.


     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;
                }
            }

        }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜