0

I have a GridView with GridViewColumn, the header uses a template with a textblox to show the column name. I want a command attached to this column, basically when the user clicks the column, a command in my VM gets called ?

this is for sorting purposes.

Thanks

Pacman
  • 2,183
  • 6
  • 39
  • 70

2 Answers2

4

Probably too late to help you but for anyone else looking for an answer, you can do this using Attached Behaviours and the GridViewColumnHeader.Click event (see this MSDN article on sorting a GridView on header item click).

My code was as follows; XAML:

<ListView Width="Auto" Height="Auto" Margin="12,12,12,12"
  ItemsSource="{Binding SearchResults}" 
  behav:GridViewColumnHeaderClick.Command="{Binding SortViewCommand}">

(where 'behav' is the namespace for my attached behaviours). The attached behaviour classes look like this:

public class GridViewColumnHeaderClick
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(GridViewColumnHeaderClick), new UIPropertyMetadata(null,
            GridViewColumnHeaderClick.CommandChanged));

    public static readonly DependencyProperty CommandBehaviourProperty =
        DependencyProperty.RegisterAttached("CommandBehaviour", typeof(GridViewColumnHeaderClickCommandBehaviour), typeof(GridViewColumnHeaderClick),
            new UIPropertyMetadata(null));

    public static ICommand GetCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(CommandProperty, value);
    }

    public static GridViewColumnHeaderClickCommandBehaviour GetCommandBehaviour(DependencyObject obj)
    {
        return (GridViewColumnHeaderClickCommandBehaviour)obj.GetValue(CommandBehaviourProperty);
    }

    public static void SetCommandBehaviour(DependencyObject obj, GridViewColumnHeaderClickCommandBehaviour value)
    {
        obj.SetValue(CommandBehaviourProperty, value);
    }

    private static void CommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumnHeaderClick.GetOrCreateBehaviour(sender).Command = e.NewValue as ICommand;
    }

    private static GridViewColumnHeaderClickCommandBehaviour GetOrCreateBehaviour(DependencyObject element)
    {
        GridViewColumnHeaderClickCommandBehaviour returnVal = GridViewColumnHeaderClick.GetCommandBehaviour(element);

        if (returnVal == null)
        {
            ListView typedElement = element as ListView;

            if (typedElement == null)
            {
                throw new InvalidOperationException("GridViewColumnHeaderClick.Command property can only be set on instances of ListView");
            }

            returnVal = new GridViewColumnHeaderClickCommandBehaviour(typedElement);

            GridViewColumnHeaderClick.SetCommandBehaviour(element, returnVal);
        }

        return returnVal;
    }
}

and:

public class GridViewColumnHeaderClickCommandBehaviour
{
    public GridViewColumnHeaderClickCommandBehaviour(ListView element)
    {
        element.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(this.ClickEventHandler));
    }

    public ICommand Command { get; set; }

    private void ClickEventHandler(object sender, RoutedEventArgs e)
    {
        ICommand localCommand = this.Command;
        object parameter = e.OriginalSource as GridViewColumnHeader;

        if ((localCommand != null) && localCommand.CanExecute(parameter))
        {
            localCommand.Execute(parameter);
        }
    }
}

and then your command can be based on the event handler described in the MSDN article:

    private void SortResults(string sortBy, ListSortDirection direction)
    {
        ICollectionView dataView = CollectionViewSource.GetDefaultView(this.SearchResults);    // where SearchResults is the data to which the ListView is bound
        dataView.SortDescriptions.Clear();

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

    private void SortViewCommandHandler(object parameter)
    {
        GridViewColumnHeader typedParameter = parameter as GridViewColumnHeader;

        ListSortDirection direction;

        if (typedParameter != null)
        {
            if (typedParameter.Role != GridViewColumnHeaderRole.Padding)
            {
                if (typedParameter != this.previousSortHeader)
                {
                    direction = ListSortDirection.Ascending;
                }
                else
                {
                    if (this.previousSortDirection == ListSortDirection.Ascending)
                    {
                        direction = ListSortDirection.Descending;
                    }
                    else
                    {
                        direction = ListSortDirection.Ascending;
                    }
                }

                string headerLabel = typedParameter.Column.Header as string;

                this.SortResults(headerLabel, direction);

                if (direction == ListSortDirection.Ascending)
                {
                    typedParameter.Column.HeaderTemplate =
                      Resources["HeaderTemplateArrowUp"] as DataTemplate;
                }
                else
                {
                    typedParameter.Column.HeaderTemplate =
                      Resources["HeaderTemplateArrowDown"] as DataTemplate;
                }

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

                this.previousSortHeader = typedParameter;
                this.previousSortDirection = direction;
            }
        }
    }

I haven't yet thought of an MVVM-ish way to set the header template (the view should obviously be bound to something in here) so you're on your own there!

Note that I'm deviating slightly from the Josh Smith article in my implementation of the attached behaviour - having a separate class makes multiple stateful handlers easier than using a static event handler method, so it's a pattern I follow in general.

sth
  • 222,467
  • 53
  • 283
  • 367
Dan Parsonson
  • 1,010
  • 9
  • 13
  • I kind of get it. So, I put the SortViewCommand in my ViewModel and have it invoke the SortResultViewCommandHandler()? – John Jesus Apr 13 '14 at 01:00
  • So sorry, I'm way too late replying, but for the benefit of any other readers - yes, what you say is correct. – Dan Parsonson Aug 25 '15 at 09:03
0

You can replace the textblock in the header template with a button and then attach a command to it. If you want, you can set a style for the buttons to remove the borders.

Jogy
  • 2,465
  • 1
  • 14
  • 9
  • here is my DataTemplate: problem is, when I change the column's width, the button's size stays constant, I need the button to take over the entire Column size. – Pacman Mar 17 '11 at 17:17
  • You don't need the DockPanel, and you need to set the HorizontalAlignment, not the HorizontalContentAlignment of the Button. Try this template: Button HorizontalAlignment="Stretch"> – Jogy Mar 18 '11 at 08:03