gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 23:14:31 24 4
gpt4 key购买 nike

我正在尝试实现一个与标准 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>

最佳答案

你在评论中写道:

The DrawingContext supplied to the OnRender() method only seems to support very basic rendering commands. Surely you don't have to re-write the rendering code for a standard WPF control, but I am not seeing a way to draw them on my own



如果“基本”是指您仅限于 DrawingContext操作,然后是的。这正是它的用途。这实际上是 WPF 的绘图 API。在更高的层次上,您正在处理隐藏实际绘图事件的视觉和框架元素。但是要覆盖这些对象的绘制方式,需要深入到该绘制级别,根据需要替换或补充它。

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

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

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

在某些情况下,只需创建该集合并直接使用 ItemsControl对象可能足以满足您的需求。但是如果你想要一个完整的、可重用的用户定义控件,它理解页眉和页脚的概念,你可以包装 ItemsControlUserControl公开您需要的属性的对象,包括 HeaderFooter属性(property)。这是一个可能看起来像的示例:

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

C#:
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 ,如 ItemTemplate , ItemTemplateSelector等。你必须弄清楚它们是什么,并绑定(bind)属性,源是 UserControl。和目标 ItemsControl在你的 UserControl 中声明为依赖属性尚未属于 UserControl 的任何类类型。

这是一个小示例程序,显示了如何使用上述内容:

XAML:
<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.DataContext Window 的属性(property)对象本身。这通常不是一个好主意——最好有一个合适的 View 模型用作数据上下文——但对于像这样的简单程序来说,这很好。同样, HeaderFooter属性通常会绑定(bind)到某个 View 模型属性,而不是仅仅将一个框架元素的属性绑定(bind)到另一个。

C#:
public partial class MainWindow : Window
{
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();

public MainWindow()
{
InitializeComponent();

Items.Add("Item #1");
Items.Add("Item #2");
Items.Add("Item #3");
}

private static readonly Random _random = new Random();

private void Button_Click(object sender, RoutedEventArgs e)
{
switch (Items.Count > 0 ? _random.Next(2) : 0)
{
case 0: // add
Items.Insert(_random.Next(Items.Count + 1), $"Item #{_random.Next()}");
break;
case 1: // remove
Items.RemoveAt(_random.Next(Items.Count));
break;
}
}
}

关于c# - 如何实现带有页眉和页脚的 WrapPanel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43008469/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com