Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Tuesday, November 16, 2010

WPF Themes - Ensuring Hot Keys Display properly

In going through the themes in the WPF Themes project on CodePlex I've encountered many inconsistencies.  Sometimes these inconsistencies are annoying and other times they require much more complicated fixes.  All these issues are instructive because they teach us things to be aware of when building our own themes or even our own templates.


I've written about some of the more complex issues I've encountered in the past.  Today I'm just going to talk about one of the more simple problems.  Some themes in the WPF Themes do not properly display the access keys.  The underscore used to indicate the key was being displayed instead of removed.  This is a simple problem to fix.  ContentPresenters have a property RecognizesAccessKey.  If this property is not set, it defaults to false and the access keys will not work.  Writing RecognizesAccessKey="True" will make the access keys work properly.


I would have preferred the default for this property to true.  In the general case I want the access keys to be active.  An even better solution would be if there was a global way to specify the default for it.  Using access keys is usually what you want to be application wide and it'd eliminate little presentation bugs to if this was possible. Its easy to forget to set this property on a ContentPresenter which can manifest in hard to find locations.  Its an easy problem to fix, but annoying when you discover it.

Monday, November 1, 2010

WPF Gird Causing Memory Leak

There is a fundamental problem in WPF that can cause a memory leak in applications using it.  The problem is that a ColumnCollection in a Grid can become pinned in memory. It is not in all cases but in a specific case in the application I work on.  I am not 100% about what in the structure caused it but there are 3 separate potential contributing culprits that I've identified.  Without extensive testing of the different scenarios I won't be able to identify exactly what the cause was.  The three potential contributing culprits are:

1.     User control inside a DataTemplate with a Grid.  It does not matter how deep in the control the grid was in the control all Grids were getting caught
2.     Binding column widths from a parent grid to an inner grid through a border control.  This was done to emulate a grid like behavior but allow for multiple control types within the grid
3.     Binding and unbinding the collection view source every time the source of the observable collection changed. 
In trying to fix this problem it seemed as if it was related to the DataTemplate but it is unclear exactly what caused the issue. 
The actual leak came from the pinning of the Grid.ColumnCollection.  The pinning of that caused all the associated control to be pinned as well.  The Grid.RowCollection did not seem to have the same issue.  This was still an issue even if there were no columns defined.  It also occurred if a Grid was used inside a control from the same assembly.  If the Grid was anywhere inside a user defined control in the same assembly, at any level, the issue would occur, even if it was in another user control.  Crossing the assembly boundary appears to make this not occur. 
To fix the issue I replaced the Grids in the affected controls.  The grids were replaced with a combination of StackPanels and DockPanels which can be used to replicate the grid.  In this case they were actually more appropriate than using a Grid anyway. 
The lesson from this appears to be that the use of a Grid over other LayoutPanel types should be carefully considered.  If memory leaks start to appear with the Grid it needs to be changed to another Layout type.

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

Wednesday, February 10, 2010

SourceName In MultiTriggers in WPF Themes

There is a bug in the WPF code that can cause a null pointer exception.  The null pointer exception occurs on line 5924 of System.Windows.StyleHelper.cs :

object evaluationValue = evaluationNode.GetValue( conditions[i].Property );

The problem has to do when you are defining a MultiTrigger on an element in a theme.  This bug manifested itself when on our project we were refreshing a collection that was bound to a TreeViewer and the TreeViewItem style had a MultiTrigger in it.  The style in question comes from the WPFThemes project and was the ExpressionDark Theme (the problem occurs in about 7 of the other themes in the project). The one Condition on the MultiTrigger in the theme has a SourceName defined for it.  The SourceName was not needed because it was the element right inside the root element anyway.  This bug was especially subtle because it would only manifest itself after other conditions would have happened that would cause the MultiTrigger to fire.

 I suspect, but have not verified, that the precondition for causing this bug is having a currently selected TreeViewItem that is removed upon refreshing the control and a newly generated item in the refresh was being set as the selected item in its stead.  The trigger was checking the condition but the new item hadn't fully been generated so when it was looking for the child element it didn't yet exist.  This makes sense in context  of the code in the StyleHelper class.  The code that returns the element using the GetChild method defined on line 6388.  The comment inside the method says:


 // Notice that if we are requesting a childIndex that hasn't been
 // instantiated yet we return null. This could happen when we are 
 // invalidating the dependents for a property on a TemplateNode and
 // the dependent properties are meant to be on template nodes that
 // haven't been instantiated yet.


This method was returning null because


if (styledChildren == null || childIndex > styledChildren.Count)
was evaluating to true because styledChilderen was null.

The null pointer exception was occurring because line 5924


object evaluationValue = evaluationNode.GetValue( conditions[i].Property );



was assuming evaluationNode as not null.

It was assuming it could do this because of the Debug.Assert on line 5903:


 Debug.Assert(evaluationNode != null, 
                    "Couldn't find the node corresponding to the ID and name given in the trigger.  This should have been caught somewhere upstream, like StyleHelper.SealTemplate()." );


This brings up a point about using Debug.Asserts in code.  People often use them (I see it on my project often) when they don't want to perform the null check overhead or other conditions that they feel should never happen.  The idea is that you should catch any of these cases in testing.  Obviously this is a case that was over looked in Microsoft's testing.  The method that set the value of evaluationNode before the Assert has a perfectly legitimate reason for returning null (see the comments earlier) then its not reasonable to use the Assert here.  More on a future post about using Asserts versus null checks.

The work around for this bug was to remove the SourceName from the MultiTrigger.  This does not change the style in a meaningful way and removes the null pointer exception

Sunday, October 18, 2009

Windows Forms & WPF Element Host Bug And How to Fix It

If you haven't had the chance to look at what Microsoft did with the advent of the Windows Presentation Framework (WPF), I suggest you check it out.  As a revamp of how Windows Applications are written it is pretty spot on with what needed to be done.  I could ramble about how much I like WPF for a while but that's not the point of this post. 

In an ideal world we would be able to rewrite our applications with new technologies, especially ones that improve our maintainability and improve its over all quality.  However, in the real world this is not really feasible.  We have budget, time, and customer constraints.  Many times existing code has domain knowledge impeded in a non-extractable way.    As much we as developers want to play with technologies and use the latest greatest things these constraints don't allow us to.  Microsoft has recognized that we want to use new technologies in existing applications or we want to take an incremental approach to transition our applications.  In order to help with either of these plans they have provided the ElementHost control which allows you to put WPF controls on a Windows Form. 

The ElementHost is great conceptually and great when it is working, however it has a fundamental bug.  What is this bug you may ask?  The bug is that by default the controls contained in the element host will always be disabled because the enable property won't propagate through to them.  This basically makes them read only controls.  The good news is that there is a work around for it, however it is non-intuitive because it uses Win32 Interop.  I was only able to know what to do once I found Microsoft's page about the bug.  There are not really any references to it unless you search very specifically.  The solution to the problem can be found here: http://support.microsoft.com/kb/955753 

I recommend you create a class that derive a class from ElementHost and having that as part of your repository and use that for incorperating WPF controls into windows forms.

A couple side notes.  This is only necessary if you have a Windows Form that you want to place WPF controls on.  You can open a WPF Window in a Windows Form application as if was just another form so this is not necessary if you just want to combine WPF and Windows Forms inside the same application.