WPF Tips for graphically displaying a sequence on/off durations
I'm working on an application where users can select between multiple sequences of on/off durations. Sequences always start with the on period and can have a varying length (but always in on/off pairs): e.g.
var sequences = new []
{
new int[] { 10, 15 }, // 10 ms on, 15 ms off
new int[] { 15, 10, 5, 10 } // 15 ms on, 10 ms off, 5 ms on, 10 ms off
};
The sequences have a maximum duration of 10 seconds and will be repeated. One special sequence defines no on/off durations: it is always on (though I might be able to change that to {1,0} or something).
Instead of displaying the numbers on screen I'd like to show a little graphical representation for the f开发者_运维百科ull 10 second duration (repeating shorter sequences) so the user can compare patterns. These will be displayed in a combo box that resizes with the window. For the examples above it would look something like the following (where X is a filled in background)
xx xx xx xx xx xx xx...
xxx x xxx x xxx x xxx x ...
I suppose I'll have to use a value converter (if only for the special value), but am uncertain what the best/easiest way of creating the graphs is, especially with the resize requirement and repeating the shorter sequences. A canvas, something else?
I'd greatly appreciate any tips!
I would follow this basic approach:
Write a value converter that takes each sequence and repeats the sequence into the full 10 seconds, encoding each chunk of time with a class that specifies whether the period is 'On' and the duration.
For each sequence, bind to the ItemsSource of an ItemsControl. For the ItemsPanel, use a StackPanel with Horizontal orientation. For the ItemTemplate, use a Rectangle or whatever other visual you'd like for a chunk of time, with the Width bound to the duration. You've also included a handy 'IsOn' property now so that you can easily visualize the On/Off state. Don't worry about scaling the Width at this point.
Place the ItemsControl in a ViewBox, which can be allowed to stretch to its parent container. Now you have a visual that provides the correct proportions of duration and scales with size.
Here's a bare-bones implementation (no error handling or any attempt to make it pretty):
UDPATE: Fixed a bug that didn't properly truncate repeating sequence at 10 seconds.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace TestWpf
{
public class SeqSegment
{
public bool IsOn { get; set; }
public int Duration { get; set; }
}
public class SeqConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var result = new List<SeqSegment>();
var seq = (int[]) value;
int time = 0;
int i = 0;
bool isOn = true;
while (time < 10000)
{
result.Add(new SeqSegment { Duration = Math.Min(seq[i], 10000 - time), IsOn = isOn });
isOn = !isOn;
time += seq[i];
i++;
if (i >= seq.Length)
i = 0;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public IEnumerable<int[]> TestSequences
{
get
{
yield return new[] {10, 5000, 10, 8};
yield return new[] {500, 5000};
yield return new[] {50, 400, 30, 10};
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
}
XAML:
<Window x:Class="TestWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestWpf="clr-namespace:TestWpf" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<TestWpf:SeqConverter x:Key="SeqConverter"/>
<DataTemplate x:Key="SeqSegTemplate">
<Rectangle x:Name="Rect" Width="{Binding Duration}" Fill="Blue"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsOn}" Value="True">
<Setter TargetName="Rect" Property="Fill" Value="Green"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="SeqTemplate">
<Viewbox Height="50" Stretch="Fill">
<ItemsControl ItemsSource="{Binding Converter={StaticResource SeqConverter}}" ItemTemplate="{StaticResource SeqSegTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Height="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding TestSequences}" ItemTemplate="{StaticResource SeqTemplate}"/>
</Grid>
</Window>
精彩评论