Sunday, February 19, 2017

Passing Action as a parameter

This has nothing to do with WPF but while working on a problem that was best solved using an Action I began to wonder how you would pass an action around across thread, process, and machine boundaries.

Here is a console application that demonstrates the use of an action, passing it across a thread boundary, and then a process and machine boundary.

Start a console application in visual basic and call it "Actions". Then add a WCFService project and leave the name as WcfReferenceLibrary1. Here is the solution explorer.


The contents of Module1.vb are...

Imports System.Threading
Module Module1

    Sub Main()
        Console.WriteLine("Action on current thread")

        Dim a As Action(Of String)
        a = Sub(s) Console.WriteLine(s)
        a("It Worked!")
        Console.WriteLine("---------------------------")

        Console.WriteLine("Action on another thread")
        Dim t As New Thread(New ParameterizedThreadStart(AddressOf newThread))
        t.Start(a)
        ' Wait for the thread to terminate
        Do While (t.IsAlive)
            Thread.Sleep(10)
        Loop
        Console.WriteLine("---------------------------")

        Console.WriteLine("Action on another process")
        Console.WriteLine("Can't be done - process only takes string arguments")
        Console.WriteLine("---------------------------")

        ' You have to add the service reference before the following
        ' code will compile
        Try
            Console.WriteLine("Action on another machine")
            Dim sr As New ServiceReference1.Service1Client
            sr.Work(a)
            Console.WriteLine("---------------------------")
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try

        Console.WriteLine()
        Console.WriteLine("Press any key to exit")
        Console.ReadKey()
    End Sub

    Sub newThread(a As Action(Of String))
        a("Thread Worked!")
    End Sub
End Module

The IWCFService1,vb and WCFService1,vb are in their own project and look like this...
<ServiceContract()>
Public Interface IService1

    <OperationContract()>
    Sub Work(ByVal a As Action(Of String))


End Interface

Public Class Service1
    Implements IService1

    Public Sub Work(ByVal a As Action(Of String)) Implements IService1.Work
        a("Machine Worked!")
    End Sub

End Class

Build the Service1 project.

To add the Service1 reference to the Actions project you right click on the "Actions" project and select "Add" -> "Service Reference". Click on [Discover], expand "Design_Item_Addresses...", click on Service1 and click [OK].


When you run the solution you will see output like this...


There are four tests here - what do they tell us?
1. Using an action in the same thread it is defined works fine.
2. Using an action in a different thread but in the same process also works fine.
3. There is no way to pass an action to a process using command parameters because they must be strings.
4. WCF allows you to define and compile a method that takes an Action but actions cannot be serialized so the method call fails at run time.

Thursday, February 16, 2017

Logging Binding Errors

This targets Framework 4.0

I recently had a perplexing problem that binding was failing on certain computers but not most of them. I was binding to indexed properties but this was failing on 32 bit computers and not on 64 bit. No exceptions were being thrown and I could not reproduce the error on any of my development machines.

The quick solution turned out to be to install Framework4.6.1 on the problem computers. The project targets Framework 4.0 Client so I don't understand why installing a later framework solved the problem. A colleague suggested it's a bug in Framework 4.0 and that's better than any theory I have.

While researching the problem I found an interesting post on StackOverflow.com that explains how to trap binding errors in code so you can log them. The article is written in C# so I'm reproducing a full project here in VB. Here is the post that I have derived my solution from. Credit to Dean Chalk and Crono.


Start a new VB WPFApplication in Visual Studio 2015 and call it LogBindingErrors.



Replace the MainWindow.xaml with the following XAML which simply defines the DataContext and a TextBlock bound to an, as yet non-existent, property.

<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:LogBindingErrors"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource self}}">
    <Grid>
        <TextBlock Text="{Binding TextToDisplay}"></TextBlock>
    </Grid>
</Window>

Let's define the property now. We don't need INotifyPropertyChanged for this project.

Class MainWindow
    Public Property TextToDisplay As String = "Hello"
End Class

If we run the application now we get the following window.


Let's deliberately break the application by changing the name of the property like so...

Class MainWindow
    Public Property TextToDisplayX As String = "Hello"
End Class

Now the textblock will not be populated and we will see an error in the output window when we run the application.

System.Windows.Data Error: 40 : BindingExpression path error: 'TextToDisplay' property not found on 'object' ''MainWindow' (Name='')'. BindingExpression:Path=TextToDisplay; DataItem='MainWindow' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

When this only happens on non-development machines we can't see it so now we will add the code to log binding errors. 

Start by adding a new class to MainWindow.xaml.vb so that it looks like this...

Class MainWindow
    Public Property TextToDisplayX As String = "Hello"

    Public Class BindingErrorListener
        Inherits Diagnostics.TraceListener

        Private LogAction As Action(Of String)
        Public Shared Sub Listen(LogAction As Action(Of String))
            System.Diagnostics.PresentationTraceSources.DataBindingSource.Listeners.Add(New BindingErrorListener() With {.LogAction = LogAction})
        End Sub

        Public Overrides Sub Write(message As String)
        End Sub
        Public Overrides Sub WriteLine(message As String)
            LogAction(message)
        End Sub
    End Class

End Class

Basically we are defining a new TraceListener and adding to the Listeners collection so that when the listener invokes WriteLine the method defined by our listener's Listen method is invoked. You instantiate the class in the page's New method like this...

    Public Sub New()
        BindingErrorListener.Listen(Sub(m) MessageBox.Show(m))  ' Substitute your own logging mechanism here
        InitializeComponent()
    End Sub

Of course, you won't want to display a message box for every binding error. You will probably write to some kind of log file. When you run the application now you will see the following alert.



Now fix the property name and run the application again. You don't see an alert and the textblock is bound properly.

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.