Tuesday, February 25, 2014

Re-using User Controls

I spent most of the afternoon chasing this one down. It's for WPF version 4.0.

The problem started when I published a WPF application using ClickOnce. On two out of the five computers that installed and tested the application the user was getting a "An Exception was thrown by the Target of the Invocation" error whenever they tried to load a particular page. The page is a UserControl that I instantiate and assign to a user control on the main page. The error did not indicate if the problem was in the user control being loaded or the code that was loading the user control.

The first thing I did was to write a utility function that recurses through the InnerExceptions and concatenates all the error messages together. Then it appends the stack trace. It looks like this...



VB.Net
Public Shared Function GetFullMessage(ByVal ex As Exception) As String Dim sMsg As String = ex.Message Do While ex.InnerException IsNot Nothing ex = ex.InnerException sMsg &= vbCrLf & ex.Message Loop sMsg &= vbCrLf & ex.StackTrace() Return sMsg End Function
C#.Net
public static string GetFullMessage(Exception ex) { string sMsg = ex.Message; while (ex.InnerException != null) { ex = ex.InnerException; sMsg += Constants.vbCrLf + ex.Message; } sMsg += Constants.vbCrLf + ex.StackTrace(); return sMsg; }

After utilizing this function and deploying a new version I got an error message that suggested that I was trying to create a control with two parents (not allowed) and it was line 1313 (unlucky for some) of the user control's XAML that was doing it. Line 1313 looked like this...

<UserControl Name="EditDocumentNotesUserControl" Content="{StaticResource DocumentNotes}"/>

And the DocumentNotes resource is defined as...

<local:DocumentNotes x:Key="DocumentNotes"/>

The XAML contained another line earlier that also referenced the same StaticResource. I was trying to use the same User Control twice on the same page so I had two references to the same StaticResource which would cause the UserControl defined in the StaticResource to have two parents. A control can only have one parent. As the XAML compiler parsed my XAML it threw an exception which resulted in the very unuseful error I saw. I have no idea why three other computers did not throw the error or why it works in the VS2010 development environment.

The solution, obviously, is to define two StaticResources and reference each of them once, like this...

<local:DocumentNotes x:Key="AddDocumentNotes"/>
<local:DocumentNotes x:Key="EditDocumentNotes"/>
.
.
.
<UserControl Name="AddDocumentNotesUserControl" Content="{StaticResource AddDocumentNotes}"/>
<UserControl Name="EditDocumentNotesUserControl" Content="{StaticResource EditDocumentNotes}"/>

Friday, February 21, 2014

Hiding empty tooltips

This post is for WPF 4.0

I have a grid whose contents may overflow the size of the cell they are in. The common solution to this is to add a tooltip to the cell that contains the entire contents. The user then floats the mouse over the cell and sees the tooltip. This is easy to achieve. The example below defines a grid column that is bound to the ItemSource's Description property and has a tooltip that lasts one minute to give the slower users a chance to read the full description. Sometimes five seconds just isn't enough :-)

<DataGridTextColumn Width="200" Binding="{Binding Description}">
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockDefaultStyle}">
            <Setter Property="ToolTip" Value="{Binding Description}">
            <Setter Property="ToolTipService.ShowDuration" Value="60000"> 
        </Style> 
    </DataGridTextColumn.ElementStyle> 
</DataGridTextColumn> 

The only downside to this is that the user sees an empty tooltip if the content of the cell is empty. I wrote a style in my application's resource dictionary to hide the tooltip if the content is empty. Notice the definition of the sys namespace which is needed to define an empty string in XAML.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <Style TargetType="{x:Type ToolTip}">
        <Style.Triggers>
            <Trigger Property="Content" Value="{x:Static sys:String.Empty}">
                <Setter Property="Visibility" Value="Collapsed"/>
            </Trigger> 
            <Trigger Property="Content" Value="{x:Null}"> 
                <Setter Property="Visibility" Value="Collapsed"/> 
            </Trigger> 
        </Style.Triggers> 
    </Style> 
</ResourceDictionary>

Now the application will hide all empty tooltips.

Thursday, February 6, 2014

Routed Commands

When I first came across Routed Commands I thought it was cool but didn't really make my life any easier. I was wrong - they are a good way to achieve very desirable functionality.

Basically a Routed Command is a way to define executable code and then bind controls to the command instead of directly to the executable code. This gives us two features. We can consistently control whether a command is available or not and also consistently define how the code is executed.

WPF predefines several Routed Commands. You should ignore them and define your own. It's easy.

Let's assume we want to create an Edit Command. It's on a search screen and it's only available if the user has exactly one search result highlighted. We want to edit the currently selected document if the user double-clicks a row in the search results grid or if they click an [Edit] button.

Start by defining our new EditCommand. Under the window resources add the new command.

<Window.Resources>
    <RoutedUICommand x:Key="EditCommand" Text="Edit">
</Window.Resources>

Now we define how we decide whether the command is available or not and how to execute it.
<Window.CommandBindings>
    <CommandBinding Command="{StaticResource EditCommand}" CanExecute="EditCanExecute" Executed="EditExecuted"/>
</Window.CommandBindings>

Now we need to define two methods. The first is called EditCanExecute and returns CanExecute=true if the Edit Command should be available to the user. If it is not, the [Edit] button will automatically be disabled and the double-click on the search results will do nothing.

Protected Sub EditCanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
    If SearchResultsDataGrid.HasItems AndAlso SearchResultsDataGrid.SelectedItems.Count = 1 Then
        e.CanExecute = True
    Else
        e.CanExecute = False
    End If
    e.Handled = True
End Sub

Protected Sub EditExecuted(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
    ' Do whatever is needed to start editing the currently selected item

    DoEdit(SearchResultsDataGrid.SelectedItem)
    e.Handled = True
End Sub

Finally we need to attach the command to the [Edit] button and the double-click gesture on SearchResultsDataGrid. The button is easiest, you just add a Command attribute in the XAML. Note that a Click attribute is no longer needed.

<Button Name="EditButton" Width="100" Command="{StaticResource EditCommand}" Content="Edit"/>
The DataGrid can have a set of input bindings that are declared in XAML like this...
<DataGrid.InputBindings>
    <MouseBinding Gesture="LeftDoubleClick" Command="{StaticResource EditCommand}"/>
</DataGrid.InputBindings>

When EditCanExecute returns e.CanExecute=False all the attached controls are disabled (Gestures cannot be disabled, obviously). When an attached control is clicked or gesture is executed, the EditExecuted method is called. Routed command handlers always receive the same set of parameters which makes writing the event handlers simpler.

One more thing RoutedCommands gives us is the ability to consistently label the control that use them. If you notice, the declaration of the RoutedCommand has a Text attribute. This can be used to populate the content of buttons that use the command as shown below or you can style it to avoid all the repetition.

<Button Name="EditButton" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>


The advantage to this approach is that I don't have to find the 20 different ways that the availability of the edit command can be changed. Also, if the requirements change so that another control can execute the edit function, I just set its Command parameter to {StaticResource EditCommand} and everything else is taken care of.

The downside is that the CanExecute method is polled frequently (pretty much whenever the user does something) so it has to be fast. If this is an expensive decision to make, perhaps requiring a slow WCF call, this might not be the ideal solution.

Tuesday, January 21, 2014

Binding to a List - don't do it

In an earlier blog I described how to bind a datagrid to a Public Property List defined in the code behind. It turns out that if you deploy that solution it does not work. There's no error, the grid's ItemsSource has the correct number of rows, but no data shows in the DataGrid.

If you replace the List with an ObservableCollection (you will need to Import System.Collection.ObjectModel), the datagrid gets populated.

This must be because the ObservableCollection implements IPropertyChanged, an interface that allows bound controls to be made aware of changes to the collection. List does not implement this interface.

Why is the datagrid populated correctly in Visual Studio?

The List is being populated by a WCF call. I think in VS the call is quick and the collection is populated prior to the datagrid being bound to it. When I run the application on another computer via ClickOnce the WCF call takes longer so the List is not populated yet when the datagrid is bound.

Because IPropertyChanged is not implemented, subsequent changes to the List are not propagated to the datagrid.

Sunday, January 19, 2014

Binding validation rules and styles in code behind

Normally I prefer to do things in XAML because it's a little easier and there's less chance of a support programmer having to figure out how I did something. I have a requirement to make a textbox required. When a required textbox is empty I want both the textbox and its associated label to be highlighted.

This is fairly readily handled in XAML with a validation rule and a converter. We put a validation rule on the textbox and bind the color of the label to the text of the textbox using a converter. The XAML looks like this.


<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:WpfApplication13"
    Title="MainWindow" Height="350" Width="525"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <local:RequiredValidator x:Key="RequiredValidator"/>
        <local:RequiredColorConverter x:Key="RequiredColorConverter"/>
    </Window.Resources>
    <StackPanel Orientation="Horizontal" Height="30">
        <Label Name="DescriptionLabel" Content="Description:">
            <Label.Style>
                <Style TargetType="{x:Type Label}">
                    <Setter Property="Foreground" Value="{Binding ElementName=DescriptionTextBox, Path=(Validation.HasError), Converter={StaticResource RequiredColorConverter}}"/>
                </Style>
            </Label.Style>
        </Label>
        <TextBox Name="DescriptionTextBox" Width="200">
            <TextBox.Text>
                <Binding Path="Description" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
                    <Binding.ValidationRules>
                        <StaticResource ResourceKey="RequiredValidator"/>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
    </StackPanel>
</Window>


--------------------------------------------------------

Class MainWindow 

    Public Property Description As String
    Public Sub New()
        InitializeComponent()
    End Sub
End Class

As you can see will still need to define our validation rule and converter, both of which are fairly trivial classes.


Public Class RequiredValidator
    Inherits ValidationRule

    Public Overloads Overrides Function Validate(value As Object, cultureInfo As Globalization.CultureInfo) As ValidationResult

        If String.IsNullOrWhiteSpace(value) Then
            Return New ValidationResult(False, "Error")
        Else
            Return New ValidationResult(True, Nothing)
        End If
    End Function
End Class

Public Class RequiredColorConverter
    Implements IValueConverter

    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        If CBool(value) Then
            Return New SolidColorBrush(System.Windows.Media.Colors.Red)
        Else
            Return New SolidColorBrush(System.Windows.Media.Colors.Black)
        End If
    End Function

    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException("RequiredColorConverter")
    End Function
End Class

As you can see when you run the project, both the textbox and the label turn red when the textbox is empty. As a side note, I tried to bind the label to the (Validation.Errors) collection but when I did that Visual Studio kept crashing (bug?) I know you can use this in a trigger without a converter so I think for some reason you can't pass the Validation.Errors to a converter.

Well this all works fine but it's very repetitive if you have several mandatory fields in an application. Even if I created a Style I would need two styles, one for the label and one for the textbox. So I wondered how difficult it would be to do this in code instead. The idea would be to write a utility method that took a textbox and an optional label and add the validator and style in code.

    MakeTextboxRequired(EditDescriptionTextBox, EditDescriptionLabel)

It turns out that creating validation rules and styles in code is quite difficult and verbose.

Change the XAML to...
        <Label Name="DescriptionLabel" Content="Description:"></Label>
        <TextBox Name="DescriptionTextBox" Width="200" Text="{Binding Description}"></TextBox>


This is what the code looks like...


    Public Sub MakeTextboxRequired(oTextBox As TextBox, oLabel As Label)
        ' Attach a mandatory validation rule to the control
        ' Attach a binding to the color of the label

        Dim sElementName As String = oTextBox.Name
        Dim ValidationBinding As New Binding
        Dim RequiredValidator As ValidationRule = Me.FindResource("RequiredValidator")

        Try
            ValidationBinding.Path = New PropertyPath("Description")
            ValidationBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            ValidationBinding.ValidatesOnDataErrors = True
            ValidationBinding.NotifyOnValidationError = True
            ValidationBinding.ValidationRules.Add(RequiredValidator)
            oTextBox.SetBinding(TextBox.TextProperty, ValidationBinding)

            If oLabel IsNot Nothing Then
                Dim Style As New Style(GetType(Label))
                Dim ColorConverter As IValueConverter = Me.FindResource("RequiredColorConverter")
                Dim ColorBinding As New Binding
                ColorBinding.ElementName = sElementName
                ColorBinding.Path = New PropertyPath("(Validation.HasError)")
                ColorBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                ColorBinding.ValidatesOnDataErrors = True
                ColorBinding.NotifyOnValidationError = True
                ColorBinding.Converter = ColorConverter
                Style.Setters.Add(New Setter(Label.ForegroundProperty, ColorBinding))
                oLabel.Style = Style
            End If
        Catch ex As Exception
            Throw New Exception("MakeTextboxRequired: " & ex.Message)
        End Try
    End Sub

Now we can get rid of the styles in XAML and let the code do all the hard work.

I'm not saying this is a better approach, but it was an interesting exercise.

Thursday, January 16, 2014

The difference between DataGridTextColumn style and ElementStyle

WPF Version 4.0

Sometimes I can be a bit retarded. I have some XAML that looks like this

<DataGrid Name="EditItemDetailsDataGrid" ItemsSource="{Binding}" AutoGenerateColumns="false">
   <DataGrid.Columns>
       <DataGridTextColumn Header="#" Binding="{Binding Path=Sequence}" IsReadOnly="true" Foreground="Red"/>
   </DataGrid.Columns>
<DataGrid/>

Simple stuff right? Now I have a column in the backing store for this grid that is called "HasErrors". When HasErrors is one I want the Sequence column to have a red foreground, otherwise a black foreground. My first attempt was to use an existing converter called HasErrorsToBrush like this...

<DataGrid Name="EditItemDetailsDataGrid" ItemsSource="{Binding}" AutoGenerateColumns="false">
   <DataGrid.Columns>
       <DataGridTextColumn Header="#" Binding="{Binding Path=Sequence}" IsReadOnly="true" Foreground="{Binding Path=HasErrors, Converter={StaticResource HasErrorsToBrush}}"/>
   </DataGrid.Columns>
<DataGrid/>

But this doesn't work. I put a breakpoint in the converter but it's being used by other grids on the page so I couldn't tell whether this column was calling it or not. Here's a nifty tip...

Converters have a converter parameter. I modified the binding to add a parameter like this.


    Foreground="{Binding Path=HasErrors, Converter={StaticResource HasErrorsToBrush}, ConverterParamter=SEQ}"

Now when I put a break point in the converter I can see that it's never getting called with a parameter of SEQ. What the hell?

Then I realized I was binding the Foreground for the DataGridCell, not for the TextBlock element in the cell. To do that I needed to work a little harder.

<DataGrid Name="EditItemDetailsDataGrid" ItemsSource="{Binding}" AutoGenerateColumns="false">
   <DataGrid.Columns>
       <DataGridTextColumn Header="#" Binding="{Binding Path=Sequence}" IsReadOnly="true"/>
           <DataGridTextColumn.ElementStyle>
               <Style TargetType="TextBlock">
                   <Setter Property=Foreground" Value="{Binding Path=HasErrors, Converter={StaticResource HasErrorsToBrush}}"/>
               </Style>
           </DataGridTextColumn.ElementStyle>
       </DataGridTextColumn>
   </DataGrid.Columns>
<DataGrid/>

Simple stuff, to be sure, but if you're not paying attention you can waste a lot of time on simple stuff.

Wednesday, January 15, 2014

TextInput event on DataGrid

WPF Version 4.0

This had me scratching my head for quite a while. Eventually it was Microsoft's documentation that revealed the way (could be a first!).

The problem is that the TextInput event bubbles, which means the TextBox got a crack at it before the DataGrid. The TextBox marked the event as handled, so it was not getting raised for the DataGrid. However, I vaguely remembered that there is a way to attach an event handler so that it gets raised EVEN FOR HANDLED EVENTS. I found some examples on Google but they are all for C#. The syntax for doing this in VB is as follows...

myDataGrid.AddHandler(TextBox.TextInputEvent, DirectCast(AddressOf myTextInputHandler, RoutedEventHandler), True)

For C#
myDataGrid.AddHandler(TextBox.TextInputEvent, new RoutedEventHandler(myTextInputHandler), true);

This is a very powerful technique because it can also attach handlers for events the target does not directly support. For example, you could attack a click event to a Label which would cause the Label to call your event handler when anything inside that label raised a click event. You can do this even though the Label control does not support the click event. In this case the following would not work.

AddHandler MyLabel.Click, Addressof MyLabelClickHandler