作者热门文章
- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试实现一个与标准 WrapPanel 类似的自定义控件,但它允许您指定页眉和页脚。从视觉上看,这就是我想要完成的:
我创建了一个自定义控件,似乎为页眉和页脚项目留出了空间,但我无法让它们在视觉上出现。这是我第一次尝试任何类型的自定义控件,因此感谢任何帮助或输入!
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);
}
}
}
<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。在更高的层次上,您正在处理隐藏实际绘图事件的视觉和框架元素。但是要覆盖这些对象的绘制方式,需要深入到该绘制级别,根据需要替换或补充它。
Control
确实需要子类。唯一出现这种情况的情况是您需要完全控制整个渲染过程,绘制任何其他方式根本不可能的东西,或者提供所需的性能(以方便为代价)。更多时候,甚至几乎所有时间,您想要做的是利用现有控件并让它们为您完成所有繁重的工作。
CompositeCollection
的类型。 .就像听起来一样,它允许您将集合构建为其他对象的组合,包括其他集合。这样,您可以将页眉和页脚数据合并到一个集合中,该集合可以由
ItemsControl
显示。 .
ItemsControl
对象可能足以满足您的需求。但是如果你想要一个完整的、可重用的用户定义控件,它理解页眉和页脚的概念,你可以包装
ItemsControl
在
UserControl
公开您需要的属性的对象,包括
Header
和
Footer
属性(property)。这是一个可能看起来像的示例:
<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>
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
的任何类类型。
<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 模型用作数据上下文——但对于像这样的简单程序来说,这很好。同样,
Header
和
Footer
属性通常会绑定(bind)到某个 View 模型属性,而不是仅仅将一个框架元素的属性绑定(bind)到另一个。
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/
我是一名优秀的程序员,十分优秀!