我正在尝试将WPF Grid用作ItemsControl,使用附加属性来创建可缩放的钢琴键盘。键盘中的每个键可以跨越1到3列,具体取决于前面和后面的内容,如果是锐利则会跨越1行,如果是自然则跨越2行。我已经有2个附加属性用于动态设置网格的列数和行数(尽管需要调整这些属性以支持每个列/行的宽度/高度的设置)。
我现在需要实现的是ItemsSource
(键)和ItemTemplate
(PianoKeyView)的两个可附加属性。我需要在Grid控件上使用它,因为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>
这是GridExtensions中ItemTemplate可附加属性的ItemTemplateChanged处理程序的代码,请注意不能编译的行上方的两个TODO。
private static void ItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var itemTemplate = (DataTemplate)e.NewValue;
var itemsSource = GetItemsSource(d);
var itemsSourceCount = itemsSource.Count();
var itemsOrientation = GetItemsOrientation(d);
var gridChildren = ((Grid)d).Children;
gridChildren.Clear();
switch (itemsOrientation)
{
case Orientation.Horizontal:
foreach (var item in itemsSource)
{
var itemFactory = new FrameworkElementFactory(item.GetType());
//TODO: Find out where the ContentProperty for Grid is.
itemFactory.SetValue(d.ContentProperty, item);
itemTemplate.VisualTree = itemFactory;
//TODO: Find out how to add the applied itemTemplate.
gridChildren.Add(itemTemplate);
}
break;
case Orientation.Vertical:
break;
default:
throw new EnumValueNotSupportedException(itemsOrientation, nameof(itemsOrientation).ToPascalCase());
}
}
答案 0 :(得分:0)
使用Grid
ItemsControl
ItemsPanel
Grid
可以直接使用Style
实现的目标。
事实证明,所需的缺失部分是TargetType
ContentPresenter
Grid
。在此样式中,可以通过适当的转换器设置可附加的Grid.RowSpan
属性,例如Grid.Column
,Grid.ColumnSpan
和<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>
,这些转换器接收ItemsControl和Key的DataContext,返回所需的整数。这里也可以设置按键的Z-Index,以便在自然按键上方显示清晰按键。
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PianoKeyView DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
然后将ItemTemplate大大简化为以下内容:
ItemsControl.ItemsPanel
Grid
负责生成PianoKeyView
,其ContentPresenter
中包含的这些<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid wpfBindingExtensions:GridExtensions.ColumnDefinitions="{Binding Keys, Converter={StaticResource keysToColumnDefinitionsConverter}}"
wpfBindingExtensions:GridExtensions.RowDefinitions="{Binding Keys, Converter={StaticResource keysToRowDefinitionsConverter}}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
将被放入其中。
ColumnCount
我修改了我的原始RowCount
和IEnumerable<ColumnDefinition>
可附加属性,而是根据需要选择IEnumerable<RowDefinition>
/ 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);
}
}
,以便每个列/行的大小也可以传递给属性(我在这种情况下通过转换器执行此操作,该转换器接收所有PianoKeyViewModels并为每个返回一个ColumnDefinition / RowDefinition,并且具有适当的星形大小.RowDefinitions的赋值仅适用于只有自然或尖锐键的边缘情况在键盘中需要(例如B到C,E到F或单个尖锐或自然键).RowDefinitions附加属性的代码与ColumnDefinitions属性的逻辑基本相同(仅使用RowDefinitions而不是ColumnDefinitions)所以我将在这里发布一个ColumnDefinitions:
Grid.Column
有关附加属性的更多信息,请参阅&#34;使用网格作为ItemsControl的面板&#34; (http://blog.scottlogic.com/2010/11/15/using-a-grid-as-the-panel-for-an-itemscontrol.html)这是我找到原始代码的地方,我从中派生了上面的静态类。否则,这里的关键元素是: