Derek's profileDerekDevDudeBlogListsSkyDrive Tools Help

Derek K

Occupation
Location
Interests
Contract Software developer, Brisbane based.
Items of interest, that I'll one-day get around to looking at.

DerekDevDude

lives here
July 03

Hide/Show GridView Column using Attached Behaviour

The default GridView column behaviour doesn’t include direct support for hiding a column. For a better user experience I want

  1. A hidden column should not be interactive, the user shouldn’t be able to re-drag the column open.
  2. To support double-click so the user can auto-size the column.
  3. To be able to show hide a column, with it being bound to my view model.
  4. To allow the user to hide a column by setting its width to a small value.

To implement these capabilities, I’ve used an Attached Behaviour: Column.

Point 1: This was the simplest to resolve. The suggested approach for hiding a column is to set its width to zero (MSDN Forum), but this doesn’t stop the user from re-showing the column by simple dragging it open again, and I didn’t want code in my view (hence the use of an attached  behaviour). A simple solution for this is to set the column header IsHitTestVisible to false for a hidden column.

Point 2: This is a straight forward AB added to the GridViewColumnHeader. The property just listens for mouse double click events, and set the corresponding column’s width to auto.

Point 3: This was a more difficult problem.The ViewModel binds to the style for a WPF UIElement, but the column width is defined by the GridViewColumn, not the GridViewColumnHeader UIElement. My first thought was to just style a GridViewColum to bind it to my ViewModel, but since a GridViewColum is not a UIElement, styles don’t apply. My final approach was to Attache a Property “IsHidden” to the GridViewHeader this makes it easy to bind to. It also provides a convenient place for disabling header interaction (see point 1) when the column is hidden.

Point 4: My first approach for an AutoHide behaviour was to attach a property to the GridViewColumn. This had 2 down sides

  • Can’t set auto hide width in a style, so I’d have to define it in XAML for each Column. This is the same problem as trying to set column width with a style.
  • Code to discover the column header from a column is difficult and messy. I needed to scan the visual tree… it just got ugly fast (ugly leads to buggy, buggy leads to broken, broke leads to… :-) )

So alternately I opted to follow the IsHidden approach, and set the property on the column header.

I found a few ‘surprises’ implementing AutoHideWidth:

  1. Even though I attached to the header, I still needed to handle the column’s width change event, as a result, I also attached the header to the column with a private attached property; so I could easily discover it.
  2. When first attaching a property to a header, the header doesn’t have a column, so I needed to defer attaching to the column property changed event till it was set. To do this I used the DependencyPropertyDescriptor.AddValueChanged for the header’s column property.
  3. If you try and set the column width while the user is resize-dragging the column, the width will ‘bounce-back’ to some other value. So I needed to defer checking for hiding a column until the drag was finished.

To use the behaviour, you just set it in the view XAML:

<!-- Style column header with the behaviour -->
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="add:Column.AutoHideWidth" Value="10.0" />
<Setter Property="add:Column.AutoSizeOnDClick" Value="True" />
<Setter Property="add:Column.IsShowing"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=Column.Header.IsShowing, Mode=TwoWay}"
/>
</Style>
 
To get the binding between view model IsShowing property and the grid column, I just set the column header to a view model object, then bind it in the header’s style.
<!--Set the Column header to a view model object. -->
<GridViewColumn Header="{Binding Path=NameTerm}">
...
</GridViewColumn>

I’ve included a  sample showing a GridView with this behaviour attached here.
June 29

An approach for Sorting a ListView in WPF.

In WPF sorting on a ListView is managed via the collections corresponding CollectionViewSource. In XAML, a designer can define the sorting via setting the CollectionViewSource. There are also MSDN samples to show how to sort based on user clicking the column header. In my particular case I needed more complex sorting behaviour.

  1. Include a sort-direction arrow to reflect current sort order.
  2. Include a visual representation to identify the current ‘first’ sort column.
  3. Sort based on user click of the corresponding column header.

SortListView

In this demo I’ve tried to apply a MVVM style design to sorting items in a list view.

Model:
I’ve used a simple model with static sample data. The model includes an association between a Book and an Author. It also includes sample null-able properties.

View Model:
A list of Book’s is presented in a BooksViewCollection, the collection

  1. Is a collection of ViewModel Book objects.
  2. Exposes the generated WPF CollectionView as a View property.
  3. Manages the set of ‘Terms’ used for sorting the Book objects.
  4. Ensures that setting a term’s sort direction selects the term and sorts by it first.

Each sort-able column is represented as a Term.

  1. A Term has a title for the column text.
  2. A Term has a comparison function for sorting Book objects.
  3. A Term is selectable.
  4. A Term includes a sort direction property ‘Desc’ (descending).

I’ve used the DelegateCommand<T> and the Click attached behaviour from CAL-Feb 2009 release (http://compositewpf.codeplex.com/).

View:

To show a sort-direction indicator, I need to track the sort column’s current sort direction (descending / ascending / null). Ideally a column header would be a tri-state toggle button, so I could bind a sort-direction to the IsChecked property of the button. But the column header is a press-button, not a tri-state button. Initially I attempted to style the column header template to use a toggle button, but this caused other problems, such as losing the drag-reorder ability on a column. So instead I’ve included a  bound toggle button in the header’s data template, and bound this to the corresponding Term’s sort direction. I then use a command behaviour on the column header button to toggle the sort. Finally, a little styling on the toggle button so it looks like an up/down arrow (Media/UpDownToggle.xaml).

Note: A GridViewColum is not a framework element, so I couldn’t use a style to apply the HeaderTemplate. Also the HeaderContainer doesn’t get the Header object as it’s data context, so I needed to explicitly set the ToggleSort command for each column.

Code: the demo source is here (http://cid-e0c1236f30f0febc.skydrive.live.com/self.aspx/Public/ListViewSorting.zip).

May 27

A WPF ComboBox styled as a DropList

Out of the box a WPF ComboBox, in read mode (IsEditable == false) disables interaction with the current selected item, but allows interaction with list items. This behaviour causes problems if the DataTemplate for your items includes interaction elements, such as Buttons, or as in my case Hyperlinks. Ideally this behaviour should be reversed; the list items should be selectable (but not interactive) whilst the selected item should be interactive.

ComboBoxBehaviour

To solve this, firstly I created a non-clickable style for a ComboBoxItem (NoClickComboBoxItem). The trick for this is to disable hit testing on the data template using the IsHitTestVisible property:

<Style x:Key="NoClickComboBoxItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ContentPresenter
IsHitTestVisible="False"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
 
The next part is to make the selected item interactive. The default ComboBox’s control template sets the IsHitTestVisible to false for the displayed selected item, so we need to
Technorati Tags: ,,
replace the ComboBox’s Template with our own.
 
<ControlTemplate x:Key="DropListNormal" TargetType="{x:Type ComboBox}">
<Grid SnapsToDevicePixels="true" x:Name="MainGrid">
<Grid.ColumnDefinitions ... />
<Popup ... />
<ToggleButton ... />
<ContentPresenter
IsHitTestVisible="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>
</Grid>
</ControlTemplate>
 
To keep things clean, I’ve put both these ControlTemplates in a Style
 
<Style x:Key="DropListCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Template" Value="{StaticResource DropListNormal}" />
<Setter Property="ItemContainerStyle" Value="{StaticResource NoClickComboBoxItem}" />
</Style>
 
Applying this to a ComboBox is now just setting the Style
 
<ComboBox 
ItemsSource="{Binding Path=Books}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource DropListCombo}"/>


You can find a sample of this style here.

Note: I’ve based the template on the default ComboBox template, so I also need to include style’s for the button.

May 26

Binding a ComboBox with MVVM

I ran into a problem using MVVM and a ComboBox. I was using a simple approach with each list item exposing an IsSelected property, and WPF style to bind the property to the view. Strangely the initial display of the ComboBox wasn’t displaying the current selected item, till the user first dropped-down the combo. Eventually I found a link describing my problem here on MSDN; basically the drop-down ComboBoxItem’s aren’t created until needed, and as a result, the IsSelected isn’t initially applied.

The problem is easy to reproduce; a simple application with a list of items (Book 1, Book 2, Book3, Book 4), with the 3rd item selected, then bound to a combo box something like:

image The top combo is bound to a normal ObservableCollection, whilst the lower is bound to a custom PopUpCollection. The PopUpCollection exposes a SelectedValue property, which is subsequently bound to the CombBox’s SelectedValue. I’ve used a simple Interface for each item:

/// <summary>
/// A simple interface to identify a viewmodel item as selectable.
/// </summary>
public interface ISelectable
{
    bool IsSelected{get;set;}
}

The PopUpCollection itself inherits from ObservableCollection, adding a SelectedValue property. It also registers to with each contained item’s INotifyPropertyChanged event (via WeakEventListener) to track changes to IsSelected.

public class PopUpCollection<T> : ObservableCollection<T>, IWeakEventListener
where T : class,ISelectable, INotifyPropertyChanged
{
public T SelectedValue
{
get
{
return (from i in this
where i.IsSelected == true
select i).FirstOrDefault();
}
.....

Now in the view I simply bind the ComboBox SelectedValue to the PopUpCollection’s Selected value:

<StackPanel Orientation="Horizontal">
<TextBlock Text="Popup Collection" />
<ComboBox ItemsSource="{Binding Path=PopupList}"
IsSynchronizedWithCurrentItem="True"
SelectedValue="{Binding Path=PopupList.SelectedValue, Mode=TwoWay}"/>
</StackPanel>

I’ve put this in a sample app here.
April 29

Showing Design Data with WPF

when developing in WPF, using MVVM it is very useful to provide some design-time data to help check bindings, and to give some visual feed-back. Below is my (current) approach for providing design-time data:

  1. I use a simple utility class “RunMode” to report the execution context for the code. This class reports if I’m currently in Design mode, it also provides info about Debug flag etc. To check Design mode I use the following:
    bool isDesignMode = DesignerProperties.GetIsInDesignMode(new DependencyObject());
     
  2. All ViewModel classes provide a default constructor. The default constructor initialises with design-time data. If needed the RunMode can be used to check if need Design data.
  3. All my View’s include an ObjectDataProvider with Key = “Data”. This ODP is initialised with ObjectType= {x:Type ObjectViewModel}.
  4. All my View’s inherit from a common ViewBase; the ViewBase is a UserControl.
  5. The Root Visual element in the View explicitly sets its DataContext to the ODP e.g.
    <StackPanel DataContext="{StaticResource Data}">
    <!—Rest of the View –>
    </StackPanel>
  6. The ViewBase implements a handler for the DataContextChanged event. This swaps the ODP to use the DataContext for the View, rather than the stub data. There is a ‘trick’ here in that I first set the ODP.ObjectType ==null, then set the ODP.ObjectInstance. A ODP doesn’t allow both ObjectType and ObjectInstance to be defined, unless you first ‘reset’ it by clearing the ObjectType.

Advantages:

  1. Because I use a ObjectType in the View’s XAML, Expression Blend recognises the type of object, simplifying binding in Blend.
  2. I’ve found this useful for testing the ViewModel, since it provides some simple data to help testing.

Disadvantage:

  1. If you don’t use a ViewBase, you end up with code in your View’s code-behind.
  2. The ViewModel may not be the best place for design-time data, may be better to have the Model define the stub data.
  3. At run-time the ODP creates an instance of the ViewModel, though the instance isn’t needed.
  4. I have had some problems with the ODP, swapping from ObjectType to ObjectInstance seamed to cause some problems, but with 3.5 SP1 this approach works.
 
Public folders
Active presence in the virtual world