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.