Làm cách nào để liên kết một WPF DataGrid với số lượng cột khác nhau?


124

Ứng dụng WPF của tôi tạo ra các bộ dữ liệu có thể có số lượng cột khác nhau mỗi lần. Bao gồm trong đầu ra là một mô tả của từng cột sẽ được sử dụng để áp dụng định dạng. Một phiên bản đơn giản hóa của đầu ra có thể là một cái gì đó như:

class Data
{
    IList<ColumnDescription> ColumnDescriptions { get; set; }
    string[][] Rows { get; set; }
}

Lớp này được đặt là DataContext trên WPF DataGrid nhưng tôi thực sự tạo các cột theo chương trình:

for (int i = 0; i < data.ColumnDescriptions.Count; i++)
{
    dataGrid.Columns.Add(new DataGridTextColumn
    {
        Header = data.ColumnDescriptions[i].Name,
        Binding = new Binding(string.Format("[{0}]", i))
    });
}

Có cách nào để thay thế mã này bằng các ràng buộc dữ liệu trong tệp XAML không?

Câu trả lời:


127

Đây là một cách giải quyết cho các cột liên kết trong DataGrid. Vì thuộc tính Cột là ReadOnly, như mọi người nhận thấy, tôi đã tạo một Thuộc tính được đính kèm có tên là BindableColumns cập nhật các Cột trong DataGrid mỗi khi bộ sưu tập thay đổi thông qua sự kiện CollectionChanged.

Nếu chúng ta có Bộ sưu tập DataGridColumn này

public ObservableCollection<DataGridColumn> ColumnCollection
{
    get;
    private set;
}

Sau đó, chúng ta có thể liên kết BindableColumn với CộtCollection như thế này

<DataGrid Name="dataGrid"
          local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
          AutoGenerateColumns="False"
          ...>

Tài sản đính kèm BindableColumns

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;
        ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (columns == null)
        {
            return;
        }
        foreach (DataGridColumn column in columns)
        {
            dataGrid.Columns.Add(column);
        }
        columns.CollectionChanged += (sender, e2) =>
        {
            NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
            if (ne.Action == NotifyCollectionChangedAction.Reset)
            {
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in ne.NewItems)
                {
                    dataGrid.Columns.Add(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Move)
            {
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
            }
            else if (ne.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (DataGridColumn column in ne.OldItems)
                {
                    dataGrid.Columns.Remove(column);
                }
            }
            else if (ne.Action == NotifyCollectionChangedAction.Replace)
            {
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
            }
        };
    }
    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }
    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}

1
giải pháp hay cho mẫu MVVM
WPFKK

2
Một giải pháp hoàn hảo! Có lẽ bạn cần phải thực hiện một số điều khác trong BindableColumnsPropertyChanged: 1. Kiểm tra dataGrid cho null trước khi truy cập và đưa ra một ngoại lệ với lời giải thích tốt về chỉ ràng buộc với DataGrid. 2. Kiểm tra e.OldValue để tìm null và hủy đăng ký khỏi sự kiện CollectionChanged để tránh rò rỉ bộ nhớ. Chỉ để thuyết phục bạn.
Mike Eshva

3
Bạn đăng ký một trình xử lý sự kiện với CollectionChangedsự kiện của bộ sưu tập cột, tuy nhiên bạn không bao giờ hủy đăng ký nó. Theo cách đó, DataGridsẽ tồn tại miễn là mô hình khung nhìn tồn tại, ngay cả khi mẫu điều khiển có chứa DataGridở vị trí đầu tiên đã được thay thế trong khi đó. Có cách nào được đảm bảo để hủy đăng ký xử lý sự kiện đó một lần nữa khi DataGridkhông còn cần thiết nữa không?
HOẶC Mapper

1
@OR Mapper: Về mặt lý thuyết là có nhưng nó không hoạt động: WeakEventManager <ObservableCollection <DataGridColumn>, NotifyCollectionChangedEventArss> .AddHandler (cột, "CollectionChanged", (s, ne) =)
quá

6
Nó không phải là giải pháp. Lý do chính là bạn đang sử dụng các lớp UI trong ViewModel. Ngoài ra, nó sẽ không hoạt động khi bạn cố gắng tạo một số chuyển đổi trang. Khi chuyển trở lại trang với datagrid như vậy, bạn sẽ có một điểm xuất phát trong dòng dataGrid.Columns.Add(column)DataGridColumn với Header 'X' đã tồn tại trong bộ sưu tập Cột của DataGrid. DataGrids không thể chia sẻ các cột và không thể chứa các trường hợp cột trùng lặp.
Ruslan F.

19

Tôi đã tiếp tục nghiên cứu của mình và không tìm thấy bất kỳ cách hợp lý để làm điều này. Thuộc tính Cột trên DataGrid không phải là thứ tôi có thể liên kết, thực tế nó chỉ đọc.

Bryan đề xuất một cái gì đó có thể được thực hiện với AutoGenerateColumns để tôi xem xét. Nó sử dụng sự phản chiếu .Net đơn giản để xem xét các thuộc tính của các đối tượng trong ItemSource và tạo một cột cho mỗi đối tượng. Có lẽ tôi có thể tạo một loại đang bay với một thuộc tính cho mỗi cột nhưng điều này đang lạc lối.

Vì vấn đề này rất dễ bị cuốn vào mã, tôi sẽ sử dụng một phương thức mở rộng đơn giản mà tôi gọi bất cứ khi nào bối cảnh dữ liệu được cập nhật với các cột mới:

public static void GenerateColumns(this DataGrid dataGrid, IEnumerable<ColumnSchema> columns)
{
    dataGrid.Columns.Clear();

    int index = 0;
    foreach (var column in columns)
    {
        dataGrid.Columns.Add(new DataGridTextColumn
        {
            Header = column.Name,
            Binding = new Binding(string.Format("[{0}]", index++))
        });
    }
}

// E.g. myGrid.GenerateColumns(schema);

1
Giải pháp được bình chọn và chấp nhận cao nhất không phải là giải pháp tốt nhất! Hai năm sau, câu trả lời sẽ là: msmvps.com/bloss/deborahk/archive/2011/01/23/ Khăn
Mikhail

4
Không, nó sẽ không. Không phải liên kết được cung cấp nào, bởi vì kết quả của giải pháp đó là hoàn toàn khác nhau!
321X

2
Có vẻ như giải pháp của Mealek phổ biến hơn nhiều và rất hữu ích trong các tình huống sử dụng trực tiếp mã C # có vấn đề, ví dụ như trong ControlTemsheet.
EFraim

Liên kết @Mikhail bị hỏng
LuckyLikey

3
đây là đường dẫn: blog.msmvps.com/deborahk/ Kẻ
Mikhail

9

Tôi đã tìm thấy một bài viết trên blog của Deborah Kurata với một mẹo hay về cách hiển thị số lượng cột khác nhau trong DataGrid:

Tạo một DataGrid với các cột động trong ứng dụng Silverlight bằng MVVM

Về cơ bản, cô ấy tạo một DataGridTemplateColumnvà đặt ItemsControlbên trong hiển thị nhiều cột.


1
Đó không phải là kết quả giống như phiên bản được lập trình !!
321X

1
@ 321X: Bạn có thể giải thích rõ hơn về sự khác biệt quan sát được (và cũng chỉ định ý của bạn về phiên bản được lập trình , vì tất cả các giải pháp cho điều này đều được lập trình), làm ơn?
HOẶC Mapper

Nó nói "Không tìm thấy trang"
Jeson Martajaya

2
đây là đường dẫn blog.msmvps.com/deborahk/ Kẻ
Mikhail

Điều này không có gì đáng kinh ngạc !!
Ravid Goldenberg

6

Tôi đã quản lý để có thể tự động thêm một cột chỉ bằng một dòng mã như thế này:

MyItemsCollection.AddPropertyDescriptor(
    new DynamicPropertyDescriptor<User, int>("Age", x => x.Age));

Liên quan đến câu hỏi, đây không phải là giải pháp dựa trên XAML (vì như đã đề cập, không có cách nào hợp lý để thực hiện), đây cũng không phải là giải pháp hoạt động trực tiếp với DataGrid.Columns. Nó thực sự hoạt động với ItemSource ràng buộc DataGrid, thực hiện ITypedList và do đó cung cấp các phương thức tùy chỉnh để truy xuất PropertyDescriptor. Ở một nơi trong mã, bạn có thể xác định "hàng dữ liệu" và "cột dữ liệu" cho lưới của mình.

Nếu bạn có:

IList<string> ColumnNames { get; set; }
//dict.key is column name, dict.value is value
Dictionary<string, string> Rows { get; set; }

bạn có thể sử dụng ví dụ:

var descriptors= new List<PropertyDescriptor>();
//retrieve column name from preprepared list or retrieve from one of the items in dictionary
foreach(var columnName in ColumnNames)
    descriptors.Add(new DynamicPropertyDescriptor<Dictionary, string>(ColumnName, x => x[columnName]))
MyItemsCollection = new DynamicDataGridSource(Rows, descriptors) 

và lưới của bạn bằng cách sử dụng liên kết với MyItemsCollection sẽ được điền với các cột tương ứng. Các cột đó có thể được sửa đổi (bổ sung mới hoặc loại bỏ hiện tại) trong thời gian chạy động và lưới sẽ tự động làm mới bộ sưu tập cột của nó.

DynamicPropertyDescriptor được đề cập ở trên chỉ là một bản nâng cấp lên PropertyDescriptor thông thường và cung cấp định nghĩa cột được gõ mạnh với một số tùy chọn bổ sung. DynamicDataGridSource nếu không sẽ chỉ hoạt động tốt với sự kiện cơ bản.


3

Tạo một phiên bản của câu trả lời được chấp nhận xử lý việc không đăng ký.

public class DataGridColumnsBehavior
{
    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.RegisterAttached("BindableColumns",
                                            typeof(ObservableCollection<DataGridColumn>),
                                            typeof(DataGridColumnsBehavior),
                                            new UIPropertyMetadata(null, BindableColumnsPropertyChanged));

    /// <summary>Collection to store collection change handlers - to be able to unsubscribe later.</summary>
    private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> _handlers;

    static DataGridColumnsBehavior()
    {
        _handlers = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();
    }

    private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = source as DataGrid;

        ObservableCollection<DataGridColumn> oldColumns = e.OldValue as ObservableCollection<DataGridColumn>;
        if (oldColumns != null)
        {
            // Remove all columns.
            dataGrid.Columns.Clear();

            // Unsubscribe from old collection.
            NotifyCollectionChangedEventHandler h;
            if (_handlers.TryGetValue(dataGrid, out h))
            {
                oldColumns.CollectionChanged -= h;
                _handlers.Remove(dataGrid);
            }
        }

        ObservableCollection<DataGridColumn> newColumns = e.NewValue as ObservableCollection<DataGridColumn>;
        dataGrid.Columns.Clear();
        if (newColumns != null)
        {
            // Add columns from this source.
            foreach (DataGridColumn column in newColumns)
                dataGrid.Columns.Add(column);

            // Subscribe to future changes.
            NotifyCollectionChangedEventHandler h = (_, ne) => OnCollectionChanged(ne, dataGrid);
            _handlers[dataGrid] = h;
            newColumns.CollectionChanged += h;
        }
    }

    static void OnCollectionChanged(NotifyCollectionChangedEventArgs ne, DataGrid dataGrid)
    {
        switch (ne.Action)
        {
            case NotifyCollectionChangedAction.Reset:
                dataGrid.Columns.Clear();
                foreach (DataGridColumn column in ne.NewItems)
                    dataGrid.Columns.Add(column);
                break;
            case NotifyCollectionChangedAction.Add:
                foreach (DataGridColumn column in ne.NewItems)
                    dataGrid.Columns.Add(column);
                break;
            case NotifyCollectionChangedAction.Move:
                dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (DataGridColumn column in ne.OldItems)
                    dataGrid.Columns.Remove(column);
                break;
            case NotifyCollectionChangedAction.Replace:
                dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
                break;
        }
    }

    public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
    {
        element.SetValue(BindableColumnsProperty, value);
    }

    public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
    {
        return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
    }
}

2

Bạn có thể tạo một điều khiển người dùng với định nghĩa lưới và xác định các điều khiển 'con' với các định nghĩa cột khác nhau trong xaml. Cha mẹ cần một thuộc tính phụ thuộc cho các cột và một phương thức để tải các cột:

Cha mẹ:


public ObservableCollection<DataGridColumn> gridColumns
{
  get
  {
    return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
  }
  set
  {
    SetValue(ColumnsProperty, value);
  }
}
public static readonly DependencyProperty ColumnsProperty =
  DependencyProperty.Register("gridColumns",
  typeof(ObservableCollection<DataGridColumn>),
  typeof(parentControl),
  new PropertyMetadata(new ObservableCollection<DataGridColumn>()));

public void LoadGrid()
{
  if (gridColumns.Count > 0)
    myGrid.Columns.Clear();

  foreach (DataGridColumn c in gridColumns)
  {
    myGrid.Columns.Add(c);
  }
}

Con Xaml:


<local:parentControl x:Name="deGrid">           
  <local:parentControl.gridColumns>
    <toolkit:DataGridTextColumn Width="Auto" Header="1" Binding="{Binding Path=.}" />
    <toolkit:DataGridTextColumn Width="Auto" Header="2" Binding="{Binding Path=.}" />
  </local:parentControl.gridColumns>  
</local:parentControl>

Và cuối cùng, phần khó khăn là tìm nơi gọi 'LoadGrid'.
Tôi đang vật lộn với điều này nhưng có những thứ để làm việc bằng cách gọi sau InitalizeComponenttrong hàm tạo cửa sổ của tôi (childGrid là x: name trong window.xaml):

childGrid.deGrid.LoadGrid();

Mục blog liên quan


1

Bạn có thể làm điều này với AutoGenerateColumns và DataTemplate. Tôi không tích cực nếu nó hoạt động mà không có nhiều công việc, bạn sẽ phải chơi xung quanh nó. Thành thật mà nói, nếu bạn đã có giải pháp hiệu quả, tôi sẽ không thực hiện thay đổi trừ khi có lý do lớn. Kiểm soát DataGrid đang trở nên rất tốt nhưng nó vẫn cần một số công việc (và tôi còn rất nhiều việc phải làm) để có thể thực hiện các nhiệm vụ năng động như thế này một cách dễ dàng.


Lý do của tôi là đến từ ASP.Net Tôi mới biết những gì có thể được thực hiện với ràng buộc dữ liệu hợp lý và tôi không chắc chắn giới hạn của nó ở đâu. Tôi sẽ chơi với AutoGenerateColumns, cảm ơn.
Lỗi chung

0

Có một ví dụ về cách tôi làm theo chương trình:

public partial class UserControlWithComboBoxColumnDataGrid : UserControl
{
    private Dictionary<int, string> _Dictionary;
    private ObservableCollection<MyItem> _MyItems;
    public UserControlWithComboBoxColumnDataGrid() {
      _Dictionary = new Dictionary<int, string>();
      _Dictionary.Add(1,"A");
      _Dictionary.Add(2,"B");
      _MyItems = new ObservableCollection<MyItem>();
      dataGridMyItems.AutoGeneratingColumn += DataGridMyItems_AutoGeneratingColumn;
      dataGridMyItems.ItemsSource = _MyItems;

    }
private void DataGridMyItems_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            var desc = e.PropertyDescriptor as PropertyDescriptor;
            var att = desc.Attributes[typeof(ColumnNameAttribute)] as ColumnNameAttribute;
            if (att != null)
            {
                if (att.Name == "My Combobox Item") {
                    var comboBoxColumn =  new DataGridComboBoxColumn {
                        DisplayMemberPath = "Value",
                        SelectedValuePath = "Key",
                        ItemsSource = _ApprovalTypes,
                        SelectedValueBinding =  new Binding( "Bazinga"),   
                    };
                    e.Column = comboBoxColumn;
                }

            }
        }

}
public class MyItem {
    public string Name{get;set;}
    [ColumnName("My Combobox Item")]
    public int Bazinga {get;set;}
}

  public class ColumnNameAttribute : Attribute
    {
        public string Name { get; set; }
        public ColumnNameAttribute(string name) { Name = name; }
}
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.