Using AMPS in XAML Applications

  Feb 21, 2013   |      David Noor

datagrid samples c#

Introduction

AMPS provides an ideal environment for writing Windows applications that display real-time data. The combination of content filtering and state-of-the-world (“SOW”) caching in AMPS makes it perfect as a “view server” that delivers updates to desktop applications. The AMPS Client for C# provides built-in support for XAML applications in the AMPS.Xaml library. In this article, I walk through the steps involved in configuring AMPS as a view server, and how to build a sample app, OrdView, with the AMPS.Xaml components. Sample code for OrdView is available for download here.

OrdView: An Order-Display Application

Our application, OrdView, will allow users to view order information from a trading system. After launching OrdView, users specify the orders they are interested in watching via search criteria, and OrdView queries AMPS and displays the results in a grid. OrdView updates the grid in real-time to reflect the current state of the system, because AMPS continues to send information about new and changed orders. At any time, users can enter new search criteria to observe a different set of orders.

Configuring AMPS

Before building OrdView, let’s review the features in AMPS we’ll need. AMPS is a topic based publish/subcribe server with unique features that make it ideal for building this type of application.

Content filtering enables OrdView to realize high performance over a large trading system. While the trading system processes millions of trades, no one user of OrdView wants to see the entire set. AMPS allows filters to be applied at the server for any subscription. Only messages matching the filter are sent over the network to the subscriber. Any message field can be used to filter, without any additional AMPS configuration. No special configuration is required to enable content filtering; our application will simply construct and supply an appropriate filter when it places the subscription.

State-of-the-world (“SOW”) allows AMPS to store the last values seen in a given topic. OrdView displays all of the current orders once a user specifies search criteria, and uses AMPS to retrieve the current orders in the system matching the criteria by performing a sow_and_subscribe query. This special kind of query returns all of the results stored by AMPS, and causes AMPS to begin sending updates to this result set as soon as the result set has been sent. OrdView displays the results sent, and begins applying updates to the grid immediately thereafter. AMPS guarantees that the initial results and updates are synchronized: no updates can “slip through the crack” between the initial result set and the beginning of the update stream.

Configuring SOW requires specification of what fields from messages should be used as the primary key for the SOW store. This metadata is specified in the AMPS server configuration file. For OrdView, we will use the following metadata configuration:

<TopicMetaData>
  <TopicDefinition>
    <FileName>sow/%n.sow</FileName>
    <Topic>ORDERS</Topic>
    <Key>/Key</Key>
    <Key>/Symbol</Key>
    <MessageType>nvfix</MessageType>
  </TopicDefinition>
</TopicMetaData>

For more information on configuring SOW topics, review Chapter 4, “State of the World (SOW)” in the User Guide.

OOF Processing sends out-of-focus (“OOF”) messages to OrdView as orders are removed or change in such a way as to no longer match the user’s criteria. For example, if the user filters to only show orders whose “state” tag is “PENDING”, AMPS will send an OOF message to OrdView as soon as an order is removed, or its state changes from “PENDING” to any other value. OrdView uses this opportunity to delete records from the grid.

Prerequisites

In addition to having AMPS installed, you’ll need a computer running Microsoft Windows, with Microsoft Visual Studio 2010 or 2012 installed. You’ll also need to install the AMPS C# Client, available for download at /documentation/client-apis/c-sharp/. The Client includes binaries, documentation, and reference materials for both the AMPS.Client and AMPS.Client.Xaml assemblies.

Creating OrdView

To get started, we’ll create a new, empty C# WPF application in Microsoft Visual Studio. Click “New Project…” and then choose “WPF Application” in the “Visual C#” templates:

New Project

Once we’ve created the project, Visual Studio displays the XAML editor for the generated main window of our application. Since we’ll be using the AMPS C# Client to work with AMPS, choose “Add Reference…” from the “Project” menu, navigate to the bin directory of the AMPS C# Client, and select the AMPS.Client.dll and AMPS.Xaml.dll assemblies:

Add Reference

Create AMPS XAML Objects

XAML is an XML dialect that allows object to be created and manipulated declaratively. Windows applications use XAML extensively to define user interfaces and create bindings between UI components and data. Most data models are not created with direct binding in mind, so an extra layer between the true data model and the bound UI controls is common, and is often referred to as the “view model” of the application. The AMPS C# Xaml library acts as a view model for AMPS topics, bridging the gap between the AMPS C# Client and data bound controls. You can use it directly in your applications, or integrate it into a view model scheme. In this application, we use it directly.

Once the application is created and references to the AMPS assemblies are in place, it’s time to begin working on our user interface. In OrdView, we’ll display our AMPS topic in the main window of our application, so we add references to AMPS objects into the main window’s XAML. First, change the namespace declarations on the Window element to include AMPS. In the XAML view, add attributes to the Window element to make it look like this:

<Window x:Class="OrdView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:amps="clr-namespace:AMPS.Client.Xaml;assembly=AMPS.Client.Xaml"
        Title="MainWindow" Height="350" Width="525">

The addition of the “amps” namespace prefix allows us to refer to AMPS.Client.Xaml objects from our window’s XAML. There are a number of approaches for creating resources, both in XAML directly, or by adding properties to our window and referring to them later. In OrdView, we’ll add our objects to the main window’s resources using the Resources element. Inside the Window element, create a Window.Resources element, substituting your server’s parameters for the ones given here:

<Window.Resources>
    <amps:Server x:Key="MyServer" Hostname="ampServer.local" Port="9005" MessageType="nvfix"/>
    <amps:Subscription x:Key="MySubscription" Server="{StaticResource MyServer}" Topic="ORDERS" Filter="/Key=0"/>
</Window.Resources>

This code creates two objects in our window’s resource dictionary: an AMPS.Client.Xaml.Server object with the key “MyServer”, and an AMPS.Client.Xaml.Subscription object with the key “MySubscription”.

The Server object specifies the parameters to connect to an AMPS server. In this example, we connect to a server running on port 9005 of the host “ampServer.local” and use the NVFIX message type. These are hardcoded here for simplicity; you could also construct and set these properties programmatically.

The Subscription object specifies the parameters for a subscription to an AMPS SOW topic. The Server attribute specifies an AMPS.Client.Xaml.Server to use, and here we’ve used XAML data binding to bind this attribute to the Server we created on the previous line. The Subscription subscribes to the ORDERS topic with a filter of /Key=0. Later, we’ll present the user with the ability to change the filter and update the subscription.

Binding AMPS Subscriptions to our UI

When our OrdView application starts, it will now establish a connection to AMPS and place a subscription to the ORDERS topic. The subscription is a SOW subscription, and will utilize content filtering and out-of-focus (“OOF”) messages to keep itself up-to-date. Next, we use data binding and the Subscription object to bind our subscription to a data grid.

First, let’s work on the layout of our application. To keep things simple, we’ll remove the Grid that is automatically generated for our window’s layout, and replace it with a DockPanel. We set the DockPanel’s DataContext to the subscription, as we’ll add multiple bindings to the subscription before OrdView is complete. Inside the DockPanel, we create a DataGrid bound to our Subscription’s data, with the two columns we’re most interested in. Our Window’s content looks like this:

<DockPanel DataContext="{StaticResource MySubscription}">
    <DataGrid DockPanel.Dock="Top" Name="_grid" ItemsSource="{Binding Data}" AutoGenerateColumns="False">
      <DataGridTextColumn Binding="{Binding Key}" Header="Key"/>
      <DataGridTextColumn Binding="{Binding Symbol}" Header="Symbol"/>
      <DataGridTextColumn Binding="{Binding UnitPrice}" Header="UnitPrice"/>
      <DataGridTextColumn Binding="{Binding Qty}" Header="Qty"/>
    </DataGrid>
</DockPanel>

Publish Test Data

Before running OrdView, we should find or create test data to publish into our topic. Sample data for this example can be downloaded here. Use the AMPS command-line tool, ‘spark’, for example:

./spark publish -file testdata.nvfix -server localhost:9005 -topic ORDERS

Run OrdView

That’s it! Run OrdView, and you should see a window displaying the data in the ORDERS topic:

First Run

Our OrdView application not only displays new, changed and deleted data in real time, but the grid supports operations users expect, such as sorting, column reordering, and copy-and-paste.

A Word About Columns

Unlike a traditional database, AMPS allows each message to contain different or new columns. You don’t configure AMPS with message schemas, except for defining one or more Key fields. To determine the set of columns, the AMPS.Client.Xaml.Subscription examines the fields specified on each message, and adds columns whenever a new field is seen in a message. Earlier we explicitly named the columns we want to show, but we can also utilize DataGrid’s AutoGenerateColumns attribute to specify that a new grid column will be displayed any time a new field is observed in a message. These columns will be automatically named based on the field title:

<DataGrid Name="_grid" ItemsSource="{Binding Data}" AutoGenerateColumns="True"/>

In addition to setting AutoGenerateColumns to True, we need to create an event handler so that, as new columns are added to the underlying DataTable, the grid is refreshed automatically.

First, navigate to the Window’s events in the property grid. Create a new event handler for the “Loaded” event on the window by double-clicking. Add the following code to the event handler:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ((System.Data.DataView)_grid.ItemsSource).Table.Columns.CollectionChanged
        += new System.ComponentModel.CollectionChangeEventHandler(Columns_CollectionChanged)
}

And add the following member function to the Window (referenced by our event handler, above):

void Columns_CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs e)
{
    _grid.AutoGenerateColumns = false;
    _grid.AutoGenerateColumns = true;
}

Content Filtering

Our example hardcodes a filter to start with, but we’d like to give the user the ability to change the filter. In OrdView, we accomplish this by binding a TextBox to the Filter property on our subscription, allowing users to type any filter string they choose. Keeping the UI simple, we bind a single text box to the filter: once the user changes it and clicks on another control, the Subscription’s filter is automatically changed. Here’s the new XAML for the content of the Window:

<DockPanel DataContext="{StaticResource MySubscription}">
  <TextBox DockPanel.Dock="Top" Text="{Binding Filter,UpdateSourceTrigger=PropertyChanged}"/>
  <DataGrid Name="_grid" DockPanel.Dock="Bottom" ItemsSource="{Binding Data}" AutoGenerateColumns="True"/>
</DockPanel>

WPF TextBox default behavior updates its source when it loses focus. In our app, we’ve changed the behavior to update the Filter property with every keystroke, but for a more sophisticated UI, you could easily add a button that “applies” changes to the Filter. Run the program again, and you should see a small textbox above the grid where you can manipulate the filter using AMPS filter expressions. Remember that as you make these changes, the filter is updated on the server, so that your client receives no messages except those that match the filter.

Real-Time Status

Unlike most data sources, AMPS maintains a live connection to “push” new data at OrdView. Sometimes errors occur, such as when a disconnect occurs or an invalid filter is specified, and a full-featured OrdView ought to show the status of the connection in real-time. To facilitate this, each AMPS Subscription contains a read-only Error property that may be bound to a control on the UI, so that users can observe the current health of the connection. We’ll use a StatusBar at the bottom of the window for this purpose:

<DockPanel DataContext="{StaticResource MySubscription}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <TextBox DockPanel.Dock="Top" Text="{Binding Filter,UpdateSourceTrigger=PropertyChanged}"/>
    <StatusBar DockPanel.Dock="Bottom">
        <StatusBarItem Foreground="Red">
            <Binding Path="Error"/>
        </StatusBarItem>
    </StatusBar>
    <DataGrid AutoGenerateColumns="True" Name="_grid" ItemsSource="{Binding Data}" DockPanel.Dock="Bottom"/>
</DockPanel>

This StatusBar is empty most of the time, but when errors occur, either on the connection or as a result of an invalid filter, it indicated the problem:

Bad filter

Conclusion

Here’s what OrdView looks like now that we’re finished:

ItsAlive

In this article we’ve taken a look at how to use AMPS and the AMPS.Client.Xaml classes to create an efficient application that views real-time data. Underneath the covers, we use AMPS SOW, OOF, and Content Filtering features to make sure the application only displays the data we’re interested in. In a future post, we’ll take a look under the covers of AMPS.Client.Xaml to see how it bridges the world of AMPS and Windows.


Read Next:   HPC Conference: Achieving Killer Performance with storage, networking and compute in a NUMA world