Friday, March 5, 2010

.Net Framework Bug With Sorting

I came across a bug in the .Net Framework the other day.

When specifying SortDescriptions on a CollectionViewSource there is a problem when using a complex path for the property name. This occurs when part of the path being checked is null. If I have the classes defined below

class Foo{
string Name {get; set;}

}

class Bar{
Foo FooMember {get; set;}
}

If there is a CollectionViewSource that contains a list of Bar's and it is desired to sort each bar by Foo's name a SortDescription can be created as

SortDescription d = new SortDescription("FooMember.Name", ListSortDirection.Ascending);

If that SortDescription is added to the sort descriptions of the CollectionViewSoure it will properly order the items with one exception. This one exception is if there is an instance of Bar in the collection that the CollectionViewSource uses as the source. In this case an ArgumentException stating that there is a type mismatch and the element must be a string occurs.

The reason this occurs is that in the Compare method of the SoftFieldComparer obtains a strange object when its accessing the instance of Bar that has a null FooMember. Rather than just being null, its an MS.Internal.NamedObject with a _name value of "DependencyProperty.UnsetValue". This is a problem because it then tries to do a comparison betwen the string and the NamedObject which cannot be compared, which causes the ArgumentException. This case needs to be handled to be able to sort items with null members.

I have submitted this as an issue to Microsoft https://connect.microsoft.com/VisualStudio/feedback/details/539559/sortfieldcomparer-compare-method-can-throw-an-exception

Wednesday, March 3, 2010

ContentPresenter, GridViewRowPresenter, and ListViewItems

There are two distinct ways that content can be presented within control templates.  These are the GridViewRowPresenter and the ContentPresenter.  The only place that the GridViewRowPresenter is used is within a GridView to present the cell of data.  This is needed for the binding path to be evaluated properly.  If for example I have the class Foo defined below:

public class Foo{
    public string X {get; set;}
    public string Y {get; set;}
}

If I want to use a ListView presented using a GridView inside it to present the values of both X and Y as columns you would write the following assuming the FooCollectionView is a collection view source or some other collection of Foo's


<ListView ItemsSource={Binding FooCollectionView}">
  <ListView.View>
    <GridView >
      <GridViewColumn Header="X" DisplayMemberBinding="{Binding Path=X}" />
      <GridViewColumn Header="Y" DisplayMemberBinding="{Binding Path=Age}" />
    </GridView>
  </ListView.View>
</ListView>

  
If you want to define a style or control template that applies to this control instead of the normal location, where you'd put a ContentPresenter in the template you put a GridViewRowPresenter.  What happens if you use a ContentPresenter instead?  You get the content presented but its not in a grid and its just the ToString() of the object.  This obviously is not what you want to occur.

Therefore, it seems fairly obvious that you should use a GridViewRowPresenter.  It however, is not that straightforward because what if you want to not use a GridView and instead just want to list them (the same as would be in a ListBox) so you have something like the following?

<ListView ItemsSource={Binding FooCollectionView}">

What happens when you define the style with GridViewRowPresenter to present the content?  The ListView will appear to have no content because there is no GridView to present the content of.  This does not present a huge problem if you are defining a style on a per ListView instance.  However, it is a problem if you want to define a general style like you would in a theme.  This presents a big problem because either you cannot use a GridView or must always use a GridView.  You can get around this by using a ListBox anywhere you don't want to use a GridView.  This strategy would mean letting the style dictate the form of the application which shouldn't be the case.

However, the real problem occurs when defining a reusable theme.  If you want to define a general purpose theme that others can reuse without having to alter thier application such as in the WPFThems project then you need it be able to handle both cases.  On a side-note some of the themes in WPFThemes use the ContentPresenter and some use the GridRowViewPresenter.  This can make it so that the ListBox is shown properly with some themes applied and improperly with others.

The fix to this is a bit of a hack but ultimatley turns out to work.  It basically involves defining both presenters inside the control template.  So where you put the presenters you put code that looks like the following:


  <GridViewRowPresenter x:Name="gridrowPresenter"
                        Content="{TemplateBinding Property=ContentControl.Content}"/>
  <ContentPresenter x:Name="contentPresenter"
                    Content="{TemplateBinding Property=ContentControl.Content}"  Visibility="Collapsed"/>


 To get the content to correctly display doing this.  For the template the following trigger will need to be added:

   <Trigger Property="GridView.ColumnCollection" Value="{x:Null}">
     <Setter TargetName="contentPresenter" Property="Visibility" Value="Visible"/>
  </Trigger>     

This trigger will show the ContentPresenter when the GridViewRowPresenter has no content.  Since there is no GridView the GridViewRowPresenter will not display anything visually.

Obviously, this is a bit of a hack to get around a flaw with how WPF works.  Hopefully, in a future version this will be addressed in how the framework works.

Edit 11/16/10 Added  Visibility="Collapsed" property to the contentPresenter element which was a bug in the implementation