Tuesday, February 14, 2017

Using the NavigationService

This entry targets Framework 4.0

My purchasing application has a main page that contains a frame. Users can navigate pages in that frame. I provide a back button that allows them to navigate back. I want to put a tooltip on that button that lets them know what they are going to navigate back to. This is more difficult than I had expected.

I solved the problem by populating and reading the Name property of the Navigation.JournalEntry. The Name property is automatically populated by the Framework as you navigate according to the following rules.

  • The attached Name attribute.
  • Title.
  • WindowTitle and the uniform resource identifier (URI) for the current page
  • The uniform resource identifier (URI) for the current page
You can populate the Name or Title attribute in XAML like this...
<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    JournalEntry.Name="JournalEntry Name"
    Title="Title"
    >
  <!--Page Content-->
</Page>

But if you want to make the tooltip dynamic it is easiest to bind to a property. Lets walk through the process of doing this. We will create a project in Visual Studio 2015 that has a main page, an "EnterName" page where the user enters their name and navigates to a second page called "Hello". The second page will show a back button with a tool tip that shows the title from the prior page.

I will use a modified version of MVVM to build the project.

Start a new WPF application in Visual Basic (File -> New -> Project) and call it BackButtonToolTip.

We will have a MainWindow.xaml and xaml.vb. Replace MainWindow.xaml with this XAML which defines a Title textblock for the application, a back button, and a frame.
<Window x:Class="MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 mc:Ignorable="d"
 Height="350" Width="525"
 DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.CommandBindings>
        <CommandBinding Command="PreviousPage" CanExecute="PreviousPage_CanExecute" Executed="PreviousPage_Executed"></CommandBinding>
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <TextBlock Margin="10,0,10,0" FontSize="20" VerticalAlignment="Center" Text="{Binding MyTitle}"/>
            <Button Name="PreviousButton" Command="PreviousPage" VerticalAlignment="Center" Cursor="Hand" ToolTip="{Binding Path=PreviousButtonToolTip}">
                <Button.Template>
                    <ControlTemplate>
                        <TextBlock FontFamily="Webdings" Text="3" FontSize="24">
                            <TextBlock.RenderTransform>
                                <ScaleTransform ScaleX="1.5"/>
                            </TextBlock.RenderTransform>
                        </TextBlock>
                    </ControlTemplate>
                </Button.Template>
                <Button.Style>
                    <Style TargetType="Button">
                        <Setter Property="Visibility" Value="Visible"></Setter>
                        <Style.Triggers>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Visibility" Value="Collapsed"></Setter>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
        </StackPanel>
        <Frame Name="PageFrame" Grid.Row="1" NavigationUIVisibility="Hidden"></Frame>
    </Grid>
</Window>

Now we need to put a couple of dummy event handlers in our code behind to make it look like this...
Class MainWindow
    Private Sub PreviousPage_CanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
    End Sub

    Private Sub PreviousPage_Executed(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
    End Sub
End Class

When we run the application we see nothing, which is what we expect at this point. Let's add our first child page. Right-click on the project name in the solution explorer and select "Add Page". Then call the new page "EnterName".

Now we have an EnterName we can reference it in the Frame tag of the MainWindow. Change it to look like this...

<Frame Name="PageFrame" Grid.Row="1" NavigationUIVisibility="Hidden" Source="EnterName.xaml"></Frame>

The EnterName page will have a prompt, a textbox, and a navigation button. I altered the background so you can see the navigation more clearly. Note the use of "KeepAlive". You need this to persist your page's properties when you navigate back to it. When the project is complete, check out the effect of setting this false. Note the line Title="{Binding PageTitle}". By modifying the PageTitle property we can control the Name property of the NavigationJournalEntry object that defines this page in the Frame's BackStack, because it will be populated from the Title property of this page. Don't worry, it all becomes clearer later.

Here is your XAML for EnterName.xaml...
<Page x:Class="EnterName"
      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:local="clr-Namespace:BackButtonToolTip"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      DataContext="{Binding RelativeSource={RelativeSource self}}"
      Background="AliceBlue"
      KeepAlive="true"
      Title="{Binding PageTitle}">
    <Page.CommandBindings>
        <CommandBinding Command="NextPage" CanExecute="NextPage_CanExecute" Executed="NextPage_Executed"></CommandBinding>
    </Page.CommandBindings>
    <Grid Height="30">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"></ColumnDefinition>
            <ColumnDefinition Width="80"></ColumnDefinition>
            <ColumnDefinition Width="auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Text="Enter Name:" FontSize="18"/>
        <TextBox Grid.Column="1" Text="{Binding UsersName, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        <Button Grid.Column="2" Command="NextPage" Content="Navigate"/>

    </Grid>
</Page>

We need to create two more dummy event handlers in EnterName.xaml.vb.
Class EnterName
    Private Sub NextPage_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
    End Sub

    Private Sub NextPage_Executed(sender As Object, e As ExecutedRoutedEventArgs)
    End Sub
End Class

Now when we run the application we can see something. Not much, but something.

Now we can build the backing store for the textbox, add the INotifyPropertyChanged code, and wire up the [Navigate] button. Change the EnterName.xaml.vb to look like this...

Imports System.ComponentModel
Class EnterName
    Implements INotifyPropertyChanged

    Private _UsersName As String = ""

    Public Property UsersName As String
        Get
            Return _UsersName
        End Get
        Set(value As String)
            If _UsersName <> value Then
                _UsersName = value
                NotifyPropertyChanged("UsersName")
                PageTitle = "Back to 'Enter Users Name (" & _UsersName & ")'"
            End If
        End Set
    End Property

    Private _PageTitle As String = ""

    Public Property PageTitle As String
        Get
            Return _PageTitle
        End Get
        Set(value As String)
            If _PageTitle <> value Then
                _PageTitle = value
                NotifyPropertyChanged("PageTitle")
            End If
        End Set
    End Property

    Private Sub NextPage_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
        e.CanExecute = (UsersName.Length > 0)
    End Sub

    Private Sub NextPage_Executed(sender As Object, e As ExecutedRoutedEventArgs)
        Dim Frame As Frame = DirectCast(Application.Current.MainWindow.FindName("PageFrame"), Frame)
        Frame.Navigate(New Hello(UsersName))
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private Sub NotifyPropertyChanged(PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub

    Private Sub EnterName_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        DirectCast(Application.Current.MainWindow, MainWindow).MyTitle = "Get Name"
    End Sub
End Class

The NextPage_CanExecute event handler has been modified to only allow navigation if the user has entered something in the UsersName textbox. 

The NextPage_Executed event handler has been modified to navigate to a different page. The act of navigation puts the current page on the Frame's BackStack where it can be interrogated later.

We add the standard get/set handlers for our two properties but we also modify the PageTitle property whenever the user modifies the UsersName property.

At this point we have some errors because we have not yet defined our Hello page or the MyTitle property on the MainWindow. Don't panic.

The EnterName_Loaded event handler has been added. It simply reaches to the application's main window and updates a property. We need to enhance MainPage to define and use that property. Modify MainWindow.xaml.vb to look like this...

Imports System.ComponentModel

Class MainWindow
    Implements INotifyPropertyChanged

    Private _MyTitle As String = ""
    Public Property MyTitle As String
        Get
            Return _MyTitle
        End Get
        Set(value As String)
            If _MyTitle <> value Then
                _MyTitle = value
                NotifyPropertyChanged("MyTitle")
            End If
        End Set
    End Property

    Private _PreviousButtonToolTip As String = ""
    Public Property PreviousButtonToolTip As String
        Get
            Return _PreviousButtonToolTip
        End Get
        Set(value As String)
            If _PreviousButtonToolTip <> value Then
                _PreviousButtonToolTip = value
                NotifyPropertyChanged("PreviousButtonToolTip")
            End If
        End Set
    End Property

    Private Sub PreviousPage_CanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
    End Sub

    Private Sub PreviousPage_Executed(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private Sub NotifyPropertyChanged(PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub
End Class

Before we can continue we need to write the Hello page that we will navigate to (you need at least two pages to demonstrate navigation).

Add another page and call it "Hello". It will display the user name entered in the EnterName page. The XAML looks like this...
<Page x:Class="Hello"
      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:local="clr-namespace:BackButtonToolTip"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      DataContext="{Binding RelativeSource={RelativeSource self}}"
      Background="AntiqueWhite">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Hello "/>
        <TextBlock Text="{Binding UsersName}"></TextBlock>
    </StackPanel>
</Page>

The code behind is fairly trivial too...
Imports System.ComponentModel
Class Hello
    Implements INotifyPropertyChanged

    Private _UsersName As String = ""
    Public Property UsersName As String
        Get
            Return _UsersName
        End Get
        Set(value As String)
            If _UsersName <> value Then
                _UsersName = value
                NotifyPropertyChanged("UsersName")
            End If
        End Set
    End Property

    Public Sub New(UsersName As String)
        InitializeComponent()
        Me.UsersName = UsersName
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private Sub NotifyPropertyChanged(PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub

    Private Sub Hello_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        DirectCast(Application.Current.MainWindow, MainWindow).MyTitle = "Hello"
    End Sub
End Class

Note the Hello_Loaded event handler reaches through to the parent page and updates the title property to "Hello". As this is something every page in our application would do, I would refactor it into a function, but I'll let you do that. The Hello page also has a different background color.

If we run the application now we will be able to enter a name and navigate to the "Hello" page.



The back button is still not visible because it's visibility is bound to its enabled state and we haven't told it when can be executed. Let's do that now. Modify the PreviousPage event handlers in MainWindow to look like this...

    Private Sub PreviousPage_CanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
        If PageFrame IsNot Nothing AndAlso PageFrame.NavigationService.CanGoBack() Then
            e.CanExecute = True
            PreviousButtonToolTip = PageFrame.BackStack.Cast(Of Navigation.JournalEntry).FirstOrDefault.Name
        End If
    End Sub

    Private Sub PreviousPage_Executed(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
        If PageFrame.NavigationService.CanGoBack Then
            PageFrame.NavigationService.GoBack()
        End If
    End Sub

What we did here is to enable the back button (and consequently make it visible) whenever the frame has a page to go back to. We also find the first element of the BackStack and pull the name from it to assign to the tooltip. When the back button is clicked we execute GoBack.

Now we can see the entire functionality of the solution.




Thursday, February 2, 2017

Binding to an indexed property in VB

This is for framework 4.0

We know that we can have indexed properties but how do we bind to them and handle INotifyPropertyChanged? In this post, we will explore binding labels, textboxes, and datagridtextcolumns.

Start a new Visual Basic WPF Application and call it BindingToIndexedProperty.


We will create two labels, two textboxes, and a datagrid with two columns. The labels, textboxes and column headings will be dynamically populated as the application starts. There will also be a button that allows them to be changed to show INotifyPropertyChanged working. We will use an MVVM type model but simplified to use the window's class instead of a separate class.

Let's start with the XAML for the labels and textboxes. Replace the contents of MainWindow.xaml with the XAML below.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BindingToIndexedProperty"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" Grid.Row="0" Content="{Binding Text[0]}"></Label>
        <Label Grid.Column="0" Grid.Row="1" Content="{Binding Text[1]}"></Label>
        <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Text[0]}"></TextBox>
        <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Text[1]}"></TextBox>
    </Grid>
</Window>

We see in this XAML that the labels and textboxes are bound to the same indexed property. Note the use of square brackets in the paths.

In MainWindow.xaml.vb add the private and public implementations of the Text property inside the class.

Class MainWindow

    Private _Text(2) As String
    Public Property Text(index) As String
        Get
            Return _Text(index)
        End Get
        Set(value As String)
            If _Text(index) <> value Then
                _Text(index) = value
            End If
        End Set
    End Property

End Class

We can run the application and everything will bind but it won't be very interesting. We can initialize the text in the New method like this.

Public Sub New()
    PreInitialize()
    InitializeComponent()
End Sub

Private Sub PreInitialize()
    Text(0) = "Hello"
    Text(1) = "Goodbye"

End Sub

Running the application now shows the binding to the indexed property has been successful.


Now let's look at implementing INotifyPropertyChanged. Add a button to the XAML with a RoutedCommand. Here is definition of the Routed Command which goes before the opening tag of the Grid.

<Window.Resources>
    <RoutedCommand x:Key="PostInitialize"></RoutedCommand>
</Window.Resources>
<Window.CommandBindings>
    <CommandBinding Command="{StaticResource PostInitialize}" Executed="PostInitialize_Executed"/>
</Window.CommandBindings>

And the button is inserted just before the closing tag for the Grid and looks like this.

<Button Grid.Column="0" Grid.Row="3" Content="Post Initialize" Command="{StaticResource PostInitialize}"/>

We need to add the PostInitialize function to our code like this...

Private Sub PostInitialize_Executed(sender As Object, e As ExecutedRoutedEventArgs)
    Text(0) = "Bonjour"
    Text(1) = "Au Revior"
End Sub

None of this will work until we implement INotifyPropertyChanged. Start by adding an Imports statement...

Imports System.ComponentModel

and an implements clause to the class definition so that it looks like this...

Class MainWindow
    Implements INotifyPropertyChanged

Now that we have said we implement INotifyPropertyChanged we have to add the PropertyChanged event and a method to raise it. We've all seen this code before, I'm sure.

Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(Name As String)
    RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(Name))
End Sub

and finally we call NotifyPropertyChanged from the setter of the Text Property. Note we call this function with "Text". Not "Text(0)" or "Text[0]". The setter now looks like this.

Set(value As String)
    If _Text(index) <> value Then
        _Text(index) = value
        NotifyPropertyChanged("Text")
    End If
End Set

Now when we click the [Post Initialize] button the text changes which demonstrates that INotifyPropertyChanged is working.


Now let's see how we could bind the headings of a datagrid to an indexed property. Binding anything other that the Binding property in a DataGridColumn is difficult because they do not exist in the Visual Tree so they don't have a DataContext. Let's start by defining our DataGrid and it's two columns.

Add the following XAML after the <Button... > element

<DataGrid Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" IsReadOnly="true" AutoGenerateColumns="false">
    <DataGrid.Columns>

        <DataGridTextColumn Header="{Binding Text[0]}" Width="*"></DataGridTextColumn>
        <DataGridTextColumn Header="{Binding Text[1]}" Width="*"></DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

If DataGridColumns were in the Visual Tree this would work. But they're not so it doesn't. Give it a try if you don't believe me. There's an old trick using a Proxy Element that is commonly used to overcome this enormous flaw in WPF.

We start by adding the proxy element before the DataGrid like this. It will inherit it's DataContext from the first parent that has a DataContext defined which happens to be the window.

<FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"></FrameworkElement>

In the DataGridColumn's binding we can reference the proxy element using the x:Reference form even though it's not in the same visual tree as the DataGridColumn. Bear in mind that our source is the proxy element so our path is DataContext.<PropertyName>[index]. Our DataGridColumns now looks like this.

<DataGridTextColumn Header="{Binding DataContext.Text[0], Source={x:Reference ProxyElement}}" Width="*"></DataGridTextColumn>
<DataGridTextColumn Header="{Binding DataContext.Text[1], Source={x:Reference ProxyElement}}" Width="*"></DataGridTextColumn>

At this point the editor may highlight the Source clause with an error. However the compiler will not generate an error. This bug has been fixed in Framework 4.5

Note you can now use the same proxy element to bind any dependency property of any DataGridColumn on the page.


Friday, January 6, 2017

Simple Charting

This post refers to Framework 4.0.

I recently decided to write a WPF page that visualizes user logons as both a table and a chart. I could have used some third party charting controls but decided to write my own because I wanted to find out how to do it.

In this example I will generate a simple line chart based on a hard coded tuple of x,y coordinates. The top half of the page will display the chart and the lower half will display the data in a table. There will be a grid splitter that can adjust the height of the two halves of the page and the chart will resize itself when the splitter is adjusted or the page is resized.

The chart is generated by writing lines and text on a canvas. I have broken the rules of MVVM by referencing the canvas control explicitly in background code.

Start a new WPF application and call it LineChart.

Here is the initial XAML that needs to replace the default XAML in MainWindow. If you run it you will see the basic layout of the page.

<Window x:Class="LineChart.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:LineChart"
        mc:Ignorable="d"
        Title="Line Chart" Height="500" Width="500"
        DataContext="{Binding RelativeSource={RelativeSource self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="8"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Canvas Name="ChartCanvas" Grid.Row="0" Background="AliceBlue" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"></Canvas>
        <GridSplitter Grid.Row="1" Width="auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter>
        <DataGrid Grid.Row="2" AutoGenerateColumns="true" IsReadOnly="true"></DataGrid>
    </Grid>
</Window>

Let's start by making the gridsplitter look a little nicer. I added a small "gripper" image to the application and called it HorizontalGripper. It looks like this...


Add a colored rectangle to grid row 1 and change the GridSplitter to look like this...

        <Rectangle Grid.Row="1" Fill="SteelBlue"></Rectangle>
        <GridSplitter Grid.Row="1" Width="auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <GridSplitter.Background>
                <ImageBrush Stretch="None" ImageSource="HorizontalGripper.png"/>
            </GridSplitter.Background>
        </GridSplitter>


Now display the page again and see how the grid splitter stands out.


Now it's time to define our data points and populate the table.

Replace the code behind so that MainWindow.xaml.cs looks like this...

using System.Collections.Generic;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Controls;
using System.Linq;

namespace LineChart
{
    public partial class MainWindow : Window
    {
        public Point[] Points
        {
            get
            {
                return new Point[] { new Point(0,44), new Point (1,10), new Point(2,20), new Point(3,13), new Point(4,44), new Point(5,5), new Point(6,61), new Point(7,16), new Point(8,23), new Point(9,81), new Point(10,55) };
            }
        }

        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Now we can bind the datagrid to our data points by adding an ItemsSource like this...
<DataGrid Grid.Row="2" AutoGenerateColumns="true" IsReadOnly="true" ItemsSource="{Binding Points}"></DataGrid>

And now our table is populated. Yay table! Now for the real reason we came here -- the chart.


We need to scale the chart to fit in the canvas whenever the canvas size changes. There is a SizeChanged event on the Canvas control so we need to create and register a handler for it. Here is the code to register the handler.

public MainWindow()
{
    InitializeComponent();
    ChartCanvas.SizeChanged += ChartCanvas_SizeChanged;
}

The definition of the SizeChanged event handler starts like this.

void ChartCanvas_SizeChanged(object sender, RoutedEventArgs e) 
{
     double offsetLeft = 50;     // Distance from left of canvas to Y axis and right of canvas to right of X axis
     double offsetBottom = 50;   // Distance from bottom of canvas to X axis and top of canvas to top of Y axis
     SolidColorBrush AxisBrush = new SolidColorBrush(Colors.DodgerBlue);
     SolidColorBrush LineBrush = new SolidColorBrush(Colors.IndianRed);
     double canvasHeight = ChartCanvas.ActualHeight;
     double canvasWidth = ChartCanvas.ActualWidth;

     ChartCanvas.Children.Clear();

     Line XAxis = new Line();
     XAxis.X1 = offsetLeft;
     XAxis.Y1 = canvasHeight - offsetBottom;
     XAxis.X2 = canvasWidth - offsetLeft;
     XAxis.Y2 = canvasHeight - offsetBottom;
     XAxis.Stroke = AxisBrush;
     XAxis.StrokeThickness = 2;
     ChartCanvas.Children.Add(XAxis);

     Line YAxis = new Line();
     YAxis.X1 = offsetLeft;
     YAxis.Y1 = offsetBottom;
     YAxis.X2 = offsetLeft;
     YAxis.Y2 = canvasHeight - offsetBottom;
     YAxis.Stroke = AxisBrush;
     YAxis.StrokeThickness = 2;
     ChartCanvas.Children.Add(YAxis);

 }

At this point we have the X and Y axes displayed and the grid rescales whenever the canvas size changes. Our next task is to display the axis ticks and labels. I have decided to break each axis into ten equal parts so there will be 11 labels on each axis. Before we can add the ticks we need to decide what the range on each axis will be. For simplicity I have decided the range will be zero to the smallest power of ten that is equal to or larger than the largest value on the axis. 

For example, if an axis has a maximum value of 72, the axis range will be 0 to 100 with ticks every 10 units. Note I have not optimized the algebra so the algorithms are clearer.

Add the following code to the end of ChartCanvas_SizeChanged

            double MaxX = Points.Max(p => p.X), XScale = 1;
            double MaxY = Points.Max(p => p.Y), YScale = 1;

            while (MaxX > XScale) { XScale *= 10; }     // X axis will go from 0 to XScale
            while (MaxY > YScale) { YScale *= 10; }     // Y axis will go from 0 to YScale

            double tickLength = 10;

            for (int X = 0; X <= 10; X++)
            {
                Line XTick = new Line();
                XTick.X1 = offsetLeft + (canvasWidth - 2 * offsetLeft) / 10 * X;
                XTick.Y1 = canvasHeight - offsetBottom;
                XTick.X2 = XTick.X1;
                XTick.Y2 = canvasHeight - offsetBottom + tickLength;
                XTick.Stroke = AxisBrush;
                XTick.StrokeThickness = 1;
                ChartCanvas.Children.Add(XTick);
                TextBlock XLabel = new TextBlock();
                XLabel.Text = (X * XScale/ 10).ToString();
                Canvas.SetLeft(XLabel, XTick.X1 - 5);
                Canvas.SetTop(XLabel, XTick.Y2 + 10);
                ChartCanvas.Children.Add(XLabel);
            }

            for (int Y = 0; Y <= 10; Y++)
            {
                Line YTick = new Line();
                YTick.X1 = offsetLeft;
                YTick.Y1 = canvasHeight - offsetBottom - (canvasHeight - 2 * offsetBottom) / 10 * Y;
                YTick.X2 = offsetLeft - tickLength;
                YTick.Y2 = YTick.Y1;
                YTick.Stroke = AxisBrush;
                YTick.StrokeThickness = 1;
                ChartCanvas.Children.Add(YTick);
                TextBlock YLabel = new TextBlock();
                YLabel.Text = (Y * YScale/ 10).ToString();
                Canvas.SetLeft(YLabel, 5);
                Canvas.SetTop(YLabel, YTick.Y1 - 5);
                ChartCanvas.Children.Add(YLabel);
            }

We now have the ticks and labels on both axes. Time to actually plot some data.

            for (int i = 0; i < Points.Length - 1; i++)
            {
                Point point1 = Points[i];
                Point point2 = Points[i + 1];
                Line plotLine = new Line();
                plotLine.X1 = offsetLeft + (canvasWidth - 2 * offsetLeft) / XScale * point1.X;
                plotLine.Y1 = canvasHeight - offsetBottom - (canvasHeight - 2 * offsetBottom) / YScale * point1.Y;
                plotLine.X2 = offsetLeft + (canvasWidth - 2 * offsetLeft) / XScale * point2.X;
                plotLine.Y2 = canvasHeight - offsetBottom - (canvasHeight - 2 * offsetBottom) / YScale * point2.Y;
                plotLine.Stroke = LineBrush;
                plotLine.StrokeThickness = 2;
                ChartCanvas.Children.Add(plotLine);
            }
        


Friday, December 2, 2016

Consuming a WCF service using REST from Angular

I know this doesn't have much to do with WPF but I've been wanting to write RESTful WCF services for some time and consuming them from Angular was plain good fun!

You can click on any of the images below to see full size versions.

We're going to use the AdventureWorks2012 SQL Server sample database from Microsoft.

Start Visual 2015 and start a new WCF Service Application project and call it "products".



At this point Visual Studio has created a sample service called Service1 that contains examples of code. You can delete Service1 and IService1 or simply ignore them.

Our next step is to add our own service called Products. In the solution explorer, right-click on the Products project, select Add and then select New Item.


Select WCF Service and name it "Products". Click [Add]


Now your Solution Explorer looks like this and you can get a clean build.



We will write a Products service that returns a list of products. The first thing to do is to enhance our interface to include a definition of the product class and a definition of the products service. Lets' start with the product class. Open the IProducts class and remove the examples that Visual Studio put there for us. Replace the contents with the following class definition.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace Products
{
    [DataContract]
    public class Product
    {
        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string ProductNumber { get; set; }

        [DataMember]
        public string Color { get; set; }

        [DataMember]
        public decimal Price { get; set; }
    }
 }

Note the [DataContract] and [DataMember] attributes. They tell WCF how to send this class and its members across the wire to the client.

Now add the interface for the GetProductList method. Note it also has attributes that indicate how we will invoke it (with a GET) and how data will be returned (in Json format). If we wanted to be able to save a product list we would add a similar definition but with Method="POST" and referencing a different method (maybe SaveProductList). The UriTemplate would be the same.

    [ServiceContract]
    public interface IProducts
    {
        [OperationContract]
        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "ProductList/")]
        List<Product> GetProductList();
    }

To recap, this interface converts a request to http://.../ProductList/ using the GET method into a call to GetProductList() and converts the returned data into JSON which is attached to the response. In effect we have a layer that converts REST to SOAP. We will see below that we have the ability to hide or expose the underlying SOAP functionality.

At this point the IProducts.cs file has a "Products" namespace that contains a "Product" class and an IProducts interface.

Before we go any further we need to put our connection string into Web.config so we can access the database. Add the following to your Web.config file inside <configuration>.

  <connectionStrings>
    <add name="localhost" connectionString="Server=localhost;Database=AdventureWorks2012;Trusted_Connection=yes"/>
  </connectionStrings>

While we are here we can define our endpoint behaviors. Merge the following tags into <system.serviceModel> in your Web.config file. Some of these tags will appear to be invalid until they are all in the correct places. Two attributes we could change are httpGetEnabled which determines if SOAP behavior is exposed by the endpoint and includExceptionDetailInFaults which determines if detailed error messages are returned.

    <services>
      <service name="Products.Products" behaviorConfiguration="serviceBehavior">
        <endpoint address="" binding="webHttpBinding" contract="Products.IProducts" behaviorConfiguration="web"/>
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehavior">
          <serviceMetadata httpGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>

I am going to use a data extension to convert a datatable to a list of dictionaries which means I need to add a reference to System.Data.DataSetExtensions. In the Solution Explorer right-click on References and select Add Reference.

Scroll to System.Data.DataSetExtensions, click on it to make the checkbox visible, then check the checkbox and click [OK].


Now we can write the products class. Open the Products.svc.cs file. Remove all the example code and replace it with the following. If we had decided to support saving a product list we would write SaveProductList here (after defining the interface in IProducts).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace Products
{
    public class Products : IProducts
    {
        public List<Product> GetProductList() 
        {
            String SQL = "SELECT ProductID, Name, ProductNumber, Color, ListPrice FROM Production.Product ORDER BY ProductID";
            String ConnString = System.Configuration.ConfigurationManager.ConnectionStrings["localhost"].ConnectionString;
            DataTable dt = new DataTable();

            using (SqlConnection conn = new SqlConnection(ConnString))
            {
                conn.Open();
                using (SqlCommand comm = new SqlCommand(SQL, conn))
                {
                    using (SqlDataAdapter da = new SqlDataAdapter(comm))
                    {
                        da.Fill(dt);
                    }
                }
            }
            List<Product> ProductList = dt.AsEnumerable().Select(r => new Product()
            {
                ID = r.Field<int>("ProductID"),
                Name = r.Field<String>("Name"),
                ProductNumber = r.Field<String>("ProductNumber"),
                Color = r.Field<String>("Color"),
                Price = r.Field<Decimal>("ListPrice")
            }).ToList();
            return ProductList;
        }

    }
}

We define the GetProductList method that pulls the data from the database and converts it into a list of product objects. The list is automatically converted to JSON by the ResponseFormat = WebMessageFormat.Json clause in the interface's attribute.

At this point you can right-click on Products.svc and select "View in Browser". We disabled SOAP when we set httpGetEnabled="false"  above so we will see the message "Metadata publishing for this service is currently disabled". If we append "/ProductList/" to the URL we will get a JSON file back that represents all the products.

Now it is time to consume the data. Add an html page to the project called "Default" and set it as the start page. It will use some simple Angular to $http.get the JSON and render it in a basic table. Replace the contents of Default.html with this.

<!DOCTYPE html>
<html ng-app="DefaultApp">
<head>
    <title>products</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
    <script>
        angular.module("DefaultApp", [])
        .constant("productsUrl", "/Products.svc/ProductList/")
        .controller("DefaultCtrl",
        function ($scope,$http,productsUrl) {

            $scope.getData = function () {
                $http.get(productsUrl)
                .then
                (
                    function (result) { $scope.products = result.data; }
                    ,
                    function (result) { $scope.statusText = result.statusText; }
                )
            };

            $scope.getData();
        });
    </script>
</head>
<body ng-controller="DefaultCtrl">
    <div>{{statusText}}</div>
    <table>
        <tr ng-repeat="item in products">
            <td>{{item.ID}}</td>
            <td>{{item.Name}}</td>
            <td>{{item.ProductNumber}}</td>
            <td>{{item.Color}}</td>
            <td>{{item.Price | currency}}</td>
        </tr>
    </table>
</body>
</html>

Now run the project. The result will be a table of products.