I am trying to use the WPF Grid as an ItemsControl using attached properties for the purposes of creating a scalable Piano Keyboard. Each key in the keyboard may span 1 to three columns depending upon what precedes and succeeds it and will span 1 row if sharp or 2 if natural. I already have 2 attached properties for setting the Grid's Column Count and Row Count dynamically (albeit these will need to be adjusted to support the setting of each column/row's width/height).
What I now need to implement are two attachable properties for the ItemsSource
(Keys) and the ItemTemplate
(PianoKeyView). I need to use this on the Grid control because ItemsControl
only supports UniformGrid
as a Grid for its ItemsPanel
and also doesn't assignment of specific items to specific columns/rows. My Piano Keyboard would require 17 columns per octave of keys but an ItemsControl would only create 12 columns in a UniformGrid
as there would only be 12 keys passed to it. I have included an image of a 1-octave Piano Keyboard with the index of each required column included.
This is my code for the keyboard as it currently stands, I am missing the implementation for GridExtensions.ItemsSource
and GridExtensions.ItemTemplate
. GridExtensions
is a static class containing attachable properties.
<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>
And this is the code for the ItemTemplateChanged handler for the ItemTemplate attachable property in GridExtensions, note the two TODOs above the lines which do not compile.
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());
}
}