मैं एक WPF DataGrid को स्तंभों की एक चर संख्या से कैसे बांध सकता हूं?


124

मेरा WPF एप्लिकेशन डेटा के सेट उत्पन्न करता है जिसमें हर बार एक अलग संख्या में कॉलम हो सकते हैं। आउटपुट में शामिल प्रत्येक कॉलम का एक विवरण है जिसका उपयोग स्वरूपण लागू करने के लिए किया जाएगा। आउटपुट का सरलीकृत संस्करण कुछ इस तरह हो सकता है:

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

यह वर्ग WPF DataGrid पर DataContext के रूप में सेट है, लेकिन मैं वास्तव में प्रोग्रामेटिक रूप से कॉलम बनाता हूं:

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

क्या इस कोड को XAML फ़ाइल में डेटा बाइंडिंग के बजाय बदलने का कोई तरीका है?

जवाबों:


127

यहां डेटाग्रिड में बाइंडिंग कॉलम के लिए एक समाधान है। चूंकि कॉलम की संपत्ति ReadOnly है, जैसा कि सभी ने देखा, मैंने एक अटैच्ड प्रॉपर्टी बनाई, जिसे BindableColumns कहा जाता है, जो DataGrid में Columns को अपडेट करता है और कलेक्शनचेंज किए गए ईवेंट के माध्यम से कलेक्शन बदल जाता है।

अगर हमारे पास DataGridColumn का यह Collection है

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

तब हम BindableColumns को इस तरह ColumnCollection में बाँध सकते हैं

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

संलग्न संपत्ति 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
MVVM पैटर्न के लिए अच्छा समाधान
WPFKK

2
एक सही समाधान! संभवतः आपको BindableColumnsPropertyChanged में कुछ अन्य चीजें करने की आवश्यकता है: 1. डेटा एक्सेस करने से पहले इसे देखने के लिए null की जाँच करें और केवल DataGrid में बाइंडिंग के बारे में अच्छी व्याख्या के साथ एक अपवाद फेंकें। 2. स्मृति लीक को रोकने के लिए संग्रहणीय घटना से शून्य और सदस्यता समाप्त करने के लिए e.OldValue की जाँच करें। बस अपने काफिले के लिए।
माइक एशवा

3
आप CollectionChangedस्तंभ संग्रह की घटना के साथ एक ईवेंट हैंडलर पंजीकृत करते हैं, हालांकि आप इसे कभी भी अपंजीकृत नहीं करते हैं। इस प्रकार, DataGridजब तक दृश्य-मॉडल मौजूद है, तब तक इसे जीवित रखा जाएगा, भले ही नियंत्रण टेम्पलेट जिसमें DataGridपहले स्थान पर मौजूद हो, को इस बीच बदल दिया गया हो। क्या उस इवेंट हैंडलर को अनरजिस्टर्ड करने का कोई गारंटीकृत तरीका है, जब इसकी DataGridआवश्यकता नहीं है?
या मैपर

1
@OR मैपर: सैद्धांतिक रूप से वहाँ है, लेकिन यह काम नहीं करता है: WeakEventManager <ObservableCollection <DataGridColumn>, NotifyCollectionChangedEventArgs> .AddHandler (कॉलम, "CollectionChanged", (s, ne) = "स्विच ....")।
भी

6
यह समाधान के बगल में नहीं है। मुख्य कारण यह है कि आप ViewModel में यूआई कक्षाओं का उपयोग कर रहे हैं। जब आप कुछ पृष्ठ स्विच करने का प्रयास करेंगे तो भी यह काम नहीं करेगा। इस तरह के डेटाग्रिड के साथ पृष्ठ पर वापस जाने पर आपको लाइन डेटग्रिड में एक एक्सपेंशन मिलेगा dataGrid.Columns.Add(column)जिसमें हैडर 'एक्स' के साथ पहले से ही डेटाग्रिड के कॉलम संग्रह में मौजूद है। DataGrids कॉलम साझा नहीं कर सकते और डुप्लिकेट कॉलम इंस्टेंस शामिल नहीं कर सकते।
रुसलान एफ।

19

मैंने अपना शोध जारी रखा है और मुझे ऐसा करने का कोई उचित तरीका नहीं मिला है। DataGrid पर कॉलम की संपत्ति ऐसी कोई चीज़ नहीं है जिसके खिलाफ मैं बाँध सकता हूँ, वास्तव में यह केवल पढ़ा जाता है।

ब्रायन ने सुझाव दिया कि AutoGenerateColumns के साथ कुछ किया जा सकता है इसलिए मेरी नज़र थी। यह आइटम। स्रोत में वस्तुओं के गुणों को देखने के लिए सरल .Net प्रतिबिंब का उपयोग करता है और प्रत्येक के लिए एक कॉलम उत्पन्न करता है। शायद मैं प्रत्येक स्तंभ के लिए एक संपत्ति के साथ मक्खी पर एक प्रकार उत्पन्न कर सकता था लेकिन यह रास्ता बंद हो रहा है।

चूँकि यह समस्या इतनी आसानी से कोड में संकलित हो जाती है कि जब भी डेटा संदर्भ नए कॉलम के साथ अपडेट किया जाता है, मैं एक साधारण एक्सटेंशन विधि के साथ चिपका रहूँगा:

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
उच्चतम मतदान और स्वीकृत समाधान सबसे अच्छा नहीं है! दो साल बाद उत्तर होगा: msmvps.com/blogs/deborahk/archive/2011/01/23/…
मिखाइल

4
नहीं, यह नहीं होगा। वैसे भी प्रदान की गई लिंक नहीं, क्योंकि उस समाधान का परिणाम पूरी तरह से अलग है!
191 पर 321X

2
Mealek के समाधान की तरह लगता है और अधिक सार्वभौमिक है, और उन स्थितियों में उपयोगी है जहां C # कोड का प्रत्यक्ष उपयोग समस्याग्रस्त है, जैसे ControlTemplates में।
एफ़्रैम


3
यहाँ लिंक है: blogs.msmvps.com/deborahk/…
मिखाइल

9

मुझे डेबोरा कुर्ता द्वारा एक ब्लॉग लेख मिला है जिसमें एक अच्छी चाल है कि कैसे डेटाग्रिड में स्तंभों की संख्या को दिखाया जाए:

MVVM का उपयोग करके सिल्वरलाइट एप्लिकेशन में डायनामिक कॉलम के साथ डेटाग्रिड को पॉप्युलेट करना

असल में, वह एक बनाता है DataGridTemplateColumnऔर ItemsControlकई कॉलम प्रदर्शित करता है।


1
यह अभी तक क्रमादेशित संस्करण के समान परिणाम नहीं है !!
191 बजे 321X

1
@ 321X: क्या आप इस बात पर विस्तार से चर्चा कर सकते हैं कि देखे गए अंतर क्या हैं (और यह भी निर्दिष्ट करें कि आपको प्रोग्राम किए गए संस्करण से क्या मतलब है , क्योंकि इस के सभी समाधान प्रोग्राम किए गए हैं), कृपया?
या मैपर

यह कहता है "पृष्ठ नहीं मिला"
जेसन मार्तज्या

2
यहाँ लिंक blogs.msmvps.com/deborahk/…
मिखाइल

यह कमाल की कोई कमी नहीं है !!
रवि गोल्डनबर्ग

6

मैंने इसे इस तरह कोड की एक लाइन का उपयोग करके एक कॉलम को गतिशील रूप से जोड़ना संभव बनाया:

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

सवाल के संबंध में, यह एक XAML- आधारित समाधान नहीं है (क्योंकि जैसा कि उल्लेख किया गया है कि ऐसा करने का कोई उचित तरीका नहीं है), न ही यह एक समाधान है जो सीधे डेटाग्रिड के साथ काम करेगा। यह वास्तव में DataGrid बाध्य आइटम्स स्रोत के साथ चल रहा है, जो ITypedList को कार्यान्वित करता है और जैसे कि PropertyDescriptor पुनर्प्राप्ति के लिए कस्टम तरीके प्रदान करता है। कोड में एक जगह आप अपने ग्रिड के लिए "डेटा पंक्तियों" और "डेटा कॉलम" को परिभाषित कर सकते हैं।

यदि आपके पास होगा:

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

आप उदाहरण के लिए उपयोग कर सकते हैं:

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) 

और MyItemsCollection के लिए बाइंडिंग का उपयोग करके आपका ग्रिड संबंधित कॉलम के साथ आबाद होगा। उन स्तंभों को गतिशील रूप से (नए जोड़े या मौजूदा हटाए गए) को गतिशील रूप से संशोधित किया जा सकता है और ग्रिड स्वचालित रूप से इसे कॉलम संग्रह को ताज़ा कर देगा।

ऊपर वर्णित डायनेमिकPropertyDescriptor नियमित PropertyDescriptor का उन्नयन है और इसके अतिरिक्त विकल्पों के साथ दृढ़ता से टाइप किए गए कॉलम की परिभाषा प्रदान करता है। DynamicDataGridSource अन्यथा बेसिक प्रॉपर्टीस्क्रिप्ट के साथ ठीक घटना पर काम करेगा।


3

स्वीकृत उत्तर का एक संस्करण बनाया गया है जो सदस्यता रद्द करता है।

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

आप ग्रिड परिभाषा के साथ एक उपयोगकर्ता-केंद्र बना सकते हैं और xaml में विभिन्न स्तंभ परिभाषाओं के साथ 'बच्चे' नियंत्रण को परिभाषित कर सकते हैं। माता-पिता को स्तंभों के लिए एक निर्भरता संपत्ति और स्तंभों को लोड करने के लिए एक विधि की आवश्यकता होती है:

जनक:


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

बाल 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>

और अंत में, मुश्किल हिस्सा लग रहा है जहां 'लोडग्रिड' को कॉल करना है।
मैं इसके साथ संघर्ष कर रहा हूं, लेकिन InitalizeComponentमेरे विंडो कंस्ट्रक्टर (चाइल्डग्रिड x है: window.xamll में नाम) के बाद कॉल करके काम करने के लिए चीजें मिलीं:

childGrid.deGrid.LoadGrid();

संबंधित ब्लॉग प्रविष्टि


1

आप AutoGenerateColumns और DataTemplate के साथ ऐसा करने में सक्षम हो सकते हैं। मैं सकारात्मक नहीं हूं अगर यह बहुत काम के बिना काम करेगा, तो आपको इसके साथ खेलना होगा। ईमानदारी से अगर आपके पास एक काम करने वाला समाधान है तो मैं तब तक बदलाव नहीं करूंगा जब तक कि कोई बड़ा कारण न हो। डेटाग्रिड नियंत्रण बहुत अच्छा हो रहा है, लेकिन इसे अभी भी कुछ काम करने की ज़रूरत है (और मुझे बहुत कुछ सीखना बाकी है) इस तरह के गतिशील कार्यों को आसानी से करने में सक्षम होना।


मेरा कारण यह है कि ASP.Net से आने वाला मैं नया हूँ जो सभ्य डेटा बाइंडिंग के साथ किया जा सकता है और मुझे यकीन नहीं है कि इसकी सीमाएं कहाँ हैं। मैं AutoGenerateColumns के साथ एक नाटक होगा, धन्यवाद।
जेनेरिक त्रुटि

0

मेरे द्वारा प्रोग्राम करने के तरीके का एक नमूना है:

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; }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.