[WPF] Ander type view model gebruiken in een DataTemplate

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
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:

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?

Mijn iRacing profiel

Alle reacties


Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

{Binding Item} Als je het MapItem encapsuleert in je custom view model?

Of in je DataTemplate X = {Binding Item.X} etc. etc.

Zie hier voor wat ideeen: http://blogs.interknowlog...o-alternate-datacontexts/

Is redelijk flexibel.

[ Voor 34% gewijzigd door Sandor_Clegane op 27-06-2018 18:30 ]

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
Ik snap niet wat je bedoelt met je eerste twee regels.

De link is vrij duidelijk maar gaat om het binden naar de DataContext van de parent container. Dat is niet wat ik wil, ik wil een totaal nieuw object (type CustomItemViewModel) als DataContext aanmaken voor elke item in de collectie.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Als ik het goed lees stop je het MapItem in je eigen viewmodel niet? Deze wil je binden aan een datatemplate MapViewItem toch?

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
Na overleg met degene die de MapView control heeft gemaakt hebben we besloten om deze control toch aan te passen en meer flexibel te maken. De DataTemplate is nu niet meer specifiek op een MapItem object gericht. We zijn er nog niet 100% uit maar op deze manier heb ik het probleem in deze thread niet meer, dus in principe is het "opgelost"...

Zou nog steeds interesse hebben om een betere manier te weten mocht dit later nog eens terugkomen.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Mercatres
  • Registratie: September 2009
  • Laatst online: 05-10 12:50
Is het geen mogelijkheid om je CustomItemViewModel van je MapViewItem te laten overerven? Dan kan je je DataType alsnog gewoon op je CustomItemViewModel laten mappen.
Pagina: 1