Ik heb een wellicht vreemde structuur in een WPF applicatie waar ik maar niet uit kom.
De beste samenvatting die ik kan bedenken is als volgt: ik heb een control die een aantal items laat zien, waarbij ik zelf de ItemTemplate aangeef. De ItemTemplate heeft natuurlijk een bepaald DataType die door de control verwacht wordt en die kan ik niet aanpassen. Echter wil ik in de ItemTemplate mijn eigen custom control stoppen die juist wel een ander soort data type verwacht. Dit ander soort datatype is een soort encapsulatie van het originele datatype.
Om het in code om te zetten, het gaat om ongeveer zoiets:
- UserControl is gewoon de main view die ik ergens neer gooi. Deze heeft een DataContext van type MainViewModel (zie later).
- MapView is een custom control die ik helaas niet kan aanpassen (ik heb wel controle over de code maar deze wordt in andere projecten gebruikt, ik hergebruik deze view voor een ander project). De DataContext van MapView is niet de MainViewModel, omdat deze ergens in de control automatisch een DataContext van MapViewModel krijgt (via Prism MVVM library denk ik). Vandaar de wellicht vreemde binding naar "DataContext.Items" via element name. Ik denk niet dat dit belangrijk is.
- De ItemTemplate verwacht een DataTemplate met DataType MapItem (zie code later).
- Mijn eigen view CustomItemView verwacht echter een view model met type CustomItemViewModel. Ook dit kan ik eigenlijk niet aanpassen want deze view wordt ook voor andere dingen gebruikt.
De view models:
Mijn vraag is nu dus: hoe kan ik in de DataTemplate een view model van type CustomItemViewModel "maken", welke wel nog kennis heeft van het originele MapItem model (die heb ik immers nodig)? In de code zou ik die in de constructor kunnen doorsturen of binden via de property, maar beide zie ik niet echt hoe dit moet gaan werken.
Dit is de beste manier die ik kon bedenken maar die lijkt helaas niet te werken. Het idee is dat ik in de XAML de DataContext definieer als een nieuw CustomItemViewModel object, waarbij ik dan een MapItem dependency property bind aan de DataContext van het DataTemplate.
Als ik dit debug lijkt het dat de DataContext van "grid" inderdaad MapItem is (ok), maar de binding op MapItem lijkt niet te werken. De dependency property wordt nooit gezet en ik krijg dus nooit de MapItem in mijn view model.
Ik denk dat ik een fundamentele stap mis. Dit soort probleem heb ik nog nooit gehad... Snapt iemand wat ik fout doe?
De beste samenvatting die ik kan bedenken is als volgt: ik heb een control die een aantal items laat zien, waarbij ik zelf de ItemTemplate aangeef. De ItemTemplate heeft natuurlijk een bepaald DataType die door de control verwacht wordt en die kan ik niet aanpassen. Echter wil ik in de ItemTemplate mijn eigen custom control stoppen die juist wel een ander soort data type verwacht. Dit ander soort datatype is een soort encapsulatie van het originele datatype.
Om het in code om te zetten, het gaat om ongeveer zoiets:
XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <UserControl x:Name="parent"> <!-- DataContext = MainViewModel --> <c:MapView ItemsSource="{Binding DataContext.Items, ElementName=parent}"> <!-- DataContext = MapViewModel --> <c:MapView.ItemTemplate> <DataTemplate DataType="c:MapItem"> <c:CustomItemView> <!-- DataContext = MapItem --> <!-- But required DataContext = CustomItemViewModel!! --> </c:CustomItemView> </DataTemplate> </c:MapView.ItemTemplate> </c:MapView> </UserControl> |
- UserControl is gewoon de main view die ik ergens neer gooi. Deze heeft een DataContext van type MainViewModel (zie later).
- MapView is een custom control die ik helaas niet kan aanpassen (ik heb wel controle over de code maar deze wordt in andere projecten gebruikt, ik hergebruik deze view voor een ander project). De DataContext van MapView is niet de MainViewModel, omdat deze ergens in de control automatisch een DataContext van MapViewModel krijgt (via Prism MVVM library denk ik). Vandaar de wellicht vreemde binding naar "DataContext.Items" via element name. Ik denk niet dat dit belangrijk is.
- De ItemTemplate verwacht een DataTemplate met DataType MapItem (zie code later).
- Mijn eigen view CustomItemView verwacht echter een view model met type CustomItemViewModel. Ook dit kan ik eigenlijk niet aanpassen want deze view wordt ook voor andere dingen gebruikt.
De view models:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // Main view model holding collection of Map Items. public class MainViewModel { public ObservableCollection<MapItem> Items { get; } } // MapItem view model only holds info relevant to the map. public class MapItem { public int Id {get; set;} public int X {get; set;} public int Y {get; set;} public string Name {get; set;} } // CustomItemViewModel encapsulates MapItem and has additional info required for the CustomItemView. public class CustomItemViewModel { public CustomItemViewModel() { // No arguments, pass MapItem via property?? } public CustomItemViewModel(MapItem item) { Item = item; } public MapItem Item {get;set;} // Other properties } |
Mijn vraag is nu dus: hoe kan ik in de DataTemplate een view model van type CustomItemViewModel "maken", welke wel nog kennis heeft van het originele MapItem model (die heb ik immers nodig)? In de code zou ik die in de constructor kunnen doorsturen of binden via de property, maar beide zie ik niet echt hoe dit moet gaan werken.
Dit is de beste manier die ik kon bedenken maar die lijkt helaas niet te werken. Het idee is dat ik in de XAML de DataContext definieer als een nieuw CustomItemViewModel object, waarbij ik dan een MapItem dependency property bind aan de DataContext van het DataTemplate.
XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| <UserControl x:Name="parent"> <!-- DataContext = MainViewModel --> <c:MapView ItemsSource="{Binding DataContext.Items, ElementName=parent}"> <!-- DataContext = MapViewModel --> <c:MapView.ItemTemplate> <DataTemplate DataType="c:MapItem"> <!-- Give grid a name so we can bind to it --> <Grid x:Name="grid"> <c:CustomItemView> <!-- Set DataContext manually, bind to MapItem to pass it to the view model --> <c:CustomItemView.DataContext> <!-- The hope: bind the DataContext of "grid" (= type MapItem) to the MapItem property of the new viewmodel --> <local:CustomItemViewModel MapItem="{Binding ElementName=grid}" /> </c:CustomItemView.DataContext> </c:CustomItemView> </Grid> </DataTemplate> </c:MapView.ItemTemplate> </c:MapView> </UserControl> |
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| public class CustomItemViewModel : DependencyObject { public CustomItemViewModel() { } public static readonly DependencyProperty MapItemProperty = DependencyProperty.Register("MapItem", ..... etc); public MapItem Item { get { return (MapItem)GetValue(MapItemProperty); } set { SetValue(MapItemProperty, value); } } private static void OnMapItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { // Use map item var model = (CustomItemViewModel) o; model.SetProperties((MapItem)e.NewValue); } private void SetProperties(MapItem item) { MapItemId = item.Id; // ... } // Other properties public int MapItemId {get;set;} } |
Als ik dit debug lijkt het dat de DataContext van "grid" inderdaad MapItem is (ok), maar de binding op MapItem lijkt niet te werken. De dependency property wordt nooit gezet en ik krijg dus nooit de MapItem in mijn view model.
Ik denk dat ik een fundamentele stap mis. Dit soort probleem heb ik nog nooit gehad... Snapt iemand wat ik fout doe?