Cách tốt nhất để sắp xếp WPF ListView / GridView khi nhấp vào tiêu đề cột?


84

rất nhiều giải pháp trên internet đang cố gắng lấp đầy sự thiếu sót dường như rất cơ bản này từ WPF. Tôi thực sự bối rối không biết đâu sẽ là cách "tốt nhất". Ví dụ ... Tôi muốn có một ít mũi tên lên / xuống trong tiêu đề cột để chỉ ra hướng sắp xếp. Có vẻ như có 3 cách khác nhau để thực hiện việc này, một số sử dụng mã, một số sử dụng đánh dấu, một số sử dụng mã đánh dấu cộng và tất cả đều có vẻ giống như một hack.

Có ai gặp phải vấn đề này trước đây và tìm ra giải pháp mà họ hoàn toàn hài lòng không? Có vẻ kỳ lạ khi một phần chức năng cơ bản của WinForms như vậy lại bị thiếu trong WPF và cần phải được hack.


Để trả lời câu hỏi trên về cách làm cho việc sử dụng được công nhận. Thêm xmlns: use = "clr-namespace: Wpf.Util" vào không gian tên ở đầu tài liệu
xaml

Nếu có thể .. sử dụng DataGrid.
Abhijeet Nagre

Câu trả lời:


22

Tất cả thực sự phụ thuộc, nếu bạn đang sử dụng DataGrid từ Bộ công cụ WPF thì sẽ có một loại được xây dựng sẵn, thậm chí là một loại nhiều cột rất hữu ích. Kiểm tra thêm tại đây:

Vincent Sibals Blog

Ngoài ra, nếu bạn đang sử dụng một điều khiển khác không hỗ trợ sắp xếp, tôi khuyên bạn nên áp dụng các phương pháp sau:

Phân loại tùy chỉnh của Li Gao

Theo dõi bởi:

Phân loại nhanh hơn của Li Gao


111

Tôi đã viết một tập hợp các thuộc tính đính kèm để tự động sắp xếp a GridView, bạn có thể xem tại đây . Nó không xử lý mũi tên lên / xuống, nhưng nó có thể dễ dàng được thêm vào.

<ListView ItemsSource="{Binding Persons}"
          IsSynchronizedWithCurrentItem="True"
          util:GridViewSort.AutoSort="True">
    <ListView.View>
        <GridView>
            <GridView.Columns>
                <GridViewColumn Header="Name"
                                DisplayMemberBinding="{Binding Name}"
                                util:GridViewSort.PropertyName="Name"/>
                <GridViewColumn Header="First name"
                                DisplayMemberBinding="{Binding FirstName}"
                                util:GridViewSort.PropertyName="FirstName"/>
                <GridViewColumn Header="Date of birth"
                                DisplayMemberBinding="{Binding DateOfBirth}"
                                util:GridViewSort.PropertyName="DateOfBirth"/>
            </GridView.Columns>
        </GridView>
    </ListView.View>
</ListView>

8
Cảm ơn Thomas, giải pháp của bạn cho vấn đề sắp xếp rất thanh lịch, dễ sử dụng và rất linh hoạt. Nói cách khác: hoàn hảo! Mẹo cho những người khác: 1) sử dụng phiên bản cập nhật được liên kết đến trong bài viết của Thomas và 2) sử dụng phiên bản glyph đẹp hơn của Alex trong các nhận xét.
Helge Klein

Tiện ích tuyệt vời anh L!
Metro Smurf

Tôi mới làm quen với WPF và không hiểu chút gì về "tiện dụng" này. Tham khảo đó là gì? Chỉnh sửa: nm ... có một liên kết nhỏ nhỏ có tên là "View Source" đã không nhảy ra với tôi ngay lập tức. Điều này mở rộng mã nguồn cho các lớp
oscilatingcretin

Tôi rất thích điều đó! Tôi đang thêm / xóa các mục một cách động và điều này hoạt động tốt mà không cần thay đổi thứ tự. Nhưng làm cách nào để đặt trạng thái ban đầu cho việc sắp xếp? CollectionViewSource.GetDefaultView(MyList.ItemsSource).SortDescriptions.Add(new SortDescription("Number", ListSortDirection.Ascending));không hoạt động.
zee

@zee, cần làm việc, nhưng nó sẽ không hiển thị các glyph loại ... Tôi đã không được thực hiện một cách để thiết lập một trật tự ban đầu, nhưng bạn luôn có thể cố gắng sửa đổi mã của tôi;)
Thomas Levesque

23

MSDN có một cách dễ dàng để thực hiện sắp xếp trên các cột với glyphs lên / xuống. Tuy nhiên, ví dụ này không hoàn chỉnh - chúng không giải thích cách sử dụng các mẫu dữ liệu cho các glyph. Dưới đây là những gì tôi phải làm việc với ListView của mình. Điều này hoạt động trên .Net 4.

Trong ListView của bạn, bạn phải chỉ định một trình xử lý sự kiện để kích hoạt cho một lần nhấp vào GridViewColumnHeader. ListView của tôi trông như thế này:

<ListView Name="results" GridViewColumnHeader.Click="results_Click">
    <ListView.View>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding Path=ContactName}">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="Contact Name" Padding="5,0,0,0" HorizontalContentAlignment="Left" MinWidth="150" Name="ContactName" />
                </GridViewColumn.Header>
            </GridViewColumn>
            <GridViewColumn DisplayMemberBinding="{Binding Path=PrimaryPhone}">
                <GridViewColumn.Header>
                    <GridViewColumnHeader Content="Contact Number" Padding="5,0,0,0" HorizontalContentAlignment="Left" MinWidth="150" Name="PrimaryPhone"/>
                </GridViewColumn.Header>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

Trong mã phía sau của bạn, hãy thiết lập mã để xử lý việc sắp xếp:

// Global objects
BindingListCollectionView blcv;
GridViewColumnHeader _lastHeaderClicked = null;
ListSortDirection _lastDirection = ListSortDirection.Ascending;

// Header click event
void results_Click(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;
            }
        }

        string header = headerClicked.Column.Header as string;
        Sort(header, direction);

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

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

        _lastHeaderClicked = headerClicked;
        _lastDirection = direction;
    }
}

// Sort code
private void Sort(string sortBy, ListSortDirection direction)
{
    blcv.SortDescriptions.Clear();
    SortDescription sd = new SortDescription(sortBy, direction);
    blcv.SortDescriptions.Add(sd);
    blcv.Refresh();
}

Và sau đó trong XAML của bạn, bạn cần thêm hai Mẫu dữ liệu mà bạn đã chỉ định trong phương pháp sắp xếp:

<DataTemplate x:Key="HeaderTemplateArrowUp">
    <DockPanel LastChildFill="True" Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}">
        <Path x:Name="arrowUp" StrokeThickness="1" Fill="Gray" Data="M 5,10 L 15,10 L 10,5 L 5,10" DockPanel.Dock="Right" Width="20" HorizontalAlignment="Right" Margin="5,0,5,0" SnapsToDevicePixels="True"/>
        <TextBlock Text="{Binding }" />
    </DockPanel>
</DataTemplate>

<DataTemplate x:Key="HeaderTemplateArrowDown">
    <DockPanel LastChildFill="True" Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}">
        <Path x:Name="arrowDown" StrokeThickness="1" Fill="Gray"  Data="M 5,5 L 10,10 L 15,5 L 5,5" DockPanel.Dock="Right" Width="20" HorizontalAlignment="Right" Margin="5,0,5,0" SnapsToDevicePixels="True"/>
        <TextBlock Text="{Binding }" />
    </DockPanel>
</DataTemplate>

Sử dụng DockPanelvới LastChildFillset thành true sẽ giữ glyph ở bên phải của tiêu đề và để nhãn lấp đầy phần còn lại của khoảng trống. Tôi ràng buộc các DockPanelchiều rộng đến ActualWidthcác GridViewColumnHeadervì cột của tôi không có chiều rộng, cho phép họ AutoFit đến nội dung. Tuy nhiên, tôi đã đặt MinWidths trên các cột, để glyph không che tiêu đề cột. Giá trị TextBlock Textđược đặt thành một liên kết trống hiển thị tên cột được chỉ định trong tiêu đề.


1
Điều này không chỉ định vị trí trong XAML để đặt DataTemplates Grid.Resources?

4
@Mark Đây có lẽ là quá muộn để giúp bạn, nhưng các mẫu nên được đặt trong Tài nguyên của phần tử gốc, thường là <Window.Resources>hoặc <UserControl.Resources>. HTHS;)
CptRobby

1
@CptRobby xin chào .. đối với tôi, vì blcv không được khởi tạo, nó đưa ra một ngoại lệ tham chiếu rỗng ... vậy mã này sẽ hoạt động như thế nào đối với bất kỳ ai?
Jay Nirgudkar

@JayNirgudkar Tôi không phải là tác giả của cái này, Jared Harley là. Nhưng tôi có thể nói với bạn rằng blcv là thứ anh ấy đang sử dụng làm Nguồn mục của ListView của anh ấy. Bạn không cần phải làm điều tương tự. Nhấp vào liên kết MSDN mà anh ấy đã cung cấp để biết cách xử lý thay thế với Nguồn mục.
CptRobby

2
Ví dụ MSDN giả định rằng headerClicked.Column.Header(là văn bản tiêu đề) tương đương với (headerClicked.Column.DisplayMemberBinding as Binding).Path.Path(là đường dẫn liên kết). Sắp xếp trên văn bản tiêu đề không hoạt động. Rất kì lạ.
Chris

5

Tôi sử dụng MVVM, vì vậy tôi đã tạo một số thuộc tính đính kèm của riêng mình, sử dụng Thomas làm tham chiếu. Nó sắp xếp trên một cột tại một thời điểm khi bạn nhấp vào tiêu đề, chuyển đổi giữa Tăng dần và Giảm dần. Nó sắp xếp ngay từ đầu bằng cách sử dụng cột đầu tiên. Và nó hiển thị glyphs kiểu Win7 / 8.

Thông thường, tất cả những gì bạn phải làm là đặt thuộc tính chính thành true (nhưng bạn phải khai báo rõ ràng GridViewColumnHeaders):

<Window xmlns:local="clr-namespace:MyProjectNamespace">
  <Grid>
    <ListView local:App.EnableGridViewSort="True" ItemsSource="{Binding LVItems}">
      <ListView.View>
        <GridView>
          <GridViewColumn DisplayMemberBinding="{Binding Property1}">
            <GridViewColumnHeader Content="Prop 1" />
          </GridViewColumn>
          <GridViewColumn DisplayMemberBinding="{Binding Property2}">
            <GridViewColumnHeader Content="Prop 2" />
          </GridViewColumn>
        </GridView>
      </ListView.View>
    </ListView>
  </Grid>
<Window>

Nếu bạn muốn sắp xếp trên một thuộc tính khác với màn hình, bạn phải khai báo rằng:

<GridViewColumn DisplayMemberBinding="{Binding Property3}"
                local:App.GridViewSortPropertyName="Property4">
    <GridViewColumnHeader Content="Prop 3" />
</GridViewColumn>

Đây là mã cho các thuộc tính đính kèm, tôi muốn lười biếng và đưa chúng vào App.xaml.cs được cung cấp:

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data.
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace MyProjectNamespace
{
  public partial class App : Application
  {
      #region GridViewSort
      public static DependencyProperty GridViewSortPropertyNameProperty =
          DependencyProperty.RegisterAttached(
              "GridViewSortPropertyName", 
              typeof(string), 
              typeof(App), 
              new UIPropertyMetadata(null)
          );

      public static string GetGridViewSortPropertyName(GridViewColumn gvc)
      {
          return (string)gvc.GetValue(GridViewSortPropertyNameProperty);
      }

      public static void SetGridViewSortPropertyName(GridViewColumn gvc, string n)
      {
          gvc.SetValue(GridViewSortPropertyNameProperty, n);
      }

      public static DependencyProperty CurrentSortColumnProperty =
          DependencyProperty.RegisterAttached(
              "CurrentSortColumn", 
              typeof(GridViewColumn), 
              typeof(App), 
              new UIPropertyMetadata(
                  null, 
                  new PropertyChangedCallback(CurrentSortColumnChanged)
              )
          );

      public static GridViewColumn GetCurrentSortColumn(GridView gv)
      {
          return (GridViewColumn)gv.GetValue(CurrentSortColumnProperty);
      }

      public static void SetCurrentSortColumn(GridView gv, GridViewColumn value)
      {
          gv.SetValue(CurrentSortColumnProperty, value);
      }

      public static void CurrentSortColumnChanged(
          object sender, DependencyPropertyChangedEventArgs e)
      {
          GridViewColumn gvcOld = e.OldValue as GridViewColumn;
          if (gvcOld != null)
          {
              CurrentSortColumnSetGlyph(gvcOld, null);
          }
      }

      public static void CurrentSortColumnSetGlyph(GridViewColumn gvc, ListView lv)
      {
          ListSortDirection lsd;
          Brush brush;
          if (lv == null)
          {
              lsd = ListSortDirection.Ascending;
              brush = Brushes.Transparent;
          }
          else
          {
              SortDescriptionCollection sdc = lv.Items.SortDescriptions;
              if (sdc == null || sdc.Count < 1) return;
              lsd = sdc[0].Direction;
              brush = Brushes.Gray;
          }

          FrameworkElementFactory fefGlyph = 
              new FrameworkElementFactory(typeof(Path));
          fefGlyph.Name = "arrow";
          fefGlyph.SetValue(Path.StrokeThicknessProperty, 1.0);
          fefGlyph.SetValue(Path.FillProperty, brush);
          fefGlyph.SetValue(StackPanel.HorizontalAlignmentProperty, 
              HorizontalAlignment.Center);

          int s = 4;
          if (lsd == ListSortDirection.Ascending)
          {
              PathFigure pf = new PathFigure();
              pf.IsClosed = true;
              pf.StartPoint = new Point(0, s);
              pf.Segments.Add(new LineSegment(new Point(s * 2, s), false));
              pf.Segments.Add(new LineSegment(new Point(s, 0), false));

              PathGeometry pg = new PathGeometry();
              pg.Figures.Add(pf);

              fefGlyph.SetValue(Path.DataProperty, pg);
          }
          else
          {
              PathFigure pf = new PathFigure();
              pf.IsClosed = true;
              pf.StartPoint = new Point(0, 0);
              pf.Segments.Add(new LineSegment(new Point(s, s), false));
              pf.Segments.Add(new LineSegment(new Point(s * 2, 0), false));

              PathGeometry pg = new PathGeometry();
              pg.Figures.Add(pf);

              fefGlyph.SetValue(Path.DataProperty, pg);
          }

          FrameworkElementFactory fefTextBlock = 
              new FrameworkElementFactory(typeof(TextBlock));
          fefTextBlock.SetValue(TextBlock.HorizontalAlignmentProperty,
              HorizontalAlignment.Center);
          fefTextBlock.SetValue(TextBlock.TextProperty, new Binding());

          FrameworkElementFactory fefDockPanel = 
              new FrameworkElementFactory(typeof(StackPanel));
          fefDockPanel.SetValue(StackPanel.OrientationProperty,
              Orientation.Vertical);
          fefDockPanel.AppendChild(fefGlyph);
          fefDockPanel.AppendChild(fefTextBlock);

          DataTemplate dt = new DataTemplate(typeof(GridViewColumn));
          dt.VisualTree = fefDockPanel;

          gvc.HeaderTemplate = dt;
      }

      public static DependencyProperty EnableGridViewSortProperty =
          DependencyProperty.RegisterAttached(
              "EnableGridViewSort", 
              typeof(bool), 
              typeof(App), 
              new UIPropertyMetadata(
                  false, 
                  new PropertyChangedCallback(EnableGridViewSortChanged)
              )
          );

      public static bool GetEnableGridViewSort(ListView lv)
      {
          return (bool)lv.GetValue(EnableGridViewSortProperty);
      }

      public static void SetEnableGridViewSort(ListView lv, bool value)
      {
          lv.SetValue(EnableGridViewSortProperty, value);
      }

      public static void EnableGridViewSortChanged(
          object sender, DependencyPropertyChangedEventArgs e)
      {
          ListView lv = sender as ListView;
          if (lv == null) return;

          if (!(e.NewValue is bool)) return;
          bool enableGridViewSort = (bool)e.NewValue;

          if (enableGridViewSort)
          {
              lv.AddHandler(
                  GridViewColumnHeader.ClickEvent,
                  new RoutedEventHandler(EnableGridViewSortGVHClicked)
              );
              if (lv.View == null)
              {
                  lv.Loaded += new RoutedEventHandler(EnableGridViewSortLVLoaded);
              }
              else
              {
                  EnableGridViewSortLVInitialize(lv);
              }
          }
          else
          {
              lv.RemoveHandler(
                  GridViewColumnHeader.ClickEvent,
                  new RoutedEventHandler(EnableGridViewSortGVHClicked)
              );
          }
      }

      public static void EnableGridViewSortLVLoaded(object sender, RoutedEventArgs e)
      {
          ListView lv = e.Source as ListView;
          EnableGridViewSortLVInitialize(lv);
          lv.Loaded -= new RoutedEventHandler(EnableGridViewSortLVLoaded);
      }

      public static void EnableGridViewSortLVInitialize(ListView lv)
      {
          GridView gv = lv.View as GridView;
          if (gv == null) return;

          bool first = true;
          foreach (GridViewColumn gvc in gv.Columns)
          {
              if (first)
              {
                  EnableGridViewSortApplySort(lv, gv, gvc);
                  first = false;
              }
              else
              {
                  CurrentSortColumnSetGlyph(gvc, null);
              }
          }
      }

      public static void EnableGridViewSortGVHClicked(
          object sender, RoutedEventArgs e)
      {
          GridViewColumnHeader gvch = e.OriginalSource as GridViewColumnHeader;
          if (gvch == null) return;
          GridViewColumn gvc = gvch.Column;
          if(gvc == null) return;            
          ListView lv = VisualUpwardSearch<ListView>(gvch);
          if (lv == null) return;
          GridView gv = lv.View as GridView;
          if (gv == null) return;

          EnableGridViewSortApplySort(lv, gv, gvc);
      }

      public static void EnableGridViewSortApplySort(
          ListView lv, GridView gv, GridViewColumn gvc)
      {
          bool isEnabled = GetEnableGridViewSort(lv);
          if (!isEnabled) return;

          string propertyName = GetGridViewSortPropertyName(gvc);
          if (string.IsNullOrEmpty(propertyName))
          {
              Binding b = gvc.DisplayMemberBinding as Binding;
              if (b != null && b.Path != null)
              {
                  propertyName = b.Path.Path;
              }

              if (string.IsNullOrEmpty(propertyName)) return;
          }

          ApplySort(lv.Items, propertyName);
          SetCurrentSortColumn(gv, gvc);
          CurrentSortColumnSetGlyph(gvc, lv);
      }

      public static void ApplySort(ICollectionView view, string propertyName)
      {
          if (string.IsNullOrEmpty(propertyName)) return;

          ListSortDirection lsd = ListSortDirection.Ascending;
          if (view.SortDescriptions.Count > 0)
          {
              SortDescription sd = view.SortDescriptions[0];
              if (sd.PropertyName.Equals(propertyName))
              {
                  if (sd.Direction == ListSortDirection.Ascending)
                  {
                      lsd = ListSortDirection.Descending;
                  }
                  else
                  {
                      lsd = ListSortDirection.Ascending;
                  }
              }
              view.SortDescriptions.Clear();
          }

          view.SortDescriptions.Add(new SortDescription(propertyName, lsd));
      }
      #endregion

      public static T VisualUpwardSearch<T>(DependencyObject source) 
          where T : DependencyObject
      {
          return VisualUpwardSearch(source, x => x is T) as T;
      }

      public static DependencyObject VisualUpwardSearch(
                          DependencyObject source, Predicate<DependencyObject> match)
      {
          DependencyObject returnVal = source;

          while (returnVal != null && !match(returnVal))
          {
              DependencyObject tempReturnVal = null;
              if (returnVal is Visual || returnVal is Visual3D)
              {
                  tempReturnVal = VisualTreeHelper.GetParent(returnVal);
              }
              if (tempReturnVal == null)
              {
                  returnVal = LogicalTreeHelper.GetParent(returnVal);
              }
              else
              {
                  returnVal = tempReturnVal;
              }
          }

          return returnVal;
      }
  }
}

3

Tôi đã thực hiện một bản điều chỉnh theo cách của Microsoft, nơi tôi ghi đè ListViewquyền kiểm soát để thực hiện SortableListView:

public partial class SortableListView : ListView
    {        
        private GridViewColumnHeader lastHeaderClicked = null;
        private ListSortDirection lastDirection = ListSortDirection.Ascending;       

        public void GridViewColumnHeaderClicked(GridViewColumnHeader clickedHeader)
        {
            ListSortDirection direction;

            if (clickedHeader != null)
            {
                if (clickedHeader.Role != GridViewColumnHeaderRole.Padding)
                {
                    if (clickedHeader != lastHeaderClicked)
                    {
                        direction = ListSortDirection.Ascending;
                    }
                    else
                    {
                        if (lastDirection == ListSortDirection.Ascending)
                        {
                            direction = ListSortDirection.Descending;
                        }
                        else
                        {
                            direction = ListSortDirection.Ascending;
                        }
                    }

                    string sortString = ((Binding)clickedHeader.Column.DisplayMemberBinding).Path.Path;

                    Sort(sortString, direction);

                    lastHeaderClicked = clickedHeader;
                    lastDirection = direction;
                }
            }
        }

        private void Sort(string sortBy, ListSortDirection direction)
        {
            ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ItemsSource != null ? this.ItemsSource : this.Items);

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

((Binding)clickedHeader.Column.DisplayMemberBinding).Path.PathBit dòng xử lý các trường hợp tên cột của bạn không giống với đường dẫn liên kết của chúng, điều mà phương pháp của Microsoft không làm được.

Tôi muốn ngăn chặn GridViewColumnHeader.Clicksự kiện để không phải suy nghĩ về nó nữa, nhưng tôi không thể tìm ra cách để làm. Do đó, tôi thêm phần sau vào XAML cho mọi SortableListView:

GridViewColumnHeader.Click="SortableListViewColumnHeaderClicked"

Và sau đó trên bất kỳ Windowcó chứa bất kỳ số SortableListViews, chỉ cần thêm mã sau:

private void SortableListViewColumnHeaderClicked(object sender, RoutedEventArgs e)
        {
            ((Controls.SortableListView)sender).GridViewColumnHeaderClicked(e.OriginalSource as GridViewColumnHeader);
        }

Đâu Controlschỉ là ID XAML cho không gian tên mà bạn đã thực hiện SortableListViewkiểm soát.

Vì vậy, điều này ngăn chặn sự trùng lặp mã ở phía sắp xếp, bạn chỉ cần nhớ xử lý sự kiện như trên.


1
Tôi đã lấy cảm hứng từ giải pháp của bạn và đi theo con đường tương tự, để truy cập sự kiện GridViewColumnHeader.Click, bạn có thể thêm một trình xử lý trong hàm tạo. this.AddHandler (GridViewColumnHeader.ClickEvent, RoutedEventHandler mới (GridViewColumnHeaderClicked));
Derrick Moeller

3

Nếu bạn có chế độ xem danh sách và biến nó thành chế độ xem lưới, bạn có thể dễ dàng làm cho các tiêu đề cột chế độ xem lưới của bạn có thể nhấp được bằng cách thực hiện việc này.

        <Style TargetType="GridViewColumnHeader">
            <Setter Property="Command" Value="{Binding CommandOrderBy}"/>
            <Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self},Path=Content}"/>
        </Style>

Sau đó, chỉ cần đặt một lệnh ủy quyền trong mã của bạn.

    public DelegateCommand CommandOrderBy { get { return new DelegateCommand(Delegated_CommandOrderBy); } }

    private void Delegated_CommandOrderBy(object obj)
    {
        throw new NotImplementedException();
    }

Tôi sẽ giả sử rằng tất cả các bạn đều biết cách tạo ICommand DelegateCommand ở đây. điều này cho phép tôi giữ tất cả các Chế độ xem của tôi nhấp vào ViewModel.

Tôi chỉ thêm điều này để có nhiều cách để đạt được điều tương tự. Tôi đã không viết mã để thêm các nút mũi tên trong tiêu đề, nhưng điều đó sẽ được thực hiện theo kiểu XAML, bạn sẽ cần thiết kế lại toàn bộ tiêu đề mà JanDotNet có trong mã của họ.


0

Giải pháp tóm tắt tất cả các phần hoạt động của các câu trả lời và nhận xét hiện có bao gồm các mẫu tiêu đề cột:

Lượt xem:

<ListView x:Class="MyNamspace.MyListView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             ItemsSource="{Binding Items}"
             GridViewColumnHeader.Click="ListViewColumnHeaderClick">
    <ListView.Resources>

        <Style TargetType="Grid" x:Key="HeaderGridStyle">
            <Setter Property="Height" Value="20" />
        </Style>

        <Style TargetType="TextBlock" x:Key="HeaderTextBlockStyle">
            <Setter Property="Margin" Value="5,0,0,0" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>

        <Style TargetType="Path" x:Key="HeaderPathStyle">
            <Setter Property="StrokeThickness" Value="1" />
            <Setter Property="Fill" Value="Gray" />
            <Setter Property="Width" Value="20" />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="Margin" Value="5,0,5,0" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
        </Style>

        <DataTemplate x:Key="HeaderTemplateDefault">
            <Grid Style="{StaticResource HeaderGridStyle}">
                <TextBlock Text="{Binding }" Style="{StaticResource HeaderTextBlockStyle}" />
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="HeaderTemplateArrowUp">
            <Grid Style="{StaticResource HeaderGridStyle}">
                <Path Data="M 7,3 L 13,3 L 10,0 L 7,3" Style="{StaticResource HeaderPathStyle}" />
                <TextBlock Text="{Binding }" Style="{StaticResource HeaderTextBlockStyle}" />
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="HeaderTemplateArrowDown">
            <Grid Style="{StaticResource HeaderGridStyle}">
                <Path Data="M 7,0 L 10,3 L 13,0 L 7,0"  Style="{StaticResource HeaderPathStyle}" />
                <TextBlock Text="{Binding }" Style="{StaticResource HeaderTextBlockStyle}" />
            </Grid>
        </DataTemplate>

    </ListView.Resources>

    <ListView.View>
        <GridView ColumnHeaderTemplate="{StaticResource HeaderTemplateDefault}">

            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding NameProperty}" />
            <GridViewColumn Header="Type" Width="45" DisplayMemberBinding="{Binding TypeProperty}"/>

            <!-- ... -->

        </GridView>
    </ListView.View>
</ListView>

Mã phía sau:

public partial class MyListView : ListView
{
    GridViewColumnHeader _lastHeaderClicked = null;

    public MyListView()
    {
        InitializeComponent();
    }

    private void ListViewColumnHeaderClick(object sender, RoutedEventArgs e)
    {
        GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;

        if (headerClicked == null)
            return;

        if (headerClicked.Role == GridViewColumnHeaderRole.Padding)
            return;

        var sortingColumn = (headerClicked.Column.DisplayMemberBinding as Binding)?.Path?.Path;
        if (sortingColumn == null)
            return;

        var direction = ApplySort(Items, sortingColumn);

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

        // Remove arrow from previously sorted header
        if (_lastHeaderClicked != null && _lastHeaderClicked != headerClicked)
        {
            _lastHeaderClicked.Column.HeaderTemplate =
                Resources["HeaderTemplateDefault"] as DataTemplate;
        }

        _lastHeaderClicked = headerClicked;
    }


    public static ListSortDirection ApplySort(ICollectionView view, string propertyName)
    {
        ListSortDirection direction = ListSortDirection.Ascending;
        if (view.SortDescriptions.Count > 0)
        {
            SortDescription currentSort = view.SortDescriptions[0];
            if (currentSort.PropertyName == propertyName)
            {
                if (currentSort.Direction == ListSortDirection.Ascending)
                    direction = ListSortDirection.Descending;
                else
                    direction = ListSortDirection.Ascending;
            }
            view.SortDescriptions.Clear();
        }
        if (!string.IsNullOrEmpty(propertyName))
        {
            view.SortDescriptions.Add(new SortDescription(propertyName, direction));
        }
        return direction;
    }
}

1
Việc ném ra hàng trăm dòng mã chẳng có nghĩa lý gì nếu bạn không giải thích những gì họ đang làm
schizoid04

0

Chỉ muốn thêm một cách đơn giản khác để ai đó có thể sắp xếp WPF ListView

void SortListView(ListView listView)
{
    IEnumerable listView_items = listView.Items.SourceCollection;
    List<MY_ITEM_CLASS> listView_items_to_list = listView_items.Cast<MY_ITEM_CLASS>().ToList();

    Comparer<MY_ITEM_CLASS> scoreComparer = Comparer<MY_ITEM_CLASS>.Create((first, second) => first.COLUMN_NAME.CompareTo(second.COLUMN_NAME));

    listView_items_to_list.Sort(scoreComparer);
    listView.ItemsSource = null;
    listView.Items.Clear();
    listView.ItemsSource = listView_items_to_list;
}

0

Sau khi tìm kiếm rất nhiều, finaly tôi thấy đơn giản ở đây https://www.wpf-tutorial.com/listview-control/listview-how-to-column-sorting/

private GridViewColumnHeader listViewSortCol = null;
private SortAdorner listViewSortAdorner = null;
private void GridViewColumnHeader_Click(object sender, RoutedEventArgs e)
{
  GridViewColumnHeader column = (sender as GridViewColumnHeader);
  string sortBy = column.Tag.ToString();
  if (listViewSortCol != null)
  {
    AdornerLayer.GetAdornerLayer(listViewSortCol).Remove(listViewSortAdorner);
    yourListView.Items.SortDescriptions.Clear();
  }

  ListSortDirection newDir = ListSortDirection.Ascending;
  if (listViewSortCol == column && listViewSortAdorner.Direction == newDir)
    newDir = ListSortDirection.Descending;

  listViewSortCol = column;
  listViewSortAdorner = new SortAdorner(listViewSortCol, newDir);
  AdornerLayer.GetAdornerLayer(listViewSortCol).Add(listViewSortAdorner);
  yourListView.Items.SortDescriptions.Add(new SortDescription(sortBy, newDir));
}

Lớp học:

public class SortAdorner : Adorner
{
    private static Geometry ascGeometry =
        Geometry.Parse("M 0 4 L 3.5 0 L 7 4 Z");

    private static Geometry descGeometry =
        Geometry.Parse("M 0 0 L 3.5 4 L 7 0 Z");

    public ListSortDirection Direction { get; private set; }

    public SortAdorner(UIElement element, ListSortDirection dir)
        : base(element)
    {
        this.Direction = dir;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        if(AdornedElement.RenderSize.Width < 20)
            return;

        TranslateTransform transform = new TranslateTransform
            (
                AdornedElement.RenderSize.Width - 15,
                (AdornedElement.RenderSize.Height - 5) / 2
            );
        drawingContext.PushTransform(transform);

        Geometry geometry = ascGeometry;
        if(this.Direction == ListSortDirection.Descending)
            geometry = descGeometry;
        drawingContext.DrawGeometry(Brushes.Black, null, geometry);

        drawingContext.Pop();
    }
}

Xaml

<GridViewColumn Width="250">
  <GridViewColumn.Header>
    <GridViewColumnHeader Tag="Name" Click="GridViewColumnHeader_Click">Name</GridViewColumnHeader>
  </GridViewColumn.Header>
  <GridViewColumn.CellTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding Name}" ToolTip="{Binding Name}"/>
    </DataTemplate>
  </GridViewColumn.CellTemplate>
</GridViewColumn>

1
Cảm ơn. Để có thêm điểm, bạn cũng có thể thêm phần tóm tắt giải thích không? Đây hiện là chỉ liên kết và mã (đã tốt hơn chỉ liên kết ...).
Yunnosch

-2

Thử đi:

using System.ComponentModel;
youtItemsControl.Items.SortDescriptions.Add(new SortDescription("yourFavoritePropertyFromItem",ListSortDirection.Ascending);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.