There doesn't seem to be much activity on this discussion board, but I'll go ahead and post my code for the Piano Roll here.
I tried to remove elements specific to other parts of my project so that the Piano Roll can be used as is, so I hope I adjusted it correctly... (Also, the C# is translated from VB. One of these days I'll try to get used to curly brackets but that time as not yet arrived for me :)
First, the XAML for the GUI:
<UserControl x:Class="PianoRoll"
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"
mc:Ignorable="d"
d:DesignHeight="133" d:DesignWidth="413" x:Name="PRoll" >
<Grid>
<Border BorderThickness="4" BorderBrush="#FF5885AA" CornerRadius="4" Background="#FFF7F0F0">
<DockPanel>
<DockPanel x:Name="TopDB" DockPanel.Dock="Top" Height="20">
<Line HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" Stroke="Black" StrokeThickness="2" X2="{Binding ElementName=TopDB, Path=ActualWidth}"></Line>
<StackPanel Width ="70">
<ComboBox x:Name="SnapToCB" Margin="0, 0, 0, 2" SelectedValuePath="Content">
<ComboBoxItem Content="Snap" IsSelected="True" Tag="No snap"/>
<ComboBoxItem Content="1/2 note" Tag="Snap to 1/2"/>
<ComboBoxItem Content="1/4 note" Tag="Snap to 1/4"/>
<ComboBoxItem Content="1/8 note" Tag="Snap to 1/8"/>
<ComboBoxItem Content="1/16 note" Tag="Snap to 1/16"/>
</ComboBox>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Background="{DynamicResource {x:Static SystemColors.ControlColor}}" Width="{x:Static SystemParameters.VerticalScrollBarWidth}"></StackPanel>
<ScrollViewer x:Name="ScaleScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
<DockPanel>
<Canvas x:Name="ScaleCanvas" Width="{Binding ElementName=NoteCanvas, Path=ActualWidth}" Background="#FFFFE6C9"></Canvas>
<Canvas x:Name="RightFillScaleCanvas"></Canvas>
</DockPanel>
</ScrollViewer>
</DockPanel>
<DockPanel x:Name="ScrollBarDB" DockPanel.Dock="Bottom" Height="15">
<Label x:Name="RepLabel" Margin="0, -3, 0, 0" FontSize="7" DockPanel.Dock="Left" Width="{Binding ElementName=PianoStackPanel, Path=ActualWidth}" Background="#FFC7D6CD"></Label>
<ScrollBar x:Name="RLScrollBar" Orientation="Horizontal" Background="#FFD4CCE6"/>
</DockPanel>
<ScrollViewer x:Name="UDScrollViewer" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<DockPanel x:Name="MainDockPanel" HorizontalAlignment="Stretch">
<DockPanel x:Name="PianoDockPanel" DockPanel.Dock="Left" Width="70" LastChildFill="False">
<DockPanel.Background>
<ImageBrush ImageSource="pack://siteoforigin:,,,/CustomControls/Resources/PianoLong.bmp"/>
</DockPanel.Background>
<Line DockPanel.Dock="Right" Y1="0" Y2="{Binding ElementName=PianoStackPanel, Path=ActualHeight}" X1="0" X2="0" Stroke="Black" StrokeThickness="1">
<Line.Fill>
<ImageBrush/>
</Line.Fill>
</Line>
<Canvas x:Name="NoteNameCanvas"></Canvas>
</DockPanel>
<ScrollViewer x:Name="RLScrollViewer" HorizontalScrollBarVisibility="Hidden" Height="{Binding ElementName=GridCanvas, Path=ActualHeight}" VerticalScrollBarVisibility="Auto" Background="Cornsilk">
<Canvas x:Name="HolderCanvas" VerticalAlignment="Top" HorizontalAlignment="Left" >
<AdornerDecorator Height="{Binding ElementName=NoteCanvas, Path=ActualHeight}" Width="{Binding ElementName=NoteCanvas, Path=ActualWidth}" HorizontalAlignment="Left" VerticalAlignment="Top" x:Name="MeasBufAdornerLayer" Visibility="Visible" >
<Grid>
<Grid.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5" >
<GradientStop Color="#00000000" Offset="0"/>
<GradientStop x:Name="MeasBuf1" Color="#00000000" Offset="1"/>
<GradientStop x:Name="MeasBuf2" Color="#917CEE6A" Offset="1"/>
<GradientStop Color="#917CEE6A" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
</Grid>
</AdornerDecorator>
<Canvas x:Name="GridCanvas" Background="Transparent" Top="0" Left="0" Height="{Binding ActualHeight, ElementName=NoteCanvas}" >
</Canvas>
<Canvas x:Name="NoteCanvas" Left="0" Top="0" Focusable="True" Height="{Binding ElementName=GridCanvas, Path=ActualHeight}" >
<Canvas.CacheMode>
<BitmapCache EnableClearType="False" RenderAtScale="1" SnapsToDevicePixels="True" />
</Canvas.CacheMode>
</Canvas>
<Canvas x:Name="RightFillNoteCanvas"></Canvas>
</Canvas>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</DockPanel>
</Border>
</Grid>
</UserControl>
This Piano Roll is associated with one track (list of MidiEvents) from a MidiEventCollection. I did this because it seems most logical (having all note from all tracks on the same Piano Roll is huge visual mess). So, you would create a new instance of PianoRoll for each track when you open a MidiFile. (However, you have to set the DetaTicks per quarter note for each instance of PianoRoll.)
Also, some extra things I've implemented for this Piano Roll is the ability to move notes with the mouse,
a snap-to feature (1/2, 1/4, 1/8, 1/16 note or measure), from a selected from a ComboBox. Or no snap to), zooming and a numbered grid.
There are three layers of Canvases, the "Holder Canvas", the size of which is used to determine whether or not the scrollbars should be visible.
Also, a "GridCanvas" and "NoteCanvas" on top of this. This setup was necessary for zooming, which is easy to do in WPF but not without caveats.
The problem with just applying a ScaleTransform to the control is that things appear ugly as you increase the scale. I think WPF just takes a visual snapshot of the control and renders a larger image of it, which means lines get thicker, etc.. It just doesn't look nice. So you have to reset the StrokeThickness of lines within your layers to the inverse (1/ScaleTransform). This is all done in the code behind which is posted below.