This article demonstrates how to add eye-catching vector graphics to a user
interface using VG.net, a vector graphics
system integrated in Visual Studio. VG.net consists of two parts: a graphical
designer and a run-time engine. See the VG.net
download page to obtain a Lite
or evaluation version.
For this demonstration, we created a simple real-time data
simulation: a server application generates data that are sent via TCP/IP to a
client application for visualization.
This article focuses on the data visualization portion of that sample.
To run the sample:
-
Unzip the files in real_time_vis_demo.zip
to a folder.
-
Run Server.exe - it must be running before the client. The server form is
illustrated below.
-
Run Client.exe, once or many times. The client is illustrated below.
Resize the client form, and you will see the vector graphics objects
automatically scale.
- Play with the settings on the server form - try sending pulses to the client.
To run the server and client on separate machines, start Server.exe first. Then
run Client.exe from the command line, passing in the name of the server machine
as the first argument.
The source code for this sample is in the Samples folder installed by VG.net.
To edit VG.net graphics in Visual Studio, you need to download and install a
version of VG.net.
The .NET framework includes drawing classes in the System.Drawing namespace. If
you examine the classes in System.Drawing, you find many drawing functions, but
no overall system of objects that know how to draw themselves and respond to
mouse events.
VG.net provides this missing graphical system. VG.net consists of a graphical
designer and a run-time animation engine. Using the graphical designer
integrated in Visual Studio, you can create graphical components exactly
the same way you create UserControls, using the Toolbox and the Properties
window.
A Picture is a .NET class containing vector graphics elements. You
create Pictures using the VG.net Picture designer, which opens
when you double-click a Picture in the Solution Explorer.
A Picture is the top-level component in the VG.net designer,
analogous to a UserControl in the UserControl designer. As with any .NET class,
a Picture can have custom properties, methods and events.
Element is the base class for all graphical objects contained in a
Picture . Element classes include:
-
Shapes, which draw fills, edges, and text. Shape classes include Rectangle,
Ellipse, Polyline, Spline, Pie, Arc, and Path. Polyline and Path can draw
polygons.
-
Image
-
Group, created by grouping existing Elements
-
Sub Pictures
Since the Picture class inherits from Element , a Picture
can contain other Pictures as child Elements . Child
Pictures are called Sub Pictures.
You do not display a Picture directly in a Windows Form. A special
Control called a Canvas displays Pictures at
run-time.
Let us begin creating the real-time data simulation.
-
First create a new Solution in Visual Studio .NET, called "Sockets".
-
Add to the solution two Windows Forms application projects, called "Client" and
"Server".
The Server generates data in the form of four double values, each representing a
single data channel. Data are generated as follows:
-
Channel 1: normally set to 0, can be "pulsed" to 1 for brief periods at the
Server
-
Channel 2: set to a constant value selected at the Server, either 0 or 1
-
Channel 3: a sine wave between -1 and 1, with an adjustable wave period
-
Channel 4: a square wave between -1 and 1, with an adjustable wave period
The Server sends data to the Client using a TCP/IP connection. The Client
displays the current value of each data channel. To visualize the data on the
Client, we will create an LED and a gauge component.
-
Create two Windows Forms, one in each project:
-
On the Client, a Form hosting the VG.net Picture containing instances of the
LED and gauge components. The Client side Form is called "ClientForm".
-
On the Server, a Form containing the user interface controlling each data
channel.the Server side Form is called "ServerForm".
We will complete these Forms later in the article.
-
For convenience, add a sub-folder called "UI" to the Client project. This
folder will contain the LED component and the gauge component.
Let us now create the first VG.net Picture, the LED.
First, add a Picture called "LED" to the UI folder in the Client
project.
Picture Settings
To make the LED easier to construct, select the Picture called
"LED" and set the following properties:
Property |
Value |
BackColor |
black |
GridSpacing |
1 (in each dimension) |
GridSnap |
True |
GridVisible |
True |
Note
Setting the BackColor property allows to get a better idea of how
the LED will appear in the final display. Notice the BackColor property
only affects the display within the designer. A Picture object has
no background object, unless you provide one.
The Grid property settings above make it easier for you draw precisely when
zoomed in on the small LED.
LED Construction
The LED is simple. It consists of two Ellipse objects and a Pie ,
as illustrated below. The inner Ellipse represents the actual LED.
The Pie represents a shiny reflection. The outer Ellipse
represents the glow.
You can find the Ellipse and Pie Elements in the
Toolbox under the "Drawing" tab.
-
For the glow, create a first Ellipse of any size and set its
properties according to this table:
Property |
Value |
Name |
back |
Location |
(3, 3) |
Size |
(24, 24) |
DrawAction |
Fill |
We will change the fill color when we add Styles . At this point,
the Ellipse is very small. Select "To Fit" from the Zoom box on
the Drawing toolbar to zoom in to the Ellipse . Then click
"ZoomOut" to make it a little smaller.
-
For the LED itself, add another Ellipse of any size and set its
properties according to the following table:
Property |
Value |
Name |
light |
Location |
(7, 7) |
Size |
(16, 16) |
DrawAction |
Fill |
Although it is possible to change the fill of each Ellipse by
modifying the Fill property, we will use Styles instead,
so that we can change the appearance of the Ellipses at run-time
by swapping Style references.
-
Add six Styles , as illustrated below.
To do so, select the LED Picture. Open the Styles collection, found
under the "Collections" heading in the Properties window. Property settings for
the Styles are listed in this table:
Style Name |
Fill Property |
OnRed |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: Bright red: 255, 0, 0
EndColor: Light red: 255, 149, 149
FocusPoint: (.5, .66)
|
OffRed |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: Dark red: 128, 0, 0
EndColor: Darker red: 83, 0, 0
FocusPoint:(.5, .63)
|
BackRed |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: not set
StartOpacity: 0
EndColor: Bright red: 255, 0, 0
EndOpacity: .753
FocusScaling:(.65, .65)
|
OnGreen |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: Dull green: 0, 192, 0
EndColor: Bright green: 0, 255, 0
FocusPoint:(.5, .66)
|
OffGreen |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: Dark green: 0, 128, 0
EndColor: Darker green: 0, 85, 0
FocusPoint:(.5, .63)
|
BackGreen |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: not set
StartOpacity: 0
EndColor: Bright green: 0, 255, 0
EndOpacity: .702
FocusScaling:(.64, .64)
|
Once you create the Styles , you can assign them to the Style
property of each Ellipse .
-
Set the Style of the Ellipse called "back" to
BackRed, and the Style of the Ellipse called "light"
to OffRed. In the LED we created, we used path gradients to give the LED a
glowing 3D effect.
-
To enhance the 3D effect, add a Pie representing the shine of an overhead light.
It will be filled by a path gradient in the shape of a small ellipse toward the
top of the LED. First add an Pie the same location and size as the "back"
Ellipse, and set the properties of the Pie as follows:
Property |
Value |
Name |
shine |
Location |
(7, 7) |
Size |
(16, 16) |
StartAngle |
180 |
SweepAngle |
180 |
DrawAction |
Fill |
Fill |
Fill Type: PathGradientFill
GradientType: TwoColorBell
StartColor: not set
StartOpacity: 0
EndColor: White
EndOpacity: .725
PathType: EnclosedEllipse
FocusPoint: (.5, .18)
Bounds: (.25, 0, .5, .4)
WrapMode: Clamp
|
-
To make the LED easier to position and size, add a Rectangle and
set its properties according to the following table:
Property |
Value |
Name |
enclosingRectangle |
Location |
(0, 0) |
Size |
(30, 30) |
DrawAction |
Edge |
Stroke Opacity |
0 (transparent) |
The Rectangle defines the bounds of the LED, without being
visible.
This LED is limited to two colors: red or green. To enable the user to select a
LED color, we add an enum called LEDColor . Inside the LED
class, we add a field called "color" of type LEDColor , and a
property modifying that field:
public LEDColor Color
{
get { return color; }
set
{
if (color == value)
return;
color = value;
UpdateStyles();
}
}
The UpdateStyles method handles the actual updating of the Style
properties according to the color field value. UpdateStyles makes
use of a Boolean field added to LED called on . This field
specifies if the LED is on or off. The LED state is set using the On
property:
private void UpdateStyles()
{
if (color == LEDColor.Red)
{
back.Style = "BackRed";
if (on)
light.Style = "OnRed";
else
light.Style = "OffRed";
}
if (color == LEDColor.Green)
{
back.Style = "BackGreen";
if (on)
light.Style = "OnGreen";
else
light.Style = "OffGreen";
}
back.Visible = on;
}
public bool On
{
get { return on; }
set
{
if (on == value)
return;
on = value;
UpdateStyles();
}
}
First add a Picture called "HorizontalGauge" to the UI folder in
the Client project.
Picture Settings
To simplify the gauge construction, select the Picture "HorizontalGauge"
and set the following properties:
Property |
Value |
BackColor |
black |
GridSpacing |
2 (in each dimension) |
GridSnap |
True |
GridVisible |
True |
Gauge Construction
The gauge, illustrated below, consists of a border rectangle and an inner Group
of objects, containing three overlapping rectangles exactly the same size. The
width of the inner Group will be changed when a data value changes.
-
First, create the border. Add a rounded Rectangle of any size and set its
properties according to this table:
Property |
Value |
Name |
border |
CornerRadius |
7 |
Location |
(0, 0) |
Size |
(104, 24) |
Stroke |
Color = dark blue,
Width= 2
|
DrawAction |
Edge |
The following picture illustrates the Stroke settings within the Stroke dialog.
Because the "border" Rectangle is small, select "To Fit" from the Drawing
toolbar to zoom in closely.
-
Next, create the background of the value bar. Add another rounded Rectangle and
set its properties according to this table:
Property |
Value |
Name |
back |
CornerRadius |
5 |
Location |
(4, 4) |
Size |
(96, 16) |
Fill |
Solid, with Color = bright blue |
DrawAction |
Fill |
The "back" Rectangle should resemble the picture below.
-
Over this, we add another Rectangle of the same dimensions, with the same corner
radius. Select the "back" Rectangle and perform Copy followed by a Paste
operation. You now have a new Rectangle identical to the old. Make the
following changes to the properties:
Property |
Value |
Name |
shine |
Fill |
LinearGradientFill:
GradientType: TwoColorBell
StartColor: (not set)
StartOpacity: 0
EndColor: white
EndOpacity: .412
Angle: 90
|
The "shine" Rectangle over the "back" Rectangle should resemble the picture
below.
-
Now add another Rectangle of the same dimensions, with the same corner radius.
Select the "shine" Rectangle and perform Copy followed by a Paste. Make the
following changes to the properties of the new Rectangle:
Property |
Value |
Name |
ends |
Fill |
LinearGradientFill:
GradientType: TwoColorBell
StartColor: very dark blue
StartOpacity: .502
EndColor: (not set)
EndOpacity: 0
Angle: 0
|
The "ends" Rectangle over the other Rectangles should resemble the picture
below. Together, these objects create a glowing effect.
-
Now select all three of the interior Rectangles, "back", "shine", and "ends",
and click the Group command on the Drawing toolbar. The easiest way to select
all three Rectangles is to lasso them by dragging the mouse. Once you have
selected the Rectangles, create a Group by clicking on the Group icon in the
Drawing toolbar. Name the Group "valueBar". The Group enables you to change the
width of all three Rectangles in one operation at run-time.
A reusable graphical component is more flexible if it has properties that change
appearance. We will add properties to set the "border" Rectangle color, the
"back" Rectangle color, and one of the colors used in the "ends" Rectangle. We
do not expose the "shine" colors, since they are an intrinsic part of the
glowing appearance, and will work well with any other colors.
The BorderColor property, and associated methods are as follow:
public Color BorderColor
{
get { return BorderFill.Color; }
set { BorderFill.Color = value; }
}
private bool ShouldSerializeBorderColor()
{
return BorderColor != defaultBorderColor;
}
private void ResetBorderColor()
{
BorderColor = defaultBorderColor;
}
private SolidFill BorderFill
{
get { return (SolidFill)Styles["Border"].Stroke.Fill; }
}
The BorderColor property makes use of a private property, BorderFill .
The BorderFill property is merely a convenience, enclosing three
operations: getting the Stroke object from the "border" Rectangle,
getting the Fill object from the Stroke , and casting
the Fill to SolidFill. The BorderColor property
simply gets or sets the Color property of BorderFill .
The ShouldSerializeBorderColor and ResetBorderColor methods
provide a better designer experience. You can read more about ShouldSerialize
and Reset methods in the topic titled "ShouldSerialize and Reset Methods" in
the .NET Framework Developer's Guide.
The ShouldSerializeBorderColor method prevents the BorderColor
property from being serialized unless it is set to a non-default value. The ResetBorderColor
method allows the user to reset the BorderColor property to a
default value by right-clicking in the Properties window. Both of these methods
make use of the static field defaultBorderColor , which is
initiliazed to a default value in the HorizontalGauge constructor.
The BackColor and EndColor properties in the
HorizontalGauge allow the user to change the color of the "back" Rectangle, and
the non-transparent color of the "ends" Rectangle. These properties are
implemented in the same manner as the BorderColor property.
In addition to appearance properties, the HorizontalGauge has properties for
displaying a data value:
-
Value: the current data value, stored in the field called "current"
-
Minimum: the minimum value of the
Value property, stored in the
"minimum" field
-
Maximum: the maximum value of the
Value property, stored in the
"maximum" field
The implementation of these properties is as follows:
public float Minimum
{
get { return minimum; }
set
{
if (minimum == value)
return;
minimum = value;
UpdateWidth();
}
}
public float Maximum
{
get { return maximum; }
set
{
if (maximum == value)
return;
maximum = value;
UpdateWidth();
}
}
public float Value
{
get { return current; }
set
{
if (current == value)
return;
current = value;
UpdateWidth();
}
}
private void UpdateWidth()
{
// update the width of the valueBar to reflect the current value
float range = maximum - minimum;
if (range == 0)
return;
float value = current;
if (value > maximum)
value = maximum;
if (value < minimum)
value = minimum;
float width = ((value - minimum) / range) * maxBarWidth;
if (width < 1)
width = 1;
valueBar.Width = width;
}
The bulk of the work is done in the method UpdateWidth , which
scales the Width property of the "valueBar" group in proportion to
the "current" field.
The DataDisplay Picture will have some LED components, some HorizontalGauge
components, and some transparent rectangles containing text labels.
-
First compile your project, to make the LED and HorizontalGauge components
available.
-
Add a Picture called "DataDisplay" to the Client project.
-
To simplify construction, select the Picture and set the following properties:
Property |
Value |
BackColor |
black |
GridSnap |
True |
GridVisible |
True |
-
Now create two LED objects called "led1" and "led2". Make the first LED green
by changing the LEDColor property.
-
Create two HorizontalGauge objects called "gauge3" and "gauge4". Change the
three custom color properties on "gauge4", so it can be distinguished from
"gauge3". These properties are BorderColor , BackColor ,
and EdgeColor . Pick any colors you like.
-
Finally, add some labels. The labels are actually Rectangles, with DrawAction
set to Fill. To make the interior transparent, and the text white, create a Style
called "Label". In the "Label" style , modify the Fill property to
be completely transparent (Opacity 0), and modify the TextAppearance property
use white as the text color. Then set the Style property of each
label rectangle to Label.
-
The DataDisplay Picture needs to route incoming data to the LED and
HorizontalGauge components. Add four properties to DataDisplay, one for each
data channel, as shown below:
public double Value1
{
set { led1.On = value > 0; }
}
public double Value2
{
set { led2.On = value > 0; }
}
public double Value3
{
set { gauge3.Value = (float)value; }
}
public double Value4
{
set { gauge4.Value = (float)value; }
}
The final DataDisplay Picture should resemble the picture below.
-
First, compile the Client project to make the DataDisplay Picture available.
-
Modify the Form named "ClientForm" in the Client project. In the sample code,
ClientForm is tall and narrow, but you may use any size that approximates your
DataDisplay Picture aspect ratio.
-
While the Form is open in the Forms designer, open the Drawing tab of the
Toolbox. In there you will find the Canvas class. Drop a Canvas onto the Form,
and set its properties as follows:
Property |
Value |
Name |
canvas |
Dock |
Fill |
BackGround |
Black |
AutoSizePicture |
True |
AutoSizePadding |
10,10 |
-
The DataDisplay Picture should be visible as an item in the Drawing section of
the Toolbox. Drag this item onto your Form, and a DataDisplay object will
appear in the Component Tray below the Form. Rename this object to
"dataDisplay".
-
Now select "canvas" and change the Picture property to "dataDisplay". Your
Picture will appear in the Canvas, as below. The AutoSizePicture property
causes the Picture to scale and center itself within the Canvas.
AutoSizePadding determines the x and y padding used between the bounds of the
Picture and the edges of the Canvas.
In the interest of space, we will not describe the construction of the Server
Form, illustrated at the beginning of the article. The Server Form contains a
simple collection of standard Windows Forms Controls. Please examine the sample
code for details.
In the Server application, data is generated by the SignalGenerator class. This
class uses a Timer to trigger data generation at 30 millisecond intervals. All
data is computed in the function UpdateData. Sine and square wave values are
based on elapsed time from the beginning of the simulation.
SignalGenerator listens for client connection requests with a TcpListener
object. When a connection is pending, SignalGenerator accepts the request and
passes the corresponding Socket object to a new ClientWriter object. Each
ClientWriter object created sends data to a single client.
In the Client application, the ClientReader class is responsible for connecting
to a server and receiving data. It uses the TcpClient class from the .NET
Framework. When data is received, the ClientReader raises an event and invokes
the DataReceived method in the ClientForm. DataReceived is as follows:
private void DataReceived(double value1, double value2,
double value3, double value4)
{
dataDisplay.Value1 = value1;
dataDisplay.Value2 = value2;
dataDisplay.Value3 = value3;
dataDisplay.Value4 = value4;
}
The DataReceived method modifies properties in the DataDisplay class, and VG.net
updates the screen on the next display pass.
This article focused on real-time data visualization using VG.net. This
vector graphics system has many other uses. Because each graphical object
supports mouse events, just like a Control object, you can create user
interface components such as buttons and sliders using VG.net. The VG.net
installation folder contains samples of user interface objects. You can find
more tips about VG.net at www.vgdotnet.com
and the VG.net development blog.
|