如何为ItemTemplate和ItemsSource实现可附加属性

时间:2017-11-03 13:57:02

标签: c# wpf datatemplate attached-properties wpfgrid

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

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

PianoKeyboard Grid Column Indices

这是我目前的键盘代码,我错过了GridExtensions.ItemsSourceGridExtensions.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());
    }
}

1 个答案:

答案 0 :(得分:0)

使用Grid ItemsControl ItemsPanel Grid可以直接使用Style实现的目标。

事实证明,所需的缺失部分是TargetType ContentPresenter Grid。在此样式中,可以通过适当的转换器设置可附加的Grid.RowSpan属性,例如Grid.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> ,这些转换器接收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

我修改了我的原始RowCountIEnumerable<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)这是我找到原始代码的地方,我从中派生了上面的静态类。否则,这里的关键元素是:

  1. 将可附加的面板属性(例如Panel.ZOrderContentPresenter)分配给ItemsControl.ItemContainerStyle中的ItemsControl.ItemsPanel样式。
  2. ItemsPanelTemplate设置为Grid ColumnDefinitions,并从那里设置网格RowDefinitionsORDER BY
  3. 我没有在这里发布我的所有转换器,因为这个答案已经很长,但有人告诉我他们是否认为他们与发布相关。否则,这是最终结果......

    PianoKeyboardView