C# 如何实现ItemTemplate和ItemsSource的可附加属性
为了创建一个可伸缩的钢琴键盘,我正在尝试使用WPF网格作为ItemsControl,使用附加属性。键盘上的每个键可能跨越1到3列,这取决于它的前后顺序,如果是尖锐的,则跨越1行;如果是自然的,则跨越2行。我已经有两个附加属性用于动态设置网格的列数和行数(尽管这些属性需要调整以支持每列/行的宽度/高度设置) 我现在需要实现的是C# 如何实现ItemTemplate和ItemsSource的可附加属性,c#,wpf,datatemplate,attached-properties,wpf-grid,C#,Wpf,Datatemplate,Attached Properties,Wpf Grid,为了创建一个可伸缩的钢琴键盘,我正在尝试使用WPF网格作为ItemsControl,使用附加属性。键盘上的每个键可能跨越1到3列,这取决于它的前后顺序,如果是尖锐的,则跨越1行;如果是自然的,则跨越2行。我已经有两个附加属性用于动态设置网格的列数和行数(尽管这些属性需要调整以支持每列/行的宽度/高度设置) 我现在需要实现的是ItemsSource(键)和ItemTemplate(PianoKeyView)的两个可附加属性。我需要在网格控件上使用它,因为ItemsControl只支持Uniform
ItemsSource
(键)和ItemTemplate
(PianoKeyView)的两个可附加属性。我需要在网格控件上使用它,因为ItemsControl
只支持UniformGrid
作为其ItemsPanel
的网格,并且不将特定项分配给特定列/行。我的钢琴键盘每八度键需要17列,但ItemsControl只能在UniformGrid
中创建12列,因为只有12个键传递给它。我已经包括了一个1倍频程钢琴键盘的图像,包括每个所需列的索引
这是我目前的键盘代码,我缺少GridExtensions.ItemsSource
和GridExtensions.ItemTemplate
的实现GridExtensions
是一个包含可附加属性的静态类
<UserControl x:Class="SphynxAlluro.Music.Wpf.PianoKeyboard.View.PianoKeyboardView"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:converters="http://schemas.sphynxalluro.com/converters"
xmlns:local="clr-namespace:SphynxAlluro.Music.Wpf.PianoKeyboard.View"
xmlns:prism="http://www.codeplex.com/prism"
xmlns:sphynxAlluroControls="http://schemas.sphynxalluro.com/controls"
xmlns:wpfBindingExtensions="http://schemas.sphynxalluro.com/bindingExtensions"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="600">
<UserControl.Resources>
<converters:KeysToColumnsCountConverter x:Key="keysToColumnsCountConverter"/>
<converters:KeysToRowsCountConverter x:Key="keysToRowsCountConverter"/>
<converters:IsSharpToRowSpanConverter x:Key="isSharpToRowSpanConverter"/>
<converters:KeysCollectionAndKeyToColumnIndexConverter x:Key="keysCollectionAndKeyToColumnIndexConverter"/>
<converters:KeysCollectionAndKeyToColumnSpanConverter x:Key="keysCollectionAndKeyToColumnSpanConverter"/>
</UserControl.Resources>
<Grid wpfBindingExtensions:GridExtensions.ItemsSource="{Binding Keys}"
wpfBindingExtensions:GridExtensions.ItemsOrientation="Horizontal"
wpfBindingExtensions:GridExtensions.ColumnCount="{Binding Keys, Converter={StaticResource keysToColumnsCountConverter}}"
wpfBindingExtensions:GridExtensions.RowCount="{Binding Keys, Converter={StaticResource keysToRowsCountConverter}}">
<wpfBindingExtensions:GridExtensions.ItemTemplate>
<DataTemplate>
<local:PianoKeyView Grid.RowSpan="{Binding Note.IsSharp, Mode=OneTime, Converter={StaticResource isSharpToRowSpanConverter}}"
DataContext="{Binding}">
<Grid.Column>
<MultiBinding Converter="{StaticResource keysCollectionAndKeyToColumnIndexConverter}" Mode="OneTime">
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="Items"/>
<Binding/>
</MultiBinding>
</Grid.Column>
<Grid.ColumnSpan>
<MultiBinding Converter="{StaticResource keysCollectionAndKeyToColumnSpanConverter}" Mode="OneTime">
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="Items"/>
<Binding/>
</MultiBinding>
</Grid.ColumnSpan>
</local:PianoKeyView>
</DataTemplate>
</wpfBindingExtensions:GridExtensions.ItemTemplate>
</Grid>
我试图通过
网格
直接实现的功能可以通过网格
的项控件
实现
结果发现缺少的部分是一个样式
,带有TargetType
的ContentPresenter
。在这种样式中,可附加的Grid
属性(如Grid.RowSpan
、Grid.Column
和Grid.ColumnSpan
)可通过适当的转换器进行设置,这些转换器接收ItemsControl和Key的数据上下文并返回所需的整数。这里还可以设置关键点的Z索引,以便锐利的关键点出现在自然关键点之上
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.RowSpan" Value="{Binding Note.IsSharp, Mode=OneTime, Converter={StaticResource isSharpToRowSpanConverter}}"/>
<Setter Property="Grid.Column">
<Setter.Value>
<MultiBinding Converter="{StaticResource keysCollectionAndKeyToColumnIndexConverter}" Mode="OneTime">
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="Items"/>
<Binding/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Grid.ColumnSpan">
<Setter.Value>
<MultiBinding Converter="{StaticResource keysCollectionAndKeyToColumnSpanConverter}" Mode="OneTime">
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="Items"/>
<Binding/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Panel.ZIndex" Value="{Binding Note.IsSharp, Converter={StaticResource booleanToIntegerConverter}}"/>
</Style>
</ItemsControl.ItemContainerStyle>
我修改了原始的ColumnCount
和RowCount
可附加属性,改为根据需要使用IEnumerable
/IEnumerable
,以便每个列/行的大小也可以传递给属性(在本例中,我是通过一个转换器执行此操作的,该转换器接收所有PianokeyViewModel,并为每个PianokeyViewModel返回一个具有适当星形大小的ColumnDefinition/RowDefinition。RowDefinition的分配仅适用于边缘情况,在这种情况下,键盘中只需要自然键或锐键(例如,B到C,e到F或单个锐键或自然键)。RowDefinitions attached属性的代码与ColumnDefinitions属性的逻辑基本相同(仅使用RowDefinitions而不是ColumnDefinitions),因此我将在此处发布ColumnDefinitions的代码:
public static class GridExtensions
{
// Using a DependencyProperty as the backing store for ColumnDefinitions. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnDefinitionsProperty =
DependencyProperty.RegisterAttached(
nameof(ColumnDefinitionsProperty).Substring(0, nameof(ColumnDefinitionsProperty).Length - "Property".Length),
typeof(IEnumerable<ColumnDefinition>),
typeof(GridExtensions),
new PropertyMetadata(Enumerable.Empty<ColumnDefinition>(), ColumnDefinitionsChanged));
public static IEnumerable<ColumnDefinition> GetColumnDefinitions(DependencyObject obj)
=> (IEnumerable<ColumnDefinition>)obj.GetValue(ColumnDefinitionsProperty);
public static void SetColumnDefinitions(DependencyObject obj, IEnumerable<ColumnDefinition> value)
=> obj.SetValue(ColumnDefinitionsProperty, value);
private static void ColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionCollection = ((Grid)d).ColumnDefinitions;
var newColumnDefinitions = (IEnumerable<ColumnDefinition>)e.NewValue;
var columnCount = newColumnDefinitions.Count();
columnDefinitionCollection.Clear();
foreach (var newColumnDefinition in newColumnDefinitions)
columnDefinitionCollection.Add(newColumnDefinition);
}
}
公共静态类GridExtensions
{
//使用DependencyProperty作为列定义的后备存储。这将启用动画、样式设置、绑定等。。。
公共静态只读从属属性列定义属性=
DependencyProperty.RegisterAttached(
nameof(ColumnDefinitionsProperty).Substring(0,nameof(ColumnDefinitionsProperty).Length—“属性”.Length),
类型(IEnumerable),
类型(网格扩展),
新的PropertyMetadata(Enumerable.Empty(),ColumnDefinitionsChanged));
公共静态IEnumerable GetColumnDefinitions(DependencyObject obj)
=>(IEnumerable)对象GetValue(ColumnDefinitionsProperty);
公共静态void SetColumnDefinitions(DependencyObject对象,IEnumerable值)
=>对象设置值(列定义属性、值);
私有静态无效列定义更改(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var columnDefinitionCollection=((网格)d).ColumnDefinitions;
var newColumnDefinitions=(IEnumerable)e.NewValue;
var columnCount=newColumnDefinitions.Count();
columnDefinitionCollection.Clear();
foreach(newColumnDefinition中的var newColumnDefinition)
columnDefinitionCollection.Add(新建ColumnDefinition);
}
}
有关附加属性的更多信息,请参阅“使用网格作为ItemsControl的面板”(Using a Grid as Panel For a ItemsControl)(),我从中找到了衍生上述静态类的原始代码。否则,这里的关键元素是:
Grid.Column
和panel.ZOrder
)指定给ItemsControl.itemscontainerstyle
中的ContentPresenter
样式ItemsControl.ItemsPanel
设置为Grid
的ItemsPanelTemplate
,并从此处设置网格的列定义和行定义
5个转换器和4个附加DP?我想说的是,与其使视图更加复杂,不如简化视图模型,使数据的形式更便于在ItemsControl中绑定。另一个选项是在ItemsPanelTemplate中使用水平堆叠面板,并通过style/binding/whate使用负边距和Panel.ZIndexver使非相邻白键延伸并与“下方”相交黑键。@Ed Plunkett@ASh我想使用ItemsControl,但如何为12个项目生成17列?我最初将其作为
ItemsControl
使用,但PianoKey
上可附加的Grid.Column
和Grid.Row
属性与UniformGrid
不起作用,就我而言记得吗<
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PianoKeyView DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid wpfBindingExtensions:GridExtensions.ColumnDefinitions="{Binding Keys, Converter={StaticResource keysToColumnDefinitionsConverter}}"
wpfBindingExtensions:GridExtensions.RowDefinitions="{Binding Keys, Converter={StaticResource keysToRowDefinitionsConverter}}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
public static class GridExtensions
{
// Using a DependencyProperty as the backing store for ColumnDefinitions. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnDefinitionsProperty =
DependencyProperty.RegisterAttached(
nameof(ColumnDefinitionsProperty).Substring(0, nameof(ColumnDefinitionsProperty).Length - "Property".Length),
typeof(IEnumerable<ColumnDefinition>),
typeof(GridExtensions),
new PropertyMetadata(Enumerable.Empty<ColumnDefinition>(), ColumnDefinitionsChanged));
public static IEnumerable<ColumnDefinition> GetColumnDefinitions(DependencyObject obj)
=> (IEnumerable<ColumnDefinition>)obj.GetValue(ColumnDefinitionsProperty);
public static void SetColumnDefinitions(DependencyObject obj, IEnumerable<ColumnDefinition> value)
=> obj.SetValue(ColumnDefinitionsProperty, value);
private static void ColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionCollection = ((Grid)d).ColumnDefinitions;
var newColumnDefinitions = (IEnumerable<ColumnDefinition>)e.NewValue;
var columnCount = newColumnDefinitions.Count();
columnDefinitionCollection.Clear();
foreach (var newColumnDefinition in newColumnDefinitions)
columnDefinitionCollection.Add(newColumnDefinition);
}
}