2008-11-26 64 views
112

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

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

Lớp này được thiết lập như là DataContext trên WPF DataGrid nhưng tôi thực sự tạo ra các cột cách lập 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 với các ràng buộc dữ liệu trong tệp XAML thay vào đó?

Trả lời

113

Dưới đây là một cách giải quyết cho Cột Binding trong DataGrid. Vì thuộc tính Columns là ReadOnly, giống như mọi người nhận thấy, tôi đã tạo một thuộc tính đính kèm được gọi 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 này của

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

Sau đó, chúng ta có thể ràng buộc BindableColumns DataGridColumn để các ColumnCollection như thế này

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

Các đính kèm tài sản BindableColumns

+3

Đây là câu trả lời đúng, Bravo! –

+0

Tôi đồng ý, tôi thích giải pháp này tốt nhất. – Jaime

+1

giải pháp tốt cho mẫu MVVM – WPFKK

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ó sẽ làm việc mà không có rất nhiều công việc, bạn sẽ phải chơi xung quanh với nó. Thành thật mà nói nếu bạn có một giải pháp làm việc đã được tôi sẽ không thực hiện thay đổi chỉ được nêu ra, trừ khi có một lý do lớn. Việc kiểm soát DataGrid là nhận được rất tốt nhưng nó vẫn cần một số công việc (và tôi có rất nhiều học tập còn lại để làm) để có thể làm nhiệm vụ năng động như thế này một cách dễ dàng.

+0

lý do của tôi là đến từ ASP.Net Tôi mới đến những gì có thể được thực hiện với dữ liệu phong nha ràng buộc 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. –

17

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ý nào để thực hiện việc này. Thuộc tính Cột trên DataGrid không phải là thứ tôi có thể ràng buộc, trên thực tế nó chỉ đọc.

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

Kể từ khi vấn đề này được quá dễ dàng sovled trong mã tôi sẽ gắn bó với một phương pháp mở rộng đơn giản 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); 
+0

'index' làm gì? –

+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à: http://msmvps.com/blogs/deborahk/archive/2011/01/23/populating-a-datagrid-with-dynamic-columns-in-a-silverlight-application-using-using-using-using- mvvm.aspx – Mikhail

+4

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

2

Bạn có thể tạo một usercontrol 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 có một tài sản phụ thuộc cho các cột và một phương pháp để tải các cột:

phụ huynh:


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); 
    } 
} 

Child 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 đấu tranh với điều này nhưng có điều cần làm việc bằng cách gọi sau InitalizeComponent trong constructor cửa sổ của tôi (childGrid là x: tên trong window.xaml):

childGrid.deGrid.LoadGrid(); 

Related blog entry

9

Tôi đã tìm thấy một bài viết blog của Deborah Kurata với một mẹo tốt đẹp làm thế nào để hiển thị số ble cột trong một DataGrid:

Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM

Về cơ bản, cô tạo ra một DataGridTemplateColumn và đặt ItemsControl bên trong hiển thị nhiều cột.

+1

Nó hoạt động hoàn hảo cho cả Silverlight và WPF! – Mikhail

+1

Nó không phải là kết quả tương tự như phiên bản được lập trình !! – 321X

+0

@ 321X: Bạn có thể giải thích về sự khác biệt quan sát được không (và cũng chỉ định ý nghĩa của phiên bản * được lập trình *, vì tất cả các giải pháp này được lập trình), vui lòng? –

5

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

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

Đối với câu hỏi, đây không phải là một giải pháp XAML dựa trên (vì như đã nói có không có cách hợp lý để làm điều đó), không phải nó là một giải pháp mà sẽ hoạt động trực tiếp với DataGrid.Columns. Nó thực sự hoạt động với DataGrid ràng buộc ItemsSource, thực hiện ITypedList và như vậy 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 sẽ phải:

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 điện của bạn sử dụng ràng buộc để MyItemsCollection sẽ được dân cư với các cột tương ứng. Các cột đó có thể được sửa đổi (mới được thêm hoặc đã xóa hiện tại) tại 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à bản nâng cấp cho PropertyDescriptor thông thường và cung cấp định nghĩa cột được nhập mạnh mẽ với một số tùy chọn bổ sung. DynamicDataGridSource nếu không sẽ chỉ làm việc tốt với sự kiện PropertyDescriptor cơ bản.

3

Đã tạo phiên bản của câu trả lời được chấp nhận xử lý việc hủy đă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); 
    } 
} 
0

Có một ví dụ về các cách tôi làm lập 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; } 
} 
Các vấn đề liên quan