Monday, December 30, 2013

Toggling ALL datagrid detail visibility

WPF Version 4.0

My previous post explained how to use a toggle button to allow the user to toggle the details of datagrid rows individually. My users also want a single toggle button that will display or hide the details of all the datagrid rows. I started by sniffing around the Visual Tree to find all the DataGridRows and toggling their visibility but this has two problems.

If the datagrid's EnableRowVirtualization property is true (the default) then the VisualTree only includes rows that are currently displayed. If the property is false then the VisualTree will return all rows but it's very slow. I did put together some handy code for finding all children of a specific type which I have included but didn't use for this solution.


private void GetChildren(DependencyObject Parent, System.Type ChildType, List Children)
{
    int iCount = VisualTreeHelper.GetChildrenCount(Parent);
    for (int i = 0; i < iCount; i++)
    {
        DependencyObject Child = VisualTreeHelper.GetChild(Parent, i);
        if (Child.GetType() == ChildType) Children.Add(Child);
        GetChildren(Child, ChildType, Children);
    }
}

You call it like this (assuming dg is a datagrid and you want to find all its datagridrows). It places all the DataGridRow descendants of dg into the list called l.

   List<DependencyObject> l = new List<DependencyObject>();
   GetChildren(dg, typeof(DataGridRow), l);

I accidentally came across a very useful grid method called SetDetailsVisibilityForItem. There's also a Clear and Get version of this method. To hide all details for a grid, even when they're not displayed and the grid is virtualized, you just iterate through the grid's ItemsSource and call this method for each item. I created a toggle button called tb and created a Click event handler for it which looks like this.

private void tb_Click(object sender, RoutedEventArgs e)
{
    foreach (DataRowView Item in ((DataView)dg.ItemsSource))
        if (tb.IsChecked.Value)
            dg.SetDetailsVisibilityForItem(Item, Visibility.Visible);
        else
            dg.SetDetailsVisibilityForItem(Item, Visibility.Collapsed);
}

This solution is simple and quick.

Sunday, December 29, 2013

Toggling DataGrid Details

WPF version 4.5

The three default ways of displaying details for a DataGrid are VisibleWhenSelected, Visible, and Collapsed. None of these options are acceptable to our users. They want to be able to toggle a control on a grid row (ideally a [+]/[-] image) and toggle the details of the row on or off. They want to be able to have as many details open as they want. One detail at a time is not desirable. This blog introduces detail templates, row headers, and toggle buttons to achieve this.

A row header is the perfect place to put a toggle button. We could trap the click event on the button and toggle the detail visibility in code, but it's easier to bind the IsChecked property of the toggle button to it's containing row's detail visibility property using ancestor binding. Unfortunately the IsChecked property is a nullable Boolean (bool?) and the Visibility property uses the Visibility enum so we will need a simple converter to achieve the binding.

I bound the datagrid to the Products table of the AdventureWorks sample database for SQL Server 2012 in code. The datagrid shows the product name and number. The row detail shows the reorder point.

Let's start with the converter. Don't forget to compile the project as soon as you have written the converter and before you reference it in the XAML. Add a class called converters to your WPF application project. Let's assume the project is called WpfApplication8. I think the code is self-explanatory

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Globalization;
using System.Windows;

namespace WpfApplication8
{
    public class VisibilityToNullableBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Visibility)
            {
                return (((Visibility)value) == Visibility.Visible);
            }
            else
            {
                return Binding.DoNothing;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is  bool?)
            {
                return (((bool?)value) == true ? Visibility.Visible : Visibility.Collapsed);
            }
            else if (value is bool)
            {
                return (((bool?)value) == true ? Visibility.Visible : Visibility.Collapsed);
            }
            else
            {
                return Binding.DoNothing;
            }
        }
    }
}

Now that we have our converter we can reference it in the XAML. Note the xmlns:local and the local:VisibilityToNullableBooleanConverter. Now we can reference the converter. The RowHeaderStyle causes the toggle button to be displayed at the top of the row, rather than in the middle.

We set  RowDetailsVisibilityMode to collapsed so that the details will not be displayed when a row is selected - only when the toggle button is checked.

<x:Class="WpfApplication8.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication8"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:VisibilityToNullableBooleanConverter x:Key="VisibilityToNullableBooleanConverter"/>
        <Style x:Key="RowHeaderStyle" TargetType="{x:Type DataGridRowHeader}">
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="Height" Value="25"/>
        </Style>
    </Window.Resources>
    <DataGrid Name="dg" AutoGenerateColumns="False" RowDetailsVisibilityMode="Collapsed" RowHeaderStyle="{StaticResource RowHeaderStyle}">
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Reorder Point:" Margin="10,0,10,0"/>
                    <TextBlock Text="{Binding ReorderPoint}"/>
                </StackPanel>
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
        <DataGrid.RowHeaderTemplate>
            <DataTemplate>
                <ToggleButton  Width="15" Height="15" IsChecked="{Binding Path=DetailsVisibility, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}, Converter={StaticResource VisibilityToNullableBooleanConverter}}"/>
            </DataTemplate>
        </DataGrid.RowHeaderTemplate>
            <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
            <DataGridTextColumn Header="Product Number" Binding="{Binding ProductNumber}"/>
        </DataGrid.Columns>
    </DataGrid>
</Window>

The last thing we need is a little code-behind to fill a datatable and bind the grid to its default view.

using System.Data.SqlClient;

namespace WpfApplication8
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            string sConn = "Server=(local); Database=AdventureWorks2012;Trusted_Connection=true";
            SqlConnection conn = new SqlConnection(sConn);
            string sSQL = "SELECT Name, ProductNumber, ReorderPoint FROM [Production].[Product] ORDER BY Name";
            SqlCommand comm;
            SqlDataReader DR;
            System.Data.DataTable DT = new System.Data.DataTable();

            conn.Open();
            comm = new SqlCommand(sSQL, conn);
            DR = comm.ExecuteReader();
            DT.Load(DR);
            dg.ItemsSource = DT.DefaultView;
            DR.Close();
            conn.Close();
        }
    }
}

Wednesday, December 25, 2013

Reauthentication after Timeout

I have a requirement at the application level to lock the screen and force the user to re-authenticate after a specific period of inactivity. This solves the issue of a user going to lunch in the middle of a transaction and someone else sneaking into their cube and doing bad things. It would be easier to simply log them out after a period of inactivity, but then they would lose all their unsaved work.

The obvious way to do this is to use a timer which gets reset every time the user does something (we haven't decided what 'something' is). When the timer reaches the timeout (say 10 minutes) we would lock the screen and force re-authentication.

Although the screen is technically locked when we bring up the authentication modal dialog box, we want to display a grey, translucent panel over the screen because if we don't our users might get confused. Why we assume our users are so stupid, I will never understand. I've met them, they're not stupid.

We seem to spend 10% of our resources idiot proofing our applications for 1% of the users. Wouldn't it make sense to fire the 1% and hire someone else? I'd rather spend that 10% creating wizzo features for the 10% of the smartest users. We have a super smart user, let's call her "Faith". She has lot's of good ideas but we can't do most of them because we're spending so much time idiot proofing the application for someone in Accounting, let's call him "Fear". But I digress.

So I started by using a timers.timer but apparently that's not the right thing to do in WPF. We should use a DispatcherTimer. In my example I have a single textbox. Whenever the user types in the textbox I reset the timer. In reality you would have many textboxes so rather than attach a KeyDown handler to each one you would attach a single handler at the window. Simply attach the events that qualify as "activity". Because RoutedEvents bubble and tunnel, the window will eventually see the event, unless a lower level handler marked it as handled. Or you could use the Preview events which tunnel so the Window sees them first.

Note: If you attach the Window's KeyDown event handler in code, there is an option for the window to see it, even if a prior handler marked it as handled. This option is not available in XAML.

I chose to show/hide the protective panel by altering its size. I could have achieved the same result using the opacity or zindex. I set the timeout to two seconds for the example.

I used the Stop command in the example for simplicity but in reality I would create a custom RoutedCommand to do this.

I destroy the timer in the Finalize event handler to avoid memory leaks.


<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" TextBox.KeyDown="Window_KeyDown">
    <Window.CommandBindings>
        <CommandBinding Command="Stop" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
    </Window.CommandBindings>
    <Grid Name="LayoutGrid">
        <TextBox Name="TextBox" Width="100" Height="20" Background="Azure"/>
        <Label Name="ProtLabel" Background="gray" Opacity=".5" Width="0" Height="0"/>
    </Grid>
</Window>


Imports System.Windows.Threading
Class MainWindow

    Protected t As DispatcherTimer
    Public Sub New()
        InitializeComponent()

        ' Create and start the timer
        t = New DispatcherTimer()
        t.Interval = TimeSpan.FromSeconds(2)
        AddHandler t.Tick, AddressOf Timeout
        t.Start()
    End Sub

    Private Sub CommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
        e.CanExecute = True
        e.Handled = True
    End Sub

    Private Sub CommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs)
        ' Stop the timer, protect the page, ask for authentication
        t.Stop()
        ProtLabel.Width = LayoutGrid.ActualWidth
        ProtLabel.Height = LayoutGrid.ActualHeight
        If MessageBox.Show("Are you who you say you are?", "Logon", MessageBoxButton.YesNo) = MessageBoxResult.No Then
            Me.Close()
        End If
        ' User authenticated so start the timer and unprotect the page
        t.Start()
        ProtLabel.Width = 0
        ProtLabel.Height = 0
    End Sub

    Private Sub Timeout()
        ApplicationCommands.Stop.Execute(Nothing, Nothing)
    End Sub

    Private Sub Window_KeyDown(sender As Object, e As KeyEventArgs)
        ' Reset the timer
        t.Stop()
        t.Start()
    End Sub

    Protected Overrides Sub Finalize()
        ' Destroy the timer
        MyBase.Finalize()
        t.Stop()
        t = Nothing
    End Sub
End Class

Monday, December 23, 2013

TabControl - so many issues

I expect Microsoft controls to have limited functionality but to be bug free. At least, the bugs they have should be so subtle that the average developer will not be affected by them. But the version of TabControl that ships with WPF 4.0 and 4.5 (and probably all the other versions) is riddled with bugs. Here's a list of the issues I've found...

ClipToBounds does not work.

It doesn't matter what value you assign to ClipToBounds, the content of a TabControl is always clipped. Take a look at the following XAML for an example. The contents of column 1 slide into column 0 because of the negative left margin. But the contents of column2 are truncated because they are inside a TabControl. No combination of values for ClipToBounds will change this. I've even tried a translate RenderTransform but nothing works.


<window height="350" title="MainWindow" width="525" x:class="MainWindow" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <grid showgridlines="True">
        <grid .columndefinitions="">
            <columndefinition>
            <columndefinition>
            <columndefinition>
        </grid>
        <label content="Column 1" grid.column="1" margin="-25,0,0,0">
        <tabcontrol cliptobounds="False" grid.column="2" name="tc">
            <tabitem header="Header">
                <label content="Column 2" margin="-25,0,0,0">
            </tabitem>
        </tabcontrol>
    </grid>
</window>

No PreviewSelectionChanged event

An essential feature of all navigation controls is the ability to cancel navigation in code. For example, you don't want a user to leave a page that has unsaved data on it without asking the user first. However the TabControl has no cancellable navigation event. The best you can do is undo the navigation after it has happened. This is nasty. But wait - it gets worse.

If you undo a navigation (by changing the SelectedIndex back to it's original value) in the SelectionChanged event, the SelectionChanged event will call itself. After doing this two or three times the SelectionChanged event is no longer called. This is probably due to some other bug in TabControl. The solution (see my earlier blog) is to use the dispatcher to undo to Navigation on a different thread. This is a ridiculously complex solution to what should have been a trivial and common requirement.

Routed Commands don't disable TabControl

One of the cool features of routed commands is that they can enable/disable the controls they are bound to as they become executable or not. But there's a problem.

The problem here is not unique to the TabControl, it's more of a limitation of the RoutedCommand. There is no way to bind the IsEnabled property of a control to the CanExecute status of a command when the control cannot be executed.

This is particularly annoying in the case of a TabControl when we may need to disable an entire tab when a particular routed command is not executable, even though we don't expect the command to be executed if the tab is selected.

The only solution is to explicitly enable/disable the control in the CanExecute event. Obviously this approach breaks as soon as you try to write generic RoutedEvent handlers.

myCheckBox.IsEnabled = e.CanExecute;


Wednesday, December 18, 2013

Preventing the user from changing the tab

A common scenario for applications is to keep track of unsaved changes and prevent the user from accidentally losing the changes by moving off the page or closing the application. This is commonly known as Dirty Page logic.

There are several ways to approach it - our standard is to warn the user and give them a chance to remain on the dirty page. Our pages are broken up into a Search tab, an Add tab, an Edit tab, etc. If the user has modified data on an Add or Edit tab and clicks on another tab we want to display a dialog box and give them an opportunity to stay on the current tab.

In WPF the only event available that is consistently raised when a user tries to change tabs on a tab control is the SelectionChanged event. This is raised AFTER the selection has changed so if you want to keep the user on the current tab you have to set the tab control's SelectedIndex back to what it was BEFORE the event was changed. This requires you to track the current index. No big deal. The real problem is that setting the SelectedIndex back only works a few times, then stops. Try this XAML and code behind...


<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <TabControl Name="t1" Grid.Row="0" SelectionChanged="t1_SelectionChanged_1" >
            <TabItem Name="Search" Header="Search">
                <CheckBox Name="CanEditCheckbox" Content="Can Edit?" ></CheckBox>
            </TabItem>
            <TabItem Name="Edit" Header="Edit">
                <Label Name="EditLabel" Content="Edit"></Label>
            </TabItem>
        </TabControl>
        <Label Name="MessageLabel" Grid.Row="1"/>
    </Grid>
</Window>


Class MainWindow

    Private Sub t1_SelectionChanged_1(sender As Object, e As SelectionChangedEventArgs)

        If t1.SelectedIndex = 1 Then
            If Not CanEditCheckbox.IsChecked Then
                t1.SelectedIndex = 0
                MessageLabel.Content = "No Edit You! " & Now()
            Else
                MessageLabel.Content = "OK You Edit Now! " & Now()
            End If
        End If
    End Sub

End Class


Each time you click on the [Edit] tab you will see a message saying "No Edit You!" with the time after it. However after you have clicked it two or three times the time stops updating. In fact, the SelectionChanged event handler is no longer being called. That's because we're changing the SelectedIndex back to zero on the same thread. It immediately calls the SelectionChanged event handler again and stuff gets very messy.

To fix the problem we have to set the index back in a background thread. Fortunately WPF makes this fairly easy. Try changing the code behind like this (note the new Import)...


Imports System.Windows.Threading

Class MainWindow

    Private Sub t1_SelectionChanged_1(sender As Object, e As SelectionChangedEventArgs)

        If t1.SelectedIndex = 1 Then
            If Not CanEditCheckbox.IsChecked Then
                Application.Current.Dispatcher.BeginInvoke(DirectCast(Sub() t1.SelectedIndex = 0, Action), DispatcherPriority.Render, Nothing)
                MessageLabel.Content = "No Edit You! " & Now()
            Else
                MessageLabel.Content = "OK You Edit Now! " & Now()
            End If
        End If
    End Sub

End Class


All we did was create a lambda function to change the SelectedIndex and run it in a lower priority background thread. It doesn't get executed until our event handler is completed and nothing gets messed up.

Base Classes and WPF

Well I got far enough into my application's first page to understand how much I can put into a base class. I discovered a gotcha that had me scratching my head for a while, so when I found the solution I thought I better write it down. Here it is...

Let's suppose you have a XAML page, let's call it OrderEntry. Your XAML and code behind will be something like this...
<UserControl x:Class="OrderEntry"..../>
</UserControl>


Public Class OrderEntry
.
.
.
End Class

Now in order to subclass OrderEntry I created a base class called BaseOrderScreens and inherited OrderEntry from the new class. I thought all I would have to do is add an Inherits clause so that the code behind looks like this...

Public Class BaseOrderScreens
    Inherits System.Windows.Controls.UserControl

End Class



Public Class OrderEntry
    Inherits BaseOrderScreens
.
.
.
End Class

But when I made the change and compiled I got a strange error message.
Base class 'System.Windows.Controls.UserControl' specified for class 'OrderEntry' cannot be different from the base class 'BaseOrderScreens' of one of its other partial types.
This is where it gets fun. The error is in the generated partial class OrderEntry.g.vb which is inheriting the partial OrderEntry class from UserControl. My part of the OrderEntry class inherits the class from BaseOrderScreens. The compiler does not allow multiple inheritance so I have to convince the generated code to inherit from BaseOrderScreens. How to do this?

It turns out the generated partial class is inheriting OrderEntry from UserControl because my XAML has a root element with a TagName of UserControl. If the root element was OrderEntry all would be good. I have to modify my XAML to contain a reference to my namespace (see xmlns:local) and then alter the root element. It now looks like this...

<local:BaseOrderScreens x:Class="OrderEntry"
             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:OrderProcessing"
             mc:Ignorable="d" 
             d:DesignHeight="800" d:DesignWidth="1000">
.
.
.
</local:BaseOrderScreens >

To make this even more difficult to figure out, the VS editor highlights the root element name as an error until you compile.

Sunday, December 15, 2013

Creating a non-rectangular user control

My new project has a consistent tree on the left of every page, a grid splitter, and a user control on the right. The user control changes depending on what the user is doing and each page has a custom set of buttons in a panel at the bottom. Unfortunately several pages have more buttons than can be fit across the bottom of the user control, especially when the page is displayed at 1024x768 resolution. I proposed using a wrap panel so the 1280x1024 users would see a single row and the 1024x768 users would see the buttons wrapped. But noooooooooooooo we have to do everything the hard way. The ladies that make these decisions want the button bar to spread across the entire screen. In other words they want a non-rectangular user control. Obviously this is impossible.

I could have added a second user control to the main page - one for the bulk of the controls and the other for the buttons, but wiring up routed commands between two different user controls is difficult and fragile. It also requires a lot of work at run time. So I decided to leverage negative margins. All I have to do is initialize the margin of the button panel at startup and also adjust it as the grid splitter moves. This turns out to require less than ten lines of code. Then the buttons are still inside the same user control as the rest of the page and no special wiring is required.

Here's the xaml for the MainWindow.
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Width="1024" Height="768">
    <Grid Name="MainGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Name="TreeCol" Width="200"></ColumnDefinition>
            <ColumnDefinition Name="SplitterCol" Width="10"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Background="Pink" Content="Tree"/>
        <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DragDelta="GridSplitter_DragDelta"/>
        <UserControl Name="UC" Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"/>
    </Grid>
</Window>

The MainWindow code behind is responsible for tracking the movement of the grid splitter.

Class MainWindow
Public Sub New()
    InitializeComponent()
    UC.Content = New UserControl1
End Sub

Private Sub GridSplitter_DragDelta(sender As Object, e As Primitives.DragDeltaEventArgs)
    Dim d As Double = e.HorizontalChange
    ' The user control saved the button panel so we can get it here
    ' Each user control will save its button panel
    Dim ButtonLabel As FrameworkElement = Application.Current.Properties("ButtonPanel")
    Dim m As Thickness = ButtonLabel.Margin
    m.Left -= d
    ButtonLabel.Margin = m
End Sub
End Class

Now for the user control. We could have embedded it into MainWindow but my project requires the ability to dynamically load user controls. Fortunately this doesn't increase complexity very much. It only requires the user control to save a reference to its button panel in Application.Resources so that MainWindow can find it easily and adjust its left margin as the grid splitter moves. The User Control xaml looks like this.
<UserControl x:Class="UserControl1"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.CommandBindings>
        <CommandBinding Command="Open" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"/>
    </UserControl.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="100"></RowDefinition>
        </Grid.RowDefinitions>
        <Label Name="ClickPanel" Grid.Row="0" Content="Click [Open]"/>
        <Label Name="ButtonLabel" Grid.Row="1" Background="Beige" Margin="-210,0,0,0">
            <StackPanel Orientation="Horizontal">
                <Button Content="Open" Command="Open"/>
            </StackPanel>
        </Label>
    </Grid>
</UserControl>

Note how we are able to use regular command bindings - that was the whole point of this exercise. The initial left margin of the Button Label is -210 which is the initial width of the first and second columns of the MainWindow grid. Now the only thing the user control's code behind has to do is save a reference to its button panel to the Application Resources like this...

Public Class UserControl1

    Public Sub New()
        InitializeComponent()
        Application.Current.Properties("ButtonPanel") = ButtonLabel
    End Sub
    Private Sub CommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs)
        ClickPanel.Content = "Well Done!"
    End Sub

    Private Sub CommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
        e.CanExecute = True
        e.Handled = True
    End Sub
End Class

Note how the user control's code behind implements the command's CanExecute and Executed methods. Everything is in the user control, the CommandBinding, the buttons, and the CanExecute and Executed commands. This keeps everything simple(r).
The words "Click [Open]" and the [Open] button are part of the same user control. The Tree is in the parent window.

Friday, December 13, 2013

Finding a column in a DataGrid by Name

WPF Version 4.0

The various types of datagrid columns do not expose a Name attribute, probably because they do not derive from FrameworkElement, unlike the parent DataGrid class. Pretty much every class in WPF does expose an x:Name attribute so the markup...

<DataGridTextColumn x:Name="Description" Binding="{Binding Path=Description}"/>

will assign the name "Description" to the column. But how do we find it in code? Easy...

MyDataGrid.FindName("Description")

which is much nicer than...

MyDataGrid.Columns(8)

But, there's a gotcha. Consider the markup below...

<DataGrid Name="DataGrid1" ItemsSource="{Binding Path=Path1}" AutoGenerateColumns="false">
    <DataGrid.Columns>
        <DataGridTextColumn x:Name="Description" Binding="{Binding Path=Description}"/>
    </DataGrid.Columns>
</DataGrid>
<DataGrid Name="DataGrid2" ItemsSource="{Binding Path=Path2}" AutoGenerateColumns="false">
    <DataGrid.Columns>
        <DataGridComboBoxColumn x:Name="Description" Binding="{Binding Path=Description}"/>
    </DataGrid.Columns>
</DataGrid>

Now watch this carefully. What do you think this line of code will do?

Debug.Print(DirectCast(DataGrid2.FindName("Description"),DataGridComboBoxColumn).ToString())

It will actually throw an invalid cast exception. Even though you executed the FindName method on DataGrid2 it will return the Description column from DataGrid1 which is a DataGridTextColumn and cannot be cast to DataGridComboBoxColumn.

Columns must be uniquely named in the Window or User Control.

Even though you cannot retrieve the name of a column in a grid's column collection i.e. there is no way to code If Grid1.columns(0).Name = "Col1" Then, you can test for equality like this...

If Grid1.Columns(0) is Col1 Then...

Note: In WPF 4.5 under VS2012 you get a design time error if you give the same name to two columns in different grids.

Binding a Window's DataContext to the Window

I have a window that displays information contained in list of objects. I want the window's datacontext to be the window itself, then have a grid with an itemssource that points to the list. The tricky part turns out to be setting the window's datacontext. The obvious way to do this is with a line of code like this...

    Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        Me.DataContext = Me
    End Sub

But I want to do this in the XAML, not because I have to, but because I think I should be able to. I thought this fragment of xaml would do the trick...

<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">
    <Window.DataContext>
        <local:MainWindow/> 
    </Window.DataContext>
... 
But this throws a stack overflow error. The difference between the code behind and the markup is that the code behind sets the window's datacontext to the window whereas the markup sets the window's datacontext to a new window, and the new window has it's datacontext set to a new window, and the new window has... etc, etc.

The correct markup is...
DataContext="{Binding RelativeSource={RelativeSource Self}}"

This markup binds the window's datacontext to itself just like the code behind does. Here's an example of how to bind a window to itself, then bind a grid to a public property and list the contents.

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <DataGrid ItemsSource="{Binding Path=DC}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Key"  Binding="{Binding Path=Key}"/>
            <DataGridTextColumn Header="Name"  Binding="{Binding Path=Name}"/>
        </DataGrid.Columns>
    </DataGrid>
</Window>



Class MainWindow 

    Public Class cDC
        Public Property Key As Integer
        Public Property Name As String
        Public Sub New(Key As Integer, Name As String)
            Me._Key = Key
            Me._Name = Name
        End Sub
    End Class
    Public Property DC As New List(Of cDC) From {New cDC(1, "Alpha"), New cDC(2, "Beta"), New cDC(3, "Gamma")}

End Class

Thursday, December 12, 2013

Custom Markup with multiple constructors

As we saw in earlier blogs, it is easy to create Markup Extensions. They are simply classes that inherit from MarkupExtension. When you use positional, instead of named, parameters the xaml parser looks for a constructor that matches the signature in the markup. This lets us do some interesting things. Let's suppose we wanted to write a markup extension that calculates the area of a circle, rectangle, or triangle simply by passing one, two, or three integers. We just write three constructors. Here's the markup extension code.

Imports System.Windows.Markup
Public Class AreaExtension
    Inherits MarkupExtension

    Private Area As Double
    Private Text As String
    Public Sub New(r As Integer)
        Area = Math.PI * (r ^ 2)
        Text = "Area of circle of radius " & r & " is " & Math.Round(Area, 2)
    End Sub
    Public Sub New(x As Integer, y As Integer)
        Area = x * y
        Text = "Area of rectangle of sides " & x & " and " & y & " is " & Area
    End Sub
    Public Sub New(s1 As Integer, s2 As Integer, s3 As Integer)
        Dim s As Integer = (s1 + s2 + s3) / 2
        Area = Math.Sqrt(s * (s - s1) * (s - s2) * (s - s3))
        Text = "Area of triangle with sides " & s1 & ", " & s2 & ", and " & s3 & " is " & Math.Round(Area, 2)
    End Sub
    Public Overrides Function ProvideValue(serviceProvider As System.IServiceProvider) As Object
        Return Text
    End Function
End Class
See the three constructors? Now this is how we call them.
<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">
    <WrapPanel>
        <Label Width="400" Content="{local:Area 2}"/>
        <Label Width="400" Content="{local:Area 3,4}"/>
        <Label Width="400" Content="{local:Area 5,6,7}"/>
    </WrapPanel>
</Window>

And here's the result.

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>

Thursday, November 28, 2013

A simple converter

This entry references WPF 4.0

In the previous post I showed how to bind a non dependent property using styles. But the value I'm binding to is an integer and the property I'm binding to is a brush so I need a converter. This is how I did it.

1. The first thing to do is write the converter. This is a class that implements IValueConverter. I put all my converters in a single file but it's up to you. You have to write a Convert and a ConvertBack method. I didn't worry about ConvertBack because I'm only binding one way. If I pass 1 it returns a red brush otherwise it returns a black brush. It looks like this.

Public Class ErrorToBrushConverter
    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

        If TypeOf value Is Integer Then
            Select Case value
                Case 1
                    Return New SolidColorBrush(System.Windows.Media.Colors.Red)
                Case Else
                    Return New SolidColorBrush(System.Windows.Media.Colors.Black)
            End Select
        Else
            Return New SolidColorBrush(System.Windows.Media.Colors.Black)
        End If
    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
        Return 0
    End Function
End Class

2. It is very important to build the project before the next step. Otherwise you will try to reference a class that doesn't exist which will throw an error that will prevent you from compiling the class. Catch-22.

3. Add a reference to your namespace somewhere in the logical tree so you can reference your new converter class. The easiest way is to add this to your Window or UserControl tag in the xaml. You may even have this already for other resources.

xmlns:local="clr-namespace:<your namespace>"

4. Now create a static resource in the same window or usercontrol.

<window.resources>
    <local:ErrorToBrushConverter x:Key="ErrorToBrushConverter"/>
</window.resources>

The 'ErrorToBrushConverter' after local: is the name of the converter class we wrote. The 'ErrorToBrushConverter' after x:Key is the StaticResource name we reference in the Syle tag (see the previous post). This is how we tie the two together.

So the Style line (see previous post) that says...
<Setter Property="Foreground" Value="{Binding Path=HasErrors, Converter={StaticResource ErrorToBrushConverter}}"/>

effectively takes the integer value of the HasErrors field of the underlying dataarow for this grid row (which will be 0 or 1 because that's how I wrote the SQL), finds the converter specified in the StaticResource called ErrorToBrushConverter, calls the Convert method passing the value of HasErrors, takes the return value which will be a black or red brush, and assigns it to the Foreground property. And it does this quickly.

This might seem very complex but it's really not that much more complex that writing a stylesheet and referencing it. The good new is that, like a stylesheet, it's very reusable. You can put the static resource definition in a resource file or in the application's resources so every page of your application can use it. You can even declare the Style itself as a static resource which makes attaching it to a datagridtextcolumn very simple.

Change property in one datagrid column based on a value in another

This entry references WPF 4.0

This is the problem that drove me to create this blog. I wanted to change the foreground property of the document number in a datagrid to indicate when the document contained errors. With an Ultrawebgrid in .Net I would have written a RowIntialize event handler, checked the content of a hidden HasErrors column, and changed the color of the document number cell accordingly.

I Googled the problem and found about four different answers. One of them showed how to modify the Textblock class so that the foreground property became a dependent property and could be bound blah blah. I think the author must have been on crack. The answer is easy.

The STYLE tag is not your grandfather's stylesheet. It is dynamic. Style values can be bound and they can set any property, not just dependent properties. So you can use a STYLE to effectively bind any property.

So my <DataGridTextColumn> looks like this...

<DataGridTextColumn Header="Number" Binding="{Binding Path=DocumentNumber}">
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Foreground" Value="{Binding Path=HasErrors, Converter={StaticResource ErrorToBrushConverter}}"/>
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

There are a couple of things to note here..

It is difficult to get to the contents of the grid now. But you don't need to because it's so easy to get to the underlying data. I don't need a hidden column called HasErrors, it's just a column in the grid's dataview.

I needed to write a converter. As SQL can't return a brush type, and that's what the foreground property is, I have to convert an integer to a brush. 

Introduction

I'm creating this blog because I'm starting to use WPF and WCF on a new project at work, not because I'm experienced in using these technologies. The problem I'm seeing is that people like me are posting simple questions and getting bad answers. Part of the problem is we don't know the correct questions to ask and part of the problem is that other people don't stop to consider what problem they are trying to solve. Also WPF has evolved over the past five years and posts that made sense five years ago are irrelevant now.

Each of my posts will explain a problem I solved and will include the version of WPF I solved it with. That way, in three years when we are on version 6.0, readers can evaluate the relevance of my solution.