Sorting a WPF ListView in Grid Mode

There is an article on MSDN that demonstrates how to enable sorting column for a ListView used with Grid layout. The solution presented there works perfectly, but I do not want to put code behind my windows, because I work with MVVM approach. The solution is wrapping everything in a behavior, so I took the code from original MSDN example and I wrapped inside a behavior.

public class ListViewGridSortableBehavior : Behavior
    {
        protected override void OnAttached()
        {
            if (AssociatedObject != null)
            {
                AssociatedObject.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridHeaderClickEventHandler));
            }
            base.OnAttached();
        }

        GridViewColumnHeader _lastHeaderClicked = null;
        ListSortDirection _lastDirection = ListSortDirection.Ascending;

        void GridHeaderClickEventHandler(object sender, RoutedEventArgs e)
        {
            GridViewColumnHeader headerClicked =
                  e.OriginalSource as GridViewColumnHeader;
            ListSortDirection direction;

            if (headerClicked != null)
            {
                if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
                {
                    if (headerClicked != _lastHeaderClicked)
                    {
                        direction = ListSortDirection.Ascending;
                    }
                    else
                    {
                        if (_lastDirection == ListSortDirection.Ascending)
                        {
                            direction = ListSortDirection.Descending;
                        }
                        else
                        {
                            direction = ListSortDirection.Ascending;
                        }
                    }

                    if (_lastHeaderClicked != null) 
                    {
                        SetSortDownVisibility(_lastHeaderClicked, Visibility.Collapsed);
                        SetSortUpVisibility(_lastHeaderClicked, Visibility.Collapsed);
                    }

                    //string header = headerClicked.Column.Header as string;
                    String sortString = GetSortHeaderString(headerClicked);
                    if (String.IsNullOrEmpty(sortString)) return;

                    Sort(sortString, direction);

                    if (direction == ListSortDirection.Ascending)
                    {
                        SetSortDownVisibility(headerClicked, Visibility.Collapsed);
                        SetSortUpVisibility(headerClicked, Visibility.Visible);
                    }
                    else
                    {
                        SetSortDownVisibility(headerClicked, Visibility.Visible);
                        SetSortUpVisibility(headerClicked, Visibility.Collapsed);
                    }

                    // Remove arrow from previously sorted header 
                    if (_lastHeaderClicked != null && _lastHeaderClicked != headerClicked)
                    {
                        _lastHeaderClicked.Column.HeaderTemplate = null;
                    }


                    _lastHeaderClicked = headerClicked;
                    _lastDirection = direction;
                }
            }

        }
        private void Sort(string sortBy, ListSortDirection direction)
        {
            ICollectionView dataView =
              CollectionViewSource.GetDefaultView(AssociatedObject.ItemsSource);

            dataView.SortDescriptions.Clear();
            SortDescription sd = new SortDescription(sortBy, direction);
            dataView.SortDescriptions.Add(sd);
            dataView.Refresh();
        }

        public static readonly DependencyProperty SortHeaderStringProperty =
           DependencyProperty.RegisterAttached
           (
               "SortHeaderString",
               typeof(String),
               typeof(GridViewColumnHeader),
               new UIPropertyMetadata(String.Empty)
           );

        public static String GetSortHeaderString(DependencyObject obj)
        {
            return (String)obj.GetValue(SortHeaderStringProperty);
        }

        public static void SetSortHeaderString(DependencyObject obj, String value)
        {
            obj.SetValue(SortHeaderStringProperty, value);
        }

        public static readonly DependencyProperty SortDownVisibilityProperty =
          DependencyProperty.RegisterAttached
          (
              "SortDownVisibility",
              typeof(Visibility),
              typeof(GridViewColumnHeader),
              new UIPropertyMetadata(Visibility.Collapsed)
          );

        public static Visibility GetSortDownVisibility(DependencyObject obj)
        {
            return (Visibility)obj.GetValue(SortDownVisibilityProperty);
        }

        public static void SetSortDownVisibility(DependencyObject obj, Visibility value)
        {
            obj.SetValue(SortDownVisibilityProperty, value);
        }

        public static readonly DependencyProperty SortUpVisibilityProperty =
         DependencyProperty.RegisterAttached
         (
             "SortUpVisibility",
             typeof(Visibility),
             typeof(GridViewColumnHeader),
             new UIPropertyMetadata(Visibility.Collapsed)
         );

        public static Visibility GetSortUpVisibility(DependencyObject obj)
        {
            return (Visibility)obj.GetValue(SortUpVisibilityProperty);
        }

        public static void SetSortUpVisibility(DependencyObject obj, Visibility value)
        {
            obj.SetValue(SortUpVisibilityProperty, value);
        }
    }

The code is really simple, I just add and handler to the ClickEvent of the GridViewColumnHeader for the ListView and then used the code from MSDN example to do the sorting. I’ve added also a couple of Dependency Properties called SortDownVisibility and SortUpVisibility that determines the visibility of graphical indicator of the current sorting. I also added a property called SortHeaderString that will contains the name of the property that should be used to sort. Thanks to this simple code I can simply enable sorting in XAML.

<ListView ItemsSource="{Binding SearchesResult}" HorizontalContentAlignment="Stretch" >
    <i:Interaction.Behaviors>
        <Behaviours:ListViewGridSortableBehavior />
    </i:Interaction.Behaviors>

Now I need to enable sorting for all sortable columns, using my SortHeaderString property.

<GridViewColumn  DisplayMemberBinding="{Binding Dto.Author}" >
    <GridViewColumn.Header>
        <GridViewColumnHeader Behaviours:ListViewGridSortableBehavior.SortHeaderString="Dto.Author">
            <StackPanel Orientation="Horizontal">
                <Label Content="?" Visibility="{Binding Path=SortDownVisibility, RelativeSource={RelativeSource AncestorType={x:Type GridViewColumnHeader}}}"></Label>
                <Label Content="?" Visibility="{Binding Path=SortUpVisibility, RelativeSource={RelativeSource AncestorType={x:Type GridViewColumnHeader}}}"></Label>
                <Label Content="Author"></Label>
            </StackPanel>
        </GridViewColumnHeader>
    </GridViewColumn.Header>
</GridViewColumn>

Thanks to my Dependency Properties, it is really simple to enable sorting for a column because I need only to populate the SortHeaderString property of the Header. In previous example, the column is bound to Dto.Author property, the header shows the string Author, and I’ve also added a couple of Arrows to show the actual sorting. No resource is needed because I’ve used a couple of Unicode chars to represent up and down arrows.

Here is the result

image

Figure 1: Clickable and sortable header in ListView with Grid layout

This simple approach is perfectly compatible with MVVM and permits you to choose the exact layout of the header while maintaining the option to sort with clickable headers.

Gian Maria.

Change appearance of a control if the windows is maximized in WPF

Yesterday I was presented with an interesting question:

How can I design a user control that change appearance when the windows containing it is maximized?

Thanks to WPF binding and Triggers the solution is simple, suppose you want to create a border control that change thickness when the windows that contains this User Control is maximized, here is the code.

 <Border BorderBrush="Black"
                HorizontalAlignment="Left" 
                Height="100" 
                Margin="124,54,0,0" 
                VerticalAlignment="Top"
                Width="100">
            <Border.Style>
                <Style TargetType="Border">
                    <Setter Property="BorderThickness" Value="1" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding 
                                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, 
                                Path=WindowState}" 
                              Value="Maximized">
                            <Setter Property="BorderThickness" Value="10" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
        </Border>

The solution is using a simple style that determines the value for the BorderThickness property of the Border Control. The key part is that I did not set an explicit value for BorderThickness in declaration of the control, but only in the style. This is necessary because value set at control level has higher priority than the style. The real power of WPF binding permits you to bind to RelativeSource with FindAncestor where Type is a Window. That specific binding is really useful in a UserControl, because you cannot know any property name of containers, but it is easy to find the first ancestor of type Window. Then you can simply create the trigger with this specific binding and change the property BorderThickness of the Border to 10 when the windows containing the user control gets maximized.

Gian Maria.

Wpf Design Time Data part 3

If you followed the suggestion of my first couple of posts you should have a dedicated Design Time view model for each real View Model in your application so you can effectively use Design Time Data. You should be aware that Design Time data can be directly manipulated by the designer. In property windows you can see all properties of the Windows object (remember to select the windows), so you can locate the DataContext property and expand it to view its content.

image

Figure 1: Visual Studio designer is able to edit the values of properties of DesignTimeData View Model

Since the designer knows the type of the DataContext it permits you to edit design time value properties directly in Visual Studio. Suppose you want to verify how the UI look when the MainFilter property is a real long string, you can simply change the MainFilter design time data value, inserting a real long string.

image

Figure 2: You can edit properties of Design Time View Model

The designer will immediately reflect the change so you are immediately able to see how the layout of your form is affected from this real long string. If you think this is cool, I can tell you that the VS designer is also able to create complex objects so you do not need to write C# code in constructor of Design Time Data View Model to populate property if you do not want to.

Pressing the button with ellipsis on the Logs property makes you to edit the Logs property directly in the editor, thanks to the build in Collection Editor. This is really cool because you can now decide how much object you want in the collection at design time, but, Houston we have a problem, you can modify properties, but you are not able to create complex objects.

image 

Figure 3: You are not able to fully construct a LogMessageViewModel with the designer

The problem shown in Figure 3 derive from this fact: WPF designer has lots of default designers that permits you to edit standard properties of objects, but if a property if a complex object, like property called Log in Figure 3, it does not know witch editor to use.

If you think to this limitation a little bit, it seems strange, because after all I’m able to create new LogMessageViewModel objects and edit all the properties with Collection Editor; why I’m not able to create object of LogMessage Type from the editor? After all if the Collection editor is able to create complex object I should be able to populate complex properties in the same way. The solution to this problem is specifying to WPF  the editor you want to use with the attribute TypeConverterAttribute. Since I do not want to clutter my ViewModels with attribute pertinent to designer, the simplest solution is to create a DesignTime subclass for each ViewModel, so I proceed to create the LogMessageViewModelDesignData

[NewItemTypes(typeof(LogMessageViewModelDesignData))]
public class LogMessageViewModelDesignData : LogMessageViewModel
{
    [NewItemTypes(typeof(LogMessage))]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public new LogMessage Log { get; set; }
}

Thanks to this approach I’m able to hide the original Log property with a property that has the appropriate attributes for the designer. This is the same trick used to solve the BindingCollection problem we saw in the previous post, this time I hide the property just to specify the TypeConverterAttribute, so the designer knows how to edit my object. The NewItemTypes attribute is also used to specify the exact type of object I want to create from the designer. Now I need to change the property Logs in the RawLoggerViewModelDesignData, because now it will be a collection of this specific Design Time object

public new ObservableCollection<LogMessageViewModelDesignData> Logs { get; set; }

If you look at the previous post you already know that I hided the original Logs property because it is of ICollectionViewType, a type that is not usable at design time, and I used an ObservableCollection of LogMessageViewModel, but nothing restrict me to declare that this collection contains different objects respect the real ViewModel, as long as the new class has same properties as the real one. It turns out that I can use my new LogMessageViewModelDesignData class as the type attribute for the ObservableCollection and this improves a lot the experience for users that wants to manipulate Design Time data directly from Visual Studio.

image

Figure 4: You can now edit the Log property directly from the designer.

As you can see from Figure 4 the designer actually render a beautiful “New” button that permits you to create new instance for a Complex property. If you press that button a new instance of LogMessage is created and you can edit directly inside the designer. Actually this is a simple commodity to embed design time data directly inside the windows, here is the code that this will generate

image

Figure 5: The designer is simply generating XAML code to populate the properties of the DataContext with the designData attribute

As you can see, this is the very same approach taken on the first post, creating an instance of the ViewModel and populate its property at design time in XAML, the real difference with this approach is we are actually using dedicated subclasses to manage design time data, to overcome limitation in the original viewmodel (Es. filtered collection pattern). This is really cool because gives you the best of both approach, you can programmatically generate meaningful Design Time Data, but you can always override them to verify how the layout of the View will appear with different data.

If you want to revert to the original design time data you can simply delete corresponding XAML part in the Window code, or you can reset the value from designer

image

Figure 6: Pressing the small black square box near the property permits you to reset design time data.

This technique is really useful to populate design time data directly in code to have meaningful value, but giving to the people that will skin your application the ability to change these value to verify how the interface will look with different data.

This is one of the real advantage of using a full MVVM approach. I always heard comment by people telling that MVVM is useful only for Unit Testing the View Model, but this is actually not true. A good MVVM architecture creates a complete separation from the UI appearance and UI Logic and give to the designer an unique experience thanks to Design Time Data.

Alk.

Survive binding of DataGrid in WPF

DataGrid is one of the most annoying control in WPF because it sometimes has really different behaviors from other controls. The very first problem is that inside DataGridColumns the DataContext is not what you expect. Suppose you have this simple scenario in MVVM: an ObservableCollection of Objects, each one has IsSelected property and you want to bind that property to a DataGridCheckBox column with a CheckBox in the header that permits you to Select all and Unselect all.

The simplest solution in MVVM architecture is creating a property called IsAllSelected in the main ViewModel, monitor whenever this property changes and at each change, update all objects inside the collection, setting the IsSelected to the value of the IsAllSelected.

public Boolean IsAllSelected
{
    get { return _AllSelected; }
    set { this.Set(p => p.IsAllSelected, value, ref _AllSelected); }
}

private Boolean _AllSelected;

private void IsAllSelectedChanged()
{
    foreach (var vm in SearchesResult)
    {
        vm.IsSelected = IsAllSelected;
    }
}

Once this structure is in place (I have some structures that avoid me the burden to write code inside the setter of the property) I’m sure that whenever the IsAllSelected property changes value, my infrastructure will call the IsAllSelectedChanged. Given this structure you can write the following XAML code

<DataGrid.Columns>
    <DataGridCheckBoxColumn Binding="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
        <DataGridCheckBoxColumn.Header>
            <CheckBox IsChecked="{Binding IsAllSelected}" />
        </DataGridCheckBoxColumn.Header>
    </DataGridCheckBoxColumn>
   ...
</DataGrid.Columns>

It is really simple, just customize the header of a DataGridCheckBoxColumn with a single CheckBox and Bind the IsChecked to the IsAllSelected property. This sounds really reasonable, because the header of a DataGridColumn should have the same DataContext of the container of the DataGrid… and here it comes the surprise. It does not work. You click on the checkbox in the header but nothing happens and if you search in Visual Studio output window you find this error

System.Windows.Data Error: 40 : BindingExpression path error: ‘IsAllSelected’ property not found on ‘object’ ”CheckBox’ (Name=”)’. BindingExpression:Path=IsAllSelected; DataItem=’CheckBox’ (Name=”); target element is ‘CheckBox’ (Name=”); target property is ‘IsChecked’ (type ‘Nullable`1′)

DataGridColumns does not share the same DataContext of DataGrid and this invalidate all the binding you can set on columns, unless you create a fix. Since all the DataGridColumns are inside the DataGrid, you expect them to share the same DataContext, but this is not true and the reason is in how the DataGrid creates internal control. To solve this I wrote the following code in the App.xaml.cs so it got executed at the very start of the application.

FrameworkElement.DataContextProperty.OverrideMetadata(typeof(DataGrid),
new FrameworkPropertyMetadata
    (null, FrameworkPropertyMetadataOptions.Inherits,
    new PropertyChangedCallback(OnDataContextChanged)));

The above code is telling WPF that I want to override the standard metadata for the DataGridControl so I can be notified each time the DataContext of a DataGrid changes. Now I have the ability to fix the DataContext problem, manually assigning the right DataContext to each DataGridColumn.

public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    DataGrid grid = d as DataGrid;
    if (grid != null)
    {
        foreach (DataGridColumn col in grid.Columns)
        {
            col.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
            var header = col.Header as FrameworkElement;
            if (header != null) 
            {
                header.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
            }
        }
    }
}

The code is simple, and based on an old article I read long time ago, I basically scan all the columns that are in the grid.Columns collection and sets the DataContext of the column to the value of the DataContext of the whole DataGrid. But this is not enough to fix the problem of the header, because I need to try to cast the Header to FrameworkElement, so if the header is a Framework element I need to set the DataContext … and this because the Header of the DataGridColumn does not inherit the same DataContext of the Column that contains the Header… so annoying.

Luckily enough, this fix is valid for all the DataGrid of the whole program, so at least it permits me to write once and have a less annoyng DataGrid in my software :)

Alk.

SyncronizationContext.Current is not null, still not be able to access UI Controls in WPF

As you probably already know, you can access WPF controls only from a UI Thread and when I use MVVM each PropertyChanged message check for the need to execute on the UiThread to avoid cross-thread problems. Instead of using the Dispatcher in each property changed sometimes you can find code that does a little optimization like this one.

if (SynchronizationContext.Current != null)
    SynchronizationContext.Current.Send(delegate { OnPropertyChanged(propertyName); }, null);

The above code simply check if the Current synchronization Context is not null, if this condition is true, we are in a UI Thread so we can simply raise the OnPropertyChanged event directly in this thread (with the Send method), because it is associated with a UI and we have no need to use the Dispatcher. The else branch was omitted, but actually is a simple use of a saved instance of the UI Dispatcher to raise the property changed event on the WPF ui thread.

After one year, I discovered that sometimes I still got some cross thread exception and if I check the stack trace of logs I verified that the thread that is generating this exception has SyncronizationContext.Current not null, so you start wondering why you still got exception if the code that is raising the PropertyChanged is belonging to the UI.

In this specific situation the source of the problem is due to WebBrowser control because you should know that a Wpf BrowserControl is a wrapper for the standard winform Browser control, thus if you are in some callback of a WebBrowser control (es. DocumentCompleted) you are in a thread with SyncronizationContext.Current not null, but that specific SyncronizationContext cannot access WPF control because it is a Winform one, thus you still have a cross-thread exception.

The simplest solution is capturing a reference to the original SyncronizationContext.Current during startup of the program, and change the above check to verify also if the Current syncronization context is the very same of the startup of the software

if (SynchronizationContext.Current != null && SynchronizationContext.Current == mainSyncContext)  
  SynchronizationContext.Current.Send(delegate { OnPropertyChanged(propertyName); }, null);

In app.xaml.cs, during the startup of the program I grab a reference to the current synchronization context, I store a reference inside a static variable called mainSyncContext and this permits to check if we are in the main UI thread of WPF verifying that the current synchronization context is the very same I got during application startup. To understand what I’m telling you, here is a snapshot of what happens during the execution of the software

image

Figure 1: As you can see I have a current Synchronization context that is not the Wpf Dispatcher one

As you can see in this specific situation I have SyncronizationContext.Current not null, but is not the same context I have at startup of the application. Loking in the stack trace I verified that this code was called from the event handler of OnNavigated event of a WebBrowser control. From Figure1 you can also verify that mainSyncContext is of type DispatcherSyncronizationContext, (the context used in WPF applications) while the current Sync Context is a generic SynchronizationContext, thus confirming that I’m dealing with two different SynchronizationContext in the same application.

Alk.