2014-12-15 23 views
7

Próbuję utworzyć niestandardową kontrolę użytkownika, z podobną funkcjonalnością jak DataGrid (ale DataGrid nie jest tu odpowiednią opcją).Niestandardowa kontrola z kaskadowym DataContext do elementów potomnych w kolekcji

Co chciałbym osiągnąć coś takiego:

<my:CustomList ItemsSource="{Binding Items}"> 
    <my:CustomList.Columns> 
     <my:Column Width="60" Binding="{Binding MyCustomProperty}" /> 
    </my:CustomList.Columns> 
</my:CustomList> 

gdzie przedmioty będzie pochodzić z ViewModel (na przykład) tak:

public ObservableCollection<Item> Items { get; set; } 

public class Item 
{ 
    public string MyCustomProperty { get; set; } 
    public string MyAnotherCustomProperty { get; set; } 
} 

Problem mam tylko z wiązaniem do MyCustomProperty.

Jeśli dziedziczę kontrolkę niestandardową z DataGrid i korzystam z jej kolumn, przepływy danychContext od ItemSource do powiązań w kolumnach po prostu dobrze. Chciałbym zrobić to samo z moją kontrolą niestandardową, która nie dziedziczy po DataGrid. Jaka jest magia kryjąca się za tym, że DataGrid.Columns pobiera kontekst z ItemsSource?

Edit: Zadam inną drogę na ten temat:

Gdybym realizować niestandardowe DataGridColumn

public class MyDataGridColumn : DataGridBoundColumn 
{ 
    private Binding _bindingSubText; 

    public Binding BindingSubText 
    { 
     get 
     { 
      return _bindingSubText; 
     } 
     set 
     { 
      if (_bindingSubText == value) return; 
      var oldBinding = _bindingSubText; 
      _bindingSubText = value; 
      OnBindingChanged(oldBinding, _bindingSubText); 
     } 
    } 

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) 
    { 
     var textTextBlock = new TextBlock(); 
     var bindingText = Binding ?? new Binding(); 
     textTextBlock.SetBinding(TextBlock.TextProperty, bindingText); 

     var textSubTextBlock = new TextBlock(); 
     var bindingSubText = BindingSubText ?? new Binding(); 
     textSubTextBlock.SetBinding(TextBlock.TextProperty, bindingSubText); 

     var stackPanel = new StackPanel() { Orientation = Orientation.Vertical }; 
     stackPanel.Children.Add(textTextBlock); 
     stackPanel.Children.Add(textSubTextBlock); 

     return stackPanel; 
    } 

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) 
    { 
     // I don't want to edit elements 
     return null; 
    } 
} 

i spróbuj użyć go w XAML tak:

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False"> 
    <DataGrid.Columns> 
     <my:MyDataGridColumn Binding="{Binding MyCustomProperty}" BindingSubText="{Binding MyAnotherCustomProperty}" /> 
    </DataGrid.Columns> 
</DataGrid> 

Powiązanie dla BindingSubText Właściwość nadal będzie pochodzić z DataContext z rodzica DataGrid, oferując mi przedmioty. MyAnotherCustomProperty miałoby skórki w projektancie, ale działałoby to poprawnie w środowisku wykonawczym (ze względu na dynamiczne powiązanie). Mój problem polega na tym, że gdy ktoś inny użyje tej niestandardowej kolumny DataGridCoolumn, będzie musiał to wiedzieć i będzie miał "zły" IntelliSense dla powiązania.

W jaki sposób jest kontekst dla właściwości Binding zestawu DataGridColumn, aby system IntelliSense działał zgodnie z oczekiwaniami?

+1

pochodzi z ItemsControl i zarządza nimi, od czego czerpiecie kontrolę? – ZSH

+0

Mój pochodzi również z ItemsControl. –

+0

jak zdefiniowałeś (tworząc tester)? – ZSH

Odpowiedz

-1

Myślę, że jeden z przodków DataGrid obsługuje to (być może ItemsControl). Jeśli twoja kontrola nie pochodzi z ItemsControl, musisz to ręcznie obsłużyć (dodając nową kolumnę do kontrolki, ustaw jej kontekst danych jawnie).

Teraz widzę, że twoja kontrola pochodzi również z ItemsControl. Proponuję poradzić sobie z tym ręcznie.

2

Twoje pytanie jest naprawdę szerokie, ale przede wszystkim jeśli chcesz rozszerzyć DataGrid, zastanów się nad jego stylem i dodaj wyzwalacze lub coś w tym stylu.

Jest to sprawa w twoim projekcie .. i trudno powiedzieć, które z nich jest dobre, a co złe.

Właściwość columns w DataGrid i GridView jest mniej więcej po prostu fikcyjnym obiektem przechowującym wartości, które komórki będą później potrzebować np. Szablony komórek, powiązanie, szerokość, wysokość ... itd. Powiedzmy, że kolumny nie mają true DataContext ...

Kontekst danych jest przekazywany w dół funkcyjnym drzewem wizualnym/logicznym, ale właściwości kolumn nie istnieją. To dlatego masz problemy. Wszystkie wiązania, które można ustawić w takim obiekcie kolumny, są w rzeczywistości obiektami zastępczymi. Jednak komórki biorą udział w drzewku wizualnym, ponieważ są one własnymi kontrolkami, a więc po wygenerowaniu komórki robią takie rzeczy, zanim zostaną narysowane: cellTextBlock.SetBinding (TextBlock.TextProperty, columnBindingPlaceHolder), cellTextBlock.SetBinding (TextBlock. HeightProperty, columnHeightPlaceHolder).

Komórki wykorzystać te zastępcze z kolumnami

Jeśli chcesz jednak z własnego mienia kolumny niestandardowe mają takie same DataContext jako DataGrid rozważyć zmianę ObseravableCollection do FreezableCollection. Również uczyń obiekt Kolumna obiektem Freezable. Po prostu współpracuj z Freezables. Ponieważ w WPF mają możliwość dziedziczenia DataContext. Pędzle w Wpf to na przykład freezable.

Mam nadzieję, że to pomoże ci dalej.

+1

Nadal można (logicznie?) Oczekiwać, że właściwość na Visual/Logiczny element drzewa otrzyma tę pomoc DataContext z oprzyrządowania. Być może problem tkwi w oprzyrządowaniu ... Z tego, co powiedziałeś, to znaczy, czy jestem w błędzie? –

+0

Próbowałem z freezables, ale nie zbliżyłem się do rozwiązania. W każdym razie, dzięki za pomoc. Domyślam się, że zasłużyłeś na nagrodę :) –

+0

Przykro mi, człowieku, byłem zajęty jedzeniem rzeczy, poślubiłem Boże Narodzenie :) oto świetny artykuł o freezable http://joshsmithonwpf.wordpress.com/2008/07/22/enable-elementname- bindings-with-elementspy/ josh smith wie dużo o wpf, tworzy elementspy, która jest klasą, która dziedziczy z freezable –