Josh Smith

Follow me down the rabbit hole of cutting edge technology.
Synchronizing Field Widths between FieldLayouts in XamDataGrid

This blog post shows how to force the width of Fields in a child FieldLayout to have the same width as the Fields in the parent/master FieldLayout.  This is a stopgap solution, useful only until XamDataGrid has native support for this feature.  The desired result is that the XamDataGrid's top-level records have resizable cells, and the non-resizable cells in child records keep their widths the same as the corresponding cells in their parent record.  Also, we will align the text in the child records with the text in the parent record, so that the XamDataGrid ends up looking like this:


In the screenshot above, the XamDataGrid has two master records, each of which represent a country.  The master records have child records, which represent states or cities within that country.  Every location in the grid has a name and a description, so it makes sense to keep all of the fields the same width and have their values line up.  This technique uses the hierarchical display support of XamDataGrid to provide a clean, simple listing of hierarchical data.  In addition, if you were to adjust the width of the Field headers or cells in a master record, the corresponding cells in the detail records would resize to the same width.

First, let's examine the XAML in the demo application's main Window that configures the XamDataGrid.  The most important parts are bold:

<igDP:XamDataGrid
  x:Name="xamDataGrid"
  DataSource="{Binding}"
  FieldLayoutInitialized="xamDataGrid_FieldLayoutInitialized"
  GroupByAreaLocation="None"
  >

  <igDP:XamDataGrid.Resources>
    <!--
    This Style enables us to monitor changes to field widths.
    -->
    <Style TargetType="{x:Type igDP:LabelPresenter}">
      <EventSetter
        Event="SizeChanged"
        Handler="OnLabelPresenterSizeChanged"
        />
    </Style>
  </igDP:XamDataGrid.Resources>

  <igDP:XamDataGrid.DataContext>
    <ObjectDataProvider
      MethodName="GetData"
      ObjectType="{x:Type local:DataSource}"
      />
  </igDP:XamDataGrid.DataContext>

  <igDP:XamDataGrid.FieldLayouts>
    <igDP:FieldLayout Key="master">
      <igDP:FieldLayout.Fields>
        <igDP:Field Name="ID" Visibility="Collapsed" />
        <igDP:Field Name="Name" />
        <igDP:Field Name="Description">
          <igDP:Field.Settings>
            <igDP:FieldSettings LabelWidth="260" CellWidth="260" />
          </igDP:Field.Settings>
        </igDP:Field>
      </igDP:FieldLayout.Fields>
      <igDP:FieldLayout.FieldSettings>
        <igDP:FieldSettings>
          <igDP:FieldSettings.CellValuePresenterStyle>
            <Style TargetType="{x:Type igDP:CellValuePresenter}">
              <Setter Property="FontWeight" Value="Bold" />
            </Style>
          </igDP:FieldSettings.CellValuePresenterStyle>
        </igDP:FieldSettings>
      </igDP:FieldLayout.FieldSettings>
    </igDP:FieldLayout>

    <igDP:FieldLayout Key="detail">
      <igDP:FieldLayout.Fields>
        <igDP:Field Name="ParentID" Visibility="Collapsed" />
        <igDP:Field Name="Name" />
        <igDP:Field Name="Description" />
      </igDP:FieldLayout.Fields>
      <igDP:FieldLayout.FieldSettings>
        <igDP:FieldSettings AllowResize="False" LabelMaxHeight="0">
          <igDP:FieldSettings.CellValuePresenterStyle>
            <!--
            This Style aligns the child record cell text with the parent cells
            regardless of whether the record is selected, mouseover, etc.
            -->
            <Style TargetType="{x:Type igDP:CellValuePresenter}">
              <Setter Property="Margin" Value="-3,0,0,0" />
              <Style.Triggers>
                <DataTrigger
                  Binding="{Binding
                    RelativeSource={RelativeSource AncestorType={x:Type igDP:DataRecordPresenter}},
                    Path=IsMouseOver}"
                  Value="True"
                  >
                  <Setter Property="Margin" Value="-3,0,0,0" />
                </DataTrigger>
                <DataTrigger
                  Binding="{Binding
                    RelativeSource={RelativeSource AncestorType={x:Type igDP:DataRecordPresenter}},
                    Path=IsSelected}"
                  Value="True"
                  >
                  <Setter Property="Margin" Value="-3,0,0,0" />
                </DataTrigger>
              </Style.Triggers>
            </Style>
          </igDP:FieldSettings.CellValuePresenterStyle>
        </igDP:FieldSettings>
      </igDP:FieldLayout.FieldSettings>
      <igDP:FieldLayout.Settings>
        <igDP:FieldLayoutSettings HighlightAlternateRecords="True" />
      </igDP:FieldLayout.Settings>
    </igDP:FieldLayout>
  </igDP:XamDataGrid.FieldLayouts>

  <igDP:XamDataGrid.FieldSettings>
    <igDP:FieldSettings AllowEdit="False" CellClickAction="SelectRecord" />
  </igDP:XamDataGrid.FieldSettings>

</igDP:XamDataGrid>

All of the magic here is done via Styles.  The control's Resources collection has a typed Style that targets LabelPresenter.  That Style adds a handler to a LabelPresenter's SizeChanged event.  This is how we can detect changes to the width of Fields at runtime.  The other Style is applied to the child FieldLayout's CellValuePresenterStyle.  It ensures that the text in the child record cells is pushed a little to the left, thus ensuring that it lines up with the text in the parent row.

The code-behind is not too complicated.  It just handles the SizeChanged event of every LabelPresenter to verify that the child Fields are the same width as the parent Fields.  It also handles the FieldLayoutInitialized event of the XamDataGrid so that it can bind the child Field widths to the parent Field widths.  The demo Window's code is below:

public partial class Window1 : Window
{
    FieldLayout _masterFieldLayout;

    public Window1()
    {
        InitializeComponent();
    }

    // Invoked when a field in the datagrid is resized.
    void OnLabelPresenterSizeChanged(object sender, SizeChangedEventArgs e)
    {
        var pres = sender as LabelPresenter;
        if (pres == null || pres.Field.Owner != _masterFieldLayout)
            return;

        // Ignore tiny changes because they can lead to infinite layout loops.
        double diff = Math.Abs(pres.Field.Settings.LabelWidth - pres.ActualWidth);
        if (diff <= 1)
            return;

        // Set the LabelWidth property so that the LabelWidthResolved is recalculated.
        // That forces the binding to update the width of the corresponding field in
        // the detail layout.
        pres.Field.Settings.LabelWidth = pres.ActualWidth;
    }

    void xamDataGrid_FieldLayoutInitialized(object sender, FieldLayoutInitializedEventArgs e)
    {
        if (_masterFieldLayout == null)
        {
            _masterFieldLayout = e.FieldLayout;
        }
        else
        {
            // Get all of the visible fields in the master layout.
            List<Field> masterFields =
                (from f in _masterFieldLayout.Fields
                 where f.VisibilityResolved == Visibility.Visible
                 select f)
                .ToList();

            // Get all of the visible fields in the detail layout.
            List<Field> detailFields =
                (from f in e.FieldLayout.Fields
                 where f.VisibilityResolved == Visibility.Visible
                 select f)
                .ToList();

            int iterations = Math.Min(masterFields.Count, detailFields.Count);

            // Bind the width of each field in the detail layout to
            // the resolved/actual width of the corresponding field
            // in the master layout.
            for (int n = 0; n < iterations; ++n)
            {
                BindingOperations.SetBinding(
                    detailFields[ n ].Settings,
                    FieldSettings.LabelWidthProperty,
                    new Binding
                    {
                        Path = new PropertyPath("LabelWidthResolved"),
                        Source = masterFields[ n ]
                    });
            }
        }
    }
}

Download the source code here.  The solution was built and tested in Visual Studio 2008, using NetAdvantage for WPF v8.1.

Data Binding the IsVisible Property of ContextualTabGroup

XamRibbon allows you to have groups of tabs that only display when the application is in a certain state or condition.  In other words, it offers context-sensitive tab groups.  You can use them by adding ContextualTabGroup objects to the ContextualTabGroups property of XamRibbon.  If you want to hide a ContextualTabGroup, you simply set its IsVisible property to false.

What if you want to data bind IsVisible to a property on a ViewModel object?  Then the situation becomes a little bit more difficult, because ContextualTabGroup is not a visual element and does not exist in the element tree.  A brief glance in Mole can prove that this is the case:


Notice how the Visual Tree, on the left, does not contain a ContextualTabGroup instance, but the Visual Tree elements have their DataContext set to an instance of ContextualTabGroup.  It turns out that ContextualTabGroup is more like a container for settings, not a full-fledged UI element.  One important ramification of this seemingly minor detail is that ContextualTabGroup objects are not in the logical tree, which means they do not have an inheritance context, which means that their properties cannot be data bound like properties of a normal UI element in the element tree.

I recently wrote of a way to work around the fact that some objects declared in XAML do not have an inheritance context, in my “Binding a XamDataGrid Field Property” post.  In this post, I will show another, far more elegant, way to deal with this problem.  I am basing my work here on the “Model-see, Model-do, and the Poo is Optional” post by Mike Hillberg, of Microsoft.

My objective is simple.  I want to have a CheckBox bound to a property on a simple ViewModel class, and the IsVisible property of a ContextualTabGroup bound to that same property.  When the CheckBox is checked, the tab group displays.


When the CheckBox is unchecked, the tab group goes away.

The ViewModel class, an instance of which serves as the Window’s DataContext, looks like this:

class MyViewModel: INotifyPropertyChanged
{
    bool _showContextualTabGroup;
    public bool ShowContextualTabGroup
    {
        get { return _showContextualTabGroup; }
        set
        {
            if (value == _showContextualTabGroup)
                return;

            _showContextualTabGroup = value;

            this.OnPropertyChanged("ShowContextualTabGroup");
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

The trick to making this work is in my DataContextSpy class.  It provides an artificial inheritance context with which the ContextualTabGroup can gain access to the Window’s ViewModel via one simple data binding expression.   Naturally, you can use DataContextSpy for much more than just this one particular task.  Here is the class definition:

public class DataContextSpy
    : Freezable // Enable ElementName and DataContext bindings
{
    public DataContextSpy()
    {
        // This binding allows the spy to inherit a DataContext.
        BindingOperations.SetBinding(this, DataContextProperty, new Binding());
    }

    public object DataContext
    {
        get { return (object)GetValue(DataContextProperty); }
        set { SetValue(DataContextProperty, value); }
    }

    // Borrow the DataContext dependency property from FrameworkElement.
    public static readonly DependencyProperty DataContextProperty =
        FrameworkElement.DataContextProperty.AddOwner(typeof(DataContextSpy));
           
    protected override Freezable CreateInstanceCore()
    {
        // We are required to override this abstract method.
        throw new NotImplementedException();
    }
}

Now here is the XAML content of the Window, which contains the XamRibbon and DataContextSpy:

<DockPanel>
  <CheckBox
    DockPanel.Dock="Bottom"
    Content="Show ContextualTabGroup"
    IsChecked="{Binding Path=ShowContextualTabGroup}"
    Margin="10"
    />

  <igRibbon:XamRibbon DockPanel.Dock="Top">
    <igRibbon:XamRibbon.Resources>
      <local:DataContextSpy x:Key="spy" />
    </igRibbon:XamRibbon.Resources>

    <igRibbon:XamRibbon.ContextualTabGroups>
      <igRibbon:ContextualTabGroup
        Caption="I am a ContextualTabGroup"
        IsVisible="{Binding
          Source={StaticResource spy},
          Path=DataContext.ShowContextualTabGroup}"

        >
        <igRibbon:RibbonTabItem
          Header="I am a RibbonTabItem in the ContextualTabGroup"
          />
      </igRibbon:ContextualTabGroup>
    </igRibbon:XamRibbon.ContextualTabGroups>
  </igRibbon:XamRibbon>   
 
</DockPanel>

You can download the source code here. Note: this project requires Visual Studio 2008 and NetAdvantage for WPF v7.2 or later to compile.

Persisting XamDataGrid Field Widths between Runs of an Application
XamDataGrid has many great features, but as of NetAdvantage for WPF v7.2, it does not yet provide a generic way to save and load user settings.  A common requirement for a grid-centric application is that the columns/fields in the grid retain their widths from one run of the application to the next.  I have created a stopgap solution, in the form of a utility class, that we can use until XamDataGrid has native support for this functionality.

I created a class called XamDataGridFieldWidthManager that you can use to handle the saving, loading, and applying of field widths.  It will take a XamDataGrid like this...

 

...and, upon calling its SaveFieldWidths method, it will generate the following XML...

<?xml version="1.0" encoding="utf-8"?>
<field-layouts>
  <field-layout id="master">
    <field name="ID" width="52" />
    <field name="Column 1" width="77" />
    <field name="Column 2" width="71" />
    <field name="Column 3" width="148" />
    <field name="Column 4" width="168" />
  </field-layout>
  <field-layout id="detail">
    <field name="ParentID" width="258" />
    <field name="Whatever" width="190" />
  </field-layout>
</field-layouts>

You can save that XML somewhere, and next time that you display that XamDataGrid in your program, you pass the XML back to XamDataGridFieldWidthManager so that those persisted field widths are applied to the new fields.

Here is how the demo program uses XamDataGridFieldWidthManager:

public partial class Window1 : Window
{
    const string SETTINGS_FILE = "field-widths.xml";
    readonly XamDataGridFieldWidthManager _widthManager;

    public Window1()
    {
        this.InitializeComponent();

        _widthManager = this.CreateWidthManager();

        // NOTE: You *must* attach a handler to the RequestFieldLayoutID event.
        _widthManager.RequestFieldLayoutID += this.OnWidthManagerRequestFieldLayoutID;
    }

    XamDataGridFieldWidthManager CreateWidthManager()
    {
        StreamReader streamReader = null;
        try
        {
            XmlReader xmlReader = null;

            if (File.Exists(SETTINGS_FILE))
            {
                streamReader = new StreamReader(SETTINGS_FILE);
                xmlReader = XmlReader.Create(streamReader);
            }

            return new XamDataGridFieldWidthManager(this.xamDataGrid, xmlReader);
        }
        finally
        {
            if (streamReader != null)
                streamReader.Dispose();
        }
    }

    void OnWidthManagerRequestFieldLayoutID(object sender, RequestFieldLayoutIDEventArgs e)
    {
        // Provide a unique identifier for each of the FieldLayouts in the XamDataGrid.

        if (e.FieldLayout.Key == this.xamDataGrid.DataSource)
            e.UniqueID = "master";
        else
            e.UniqueID = "detail";
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        base.OnClosing(e);

        if (!e.Cancel)
        {
            // Save the field widths to disk.
            var settings = new XmlWriterSettings { Indent = true };
            using (var xmlWriter = XmlWriter.Create(SETTINGS_FILE, settings))
                _widthManager.SaveFieldWidths(xmlWriter);
        }
    }
}

It is important to note that you must hook the RequestFieldLayoutID event and, in that event handling method, supply a unique identifier for each FieldLayout in the XamDataGrid.  This is necessary because XamDataGridFieldWidthManager must be able to map a <field-layout> XML element to a FieldLayout object, but it has no way to produce the same unique identifier for the same logical FieldLayout  across runs of the application.  Since I have no way to do that in a generic fashion, I consult the class's consumer for that information.

Download the source code and demo project here.  You must have Visual Studio 2008 and NetAdvantage for WPF v7.2 or later installed.
Putting a CheckBox in the cells of an UnboundField of XamDataGrid

This blog post demonstrates the correct way to put a CheckBox into the cell of an unbound field for each row in a XamDataGrid.  The demo application allows the user to dynamically add and remove customers from a 3D Pie Chart, to compare their total sales against each other.  The image below is a screenshot of this demo:

The “Compare” field on the left-hand side is an “unbound field.”  This means that it does not have a corresponding property on the data source object to which it is bound.  You can add as many UnboundField objects into a FieldLayout as you like.

This demo application’s data source is an array of my Customer class, as seen below:

public class Customer
{
    public Customer(int id, string name, double totalSales)
    {
        this.ID = id;
        this.Name = name;
        this.TotalSales = totalSales;
    }

    public int ID { get; private set; }
    public string Name { get; private set; }
    public double TotalSales { get; private set; }
}

The window contains a XamDataGrid and XamChart.  When the user checks a CheckBox in a row of the grid, we add the corresponding Customer’s data to the chart.  Here is the XAML declaration of the XamDataGrid:

<igDP:XamDataGrid
  Grid.Row="0"
  AutoFit="True"
  DataSource="{Binding}"
  InitializeRecord="XamDataGrid_InitializeRecord"
  Theme="Office2k7Black"
  >
  <igDP:XamDataGrid.CommandBindings>
    <!--
    Listen for our custom ShowInChart command.
    -->
    <CommandBinding
      Command="local:Commands.ShowInChart"
      CanExecute="ShowInChart_CanExecute"
      Executed="ShowInChart_Executed"
      />
  </igDP:XamDataGrid.CommandBindings>

  <igDP:XamDataGrid.Resources>
    <!--
    Displays a CheckBox in the ShowInChart field.
    -->
    <Style
      x:Key="ShowInChartCellStyle"
      TargetType="{x:Type igDP:CellValuePresenter}"
      >
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
            <CheckBox
              Command="local:Commands.ShowInChart"
              CommandParameter="{Binding Path=DataItem}"
              HorizontalAlignment="Center"
              IsChecked="{Binding
                  RelativeSource={RelativeSource TemplatedParent},
                  Path=Value,
                  Mode=TwoWay}"
              VerticalAlignment="Center"
              />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <!--
    Applies currency formatting to cells in the TotalSales field.
    -->
    <Style
      x:Key="TotalSalesEditorStyle"
      TargetType="{x:Type igEditors:ValueEditor}"
      >
      <Setter Property="Format" Value="c" />
    </Style>
  </igDP:XamDataGrid.Resources>

  <igDP:XamDataGrid.FieldLayouts>
    <igDP:FieldLayout>
      <igDP:FieldLayout.Fields>
        <igDP:UnboundField Name="ShowInChart" Label="Compare">
          <igDP:UnboundField.Settings>
            <igDP:FieldSettings
              CellMaxWidth="70" LabelMaxWidth="70"
              CellValuePresenterStyle="{StaticResource ShowInChartCellStyle}"
              />
          </igDP:UnboundField.Settings>
        </igDP:UnboundField>
        <igDP:Field Name="ID">
          <igDP:Field.Settings>
            <igDP:FieldSettings
              CellMaxWidth="40"
              LabelMaxWidth="40"
              />
          </igDP:Field.Settings>
        </igDP:Field>
        <igDP:Field Name="Name" Label="Customer Name" />
        <igDP:Field Name="TotalSales" Label="Total Sales">
          <igDP:Field.Settings>
            <igDP:FieldSettings
              EditorStyle="{StaticResource TotalSalesEditorStyle}"
              />
          </igDP:Field.Settings>
        </igDP:Field>
      </igDP:FieldLayout.Fields>
    </igDP:FieldLayout>
  </igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>

In the grid’s FieldLayouts section, there is one FieldLayout being created.  It’s first field is our UnboundField, named “ShowInChart.”  Notice that its CellValuePresenterStyle property is set to reference the “ShowInChartCellStyle” Style, which is declared above.  That Style applies a ControlTemplate to the CellValuePresenter in each cell of that field.  The ControlTemplate specifies that a CheckBox control should be displayed in the cell.  Looking closely at the CheckBox declaration, you can see that it’s IsChecked property is bound to its TemplatedParent, which in this case is the CellValuePresenter being templated.  We need to establish this two-way binding against the CellvaluePresenter’s Value property so that the CheckBox and cell value are kept in sync with each other.  If we did not do this, the grid would be in an inconsistent state.

When the Window loads up and the XamDataGrid is populated with data, the grid’s InitializeRecord event is handled for each row.  That code initializes the unbound field’s cell value to false, as seen below:

void XamDataGrid_InitializeRecord(object sender, InitializeRecordEventArgs e)
{
    // Initialize each ShowInChart cell to false.
    DataRecord dataRecord = e.Record as DataRecord;
    if (dataRecord != null)
        dataRecord.Cells["ShowInChart"].Value = false;
}

So far so good, but what happens when the user checks or unchecks one of those CheckBoxs?  How does the program know which Customer’s CheckBox was affected, and when it is changed?  This problem can be solved in several ways, but I prefer to use routed commands for this type of task.  In the demo app, the Commands.cs file contains this class:

public static class Commands
{
    public static readonly RoutedCommand ShowInChart = new RoutedCommand();
}

As seen in the XAML declaration previously, the XamDataGrid is given a CommandBinding for the ShowInChart command.  The command’s events are handled in the window’s code-behind, as seen below:

void ShowInChart_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    // The ShowInChart command can execute if
    // the parameter references a Customer.
    e.CanExecute = e.Parameter is Customer;
}

void ShowInChart_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Customer cust = e.Parameter as Customer;
    if (cust != null)
        this.AddOrRemoveCustomer(cust);
}

void AddOrRemoveCustomer(Customer cust)
{
    DataPointCollection dataPoints = this.chart.Series[0].DataPoints;

    string label = "Cust #" + cust.ID;

    // Check if the customer is in the chart already.
    var dataPoint = dataPoints.FirstOrDefault(dp => dp.Label == label);

    if (dataPoint == null)
    {
        // Add the customer to the pie chart.
        string tooltip =
            String.Format(
            "Name: {0} {1} Total Sales: {2}",
            cust.Name,
            Environment.NewLine,
            cust.TotalSales.ToString("c"));

        dataPoint = new DataPoint
        {
            Label = label,
            Value = cust.TotalSales,
            ToolTip = tooltip
        };

        dataPoints.Add(dataPoint);
    }
    else
    {
        // Remove the customer from the pie chart.
        dataPoints.Remove(dataPoint);
    }
}

You can download the demo source code here.  You must have Visual Studio 2008 and NetAdvantage for WPF v7.2 (or later) installed to compile and run this project.

Binding a XamDataGrid Field Property

I recently needed to bind the Visibility of a Field in a XamDataGrid to a property on my ViewModel.  I wanted to provide a way for the user to show/hide a column of images in the data grid.  My ViewModel object is a POCO (Plain Old Clr Object) that implements INotifyPropertyChanged, and represents the data and UI state of my Window.  Unfortunately a Field object is not in the element tree and does not derive from Freezable, which means it does not have an inheritance context, thus its properties cannot participate in normal WPF data binding. 

If/when WPF eventually provides a way to give any object an inheritance context, this won't be an issue, but for now there is no clean way to bind a Field's properties to objects inherited down the logical tree, or to the properties of other elements.  This blog post shows a workaround that I came up with, allowing me to easily bind the Visibility of a Field to any object.

Here is the basic idea of what I'm trying to accomplish.  First the grid displays a column of images:

 

If you then click the "Show Photos" CheckBox in the ToolBar, the column of photos is hidden, as seen below:

 

 Here is the code-behind of the Window:

public partial class Window1 : Window
{
    public Window1()
    {
        Person[] people = new Person[]
        {
            new Person("Boss Hogg", 42, "hogg.jpg"),
            new Person("Johann Bach", 50, "bach.jpg"),
            new Person("Mugatu", 39, "mugatu.gif"),
            new Person("Simon Wolcott", 24, "wolcott.jpg")
        };

        base.DataContext = new CommunityViewModel(people);
        Application.Current.Resources.Add("DATA_CommunityViewModel", base.DataContext);

        InitializeComponent();
    }
}

Notice that I'm adding the CommunityViewModel class to both the DataContext of the Window and the Resources of the App.  I set the DataContext to the ViewModel so that the XamDataGrid and CheckBox controls can bind to its properties.  I add it to the App's Resources so that the Field's Visibility binding can access it.  Here is the CommunityViewModel class:

class CommunityViewModel : INotifyPropertyChanged
{
    bool _showPhotos;

    public CommunityViewModel(IList<Person> constituents)
    {
        this.Constituents = new ReadOnlyCollection<Person>(constituents);
        _showPhotos = true;
    }

    public ReadOnlyCollection<Person> Constituents { get; set; }

    public bool ShowPhotos
    {
        get { return _showPhotos; }
        set
        {
            if (value == _showPhotos)
                return;

            _showPhotos = value;

            this.OnPropertyChanged("ShowPhotos");
        }
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

The magic happens in the XAML.  Pay close attention to the 'Photo' Field declaration:

<Window
  x:Class="XamDataGridWithBoundField.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:igDP="http://infragistics.com/DataPresenter"
  Title="Window1"
  Width="400" Height="400"
  >
  <Window.Resources>
    <Style
      x:Key="PhotoCellStyle"
      TargetType="{x:Type igDP:CellValuePresenter}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
            <Image
              Source="{Binding
                RelativeSource={RelativeSource TemplatedParent},
                Path=Content}"
              Width="60" Height="60"
              />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Window.Resources>
  <DockPanel>
    <ToolBar DockPanel.Dock="Top">
      <CheckBox
        Content="Show Photos"
        IsChecked="{Binding Path=ShowPhotos}"
        />

    </ToolBar>

    <igDP:XamDataGrid
      AutoFit="True"
      DataSource="{Binding Constituents}"
      >
      <igDP:XamDataGrid.FieldLayouts>
        <igDP:FieldLayout>
          <igDP:FieldLayout.Fields>
            <igDP:Field Label="Name" Name="Name" />
            <igDP:Field Label="Photo" Name="ImageUri">
              <igDP:Field.Settings>
                <igDP:FieldSettings
                  CellMaxWidth="70" LabelMaxWidth="70"
                  CellValuePresenterStyle="{StaticResource PhotoCellStyle}"
                  />
              </igDP:Field.Settings>
              <igDP:Field.Visibility>
                <Binding
                  Path="ShowPhotos"
                  Source="{StaticResource DATA_CommunityViewModel}"
                  >
                  <Binding.Converter>
                    <BooleanToVisibilityConverter />
                  </Binding.Converter>
                </Binding>
              </igDP:Field.Visibility>
            </igDP:Field>

            <igDP:Field Label="Age" Name="Age" />
          </igDP:FieldLayout.Fields>
        </igDP:FieldLayout>
      </igDP:XamDataGrid.FieldLayouts>
    </igDP:XamDataGrid>
  </DockPanel>
</Window>


You can download the demo source code here.  You will need to have the Infragistics NetAdvantage for WPF installed to run the application.

Using a ViewModel to harness the power of the WPF TreeView

Microsoft's WPF TreeView control can be very difficult to work with, if you try to use it the same way that you program against a Windows Forms TreeView.  WPF's TreeView is much more feature-rich and powerful, such as having support for data binding and full visual customizations.  Those powers make the control more complicated than the WinForms equivalent.  It can be difficult to use, but it does not have to be!

The key to using the WPF TreeView control properly is to provide it with a ViewModel, essentially an abstraction of the tree's state.  The TreeView binds to its ViewModel, allowing your code to ignore the TreeView altogether.  Once you start programming against your ViewModel abstraction, it is very easy to leverage the power of the TreeView.  I published an article about this topic on CodeProject.  If you are interested in learning more about it, here's the link: http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx

XamDataGrid validation via IDataErrorInfo

Karl Shifflett implemented a great way to have XamDataGrid honor the validation results exposed by a data object that implements the IDataErrorInfo interface.  Using this approach to input validation allows your application to keep the business object validation logic where it belongs...in the business objects.  I recommend you use Karl's approach until native support for IDataErrorInfo is added to XamDataGrid.

Karl's article can be found here.
 

Creating Objects that Support Edit Cancellation via IEditableObject

The IEditableObject interface provides controls with a way to allow a data source to react intelligently to being edited.  This might seem superfluous, until you consider that complex controls, such as a data grid, allow the user to cancel an editing session via the Escape key. 

For example, suppose the user is editing a row in the UltraGrid or XamDataGrid, and then realizes that he has been editing the wrong row.  If he presses the Escape key once, the grid automatically reverts the active cell to the value it had before editing began.  If the user hits Escape again, the entire row reverts to the values it had before the user started to edit its cells.

If the grid is bound to an ADO.NET container, such as DataTable, all of this magic happens for us automatically.  However, if the grid is bound to a collection of your own objects, such as custom business objects, this will not happen by default.  Your business objects will need to implement that logic, just as the ADO.NET containers do.  Perhaps your business objects will not implement that functionality, since it is not part of any business domain, but Presentation Model objects might, instead.  Regardless of the lingo, at the end of the day you will need to implement this logic somewhere!

Fortunately, this is quite easy to do.  The IEditableObject interface is all you need to implement, as seen below:

private BinaryFormatter _formatter = new BinaryFormatter();
private MyData _myState;
private MemoryStream _snapshot;


void IEditableObject.BeginEdit()
{
    if (_snapshot != null)
        return;

    _snapshot = new MemoryStream();
    _formatter.Serialize(_snapshot, _myState);
}

void IEditableObject.CancelEdit()
{
    if (_snapshot == null)
        return;

     // Restore our state to the snapshot taken when the editing session began.
     _snapshot.Position = 0;  
     _myState = _formatter.Deserialize(_snapshot) as MyData;
     this.ThrowAwaySnapshot();
}

void IEditableObject.EndEdit()
{
    this.ThrowAwaySnapshot();
}

void ThrowAwaySnapshot()
{
    if (_snapshot != null)
    {
        _snapshot.Dispose();
        _snapshot = null;
    }
}

This code assumes the MyData type (and all of its ancestor types) is decorated with the Serializable attribute, since it is serialized by the BinaryFormatter. 

The BeginEdit method starts with a check to see if we are already in an editing session.  If so, it immediately returns since there can only be one editing session at a time.  There is no guarantee regarding when and how often that method will be invoked, so this precautionary step is necessary.  When a new editing session begins, a snapshot of the object's state is taken and stored in a MemoryStream.

If the user cancels the editing session, the CancelEdit method executes.  That method deserializes the snapshot taken in BeginEdit, and applies the saved values to the editable object.  If the IEditableObject instance contains the various fields being edited, instead of having a reference to one object that contains all the values, your CancelEdit method will include code that sets all of those fields to whatever values were saved in the BeginEdit method.

When the user completes an editing session (i.e. finishes editing a row in the grid) the EndEdit method is invoked.  That gives us a chance to dispose of the snapshot data.  Once the snapshot is removed, a subsequent call to BeginEdit will cause a new editing session to begin.

New version of Tangerine

One of the reasons I love working at Infragistics is that we not only have awesome controls, but we also create cool real-world applications that put those controls to use.  Part of our mission here is to provide exemplary guidance on how to create modern user interfaces that provide great user experiences.  Those applications are referred to as 'exemplars.' 

When I joined Infragistics (for the second time) we already had a WPF exemplar, Tangerine. Now that NetAdvantage for WPF v7.2 has been released, and Visual Studio 2008 as well, we decided to upgrade Tangerine accordingly.  If would like to try out a free WPF-based e-commerce experience, read the source code, and review the documentation, please feel free to check it out!

http://community.infragistics.com/exemplars/tangerine.aspx

 

Automatic Properties and the BinaryFormatter

When I first encountered the C# 3.0 feature known as 'automatic properties' I had two, almost simultaneous, thoughts: "Oh great!" and "Oh no!"  Here is an automatic property:

public int Bar { get; set; }

This property does not reference a field, but I assure you it compiles just fine. This is very convenient for when you want to quickly add a property to a type, but do not want to bother typing all of this:

private int _bar;
public int Bar
{
    get { return _bar; }
    set { _bar = value; }
}

I was concerned about this great new language feature.  More than a few times in the past I have been burnt by the BinaryFormatter.  I would serialize an instance of a class, save it to disk, upgrade the DLL containing the class of the serialized object, and encounter an exception when deserializing that object during a subsequent run of the app.  If the type that has instances serialized was given a new field, had a field removed, or had a field renamed, the BinaryFormatter would throw an exception.  It demanded that the object being deserialized matched the type as which it was being instantiated. 

Here was my line of thinking...An automatic property uses a compiler-generated field as its backing store.  The name of that field is, practically speaking, random nonsense.  If I later decide to turn an automatic property into a normal property, with a corresponding field, I will have to change the name of that field.  The auto-generated field will disappear and my field will instead be used.  In that situation, wouldn't the BinaryFormatter throw an exception when deserializing an instance of the class, if it was serialized when the class still had an automatic property (with a compiler-generated field name)?  If so, that would be a subtle and nasty bug to track down!

I performed a test and discovered that this issue leaves us, the software debuggers of the world, in a bad situation.  It seems that BinaryFormatter has mellowed with age.  It does not throw exceptions any more when a field in the class is renamed or removed between object serialization and deserialization.  Instead, it just ignores the situation and moves on.  What this means is that, upon deserialization, the property that used to be "automatic" will no longer have its previous value.  Since its corresponding backing field was renamed, it is no longer set by BinaryFormatter.  To be honest, I would prefer that an exception was thrown in this situation just so that I knew right away that there was a problem. This is something to keep in mind when using and altering automatic properties in serializable types.

Here is the console app I created to test this:

#define AUTO

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace AutomaticPropertyTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Foo foo = null;

#if AUTO
            foo = new Foo();
            foo.Bar = 42;

            Console.WriteLine("Writing Foo with automatic property to file...");
            using (FileStream strm = new FileStream("asdf.dat", FileMode.Create))
                new BinaryFormatter().Serialize(strm, foo);
#else
            Console.WriteLine("Reading Foo with explicit backing field from file...");
            using (FileStream strm = new FileStream("asdf.dat", FileMode.Open))
                foo = new BinaryFormatter().Deserialize(strm) as Foo;
#endif

            Console.WriteLine("foo.Bar = " + foo.Bar);
            Console.ReadKey();
        }
    }

    [Serializable]
    class Foo
    {
#if AUTO
        public int Bar { get; set; }
#else
        private int _bar;
        public int Bar
        {
            get { return _bar; }
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException("value");

                _bar = value;
            }
        }
#endif
    }
}

 
First run the app with the AUTO token defined.  That will save an instance of the Foo class to disk, with an automatic property called Bar.  Then comment out the AUTO token definition and run again.  This replaces the Bar property with a "normal" property and field, and deserializes the Foo object from disk.  The result is that Bar, which was previously set to 42, will return 0.  In a real debugging scenario, this could be a nightmare to track down, especially if you were not aware of this subtle side-effect of altering automatic properties. 

I think a good rule of thumb is: do not use automatic properties in serializable types.

Posted: 04 Feb 2008, 19:53 | 12 Comments
Filed under:
Listen to NY Times Podcasts with Silverlight 2.0

I have spent a lot of time recently coming up to speed with Silverlight 2.0.  It is the alpha version of Silverlight that is programmable via .NET languages, such as C# and VB.NET.  It has been an interesting adventure so far, and I am excited to see how this platform matures.  Coming from a strong WPF background, I feel that there are many things missing in Silverlight, but the potential for a great cross-platform, cross-browser UI technology is definitely there.   If you would like to read my thoughts about how Silverlight 2.0 compares to WPF, feel free to read this post on my other blog.

As of this writing SL has limited support for calling Web services.  You can only call a service that is hosted on the server to which you deployed the SL application, and the data returned by that service must be serialized as JSON (instead of SOAP).  Despite those limitations, you can still perform remote procedure calls and create data-driven applications.  Add in the fact that Silverlight has the Downloader class, which can download resources from the Web, and you have some very powerful tools at your disposal.  This post shows how I put those tools to use, by creating a simple Silverlight 2.0 application that allows you to listen to the latest podcasts published by The New York Times.  You can download the source code at the bottom of this post.

Here is a screenshot of the application in action:

This application has several parts, none of which is too complicated.

  • Silverlight UI - A Page that contains the various elements/controls seen in the above image. The Page's code-behind calls into a Web service that returns information about all of the podcasts to display, updates TextBlocks as the state of the application changes, and starts playing a podcast when the user selects an item in the list. The UI code and XAML can be found in the SilverlightApp project.
  • Custom ListBox Control - Since the current build of Silverlight 2.0 has very few built-in controls, I used my AgListBox control to display all of the podcasts. I could have used the ListBox control provided in the SDK samples, but that control does not support keyboard navigation through its items. I wanted to have keyboard navigation, so I used my custom control instead. The AgListBox control source code can be found in the AgControls project.
  • Web Service - The SL client app asynchronously calls a Web service which returns the name and URL of every available podcast. The service includes screen-scraping code that uses regular expressions to parse out the podcast information from a Web page downloaded from the New York Times web site. I would like to give special thanks to Karl Shifflett for his great advice on how to screen-scrape the HTML doc. The NytPodcastService and screen-scraping code is in the SilverlightHost project.

The first thing I did was follow Bob Familiar's lucid walkthrough of how to set up a Visual Studio solution so that you can develop and debug a Silverlight 2.0 application that consumes a Web service.  Once I got past that initial hurdle, it was smooth sailing.

The NytPodcastService Web service is small and simple.  Here is the Web method:



Unfortunately at the time of this writing there is no RSS feed to get the latest podcasts from the Times.  Instead I download their Podcasts page and scrape the data I need from it.  That method is called from the Web method, as seen above.  Here is the screen-scraping code:

Once that Web service exists, the rest of the development effort is all about figuring out how to display the data and allow the user to play the podcasts.  The UI seen in the demo application is rather primitive and ugly, but enhancing it should be straightforward now that the core application exists.  The XAML for the Silverlight page is below:

The MediaElement named ‘_podcastPlayer' is what we use to play the podcasts, which are MP3 files.  You do not see that element on the screen; but since podcasts have no video associated with them...that is OK with me. Geeked

We will not review every line of code in the Page's code-behind, but I think it is interesting to see how the Silverlight app communicates with the Web service.  The code seen below explains how this communication takes place.

If you are familiar with using Web services, this code should look very familiar.  Since Silverlight 2.0 currently has a reduced version of the .NET framework, you can leverage your existing knowledge and skills when moving into Silverlight development.

Download the demo here.  You will need Visual Studio 2008 and the Silverlight 2.0 alpha installed on your machine to build and run this solution.  When running the application, be sure to set the SilverlightHost project as the Startup Project.

The Debugger Visualizer Item Template in VS2008 is Broken

Someone at Microsoft forgot to update the Debugger Visualizer item template for Visual Studio 2008.  Suppose that you add a visualizer to your project by selecting the "Debugger Visualizer" item template, as seen below:

The template automatically adds a bunch of boilerplate code so that you can easily get started with writing your own visualizer.  That's all well and good, but when you actually give your shiny new visualizer a test drive, you will be told that the CLR was "Unable to cast object of type 'Your Visualizer Type' to type 'Microsoft.VisualStudio.DebuggerVisualizers.DialogDebuggerVisualizer'." 

You can avoid this problem in several ways.  Naturally, you can take the high road and avoid using the item template in the first place.  If you are like me, that is a bad option because, in general, automation is a good thing.  Work smart, not hard!

You can do the "right" thing and actually fix the Debugger Visualizer item template itself.  That would probably be an interesting adventure into the world of Visual Studio template files, and would forever solve this problem on your machine.  But, if you are like me, this option sounds like too much work considering that you might never use the Debugger Visualizer item template again.  It isn't broken if you don't use it, right? ;)

It turns out that we can spend just a few seconds to fix the problem at hand.  Here's the problem: the Microsoft.VisualStudio.DebuggerVisualizers.dll assembly reference that is added to your project by the template is the wrong version.  Observe:

Notice how the Version of the assembly is 8.0.0.0.  That's the problem.  That's the version of the assembly which shipped with VS2005.  The Debugger Visualizer item template should have added Version 9.0.0.0 instead. All that you have to do is delete that assembly reference and add a new one, which references the correct version of the Microsoft.VisualStudio.DebuggerVisualizers.dll assembly.  The screenshot below shows the proper assembly to reference:

After you add that assembly reference, rebuild the project, and redeploy your visualizer, the exception seen previously does not get thrown.  Note, however, that if you intend on using your visualizer in both VS2005 and VS2008, you must have two separate builds since each version of Visual Studio requires a different version of that DebuggerVisualizers assembly to be referenced.

Mole for Visual Studio

There have been many iterations of the Mole visualizer.  The latest and greatest release is not confined to WPF applications, it works with any application!!  Karl Shifflett and Andrew Smith (of Infragistics) have really hit the ball out of the park.  I helped out with testing, editing the article, and providing feedback.  I've been too busy learning Silverlight 1.1 to keep up with their amazing pace.  Great work guys!

Read the article and get the latest Mole for Visual Studio here: http://www.codeproject.com/KB/macros/MoleForVisualStudio.aspx

Enjoy!

Welcome to my new blog!

Hi, I'm Josh Smith.  I recently re-joined Infragistics, as a Guidisan in the User Experience Group.  When I worked here before, I was a developer in the WinForms lab.  It's great to be back at Infragistics!

For those of you in the WPF world, you might know of my other blog: Josh Smith On WPF That blog is devoted to WPF.  This blog is not devoted to any particular technology, so be prepared for this blog to visit many strange and interesting technical worlds.  Considering that a substantial part of my job as a Guidisan is to keep abreast of cutting edge technologies, this blog will touch on many different places in the .NET Universe.

So fasten your seat belt, keep your arms in the blog at all times, and join me as I have fun! Cool