-2

我正在尝试使用 WPF Grid 作为 ItemsControl 使用附加属性来创建可扩展的钢琴键盘。键盘中的每个键可以跨越 1 到 3 列,具体取决于它之前和之后的内容,如果是锐利的,则跨越 1 行,如果是自然的,则跨越 2 行。我已经有 2 个附加属性用于动态设置网格的列数和行数(尽管需要调整这些属性以支持每列/行的宽度/高度的设置)。

我现在需要实现的是ItemsSource(Keys) 和ItemTemplate(PianoKeyView) 的两个可附加属性。我需要在 Grid 控件上使用它,因为ItemsControl它只支持UniformGrid作为 GridItemsPanel并且不将特定项目分配给特定的列/行。我的钢琴键盘每个八度音阶需要 17 列键,但 ItemsControl 只会在 a 中创建 12 列,UniformGrid因为只有 12 个键传递给它。我已经包含了一个 1 倍频程钢琴键盘的图像,其中包含每个所需列的索引。

PianoKeyboard 网格列索引

这是我目前的键盘代码,我缺少GridExtensions.ItemsSourceand的实现GridExtensions.ItemTemplateGridExtensions是一个包含可附加属性的静态类。

<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());
    }
}
4

1 回答 1

0

我试图Grid直接实现的目标可以通过 with ItemsControlan ItemsPanelof实现Grid

事实证明,所需的缺失部分是Style带有 aTargetType的a ContentPresenter。在这种风格中,可附加的属性,Grid如和可通过适当的转换器设置,这些转换器接受 ItemsControl 和 Key 的 DataContext 并返回所需的整数。键的 Z-Index 也可在此处设置,以便锐键出现在自然键之上。Grid.RowSpanGrid.ColumnGrid.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>

然后,这将 ItemTemplate 大大简化为以下内容:

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <local:PianoKeyView DataContext="{Binding}"/>
    </DataTemplate>
</ItemsControl.ItemTemplate>

ItemsControl.ItemsPanel负责生成这些s 包裹在其Grids中的s 将被放入其中。PianoKeyViewContentPresenter

<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <Grid wpfBindingExtensions:GridExtensions.ColumnDefinitions="{Binding Keys, Converter={StaticResource keysToColumnDefinitionsConverter}}"
              wpfBindingExtensions:GridExtensions.RowDefinitions="{Binding Keys, Converter={StaticResource keysToRowDefinitionsConverter}}"/>
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

我已经修改了我的原始属性ColumnCountRowCount可附加属性,取而代之的是适当地采用IEnumerable<ColumnDefinition>/IEnumerable<RowDefinition>以便每个列/行的大小也可以传递给属性(在这种情况下,我通过一个接收所有 PianoKeyViewModels 并返回ColumnDefinition/RowDefinition for each with proper star sizing. RowDefinitions 的分配仅适用于在键盘中只需要自然键或锐键(例如 B 到 C、E 到 F 或单个锐键或自然键)的边缘情况。 RowDefinitions 附加属性的代码本质上与 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);
    }
}

有关附加属性的更多信息,请参阅“将网格用作 ItemsControl 的面板”(http://blog.scottlogic.com/2010/11/15/using-a-grid-as-the-panel-for -an-itemscontrol.html),这是我找到从中派生上述静态类的原始代码的地方。否则,这里的关键要素是:

  1. 将可附加面板属性(例如Grid.ColumnPanel.ZOrder)分配给 中的ContentPresenter样式ItemsControl.ItemContainerStyle
  2. 将 设置ItemsControl.ItemsPanelItemsPanelTemplateof并从那里Grid设置网格ColumnDefinitions和。RowDefinitions

我没有在这里发布我所有的转换器,因为这个答案已经很长了,但是有人让我知道他们是否觉得他们与发布相关。否则,这就是最终结果......

钢琴键盘视图

于 2017-11-05T23:25:39.603 回答