2010-12-19 18 views
15

के लिए फ़ैक्टरी का उपयोग कैसे करें I DataGrid.CanUserAddRows = true सुविधा का उपयोग करना चाहूंगा। दुर्भाग्यवश, ऐसा लगता है कि केवल कंक्रीट कक्षाओं के साथ काम करता है जिसमें एक डिफ़ॉल्ट कन्स्ट्रक्टर होता है। व्यवसाय वस्तुओं का मेरा संग्रह एक डिफ़ॉल्ट कन्स्ट्रक्टर प्रदान नहीं करता है।DataGrid.CanUserAddRows = true

मैं एक कारखाने को पंजीकृत करने का एक तरीका ढूंढ रहा हूं जो जानता है कि डेटाग्रिड के लिए ऑब्जेक्ट्स कैसे बनाएं। मैंने डेटाग्रिड और ListCollectionView पर एक नज़र डाली थी लेकिन उनमें से कोई भी मेरे परिदृश्य का समर्थन नहीं करता है।

उत्तर

4

मैंने IEditableCollectionViewAddNewItem पर एक नज़र डाली थी और ऐसा लगता है कि यह कार्यक्षमता जोड़ रहा है।

MSDN

से IEditableCollectionViewAddNewItem इंटरफ़ेस वस्तु की किस प्रकार एक संग्रह में जोड़ने के निर्दिष्ट करने के लिए डेवलपर्स आवेदन सक्षम बनाता है। यह इंटरफ़ेस IEDitableCollectionView बढ़ाता है, ताकि आप संग्रह में आइटम जोड़, संपादित और हटा सकें। IEditableCollectionViewAddNewItem AddNewItem विधि जोड़ता है, जो ऑब्जेक्ट लेता है जो संग्रह में जोड़ा गया है।

  • CollectionView में वस्तुओं विभिन्न प्रकार हैं: जब संग्रह और वस्तुओं है कि आप जोड़ना चाहते हैं एक या अधिक निम्नलिखित विशेषताएं की है यह पद्धति उपयोगी है।
  • ऑब्जेक्ट्स में डिफॉल्ट कन्स्ट्रक्टर नहीं है।
  • ऑब्जेक्ट पहले से मौजूद है।
  • आप संग्रह में एक शून्य वस्तु जोड़ना चाहते हैं।

हालांकि Bea Stollnitz blog पर, आप निम्न

  • जब स्रोत नहीं डिफ़ॉल्ट निर्माता है एक नया आइटम जोड़ने में सक्षम नहीं होने की सीमा पढ़ सकते हैं बहुत अच्छी तरह से द्वारा समझा है दल। डब्ल्यूपीएफ 4.0 बीटा 2 में एक नई सुविधा है जो हमें समाधान के करीब कदम लाती है: परिचय IDitableCollectionViewAddNewItem AddNewItem विधि युक्त। आप इस सुविधा के बारे में MS12N दस्तावेज़ पढ़ सकते हैं। एमएसडीएन में नमूना दिखाता है कि नया आइटम जोड़ने के लिए अपना कस्टम UI बनाने के लिए इसका उपयोग कैसे करें ( का उपयोग करके सूची बॉक्स और नया आइटम दर्ज करने के लिए संवाद बॉक्स)। जो मैं बता सकता हूं, डेटाग्रिड अभी तक इस विधि का उपयोग नहीं करता है (हालांकि यह 100% निश्चित होने के लिए थोड़ा मुश्किल है क्योंकि प्रतिबिंब 4.0 बीटा 2 बिट्स को संकुचित नहीं करता है)।

कि इस सवाल का जवाब 2009 से है, इसलिए शायद यह डेटा ग्रिड के लिए प्रयोग करने योग्य है अब

+2

आपके महान उत्तर के लिए धन्यवाद। ListCollectionView क्लास IEDitableCollectionViewAddNewItem इंटरफ़ेस लागू करता है। मैंने परावर्तक के माध्यम से कार्यान्वयन पर एक नज़र डाली थी।माइक्रोसॉफ्ट ने इस कक्षा में बहुत सारे प्रदर्शन अनुकूलन किए। मैं फैक्ट्री विधि का उपयोग करने के लिए अपने लिए इस इंटरफेस को लागू नहीं करना चाहता हूं। – jbe

+0

@jbe। मैं समझता हूं कि :) इसके अलावा, IEDitableCollectionViewAddNewItem पर बहुत सारी जानकारी नहीं थी, कम से कम यह नहीं कि मैं ढूंढ पाया। यदि आप अपना कार्य पूरा करने का कोई तरीका ढूंढते हैं तो अपडेट करना सुनिश्चित करें –

0

सबसे आसान तरीका मैं डिफ़ॉल्ट निर्माता के बिना अपने वर्ग के लिए आवरण प्रदान करने के लिए सुझाव है कि सकता है, जिसमें स्रोत वर्ग के लिए निर्माता बुलाया जाएगा ।

/// <summary> 
/// Complicate class without default constructor. 
/// </summary> 
public class ComplicateClass 
{ 
    public ComplicateClass(string name, string surname) 
    { 
     Name = name; 
     Surname = surname; 
    } 

    public string Name { get; set; } 
    public string Surname { get; set; } 
} 

इसके लिए एक आवरण लिखें:

/// <summary> 
/// Wrapper for complicated class. 
/// </summary> 
public class ComplicateClassWraper 
{ 
    public ComplicateClassWraper() 
    { 
     _item = new ComplicateClass("def_name", "def_surname"); 
    } 

    public ComplicateClassWraper(ComplicateClass item) 
    { 
     _item = item; 
    } 

    public ComplicateClass GetItem() { return _item; } 

    public string Name 
    { 
     get { return _item.Name; } 
     set { _item.Name = value; } 
    } 
    public string Surname 
    { 
     get { return _item.Surname; } 
     set { _item.Surname = value; } 
    } 

    ComplicateClass _item; 
} 

codebehind उदाहरण के लिए यदि आप डिफ़ॉल्ट निर्माता के बिना इस वर्ग की है। अपने व्यूमोडेल में आपको अपने स्रोत संग्रह के लिए रैपर संग्रह बनाने की आवश्यकता है, जो डेटाग्रिड में आइटम जोड़ने/हटाने को संभालेगा।

public MainWindow() 
    { 
     // Prepare collection with complicated objects. 
     _sourceCollection = new List<ComplicateClass>(); 
     _sourceCollection.Add(new ComplicateClass("a1", "b1")); 
     _sourceCollection.Add(new ComplicateClass("a2", "b2")); 

     // Do wrapper collection. 
     WrappedSourceCollection = new ObservableCollection<ComplicateClassWraper>(); 
     foreach (var item in _sourceCollection) 
      WrappedSourceCollection.Add(new ComplicateClassWraper(item)); 

     // Each time new item was added to grid need add it to source collection. 
     // Same on delete. 
     WrappedSourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged); 

     InitializeComponent(); 
     DataContext = this; 
    } 

    void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.Action == NotifyCollectionChangedAction.Add) 
      foreach (ComplicateClassWraper wrapper in e.NewItems) 
       _sourceCollection.Add(wrapper.GetItem()); 
     else if (e.Action == NotifyCollectionChangedAction.Remove) 
      foreach (ComplicateClassWraper wrapper in e.OldItems) 
       _sourceCollection.Remove(wrapper.GetItem()); 
    } 

    private List<ComplicateClass> _sourceCollection; 

    public ObservableCollection<ComplicateClassWraper> WrappedSourceCollection { get; set; } 
} 

और अंत में, XAML कोड:

<DataGrid CanUserAddRows="True" AutoGenerateColumns="False" 
      ItemsSource="{Binding Path=Items}"> 
    <DataGrid.Columns> 
     <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/> 
     <DataGridTextColumn Header="SecondName" Binding="{Binding Path=Surname}"/> 
    </DataGrid.Columns> 
</DataGrid> 
+1

आपको एक रैपर की भी आवश्यकता नहीं है। आप केवल मौजूदा वर्ग से प्राप्त कर सकते हैं और एक डिफ़ॉल्ट कन्स्ट्रक्टर प्रदान कर सकते हैं। – Phil

27

समस्या:

"मुझे इस तरह एक कारखाने जानता है कि डेटा ग्रिड के लिए वस्तुओं को बनाने के लिए कैसे रजिस्टर करने के लिए तलाश कर रहा हूँ" । अगर हम DataGrid.CanUserAddRows = true सेट

और फिर डेटा ग्रिड के लिए आइटम का संग्रह बाँध जहां आइटम एक डिफ़ॉल्ट नहीं है: (व्यापार वस्तुओं की मेरे संग्रह एक डिफ़ॉल्ट निर्माता प्रदान क्योंकि यह नहीं है।)

लक्षण कन्स्ट्रक्टर, फिर डेटाग्रिड 'नई आइटम पंक्ति' नहीं दिखाता है।

कारणों:

  1. एक BindingListCollectionView जब संग्रह के लिए बाध्य किया जा रहा एक BindingList<T> है:

    जब आइटम का संग्रह किसी भी WPF ItemControl के लिए बाध्य है, WPF में या तो संग्रह गिर्द घूमती है। BindingListCollectionViewIEditableCollectionView लागू करता है लेकिन IEditableCollectionViewAddNewItem लागू नहीं करता है।

  2. ListCollectionView संग्रह संग्रह होने पर कोई अन्य संग्रह है। ListCollectionView लागू IEditableCollectionViewAddNewItem (और इसलिए IEditableCollectionView)।

विकल्प के लिए 2) डेटाग्रिड ListCollectionView पर नए आइटम का निर्माण करता है। ListCollectionView आंतरिक रूप से एक डिफ़ॉल्ट कन्स्ट्रक्टर के अस्तित्व के लिए परीक्षण करता है और यदि कोई अस्तित्व में नहीं है तो AddNew अक्षम करता है। DotPeek का उपयोग करके ListCollectionView से प्रासंगिक कोड यहां दिया गया है।

public bool CanAddNewItem (method from IEditableCollectionView) 
{ 
    get 
    { 
    if (!this.IsEditingItem) 
     return !this.SourceList.IsFixedSize; 
    else 
     return false; 
    } 
} 

bool CanConstructItem 
{ 
    private get 
    { 
    if (!this._isItemConstructorValid) 
     this.EnsureItemConstructor(); 
    return this._itemConstructor != (ConstructorInfo) null; 
    } 
} 

इस व्यवहार को ओवरराइड करने का एक आसान तरीका प्रतीत नहीं होता है।

विकल्प के लिए 1) स्थिति बहुत बेहतर है। डेटाग्रिड बाध्यकारी लिस्ट व्यू में नए आइटम बनाने का प्रतिनिधि है, जो बदले में BindingList पर प्रतिनिधि है। BindingList<T> एक डिफ़ॉल्ट कन्स्ट्रक्टर के अस्तित्व के लिए भी जांच करता है, लेकिन सौभाग्य से BindingList<T> क्लाइंट को AllowNew प्रॉपर्टी सेट करने और एक नई आइटम की आपूर्ति के लिए इवेंट हैंडलर संलग्न करने की अनुमति देता है।समाधान बाद में मिलेंगे, लेकिन यहां BindingList<T>

public bool AllowNew 
{ 
    get 
    { 
    if (this.userSetAllowNew || this.allowNew) 
     return this.allowNew; 
    else 
     return this.AddingNewHandled; 
    } 
    set 
    { 
    bool allowNew = this.AllowNew; 
    this.userSetAllowNew = true; 
    this.allowNew = value; 
    if (allowNew == value) 
     return; 
    this.FireListChanged(ListChangedType.Reset, -1); 
    } 
} 

गैर समाधान में प्रासंगिक कोड है: डेटा ग्रिड (उपलब्ध नहीं) द्वारा

  • समर्थन

यह उम्मीद करना उचित होगा डेटाग्रिड क्लाइंट को कॉलबैक संलग्न करने की अनुमति देने के लिए, जिसके माध्यम से डेटाग्रिड एक डिफ़ॉल्ट नए आइटम का अनुरोध करेगा, जैसे ऊपर BindingList<T>। जब ग्राहक की आवश्यकता होती है तो यह क्लाइंट को एक नया आइटम बनाने पर पहली दरार देगा।

दुर्भाग्यवश यह डेटाग्रिड से सीधे समर्थित नहीं है, यहां तक ​​कि .NET 4.5 में भी।

.NET 4.5 में एक नया ईवेंट 'AddingNewItem' प्रतीत होता है जो पहले उपलब्ध नहीं था, लेकिन यह केवल आपको एक नया आइटम जोड़ने के बारे में बताता है।

काम arounds:

  • व्यापार वस्तु एक ही विधानसभा में एक उपकरण के द्वारा बनाई गई: का प्रयोग कर एक आंशिक वर्ग

इस परिदृश्य बहुत संभावना नहीं लगता है, लेकिन कल्पना कीजिए कि इकाई की रूपरेखा अपनी इकाई वर्गों बनाया बिना किसी डिफॉल्ट कन्स्ट्रक्टर (संभवतः वे serializable नहीं होंगे) के साथ, तो हम केवल एक डिफ़ॉल्ट कन्स्ट्रक्टर के साथ आंशिक वर्ग बना सकते हैं। समस्या सुलझ गयी।

  • व्यापार वस्तु किसी अन्य असेंबली में है, और मुहरबंद नहीं है: व्यवसाय प्रकार का एक सुपर-प्रकार बनाएं।

यहां हम व्यवसाय ऑब्जेक्ट प्रकार से प्राप्त कर सकते हैं और एक डिफ़ॉल्ट कन्स्ट्रक्टर जोड़ सकते हैं।

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

हम जैसे

class MyBusinessObject : BusinessObject 
{ 
    public MyBusinessObject(BusinessObject bo){ ... copy properties of bo } 
    public MyBusinessObject(){} 
} 

कोड की आवश्यकता होगी और फिर कुछ LINQ इन वस्तुओं में से सूचियों के बीच परियोजना के लिए।

  • व्यापार वस्तु किसी अन्य असेंबली में है, और सील कर दी गई है (या नहीं): व्यापार वस्तु को समाहित करें।

यह बहुत आसान

class MyBusinessObject 
{ 
    public BusinessObject{ get; private set; } 

    public MyBusinessObject(BusinessObject bo){ BusinessObject = bo; } 
    public MyBusinessObject(){} 
} 

अब सब हम क्या करने की जरूरत कुछ LINQ का उपयोग इन वस्तुओं में से सूचियों के बीच परियोजना के लिए, और फिर डेटा ग्रिड में MyBusinessObject.BusinessObject करने के लिए बाध्य है। गुणों की कोई गन्दा लपेटना या मूल्यों की प्रतिलिपि बनाना आवश्यक नहीं है।

समाधान: उपयोग BindingList<T>

(हुर्रे एक मिला)

  • हम एक BindingList<BusinessObject> में व्यापार की वस्तुओं की हमारे संग्रह लपेट और फिर इस के लिए डेटा ग्रिड बाँध, कुछ के साथ तो कोड की रेखाएं हमारी समस्या हल हो जाती हैं और डेटाग्रिड उचित रूप से एक नई आइटम पंक्ति दिखाएगा।

    public void BindData() 
    { 
        var list = new BindingList<BusinessObject>(GetBusinessObjects()); 
        list.AllowNew = true; 
        list.AddingNew += (sender, e) => 
         {e.NewObject = new BusinessObject(... some default params ...);}; 
    } 
    

    अन्य समाधान

    • एक मौजूदा संग्रह प्रकार की चोटी पर IEditableCollectionViewAddNewItem लागू। शायद बहुत काम है।
    • ListCollectionView से प्राप्त होता है और कार्यक्षमता ओवरराइड करता है। मैं आंशिक रूप से इस कोशिश कर रहा था, शायद अधिक प्रयास के साथ किया जा सकता है।
+1

सावधान रहें कि अन्य लोग रिपोर्ट कर रहे हैं बाइंडिंगलिस्ट अच्छी तरह से स्केल नहीं करता है http://www.themissingdocs.net/wordpress/?p=465 – gap

7

मुझे इस समस्या का एक और समाधान मिला है। मेरे मामले में, मेरी वस्तुओं को कारखाने का उपयोग करके शुरू करने की आवश्यकता है, और इसके आसपास जाने का कोई तरीका नहीं है।

मैं BindingList<T> का उपयोग नहीं कर सका क्योंकि मेरे संग्रह को समूह, सॉर्टिंग और फ़िल्टरिंग का समर्थन करना चाहिए, जो BindingList<T> समर्थन नहीं करता है।

मैंने डेटाग्रिड के AddingNewItem ईवेंट का उपयोग कर समस्या हल की। यह लगभग entirely undocumented event न केवल आपको एक नया आइटम जोड़ा जा रहा है, बल्कि allows lets you choose which item is being added भी बताता है। AddingNewItem किसी और चीज से पहले आग लगती है; NewItemEventArgs की संपत्ति बस null है।

यदि आप ईवेंट के लिए हैंडलर प्रदान करते हैं, तो भी डेटाग्रिड उपयोगकर्ता को पंक्तियों को जोड़ने की अनुमति देने से इनकार कर देगा यदि कक्षा में डिफ़ॉल्ट कन्स्ट्रक्टर नहीं है। हालांकि, अगर आपके पास कोई है, तो विचित्र रूप से (लेकिन शुक्र है), और की NewItem संपत्ति सेट करें, इसे कभी भी नहीं कहा जाएगा।

यदि आप ऐसा करना चुनते हैं, तो आप यह सुनिश्चित करने के लिए [Obsolete("Error", true)] और [EditorBrowsable(EditorBrowsableState.Never)] जैसे गुणों का उपयोग कर सकते हैं ताकि कोई भी कभी भी कन्स्ट्रक्टर को आमंत्रित न कर सके। आप कन्स्ट्रक्टर बॉडी को अपवाद फेंक सकते हैं

नियंत्रण को कम करने से हमें वहां क्या हो रहा है यह देखने देता है।

private object AddNewItem() 
{ 
    this.UpdateNewItemPlaceholder(true); 
    object newItem1 = (object) null; 
    IEditableCollectionViewAddNewItem collectionViewAddNewItem = (IEditableCollectionViewAddNewItem) this.Items; 
    if (collectionViewAddNewItem.CanAddNewItem) 
    { 
    AddingNewItemEventArgs e = new AddingNewItemEventArgs(); 
    this.OnAddingNewItem(e); 
    newItem1 = e.NewItem; 
    } 
    object newItem2 = newItem1 != null ? collectionViewAddNewItem.AddNewItem(newItem1) : this.EditableItems.AddNew(); 
    if (newItem2 != null) 
    this.OnInitializingNewItem(new InitializingNewItemEventArgs(newItem2)); 
    CommandManager.InvalidateRequerySuggested(); 
    return newItem2; 
} 

हम देख सकते हैं, संस्करण 4.5 में, डेटा ग्रिड वास्तव में AddNewItem का इस्तेमाल करते हैं करता है। CollectionListView.CanAddNewItem की सामग्री को बस कर रहे हैं:

public bool CanAddNewItem 
{ 
    get 
    { 
    if (!this.IsEditingItem) 
     return !this.SourceList.IsFixedSize; 
    else 
     return false; 
    } 
} 

तो यह स्पष्ट नहीं होता कि हम क्यों हम अभी भी (भले ही वह एक डमी है) प्रकट करने के लिए ऐड पंक्ति विकल्प के लिए क्रम में एक निर्माता की आवश्यकता है। मेरा मानना ​​है कि उत्तर कुछ कोड में निहित है जो NewItemPlaceholder पंक्ति की CanAddNew का उपयोग करके CanAddNewItem की दृश्यता निर्धारित करता है। इसे किसी प्रकार की बग माना जा सकता है।