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.

Open a windows in a specific monitor in full screen mode in a WPF application

If you have a Multi Monitor setup in Windows and you want to open a WPF window in fullscreen in a specific monitor you have no native option, but it is quite simple to obtain such functionality. First of all you need to reference Winform assembly, because you need winform code to identify information for screens setup in the current system.

In my requirements I need to store the position of every view at the time of last usage, so I can restore its position when the user will open it a second time and I need to support fullscreen and multi-monitor setup.

I decided to save the name of the screen of each View in a dictionary as well as windows coordinates and a boolean that tells me if its maximized. The code to detect this kind of information is really simple, if the window is not maximized I need only to store Top, Left, Height and Width property, if it is maximized I need to find the name of the monitor where the windows is maximized into

position.IsMaximized = true;
var allScreens = System.Windows.Forms.Screen.AllScreens.ToList();
var locationScreen = allScreens.SingleOrDefault(s => this.Left >= s.WorkingArea.Left && this.Left < s.WorkingArea.Right);
if (locationScreen != null)
{
    position.DeviceName = locationScreen.DeviceName;
}

All windows create a Position object on OnClosed method and that information is stored in application settings. Whenever a view is opened, if a previous Position object is present you can restore the position of the form with simple code, first of all identify the name of the screen where the form was at the time of closing

var allScreens = System.Windows.Forms.Screen.AllScreens.ToList();
var screen = allScreens.SingleOrDefault(s => s.DeviceName == positionInfo.DeviceNameAtTimeOfClosing
);

If the form was closed when it is not in full screen the code to restore the position is really trivial, because I need only to restore the four positioning properties. This code fully supports multi monitor, because even if you have more than one monitor configured, you have only one coordinate system, as an example if you have a monitor to the left of your primary monitor, the Left position of the windows will be negative. This lead to the fact that to support multi monitor setup you needs only to save and restore the standard four positioning properties.

viewBase.Left = position.X;
viewBase.Top = position.Y;
viewBase.Width = position.Width;
viewBase.Height = position.Height;
viewBase.WindowState = WindowState.Normal;

ViewBase is a base class that every Window inherit from; this code is not MVVM like, but when it is time to handle positioning and other windows properties, using a base class is a simpler alternative that doing all this logic in a Base View Model and position the windows with Binding. I’ve blogged some time ago on how to save last location of Windows object with MVVM, but that solution did not implement the support for fullscreen and it is quite tedious to use.

If the form was closed while it was in full screen to restore it in the original screen you need a slightly different code, because simply restoring coordinates is clearly not enough.

viewBase.WindowState = System.Windows.WindowState.Normal;

viewBase.Left = screen.WorkingArea.Left + 10;
viewBase.Top = screen.WorkingArea.Top + 10;
viewBase.WindowState = System.Windows.WindowState.Maximized;

The trick is to be sure that the windows is in NormalState, then  position the upper left corner of the windows inside the chosen screen, with an offset of a slightly amount of pixel (in this example 10), finally you can change the WindowState to maximize the window, and it will be maximized in your screen of choice. This code works because when a window is maximized the system maximize it automatically in the screen where it is placed.

This code is one of the primary reason why a full MVVM approach does not works well. This code should be run when the windows is fully initialized and after some tentative, I found that the safest solution is to be able to handle windows events like OnInitialized instead of relaying only on binding and MVVM when it is time to manage window positioning and full screen restore.

All this logic is inside a base class and all I need to do is inherit all View from this class called ViewBase, instead of using standard Window class and all the rest is automatic. I found this approach simpler than binding Top, Left, WindowsState, etc window properties to MVVM.

Alk.

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.