Thursday, December 12, 2013

Custom Markup Extensions using named parameters

WPF Version 4.0

In my previous blog I showed how to build and consume a custom markup extension using positional parameters. But some of your most popular markup extensions have formats like...

{Binding Path=Amount}

which uses a named parameter. In this case the name is Path and the value is Amount. What would the Markup Extension look like? Well, it would have a public property called Path. Let's take our example from the previous post and modify it to use a named parameter called 'Name'. The markup extension code would look like this...

Imports System.Windows.Markup
Public Class MyExtension
    Inherits MarkupExtension

    Public Property Name As String
   
    Public Overrides Function ProvideValue(serviceProvider As System.IServiceProvider) As Object
        Return "You said " & _Name
    End Function
End Class

I renamed the Parameter property to Name and removed the constructor. If there are no positional parameters you don't need one - the default will do. After instantiating the object the XAML parser will assign each named parameter to an identically named public property. If it fails for any reason it will throw an error.

Here's the xaml that references MyExtension using a named value

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFTest"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Label Content="{local:My Name=Thing}"/>
    </Grid>
</Window>


As you type the xaml that references your markup extension intellisense automatically knows the list of public properties and displays them.


Note: Of course you could also specify the label's content with this xaml.

    <Grid>
        <Label>
            <Label.Content>
                <local:MyExtension Name="Thing"/>
            </Label.Content>
        </Label>
    </Grid>
</Window>

Custom Markup Extension

WPF Version 4.0

My inner geek got really excited when I read about Custom Markup Extensions. Over the months I've become used to {binding} and {staticresource} but it never occured to me that I could use {myspecialmarkup} until I read about it. It turns out to be extremely easy - very similar to converters, in fact. Let's walk it through. Start by creating a new WPF application called WPFTest. Make sure the default namespace is also WPFTest because we will refer to it later. Good. Now add a new class file called MarkupExtension, and add a class called MyExtension which derives from MarkupExtension. There are two methods we will add. Look at the complete code below.

Imports System.Windows.Markup
Public Class MyExtension
    Inherits MarkupExtension

    Public Property Parameter As String
    Public Sub New(Parameter As String)
        _Parameter = Parameter
    End Sub
    Public Overrides Function ProvideValue(serviceProvider As System.IServiceProvider) As Object
        Return "You said " & _Parameter
    End Function
End Class

We are going to accept a single string in the constructor and save it to a property. When the XAML is loaded the ProvideValue method is called. We will return "You said " plus the string passed to the constructor. Whatever property is bound to MyExtension will receive this string. To reference our new markup extension we have to add an xmlns and a reference to the extension in MainWindow.xaml. Look at 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:local="clr-namespace:WPFTest"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Label Content="{local:My Thing}"/>
    </Grid>
</Window>

The xmlns:local line tells the xaml that anything starting with local: can be resolved within the WPFTest assembly. When it sees local:My it looks for a class called MyExtension (the Extension is added automatically) in the WPFTest assembly. It will then call the constructor and pass the string 'Thing'. Later on, when it wants to know what value to put in the label's content it calls the ProvideValue method on the object it created earlier.

This explains why the error message you see when you mess up the content of markup refers to the ProvideValue method. Interestingly the only parameter passed to ProvideValue contains the line and column number of the markup that called the Markup Extension. So you can reference the same Markup Extension from many places but the error message can tell you exactly which piece of markup caused the error to be thrown.

Wednesday, December 11, 2013

Searching Sorted DataGrids

WPF version 4.0

I have a datagrid that shows some search results. The user can sort the results by clicking on the column headers. I also have a button that will search the grid and select the next row that contains a specific value. I started with code that looks like this. Note the code has been simplified to always return the first error. The real code to find the next error would start at the row after the current row and search to the end, then start at the beginning and search to the current row.

For i as integer = 0 to RowCount - 1
    If DirectCast(DataGrid.ItemsSource, DataView).Table.Rows(i)("HasErrors") > 0 Then
        DataGrid.SelectedIndex = i
        Exit For
    End if
Next

Which worked great until a user sorted the grid and then clicked the button. Then the code started highlighting random rows. I realized the table had not been resorted and the sequence of rows in the table no longer matched the sequence of rows in the datagrid. So, still thinking in asp.net mode, I decided to search the contents of the table instead. You can do this by using an Item container which returns a DataGridRow.

SearchResultsDataGrid.ItemContainerGenerator.ContainerFromIndex(i)

But this didn't work the way I wanted either because the datagrid is virtualized so there were only containers for the rows on the currently displayed page. If the next error was not on the current page it wouldn't be found.

I had to think like a WPF programmer. The answer is in the ItemsSource, not in the DataGrid. The secret is in noticing that the ItemsSource is a DataView, not a DataTable. The DataView is sorted when the DataGrid is sorted but the DataTable is not. I simply had to modify the code to search the DataView like this...

For i as integer = 0 to RowCount - 1
    If DirectCast(DataGrid.ItemsSource, DataView).Item(i)("HasErrors") > 0 Then
        DataGrid.SelectedIndex = i
        Exit For
    End if
Next

What threw me off is that a DataView contains a collection of items instead of a collection of rows. Who knows why? I don't!

Friday, December 6, 2013

Binding a radio button group using Converter Parameter

I have two radio buttons on a page. PayByType is a column in a one row datatable that is the DataContext of the page. When PayByType = "1" I want the first button checked, but when PayByType = "2" I want the second button checked. Also, if the user selects a button I want PayType to be set to the corresponding value for the button. I spent a while coming up with a way of achieving this but it was custom to each set of radio buttons and I didn't like that. I found the following solution on that excellent web site http://wpftutorial.net/ and modified it to avoid using Enums.

Start with a converter. This is a general purpose converter that will handle any set of values so it will work for any set of radio buttons. I have assumed the field (in my example PayByType) is a string for simplicity, but it doesn't have to be. The Convert method compares the value in the bound field (which is in the value argument) to the value in ConverterParameter (which is in the parameter argument). If they are the same it returns True. If this converter is bound to the IsChecked attribute then the radio button will be checked.

Note that the last radio button for which this converter returns true will be the checked radio button.

The ConvertBack method looks at the value argument. If the converter is bound to the IsChecked attribute then the value will be true or false. If you cause something else to be passed I simply return nothing. If the value is true, then the method returns the ConverterParameter (which is in the parameter argument). If the value is false it returns nothing.

Here is the converter...

Public Class RadioButtonConverter
    Implements IValueConverter

    Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        Return (value.ToString() = parameter.ToString())
    End Function

    Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

        If value Is Nothing Or parameter Is Nothing Then
            Return Nothing
        End If

        Dim IsChecked As Boolean = False
        Boolean.TryParse(value, IsChecked)
        If IsChecked Then
            Return parameter
        Else
            Return Nothing
        End If

    End Function
End Class


I will assume you know how to create the static resource, I called mine RadioButtonConverter so the binding looks like this...

<RadioButton Name="EditItemDetailChargeTotalRadio" Content="Charge Total" GroupName="AccountMethod" VerticalAlignment="Center" IsChecked="{Binding Path=PayByType, Mode=TwoWay, Converter={StaticResource RadioButtonConverter}, ConverterParameter='1'}"/>
<RadioButton Name="EditItemDetailChargeItemRadio" Content="Charge By Item" GroupName="AccountMethod" VerticalAlignment="Center" IsChecked="{Binding Path=PayByType, Mode=TwoWay, Converter={StaticResource RadioButtonConverter}, ConverterParameter='2'}"/>


Note the only difference in the bindings is the value in ConverterParameter.

Wednesday, December 4, 2013

Modifying styles in code

I have a requirement to dynamically set the maxlength property of an editable column of a datagrid based on a property based deep inside an object. I decided to do this in code so I found the column object and tried to add a setter to the EditingElementStyle. I got an error message that indicated that the style was already sealed and I could not modify it. What to do?

The answer is to create a new style based on the old one, modify the new style, and then assign it to the EditingElementStyle. A bit of a headache but not the hardest piece of WPF coding I've done today. This is what it looks like.

Dim Col As DataGrid.Columns(2) 
Dim Style As New System.Windows.Style(GetType(TextBox), Col.EditingElementStyle)
Style.Setters.Add(New Setter(TextBox.MaxLengthProperty, AccountStructure.Section(2).MaxLen))
Col.EditingElementStyle = Style

Multi Value Converters

I find myself with a requirement to calculate a field in a grid as the product of two other fields in the same row. I have an editable quantity field and an editable unit price field. I also have a read-only field called Extended Price which is the product of the quantity and unit price fields. One way to do this is with a Multi Value Converter. This is simply a class that implements IMultiValueConverter. The Convert method takes a list of values instead of a single value. It's also declared differently in the XAML. Here's an example of the XAML that hooks my ExtPrice column into the Quantity and UnitPrice columns.

<datagridtextcolumn binding="{Binding Path=ExtPrice, Mode=OneWay}" elementstyle="{StaticResource RightAlign}" header="Ext. Price" isreadonly="true" width="80">
    <multibinding converter="{StaticResource ExtPriceConverter}" mode="OneWay" updatesourcetrigger="LostFocus">
        <binding path="QuantityOrdered"/>
        <binding path="UnitPrice"/>
        <binding stringformat="{}{0:0.00}"/>
    </multibinding > 
</datagridtextcolumn>

Each of the <Binding Path= declarations is evaluated at added to the values collection passed to the converter. It simply expects two numbers, multiplies them, and returns the product as a currency field The converter code is below.

Public Class ExtPriceConverter
    Implements IMultiValueConverter

    Public Function Convert(values() As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert

        Try
            If values.Length >= 2 AndAlso IsNumeric(values(0)) AndAlso IsNumeric(values(1)) Then
                Return String.Format(values(0) * values(1), "C")
            Else
                Return ""
            End If
        Catch ex As Exception
            Throw New Exception("ExtPriceConverter:" & ex.Message)
        End Try

    End Function

    Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
        Throw New NotSupportedException("Not Supported")
    End Function
End Class

Sunday, December 1, 2013

Modal Dialog Windows

This entry references WPF 4.0

With WPF a modal dialog window (a window that takes focus and does not release it until it is closed) is simply a window that is opened in a special way. This is not very different from .Net modal dialogs. Let's walk through the process.

Start by creating a new WPF window, let's call it VendorSearchPopup. We will add one label, one textbox, a Cancel button and an OK button. The XAML looks something like this...

<Window x:Class="VendorSearchPopup"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Vendor Search"
    Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Label Name="VendorNumberLabel" Content="Vendor Number" Grid.Row="0" Grid.Column="0"/>
        <TextBox Name="VendorNumberTextBox" Grid.Row="0" Grid.Column="1" />
        <Button Name="OKButton" Grid.Row="1" Grid.Column="0" Content="OK"></Button>
        <Button Name="CancelButton" Grid.Row="1" Grid.Column="1" Content="Cancel"></Button>
    </Grid>
</Window>


At this point there is nothing to suggest this is anything other than a normal window. However, if we open it using the ShowDialog method it is opened modally. The code in the calling XAML.VB would look like this. The ShowDialog method returns a nullable(of Boolean) that lets us know if the user cancelled the dialog box.

Dim Dlg As New VendorSearchPopup
Dlg.Owner = me
If Dlg.ShowDialog() Then

    ' Do things here
End If

Note: If you are opening the dialog box from a user control you would replace 'me' with 'Application.Current.MainWindow' or some other appropriate window object.

There are some window attributes you can set that are useful for dialog boxes. For example...
ResizeMode="NoResize"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=VendorNumberTextBox}"

Normally you open a dialog box to get some information from the user. You do this with public properties. Let's add a public property to the code behind called VendorNumber.

Public Property VendorNumber As String

After instantiating the window but before calling ShowDialog you can set this property so it's available for binding. In the calling code, before ShowDialog add this line...

Dlg.VendorNumber = "123456"

This causes the VendorNumber property to be populated after the dialog box is instantiated but before binding occurs. Now you can bind the VendorNumberTextBox to the VendorNumber property by setting the window's datacontext to itself and specifying a binding attribute on the VendorNumberTextBox.

After the dialog box's InitializeComponent() add the line...

DataContext = Me

and add the following binding attribute to the VendorNumberTextBox in the XAML...

Text={Binding Path=VendorNumber}

Note: Funny thing - as I was researching this I found someone asking how to bind a text box to a property and the first person that responded claimed this was a 'basic binding question' and then got the answer wrong :-) Fortunately the next poster got it right.

After the ShowDialog method returns your calling code can get the user's response by looking at the dialog box's properties. For example...

MessageBox.Show("You entered " & Dlg.VendorNumber)

You also need to specify the result to return from the ShowDialog method. You can do this in code using the me.DialogResult property. Setting it true or false will cause the dialog box to exit and return true or false.

You can use the IsCancel="True" attribute to specify buttons that should cancel the dialog box when the user clicks on them. The IsCancel attribute shown below causes the dialog box to be closed and return false from the ShowDialog method (indicating the user cancelled)

<Button Name="CancelButton" Grid.Row="1" Grid.Column="1" Content="Cancel" IsCancel="true"></Button>

However, for the buttons that should close the dialog box and return true you have to write some code. In the XAML attach an event handler to the OKButton.

<Button Name="OKButton" Grid.Row="1" Grid.Column="0" Content="OK" Click="OKButton_Click"></Button>

and in the event handler you set Me.DialogResult = true. This causes the dialog box to immediately close and return true to ShowDialog. Be sure to set all the public properties that the calling code needs before you exit. Of course, if you used two-way binding they should already be set.

Private Sub OKButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
    Me.DialogResult = True
End Sub


One last thing (I promise) is that many users expect the Enter key to  activate the [OK] button. You can specify which button is activated when the user hits the enter key by specifying the IsDefault attribute. I have no idea what happens if you specify this on more than one button. I also hope this is ignored when a multiline text box has focus.

<Button Name="OKButton" Grid.Row="1" Grid.Column="0" Content="OK" Click="OKButton_Click" IsDefault="true"></Button>