C# 包装滚动条

C# 包装滚动条,c#,wpf,layout,wrappanel,C#,Wpf,Layout,Wrappanel,简单xaml: <WrapPanel Orientation="Vertical"> <Ellipse Width="100" Height="100" Fill="Red" /> <Ellipse Width="100" Height="100" Fill="Yellow" /> <Ellipse Width="100" Height="100" Fill="Green" /> </WrapPanel> 但是

简单xaml:

<WrapPanel Orientation="Vertical">
    <Ellipse Width="100" Height="100" Fill="Red" />
    <Ellipse Width="100" Height="100" Fill="Yellow" />
    <Ellipse Width="100" Height="100" Fill="Green" />
</WrapPanel>
但是
WrapPanel
停止包装任何内容(始终为一列):

这里的问题是,
ScrollViewer
(NaN,NaN)
大小提供给它的子级,因此从不进行包装

我试图通过将scroll viewer的可用高度绑定到面板的最大高度来修复它:

<ScrollViewer ...>
    <WrapPanel MaxHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ...>

这将限制面板高度(不再是
NaN
),因此现在会进行包装。但因为这也会调整面板的高度-垂直滚动条将永远不会出现:

如何添加垂直滚动条?


在我的例子中,
WrapPanel
是垂直的,这意味着它将尽可能多地填充列,然后从左到右换行到新列。当儿童不能垂直(可用空间小于儿童高度)或水平放置时,需要滚动条


这个想法可以用于标准(水平)
WrapPanel
:从左到右,满时创建新行。完全相同的问题也会出现(只是尝试了一下)。

您可以通过在scrollviewer中包装封套,然后将内部面板的高度和宽度绑定到scrollviewer视口的高度和宽度,使其与屏幕的其余部分拉伸和收缩。 我还为我的示例添加了最小高度和宽度,这确保了一旦将包裹面板推到小于其最小尺寸时,滚动条就会出现

<ScrollViewer x:Name="sv" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
        <WrapPanel MinWidth="200" Width="{Binding ElementName=sv, Path=ViewportWidth}" MinHeight="200" Height="{Binding ElementName=sv, Path=ViewportHeight}">
        <Ellipse Fill="Red" Height="200" Width="200"/>
        <Ellipse Fill="Yellow" Height="200" Width="200"/>
        <Ellipse Fill="Green" Height="200" Width="200"/>
    </WrapPanel>
</ScrollViewer>

对于
垂直方向,如果不明确设置其
高度
/
最小高度
,或者对于
水平方向,
宽度
/
最小宽度
,使用
包装箱就不可能出现这种行为。
ScrollViewer
仅当此滚动查看器包装的
FrameworkElement
不适合视口时才会显示滚动条

您可以创建自己的包裹面板,该面板根据子面板计算其最小尺寸

或者,您可以实现
行为
或附加属性。这并不像您所期望的那样,仅仅添加几个XAML标记那么容易

我们已经用附带的财产解决了这个问题。让我告诉你我们做了什么

static class ScrollableWrapPanel
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollableWrapPanel), new PropertyMetadata(false, IsEnabledChanged));

    // DP Get/Set static methods omitted

    static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var panel = (WrapPanel)d;
        if (!panel.IsInitialized)
        {
            panel.Initialized += PanelInitialized;        
        }
        // Add here the IsEnabled == false logic, if you wish
    }

    static void PanelInitialized(object sender, EventArgs e)
    {
        var panel = (WrapPanel)sender;
        // Monitor the Orientation property.
        // There is no OrientationChanged event, so we use the DP tools.
        DependencyPropertyDescriptor.FromProperty(
            WrapPanel.OrientationProperty,
            typeof(WrapPanel))
        .AddValueChanged(panel, OrientationChanged);

        panel.Unloaded += PanelUnloaded;

        // Sets up our custom behavior for the first time
        OrientationChanged(panel, EventArgs.Empty);
    }

    static void OrientationChanged(object sender, EventArgs e)
    {
        var panel = (WrapPanel)sender;
        if (panel.Orientation == Orientation.Vertical)
        {
            // We might have set it for the Horizontal orientation
            BindingOperations.ClearBinding(panel, WrapPanel.MinWidthProperty);

            // This multi-binding monitors the heights of the children
            // and returns the maximum height.
            var converter = new MaxValueConverter();
            var minHeightBiding = new MultiBinding { Converter = converter };
            foreach (var child in panel.Children.OfType<FrameworkElement>())
            {
                minHeightBiding.Bindings.Add(new Binding("ActualHeight") { Mode = BindingMode.OneWay, Source = child });
            }

            BindingOperations.SetBinding(panel, WrapPanel.MinHeightProperty, minHeightBiding);

            // We might have set it for the Horizontal orientation        
            BindingOperations.ClearBinding(panel, WrapPanel.WidthProperty);

            // We have to define the wrap panel's height for the vertical orientation
            var binding = new Binding("ViewportHeight")
            {
                RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ScrollViewer)}
            };

            BindingOperations.SetBinding(panel, WrapPanel.HeightProperty, binding);
        }
        else
        {
            // The "transposed" case for the horizontal wrap panel
        }
    }

    static void PanelUnloaded(object sender, RoutedEventArgs e)
    {
        var panel = (WrapPanel)sender;
        panel.Unloaded -= PanelUnloaded;

        // This is really important to prevent the memory leaks.
        DependencyPropertyDescriptor.FromProperty(WrapPanel.OrientationProperty, typeof(WrapPanel))
            .RemoveValueChanged(panel, OrientationChanged);
    }

    private class MaxValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return values.Cast<double>().Max();
        }

        // ConvertBack omitted
    }
}
静态类ScrollableWrapPanel
{
公共静态只读从属属性IsEnabledProperty=
DependencyProperty.RegisterAttached(“IsEnabled”,typeof(bool),typeof(ScrollableWrapPanel),new PropertyMetadata(false,IsEnabledChanged));
//省略DP Get/Set静态方法
静态void IsEnabledChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var面板=(包装)d;
如果(!面板已初始化)
{
panel.Initialized+=PanelInitialized;
}
//如果愿意,在这里添加IsEnabled==false逻辑
}
静态无效面板已初始化(对象发送方,事件参数e)
{
var面板=(包装)发送器;
//监视方向属性。
//没有方向更改事件,因此我们使用DP工具。
DependencyPropertyDescriptor.FromProperty(
WrapPanel.OrientationProperty,
类型(包装纸)
.AddValueChanged(面板、方向更改);
panel.unload+=panelunload;
//首次设置自定义行为
方向更改(面板,EventArgs.Empty);
}
静态无效方向已更改(对象发送方、事件参数e)
{
var面板=(包装)发送器;
if(panel.Orientation==Orientation.Vertical)
{
//我们可能已经将其设置为水平方向
BindingOperations.ClearBinding(panel,WrapPanel.MinWidthProperty);
//这种多重绑定可以监视孩子们的身高
//并返回最大高度。
var converter=新的MaxValueConverter();
var minHeightBiding=新的多重绑定{Converter=Converter};
foreach(panel.Children.OfType()中的var child)
{
添加(新绑定(“实际高度”){Mode=BindingMode.OneWay,Source=child});
}
BindingOperations.SetBinding(panel,WrapPanel.MinHeightProperty,minHeightBiding);
//我们可能已经将其设置为水平方向
BindingOperations.ClearBinding(panel,WrapPanel.WidthProperty);
//我们必须为垂直方向定义包裹面板的高度
变量绑定=新绑定(“视口高度”)
{
RelativeSource=new RelativeSource{Mode=RelativeSourceMode.FindAncestor,AncestorType=typeof(ScrollViewer)}
};
BindingOperations.SetBinding(panel,WrapPanel.HeightProperty,binding);
}
其他的
{
//水平缠绕面板的“转置”外壳
}
}
静态无效面板已卸载(对象发送器,RoutedEventArgs e)
{
var面板=(包装)发送器;
panel.unload-=panel unload;
//这对于防止内存泄漏非常重要。
DependencyPropertyDescriptor.FromProperty(WrapPanel.OrientationProperty,typeof(WrapPanel))
.移除已更改的值(面板、方向已更改);
}
私有类MaxValueConverter:IMultiValueConverter
{
公共对象转换(对象[]值,类型targetType,对象参数,CultureInfo区域性)
{
返回值.Cast().Max();
}
//省略了转换回
}
}
这可能不是最简单的方法,还有更多的行,只有几个XAML标记,但它工作得完美无缺

不过,您必须小心错误处理。我刚刚省略了示例代码中的所有检查和异常处理

用法很简单:<
static class ScrollableWrapPanel
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollableWrapPanel), new PropertyMetadata(false, IsEnabledChanged));

    // DP Get/Set static methods omitted

    static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var panel = (WrapPanel)d;
        if (!panel.IsInitialized)
        {
            panel.Initialized += PanelInitialized;        
        }
        // Add here the IsEnabled == false logic, if you wish
    }

    static void PanelInitialized(object sender, EventArgs e)
    {
        var panel = (WrapPanel)sender;
        // Monitor the Orientation property.
        // There is no OrientationChanged event, so we use the DP tools.
        DependencyPropertyDescriptor.FromProperty(
            WrapPanel.OrientationProperty,
            typeof(WrapPanel))
        .AddValueChanged(panel, OrientationChanged);

        panel.Unloaded += PanelUnloaded;

        // Sets up our custom behavior for the first time
        OrientationChanged(panel, EventArgs.Empty);
    }

    static void OrientationChanged(object sender, EventArgs e)
    {
        var panel = (WrapPanel)sender;
        if (panel.Orientation == Orientation.Vertical)
        {
            // We might have set it for the Horizontal orientation
            BindingOperations.ClearBinding(panel, WrapPanel.MinWidthProperty);

            // This multi-binding monitors the heights of the children
            // and returns the maximum height.
            var converter = new MaxValueConverter();
            var minHeightBiding = new MultiBinding { Converter = converter };
            foreach (var child in panel.Children.OfType<FrameworkElement>())
            {
                minHeightBiding.Bindings.Add(new Binding("ActualHeight") { Mode = BindingMode.OneWay, Source = child });
            }

            BindingOperations.SetBinding(panel, WrapPanel.MinHeightProperty, minHeightBiding);

            // We might have set it for the Horizontal orientation        
            BindingOperations.ClearBinding(panel, WrapPanel.WidthProperty);

            // We have to define the wrap panel's height for the vertical orientation
            var binding = new Binding("ViewportHeight")
            {
                RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ScrollViewer)}
            };

            BindingOperations.SetBinding(panel, WrapPanel.HeightProperty, binding);
        }
        else
        {
            // The "transposed" case for the horizontal wrap panel
        }
    }

    static void PanelUnloaded(object sender, RoutedEventArgs e)
    {
        var panel = (WrapPanel)sender;
        panel.Unloaded -= PanelUnloaded;

        // This is really important to prevent the memory leaks.
        DependencyPropertyDescriptor.FromProperty(WrapPanel.OrientationProperty, typeof(WrapPanel))
            .RemoveValueChanged(panel, OrientationChanged);
    }

    private class MaxValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return values.Cast<double>().Max();
        }

        // ConvertBack omitted
    }
}
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <WrapPanel Orientation="Vertical" local:ScrollableWrapPanel.IsEnabled="True">
    <!-- Content -->
    </WrapPanel>
</ScrollViewer
public class ColumnPanel : Panel
{
    public double ViewportHeight
    {
        get { return (double)GetValue(ViewportHeightProperty); }
        set { SetValue(ViewportHeightProperty, value); }
    }
    public static readonly DependencyProperty ViewportHeightProperty =
        DependencyProperty.Register("ViewportHeight", typeof(double), typeof(ColumnPanel),
            new FrameworkPropertyMetadata(double.PositiveInfinity, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));

    protected override Size MeasureOverride(Size constraint)
    {
        var location = new Point(0, 0);
        var size = new Size(0, 0);
        foreach (UIElement child in Children)
        {
            child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
            {
                location.X = size.Width;
                location.Y = 0;
            }
            if (size.Width < location.X + child.DesiredSize.Width)
                size.Width = location.X + child.DesiredSize.Width;
            if (size.Height < location.Y + child.DesiredSize.Height)
                size.Height = location.Y + child.DesiredSize.Height;
            location.Offset(0, child.DesiredSize.Height);
        }
        return size;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        var location = new Point(0, 0);
        var size = new Size(0, 0);
        foreach (UIElement child in Children)
        {
            if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
            {
                location.X = size.Width;
                location.Y = 0;
            }
            child.Arrange(new Rect(location, child.DesiredSize));
            if (size.Width < location.X + child.DesiredSize.Width)
                size.Width = location.X + child.DesiredSize.Width;
            if (size.Height < location.Y + child.DesiredSize.Height)
                size.Height = location.Y + child.DesiredSize.Height;
            location.Offset(0, child.DesiredSize.Height);
        }
        return size;
    }
}
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <local:ColumnPanel ViewportHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ... >
        ...
    </local:ColumnPanel>
</ScrollViewer>