Introduction
This code sample demonstrates how to create a memory usage chart in WPF, which updates in real-time.
Background
The WPF Toolkit provides a nice charting package that is customizable and extensible. This article demonstrates how to create a modular memory monitoring control
using this chart and a dispatch timer. The control updates in real time, providing the current .NET run-time memory usage. (Note: This is the total (estimated)
amount of managed memory used by your application - it doesn't include unsafe/native code.)
Using the code
You can get the full WPF Toolkit at http://wpf.codeplex.com. In my sample, I have only included the base DLL and the DataVisualizations package.
The first step is to customize the look and feel of the chart. Create a new User Control and reference WPFToolkit and System.Windows.Controls.DataVisualization.Toolkit
.
When you paste the chart XML below, the WPF designer should automatically start displaying the chart with the default look and feel.
<UserControl x:Class="WpfMemoryChart.MemoryChart"
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:Charting="clr-namespace:System.Windows.Controls.
DataVisualization.Charting;
assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns:chartingprimitives="clr-namespace:System.Windows.Controls.
DataVisualization.Charting.Primitives;
assembly=System.Windows.Controls.DataVisualization.Toolkit"
mc:Ignorable="d" >
<Grid>
<Charting:Chart Name="MemoryChartComponent"></Charting:Chart>
</Grid>
</UserControl>
I chose to use AreaSeries
, but you can also use line series (i.e., if you want the chart to look more like the Windows Task Manager).
By default, the WPF Toolkit chart displays physical points for each datapoint in the series. You can turn this off by setting the data point style:
<Charting:AreaSeries>
<Charting:AreaSeries.DataPointStyle>
<Style TargetType="Charting:AreaDataPoint">
<Setter Property="Opacity" Value="0" />
<Setter Property="Background" Value="Navy" />
</Style>
</Charting:AreaSeries.DataPointStyle>
</Charting:AreaSeries>
<Charting:Chart.Axes>
<Charting:LinearAxis Orientation="Y" ShowGridLines="False"
Visibility="Hidden" Width="0" />
<Charting:DateTimeAxis Orientation="X" Visibility="Hidden"
Height="0"></Charting:DateTimeAxis>
</Charting:Chart.Axes>
To change colors, remove the chart legend and axis labels. I've overridden the control as follows:
<ControlTemplate TargetType="{x:Type Charting:Chart}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" Padding="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<chartingprimitives:EdgePanel Name="ChartArea"
Style="{TemplateBinding ChartAreaStyle}"
Grid.Row="0" Margin="0">
<Grid Panel.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
<Border Panel.ZIndex="10" BorderBrush="#FF919191" BorderThickness="0" />
</chartingprimitives:EdgePanel>
</Grid>
</Border>
</ControlTemplate>
Once you have the chart looking the way you like in XAML, you can create a dispatch timer to update its bound datasource. In this project, I bind the chart
to a RingBuffer<T>
implementation originally created
by Florian Reischl: http://florianreischl.blogspot.com/2010/01/generic-c-ringbuffer.html.
I modified it slightly so that it implements INotifyPropertyChanged
. This was so that it can can fire notification events to any bound WPF component.
I first create an object that represents a data point, called MemorySample
:
public class MemorySample
{
public long ByteCount { get; set; }
public DateTime Timestamp { get; set; }
}
Then we create a RingBuffer
with a capacity for 60 of these items (1 minute of memory data). We start a dispatch timer to trigger once per second:
public void InitMemoryWatch()
{
const int memorySamples = 60;
MemoryStats = new RingBuffer<MemorySample>(memorySamples);
var dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += DispatcherTimerTick;
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
dispatcherTimer.Start();
}
In the handler, I generate a new memory sample and add it to the RingBuffer
:
private void DispatcherTimerTick(object sender, EventArgs e)
{
LatestMemorySample = new MemorySample
{
ByteCount = GC.GetTotalMemory(false),
Timestamp = DateTime.Now
};
_memoryStats.Add(LatestMemorySample);
}
Finally, I create binding properties for all of these data structures:
private RingBuffer<MemorySample> _memoryStats;
public RingBuffer<MemorySample> MemoryStats
{
get { return _memoryStats; }
set
{
_memoryStats = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MemoryStats"));
}
}
}
private MemorySample _latestMemorySample;
public MemorySample LatestMemorySample
{
get { return _latestMemorySample; }
set
{
_latestMemorySample = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("LatestMemorySample"));
}
}
}
Now I bind the chart to these properties:
<Charting:AreaSeries VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Path=MemoryStats}"
IndependentValuePath="Timestamp"
DependentValuePath="ByteCount">
Warning
The present version of the WPF Toolkit charting package has a big memory leak which is addressed
in the following article: http://wpf.codeplex.com/discussions/216609.
You must get a later version after this leak is fixed in order to use the memory chart in a production application (or you can manually patch the WPF Toolkit code
yourself as the author describes).