If you run the sample now, the grid already works and updates the data as expected. You can change the update parameters to make them more or less frequent, scroll the grid while it updates, and so on.
However, although the updates are happening, they are hard to understand. There are just too many numbers flashing at random positions on the screen.
Custom cells provide a better user experience with flashes and sparklines.
Flashes temporarily change the cell background when the value they contain changes. If a value increases, the cell instantly turns green and then gradually fades back to white.
Sparklines are micro-charts displayed in each cell. The micro-chart shows the last five values of the cell so users can instantly identify trends (the value is going up, down, or stable).
To use custom cells, we proceed as in the previous sample. Start by creating a FinancialCellFactory class and assign an instance of that class to the grid’s CellFactory property:
C# |
Copy Code
|
---|---|
// use custom cell factory _flexFinancial.CellFactory = new FinancialCellFactory(); // custom cell factory definition public class FinancialCellFactory : CellFactory { public override void CreateCellContent( C1FlexGrid grid, Border bdr, CellRange range) { // get cell information var r = grid.Rows[range.Row]; var c = grid.Columns[range.Column]; var pi = c.PropertyInfo; // check that this is a cell we want if (r.DataItem is FinancialData && (pi.Name == "LastSale" || pi.Name == "Bid" || pi.Name == "Ask")) { // create StockTicker element and add it to the cell var ticker = new StockTicker(); bdr.Child = ticker; // bind StockTicker to the row’s FinancialData object var binding = new Binding(pi.Name); binding.Source = r.DataItem; binding.Mode = BindingMode.OneWay; ticker.SetBinding(StockTicker.ValueProperty, binding); // add some info to the StockTicker element ticker.Tag = r.DataItem; ticker.BindingSource = pi.Name; } else { // use default implementation base.CreateCellContent(grid, bdr, range); } } } |
Our custom cell factory starts by checking that the row data is of type FinancialData and that the column is bound to the LastSale, Bid, or Ask properties of the data object. If all these conditions are met, the cell factory creates a new StockTicker element and binds it to the data.
The StockTicker element displays the data to the user. It consists of a Grid element with four columns that contain the following child elements:
Element Description | Type | Name |
---|---|---|
Current value | TextBlock | _txtValue |
Last percent change | TextBlock | _txtChange |
Up/Down icon | Polygon | _arrow |
Sparkline | Polyline | _sparkLine |
These elements are defined in the StockTicker.xaml file, which we do not list here in its entirety.
The most interesting part of the StockTicker.xaml file is the definition of the Storyboard used to implement the flashing behavior. The Storyboard gradually changes the control Background from its current value to transparent:
XAML |
Copy Code
|
---|---|
<UserControl.Resources> <Storyboard x:Key="_sbFlash" > <ColorAnimation Storyboard.TargetName="_root" Storyboard.TargetProperty= "(Grid.Background).(SolidColorBrush.Color)" To="Transparent" Duration="0:0:1" /> </Storyboard> </UserControl.Resources> |
The implementation of the StockTicker control is contained in the StockTicker.cs file. The interesting parts are commented below:
C# |
Copy Code
|
---|---|
/// <summary> /// Interaction logic for StockTicker.xaml /// </summary> public partial class StockTicker : UserControl { public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(double), typeof(StockTicker), new PropertyMetadata(0.0, ValueChanged)); |
We start by defining a DependencyProperty called ValueProperty that will be used for binding the control to the underlying data value. The Value property is implemented as follows:
C# |
Copy Code
|
---|---|
public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ticker = d as StockTicker; var value = (double)e.NewValue; var oldValue = (double)e.OldValue; |
In order to implement the sparkline, the control needs access to a more than just the current and previous values. This is accomplished by storing the FinancialData object in the control’s Tag property, then calling the FinancialData.GetHistory method.
In fact, the previous value provided by the event’s OldValue property is not reliable in this case anyway. Because the grid virtualizes the cells, the StockTicker.Value property may have changed because the control was just created as its cell scrolled into view. In this case, the control had no previous value. Getting the previous values from the FinancialData object takes care of this problem also.
C# |
Copy Code
|
---|---|
// get historical data var data = ticker.Tag as FinancialData; var list = data.GetHistory(ticker.BindingSource); if (list != null && list.Count > 1) { oldValue = (double)list[list.Count - 2]; } |
Once the values are available, the control calculates the latest change as a percentage and updates the control text:
C# |
Copy Code
|
---|---|
// calculate percentage change var change = oldValue == 0 || double.IsNaN(oldValue) ? 0 : (value - oldValue) / oldValue; // update text ticker._txtValue.Text = value.ToString(ticker._format); |
The percent change is also used to update the up/down symbol and the text and flash colors. If there is no change, the up/down symbol is hidden and the text is set to the default color.
If the change is negative, the code makes the up/down symbol point down by setting its ScaleY transform to -1, and sets the color of the symbol, text, and flash animation to red.
If the change is positive, the code makes the up/down symbol point up by setting its ScaleY transform to +1, and sets the color of the symbol, text, and flash animation to green.
C# |
Copy Code
|
---|---|
// update symbol and flash color var ca = ticker._flash.Children[0] as ColorAnimation; if (change == 0) { ticker._arrow.Fill = null; ticker._txtChange.Foreground = ticker._txtValue.Foreground; } else if (change < 0) { ticker._stArrow.ScaleY = -1; ticker._txtChange.Foreground = ticker._arrow.Fill = _brNegative; ca.From = _clrNegative; } else { ticker._stArrow.ScaleY = +1; ticker._txtChange.Foreground = ticker._arrow.Fill = _brPositive; ca.From = _clrPositive; } |
Next, the code updates the sparkline by populating the Points property of the sparkline polygon with the value history array provided by the early call to the FinancialData.GetHistory method. The sparkline polygon’s Stretch property is set to Fill, so the line scales automatically to fit the space available.
C# |
Copy Code
|
---|---|
// update sparkline if (list != null) { var points = ticker._sparkLine.Points; points.Clear(); for (int x = 0; x < list.Count; x++) { points.Add(new Point(x, (double)list[x])); } } |
Finally, if the value actually changes and the control has not just been created, the code flashes the cell by calling the StoryBoard.Begin method.
C# |
Copy Code
|
---|---|
// flash new value (but not right after the control was created) if (!ticker._firstTime) { ticker._flash.Begin(); } ticker._firstTime = false; } |
That concludes the StockTicker control. If you run the sample application now and check the “Custom Cells” checkbox, you immediately see a much more informative display, with cells flashing as their values change and sparklines providing a quick indication of value trends.