I'm looking at creating a list of rectangles that specify their location by an x,y coordinate, however I'm seeing an issue with the alignment of the graphics. I'm using a list box with a custom layout panel.
Here's the XAML for the main window:
<Window x:Class="WpfFunkyPanel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfFunkyPanel"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<system:Double x:Key="barWidth">30</system:Double>
<system:Double x:Key="trackHeight">24</system:Double>
<Style x:Key="PatternGridStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="local:GridPanel.Bar" Value="{Binding Bar}"/>
<Setter Property="local:GridPanel.Track" Value="{Binding Track}"/>
</Style>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource PatternGridStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<local:GridPanel VerticalAlignment="Top" BarWidth="{StaticResource barWidth}" TrackHeight="{StaticResource trackHeight}">
<local:GridPanel.Background>
<DrawingBrush TileMode="Tile" ViewboxUnits="Absolute" ViewportUnits="Absolute">
<DrawingBrush.Viewbox>
<Rect X="0" Y="0" Width="{StaticResource barWidth}" Height="{StaticResource trackHeight}"/>
</DrawingBrush.Viewbox>
<DrawingBrush.Viewport>
<Rect X="1" Y="0" Width="{StaticResource barWidth}" Height="{StaticResource trackHeight}"/>
</DrawingBrush.Viewport>
<DrawingBrush.Drawing>
<GeometryDrawing Brush="LightGray">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="1"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathFigure IsFilled="True">
<LineSegment>
<LineSegment.Point>
<Point X="{StaticResource barWidth}" Y="0"/>
</LineSegment.Point>
</LineSegment>
<LineSegment>
<LineSegment.Point>
<Point X="{StaticResource barWidth}" Y="{StaticResource trackHeight}"/>
</LineSegment.Point>
</LineSegment>
<LineSegment>
<LineSegment.Point>
<Point X="0" Y="{StaticResource trackHeight}"/>
</LineSegment.Point>
</LineSegment>
<LineSegment/>
</PathFigure>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</local:GridPanel.Background>
</local:GridPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,1,0,1">
<Rectangle Fill="Gray" Width="28" Height="22"></Rectangle>
<TextBlock Text="{Binding Text}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Here's the grid panel:
using System;
namespace WpfFunkyPanel
{
using System.Windows;
using System.Windows.Controls;
public class GridPanel : Panel
{
public static readonly DependencyProperty BarProperty = DependencyProperty.RegisterAttached(
"Bar",
typeof(int),
typeof(GridPanel));
public static readonly DependencyProperty TrackProperty = DependencyProperty.RegisterAttached(
"Track",
typeof(int),
typeof(GridPanel));
public static readonly DependencyProperty BarWidthProperty = DependencyProperty.Register(
"BarWidth",
typeof(double),
typeof(GridPanel),
new FrameworkPropertyMetadata(
28.0,
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty TrackHeightProperty = DependencyProperty.Register(
"TrackHeight",
typeof(double),
typeof(GridPanel),
new FrameworkPropertyMetadata(
24.0,
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static int GetBar(DependencyObject obj)
{
return (int)obj.GetValue(BarProperty);
}
public static void SetBar(DependencyObject obj, int value)
{
obj.SetValue(BarProperty, value);
}
public static int GetTrack(DependencyObject obj)
{
return (int)obj.GetValue(TrackProperty);
}
public static void SetTrack(DependencyObject obj, int value)
{
obj.SetValue(TrackProperty, value);
}
public GridPanel()
{
}
protected override Size MeasureOverride(Size availableSize)
{
Size size = new Size();
foreach (UIElement child in InternalChildren)
{
int bar = GetBar(child);
int track = GetTrack(child);
double width = (bar + 1) * BarWidth;
double height = (track + 1) * TrackHeight;
size.Width = Math.Max(size.Width, width);
size.Height = Math.Max(size.Height, height);
}
return size;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in InternalChildren)
{
int bar = GetBar(child);
int track = GetTrack(child);
Rect rect = new Rect(bar * BarWidth, track * TrackHeight, BarWidth, TrackHeight);
child.Arrange(rect);
}
return finalSize;
}
public double BarWidth
{
get
{
return (double)this.GetValue(BarWidthProperty);
}
set
{
SetValue(BarWidthProperty, value);
}
}
public double TrackHeight
{
get
{
return (double)this.GetValue(TrackHeightProperty);
}
set
{
SetValue(TrackHeightProperty, value);
}
}
}
}
Here's the pattern item:
namespace WpfFunkyPanel
{
public class PatternItem
{
private readonly int bar;
private readonly int track;
private readonly string text;
public PatternItem(int bar, int track, string text)
{
this.bar = bar;
this.track = track;
this.text = text;
}
public int Bar
{
get
{
return bar;
}
}
public int Track
{
get
{
return track;
}
}
public string Text
{
get
{
return text;
}
}
}
}
Here's the collection view model:
namespace WpfFunkyPanel
{
using System.Collections.ObjectModel;
public class PatternContainer
{
private int bars;
private int tracks;
private ObservableCollection<PatternItem> items;
public PatternContainer()
{
bars = 32;
tracks = 24;
items = new ObservableCollection<PatternItem>();
}
public void Add(PatternItem item)
{
items.Add(item);
}
public int Bars
{
get
{
return bars;
}
}
public int Tracks
{
get
{
return tracks;
}
}
public ObservableCollection<PatternItem> Items
{
get
{
return items;
}
}
}
}
Finally, the main window code behind:
using System.Windows;
namespace WpfFunkyPanel
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var container = new PatternContainer();
for (int bar = 0; bar < 16; bar++)
{
container.Add(new PatternItem(bar, 0, "A" + (bar + 1).ToString()));
}
container.Add(new PatternItem(3, 2, "C4"));
container.Add(new PatternItem(5, 5, "G6"));
DataContext = container;
}
}
}
Here's what it looks like, with a rectangle selected:
The problem is the selection box in blue is offset to the left. Also, in order to get things to align, I had to set the viewport to 1,0,w,h not 0,0,w,h as I would have expected.