如何实现带有页眉和页脚的WrapPanel

时间:2017-03-24 20:21:06

标签: c# wpf xaml custom-controls panel

我正在尝试实现一个与标准WrapPanel类似的自定义控件,但是它允许您指定页眉和页脚。在视觉上,这正是我想要实现的目标:

enter image description here

我创建了一个自定义控件,似乎为页眉和页脚项目留出了空间,但我无法让它们直观显示。这是我第一次尝试任何类型的自定义控件,所以感谢任何帮助或输入!

C#

using System;
using System.Windows;
using System.Windows.Controls;

namespace MyProject.Extras
{
    public class HeaderedFooteredPanel : Panel
    {
        public FrameworkElement Header
        {
            get { return (FrameworkElement) GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }
        public FrameworkElement Footer
        {
            get { return (FrameworkElement)GetValue(FooterProperty); }
            set { SetValue(FooterProperty, value); }
        }

        public static DependencyProperty HeaderProperty = DependencyProperty.Register(
            nameof(Header),
            typeof(FrameworkElement),
            typeof(HeaderedFooteredPanel),
            new PropertyMetadata((object)null));
        public static DependencyProperty FooterProperty = DependencyProperty.Register(
            nameof(Footer),
            typeof(FrameworkElement),
            typeof(HeaderedFooteredPanel),
            new PropertyMetadata((object)null));

        protected override Size MeasureOverride(Size constraint)
        {
            double x = 0.0;
            double y = 0.0;
            double largestY = 0.0;
            double largestX = 0.0;

            var measure = new Action<FrameworkElement>(element =>
            {
                element.Measure(constraint);
                if (x > 0 &&                                                // Not the first item on this row
                    (x + element.DesiredSize.Width > constraint.Width) &&   // We are too wide to fit on this row
                    ((largestY + element.DesiredSize.Height) <= MaxHeight)) // We have enough room for this on the next row
                {
                    y = largestY;
                    x = element.DesiredSize.Width;
                }
                else
                {
                    /* 1) Always place the first item on a row even if width doesn't allow it
                     *      otherwise:
                     * 2) Keep placing on this row until we reach our width constraint
                     *      otherwise:
                     * 3) Keep placing on this row if the max height is reached */

                    x += element.DesiredSize.Width;
                }

                largestY = Math.Max(largestY, y + element.DesiredSize.Height);
                largestX = Math.Max(largestX, x);
            });

            measure(Header);

            foreach (FrameworkElement child in InternalChildren)
            {
                measure(child);
            }

            measure(Footer);

            return new Size(largestX, largestY);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0.0;
            double y = 0.0;
            double largestY = 0.0;
            double largestX = 0.0;

            var arrange = new Action<FrameworkElement>(element =>
            {
                if (x > 0 &&                                                // Not the first item on this row
                    (x + element.DesiredSize.Width > finalSize.Width) &&    // We are too wide to fit on this row
                    ((largestY + element.DesiredSize.Height) <= MaxHeight)) // We have enough room for this on the next row
                {
                    y = largestY;
                    element.Arrange(new Rect(new Point(0.0, y), element.DesiredSize));
                    x = element.DesiredSize.Width;
                }
                else
                {
                    /* 1) Always place the first item on a row even if width doesn't allow it
                     *      otherwise:
                     * 2) Keep placing on this row until we reach our width constraint
                     *      otherwise:
                     * 3) Keep placing on this row if the max height is reached */

                    element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
                    x += element.DesiredSize.Width;
                }

                largestY = Math.Max(largestY, y + element.DesiredSize.Height);
                largestX = Math.Max(largestX, x);
            });

            arrange(Header);

            foreach (FrameworkElement child in InternalChildren)
            {
                arrange(child);
            }

            arrange(Footer);

            return new Size(largestX, largestY);
        }
    }
}

XAML中的用法:

<ItemsControl ItemsSource="{Binding SomeItems}" ItemTemplate="{StaticResource SomeTemplate}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <extras:HeaderedFooteredPanel>
                <extras:HeaderedFooteredPanel.Header>
                    <TextBlock Text="Header" />
                </extras:HeaderedFooteredPanel.Header>
                <extras:HeaderedFooteredPanel.Footer>
                    <TextBlock Text="Footer" />
                </extras:HeaderedFooteredPanel.Footer>
            </extras:HeaderedFooteredPanel>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

1 个答案:

答案 0 :(得分:2)

你在评论中写道:

  

提供给OnRender()方法的DrawingContext似乎只支持非常基本的渲染命令。当然,您不必重新编写标准WPF控件的渲染代码,但我没有看到自己绘制它们的方法

如果是&#34;基本&#34;你的意思是你只限于DrawingContext次操作,然后是。这正是它的真正含义。这实际上是WPF的绘图API。在更高级别,您正在处理视觉效果和框架元素,这些元素隐藏了实际的绘图活动。但是为了覆盖这些物体的绘制方式,需要深入到绘制水平,替换它或根据需要补充它。

可能出现的一个重大困难(除了在该级别处理绘图的更基本的困难)是在那个级别,没有数据模板这样的东西,也没有办法访问其他元素的渲染行为。你必须从头开始绘制所有。这将最终否定WPF如此有用的大部分内容:通过使用内置控件和可以控制其外观的属性,方便而强大地控制数据的精确屏幕表示。

我很少发现真正需要自定义Control子类。唯一出现这种情况的时候,你需要完全控制整个渲染过程,绘制一些根本不可能的东西,或提供所需的性能(以便利性为代价)。 很多更频繁,几乎所有的时间甚至,你想要做的是利用现有的控制,让他们为你做所有繁重的工作。

在这种特殊情况下,我认为解决问题的关键是名为CompositeCollection的类型。就像听起来一样,它允许您将集合构建为其他对象的组合,包括其他集合。有了这个,您可以将页眉和页脚数据合并到一个可以由ItemsControl显示的集合中。

在某些情况下,只需创建该集合并直接将其与ItemsControl对象一起使用即可满足您的需求。但是如果你想要一个完整的,可重用的用户定义控件来理解页眉和页脚的概念,你可以将ItemsControl包装在一个公开你需要的属性的UserControl对象中,包括{{ 1}}和Header属性。以下是可能的示例:

<强> XAML:

Footer

<强> C#:

<UserControl x:Class="TestSO43008469HeaderFooterWrapPanel.HeaderFooterWrapPanel"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:TestSO43008469HeaderFooterWrapPanel"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <ItemsControl x:Name="wrapPanel1">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <WrapPanel IsItemsHost="True"/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
  </ItemsControl>
</UserControl>

当然注意到,这样做,你需要&#34;转发&#34;您希望能够设置的所有各种控件属性,从public partial class HeaderFooterWrapPanel : UserControl { private const int _kheaderIndex = 0; private const int _kfooterIndex = 2; private readonly CompositeCollection _composedCollection = new CompositeCollection(); private readonly CollectionContainer _container = new CollectionContainer(); public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( "Header", typeof(string), typeof(HeaderFooterWrapPanel), new PropertyMetadata((o, e) => _OnHeaderFooterPropertyChanged(o, e, _kheaderIndex))); public static readonly DependencyProperty FooterProperty = DependencyProperty.Register( "Footer", typeof(string), typeof(HeaderFooterWrapPanel), new PropertyMetadata((o, e) => _OnHeaderFooterPropertyChanged(o, e, _kfooterIndex))); public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( "ItemsSource", typeof(IEnumerable), typeof(HeaderFooterWrapPanel), new PropertyMetadata(_OnItemsSourceChanged)); private static void _OnHeaderFooterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e, int index) { HeaderFooterWrapPanel panel = (HeaderFooterWrapPanel)d; panel._composedCollection[index] = e.NewValue; } private static void _OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { HeaderFooterWrapPanel panel = (HeaderFooterWrapPanel)d; panel._container.Collection = panel.ItemsSource; } public string Header { get { return (string)GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } public string Footer { get { return (string)GetValue(FooterProperty); } set { SetValue(FooterProperty, value); } } public IEnumerable ItemsSource { get { return (IEnumerable)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public HeaderFooterWrapPanel() { InitializeComponent(); _container.Collection = ItemsSource; _composedCollection.Add(Header); _composedCollection.Add(_container); _composedCollection.Add(Footer); wrapPanel1.ItemsSource = _composedCollection; } } 对象到UserControl。有些内容,例如ItemsPanel,您可以设置Background并获得所需的效果,但其他内容特别适用于UserControl,例如ItemsControl,{{1您必须弄清楚它们是哪些,并绑定属性,其中源为ItemTemplate,目标为ItemTemplateSelector,并声明为您的依赖属性UserControl类任何不属于ItemsControl类型的类。

这是一个小样本程序,展示了如何使用上述内容:

<强> XAML:

UserControl

出于说明目的,我已将UserControl属性设置为<Window x:Class="TestSO43008469HeaderFooterWrapPanel.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:l="clr-namespace:TestSO43008469HeaderFooterWrapPanel" xmlns:s="clr-namespace:System;assembly=mscorlib" DataContext="{Binding RelativeSource={x:Static RelativeSource.Self}}" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Grid.Row="0"> <TextBlock Text="Header: "/> <TextBox Text="{Binding Header, ElementName=headerFooterWrapPanel1, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="1"> <TextBlock Text="Footer: "/> <TextBox Text="{Binding Footer, ElementName=headerFooterWrapPanel1, UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> <Button Content="Random List Change" Click="Button_Click" HorizontalAlignment="Left" Grid.Row="2"/> <l:HeaderFooterWrapPanel x:Name="headerFooterWrapPanel1" ItemsSource="{Binding Items}" Header="Header Item" Footer="Footer Item" Grid.Row="3"> <l:HeaderFooterWrapPanel.Resources> <DataTemplate DataType="{x:Type s:String}"> <Border BorderBrush="Black" BorderThickness="1"> <TextBlock Text="{Binding}" FontSize="16"/> </Border> </DataTemplate> </l:HeaderFooterWrapPanel.Resources> </l:HeaderFooterWrapPanel> </Grid> </Window> 对象本身。这通常不是一个好主意 - 最好有一个合适的视图模型用作数据上下文 - 但对于像这样的简单程序,它没什么问题。同样,Window.DataContextWindow属性通常绑定到某个视图模型属性,而不是仅将一个框架元素的属性绑定到另一个。

<强> C#:

Header