Josh Smith

Follow me down the rabbit hole of cutting edge technology.
Display Multiple Icons in a XamDataGrid Field

This blog post demonstrates how to, optionally, display multiple icons/images/indicators in an UnboundField of the XamDataGrid.  This is a common use case of a data grid control, where one column displays multiple status values.  In this example, we have a single column that indicates whether each person displayed in the data grid is an employee and/or customer of some company.  When you run the demo program, it looks like this:



Download the demo project here.

The data source in this simple demo is an array of Person objects, where the Person class is defined as:

public class Person
{
    public Person(string firstName, string lastName, bool isEmployee, bool isCustomer)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
        this.IsCustomer = isCustomer;
        this.IsEmployee = isEmployee;
    }

    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public bool IsEmployee { get; private set; }
    public bool IsCustomer { get; private set; }
}

The values of the two Boolean properties are displayed in the same field, as seen above.  That field is declared as an UnboundField object, because it is not bound to a single, specific property on the data source objects.  Now let’s see how that UnboundField is configured such that it conditionally displays the two indicators that tell the user if a person is an employee and/or customer.

<!--
This unbound field contains icons that indicate if the person
represented by a record is an Employee and/or Customer.
-->
<igDP:UnboundField Label=" E / C">
  <igDP:UnboundField.Settings>
    <igDP:FieldSettings
      AllowEdit="False"
      AllowGroupBy="False"
      AllowResize="False"
      AllowSummaries="False"
      LabelClickAction="Nothing"
      LabelMaxWidth="45" CellMaxWidth="45"
      >
      <igDP:FieldSettings.CellValuePresenterStyle>
        <Style TargetType="{x:Type igDP:CellValuePresenter}">
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
                <DockPanel>
                  <!-- Give the left edge of the cell a vertical line. -->
                  <Border
                    DockPanel.Dock="Left"
                    BorderBrush="#FFEEEEEE"
                    BorderThickness="1,0,0,0"
                    />
                  <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                    <Grid x:Name="EmployeeIcon" ToolTip="Employee">
                      <!-- Drop shadow behind icon -->
                      <Ellipse
                        Fill="#FF444444"
                        Margin="1,0.5,0,0"
                        Width="12" Height="12"
                        />
                      <!-- Employee icon -->
                      <Ellipse                               
                        Fill="LawnGreen"
                        Width="12" Height="12"
                        />
                    </Grid>
                    <Grid x:Name="CustomerIcon" Margin="6,0,0,0" ToolTip="Customer">
                      <!-- Drop shadow behind icon -->
                      <Ellipse
                        Fill="#FF444444"
                        Margin="1,0.5,0,0"
                        Width="12" Height="12"
                        />
                      <!-- Customer icon -->
                      <Ellipse                                  
                        Fill="Orange"
                        Width="12" Height="12"
                        />
                    </Grid>
                  </StackPanel>
                </DockPanel>
                <!-- These triggers hide the icons that do not apply to a person. -->
                <ControlTemplate.Triggers>
                  <DataTrigger Binding="{Binding Path=DataItem.IsEmployee}" Value="False">
                    <Setter TargetName="EmployeeIcon" Property="Visibility" Value="Hidden" />
                  </DataTrigger>
                  <DataTrigger Binding="{Binding Path=DataItem.IsCustomer}" Value="False">
                    <Setter TargetName="CustomerIcon" Property="Visibility" Value="Hidden" />
                  </DataTrigger>
                </ControlTemplate.Triggers>

              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </igDP:FieldSettings.CellValuePresenterStyle>
    </igDP:FieldSettings>
  </igDP:UnboundField.Settings>
</igDP:UnboundField>

That XAML is rather verbose, because I wanted to add a subtle drop shadow effect to each indicator, but without using the DropShadowBitmapEffect (which is not hardware-accelerated unless running on .NET 3.5 SP1 or later).  The template’s triggers are responsible for hiding the indicators that do not apply to a person.

This project was built against NetAdvantage for WPF v8.1 using Visual Studio 2008.  You can download the source code here.

Posted: 06 Sep 2008, 11:32 | 0 Comments
Filed under:
Adding CheckBoxes to the Record Selectors in XamDataGrid

This blog post explains how to add CheckBox controls to the record selectors in XamDataGrid.  We will also see how to add a CheckBox to the header area above the record selectors, so that you can have Check All / Uncheck All behavior.   You can download the demo application here.

The screenshot below shows the demo application in action:


If you were to check Ned's CheckBox, all of the records would be in the checked state, so the header CheckBox would automatically enter the checked state.  If you toggle the state of the header CheckBox, all of the other CheckBoxes beneath it would assume that new check state.  If you click the button on the bottom, a MessageBox would show you which items are checked and unchecked.

In my demo app, the data objects are of type Person, as seen below:

/// <summary>
/// A simple data object that stores
/// information about a person.
/// </summary>
public class Person
{
    public static Person[] GetPeople()
    {
        return new Person[]
        {
            new Person("Jane", 23),
            new Person("Mike", 45),
            new Person("Ned", 67),
        };
    }

    public Person(string name, int age)
    {
        this.Name = name;
        this.Age = age;
    }

    public int Age { get; private set; }
    public string Name { get; private set; }
}

Notice that the Person class does not have any notion of being "checked."  This makes sense, because a person in the real world is not checked or unchecked.  In order to extend the semantics of a person, and make changes to a person observable to other objects, I created a PersonViewModel class.  That class is below:

/// <summary>
/// A presentation-friendly wrapper for the Person
/// class, which has support for being 'checked.'
/// </summary>
public class PersonViewModel : INotifyPropertyChanged
{
    readonly Person _person;
    bool _isChecked;

    public PersonViewModel(Person person)
    {
        _person = person;
    }

    public bool IsChecked
    {
        get { return _isChecked; }
        set
        {
            if (value == _isChecked)
                return;

            _isChecked = value;

            this.OnPropertyChanged("IsChecked");
        }
    }


    public int Age { get { return _person.Age; } }
    public string Name { get { return _person.Name; } }
   

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

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

    #endregion
}

The XamDataGrid displays a list of PersonViewModel objects.  The DataItem property of each DataRecord in the grid references a PersonViewModel.  The following Style exists in the XamDataGrid's Resources collection, applying a CheckBox to the record selector of each row.

<Style TargetType="{x:Type igDP:RecordSelector}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type igDP:RecordSelector}">
        <CheckBox
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          IsChecked="{Binding Path=DataItem.IsChecked}"
          >
        </CheckBox>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The collection of PersonViewModel objects to which the XamDataGrid is bound exists in an instance of my CommunityViewModel class.  That class serves two purposes.  First, it contains the list of PersonViewModel objects in its Members property.  Second, it provides a get/set property called AllMembersAreChecked.  This property is used to maintain the state of the CheckBox in the header area above the record selectors, and to update the IsChecked property of all PersonViewModel objects when the property is set.  Here is the CommunityViewModel class:

public class CommunityViewModel : INotifyPropertyChanged
{
    public CommunityViewModel(List<PersonViewModel> members)
    {
        this.Members = members;

        foreach (PersonViewModel member in members)
            member.PropertyChanged += delegate
            {
                this.OnPropertyChanged("AllMembersAreChecked");
            };
    }

    public List<PersonViewModel> Members { get; private set; }

    public bool? AllMembersAreChecked
    {
        get
        {
            bool? value = null;
            for (int idx = 0; idx < this.Members.Count; ++idx)
            {
                if (idx == 0)
                {
                    value = this.Members[0].IsChecked;
                }
                else if (value != this.Members[idx].IsChecked)
                {
                    value = null;
                    break;
                }
            }

            return value;
        }
        set
        {
            if (value == null)
                return;

            foreach (PersonViewModel member in this.Members)
                member.IsChecked = value.Value;
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

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

    #endregion
}

The Style used to apply a CheckBox to the header area is quite similar to the one we saw previously.  One difference is that this Style explicitly sets the target element's Visibility property to ‘Visible', otherwise we would never see the CheckBox.  That Style is below:

<Style TargetType="{x:Type igDP:HeaderPrefixArea}">
  <Setter Property="Visibility" Value="Visible" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type igDP:HeaderPrefixArea}">
        <CheckBox
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          IsChecked="{Binding Path=DataPresenter.DataContext.AllMembersAreChecked}"
          >
        </CheckBox>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Download the demo project here.  The demo was built in Visual Studio 2008, using NetAdvantage for WPF v8.1.

Posted: 04 Sep 2008, 08:44 | 2 Comments
Filed under:
Article about xamSalesManager

In the recently released NetAdvantage for WPF v8.1, the xamShowcase application contains a new sample called xamSalesManager.  I was the Developer on that project, and worked closely with some talented Visual Designers from the Experience Design Group.  The application uses several of our WPF components to create a stellar Executive Dashboard interface for a Regional Sales Manager.  The application was structured by applying the Model-View-ViewModel pattern.

 

You can check out xamSalesManager, and all of the other showcase samples, either by installing the product or by running it in your browser.  The XBAP version of xamShowcase is located here:

http://xamples.infragistics.com/

I wrote a whitepaper about the application, which was subsequently edited and posted on CodeProject.  If you would like to learn more about how xamSalesManager was designed and implemented, I suggest you check out this article:

http://www.codeproject.com/KB/showcase/InsideXamSalesManager.aspx

Enjoy!

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: