2015-09-05 24 views
7

Jak mam to wdrożyć?UWP DataTemplates dla wielu typów przedmiotów w ListView

Powiedzmy to mój model:

public interface IAnimal 
{ 
    string Name { get; } 
} 
public class Fish : IAnimal 
{ 
    public string Name { get; set; } 
    public int ScalesCount { get; set; } 
} 
public class Dog : IAnimal 
{ 
    public string Name { get; set; } 
    public string CollarManufacturerName { get; set; } 
} 

public class ViewModel 
{ 
    public ObservableCollection<IAnimal> Animals { get; set; } 

    public ViewModel() 
    { 
     this.Animals = new ObservableCollection<IAnimal>(); 
     this.Animals.Add(new Fish { Name = "Carl", ScalesCount = 9000 }); 
     this.Animals.Add(new Dog { Name = "Fifi", CollarManufacturerName = "Macrosoft" }); 
    } 
} 

Dla ilości kodu w tej kwestii prosimy o założenie, że INotifyPropertyChanged jest realizowany w miarę potrzeby, i że ViewModel jest prawidłowo zainicjowany na stronie.

Jak mogę używać własnych odpowiedników DataTemplates? W WPF chciałbym zdefiniować wiele DataTemplates bez x:Key, ale ze zdefiniowanym DataType i pozwolić ListView wybrać, które z nich korzystać w oparciu o typ elementu. UWP tego nie lubi; kompilator po prostu stwierdza Dictionary Item "DataTemplate" must have a Key attribute. Jak więc osiągnąć mój cel?

Obecna próba

Moja obecna próba jest, aby zwyczaj DataTemplateSelector, co wydaje się dość prosta.

public class MyDataTemplateSelector: Windows.UI.Xaml.Controls.DataTemplateSelector 
{ 
    public ObservableCollection<TemplateMatch> Matches { get; set; } 

    public DataTemplateSelector() 
    { 
     this.Matches = new ObservableCollection<TemplateMatch>(); 
    } 

    protected override DataTemplate SelectTemplateCore(object item) 
    { 
     return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template; 
    } 

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) 
    { 
     return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template; 
    } 
} 

public class TemplateMatch 
{ 
    public Type TargetType { get; set; } 
    public DataTemplate Template { get; set; } 
} 

zdefiniować go w XAML tak:

<ListView ItemsSource="{x:Bind ViewModel.Animals}"> 
    <ListView.ItemTemplateSelector> 
     <cmp:MyDataTemplateSelector> 
      <cmp:MyDataTemplateSelector.Matches> 
       <cmp:TemplateMatch TargetType="model:Dog" Template="{StaticResource DogTemplate}"/> 
       <cmp:TemplateMatch TargetType="model:Fish" Template="{StaticResource FishTemplate}"/> 
      </cmp:MyDataTemplateSelector.Matches> 
     </cmp:MyDataTemplateSelector> 
    </ListView.ItemTemplateSelector> 
</ListView> 

Niestety kiedy uruchomić ten wyjątek występuje podczas wykonywania, stwierdzając Failed to create a 'Ui.Components.TemplateMatch' from the text 'model:Dog'. Więc wydaje wiążące do właściwości Type nie jest takie proste.

Każda pomoc jest doceniana!

Proszę zauważyć, że chciałbym użyć właściwości typu Type, w przeciwieństwie do string, w której chciałbym podać nazwę typu CLR i użyć odbicia, aby wywołać typ, głównie dlatego, że nie chcę mieszanych CLR i XML przestrzenie nazw pojawiają się w XAML. Jeśli możesz znaleźć sposób na wywołanie tego typu przy użyciu przestrzeni nazw XML, z chęcią przyjmiemy to jako odpowiedź.

+0

czy wypróbowałeś TargetType = "{x: Typ modelu: Pies}", ponieważ właściwość jest typu "Typ", a nie ciąg znaków. –

+0

Zrobiłem. UWP nie obsługuje już znaczników x: Type. To byłoby rozwiązanie dla WPF. :) –

+0

Dlaczego nie utworzyć instrukcji warunkowej wewnątrz metody wybierania szablonu zamiast używać TemplateMatch? – Lance

Odpowiedz

2

Znalazłem obejście. Jeśli jesteś w stanie tworzyć instancje tych typów - można go używać do wykrywania typów:

[ContentProperty(Name = nameof(Matches))] 
public class TypeTemplateSelector : DataTemplateSelector 
{ 
    public ObservableCollection<TemplateMatch> Matches { get; set; } 
    public TypeTemplateSelector() 
    { 
     this.Matches = new ObservableCollection<TemplateMatch>(); 
    } 

    protected override DataTemplate SelectTemplateCore(object item) 
    { 
     return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent; 
    } 

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) 
    { 
     return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent; 
    } 
} 

[ContentProperty(Name = nameof(ItemOfType))] 
public class TemplateMatch 
{ 
    public object ItemOfType { get; set; } 
    public DataTemplate TemplateContent { get; set; } 
} 

XAML:

<controls:TypeTemplateSelector> 
    <controls:TemplateMatch TemplateContent="{StaticResource FishTemplate}"> 
     <models:Fish/> 
    </controls:TemplateMatch> 
    <controls:TemplateMatch TemplateContent="{StaticResource DogTemplate}"> 
     <models:Dog/> 
    </controls:TemplateMatch> 
</controls:TypeTemplateSelector> 
+0

@ Odpowiedź Nicka jest podobna do tej (odpowiedź @ NekitoSP). Ale podoba mi się sposób, w jaki ten porównuje typy, zamiast porównywać pełny łańcuch nazw. – terry

0

Wskazówkę jest w błędzie.

udało się stworzyć 'Ui.Components.TemplateMatch' z tekstem 'modelu psa

Uwaga 'model: Dog' zbliża się do swojej selektora jak nie tekst typu.

Zmień swoją nieruchomość TemplateMatch klasa TargetType ciąg zamiast typu jak poniżej: -

public class TemplateMatch 
{ 
    public string TargetType { get; set; } 
    public DataTemplate Template { get; set; } 
} 

Następnie zmienić klasę wyboru szablonu, aby przeczytać

public class MyDataTemplateSelector : DataTemplateSelector 
{ 
    public ObservableCollection<TemplateMatch> Matches { get; set; } 

    public MyDataTemplateSelector() 
    { 
     Matches = new ObservableCollection<TemplateMatch>(); 
    } 

    protected override DataTemplate SelectTemplateCore(object item) 
    { 
     return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template; 
    } 

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) 
    { 
     return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template; 
    } 
} 

końcu zmienić XAML czytać

<ListView ItemsSource="{x:Bind ViewModel.Animals}"> 
    <ListView.ItemTemplateSelector> 
     <cmp:MyDataTemplateSelector> 
      <cmp:MyDataTemplateSelector.Matches> 
       <cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Dog" Template="{StaticResource DogTemplate}"/> 
       <cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Fish" Template="{StaticResource FishTemplate}"/> 
      </cmp:MyDataTemplateSelector.Matches> 
     </cmp:MyDataTemplateSelector> 
    </ListView.ItemTemplateSelector> 
</ListView> 

Chodzi o to, aby zapomnieć o próbie przekazania go do selektora na ype i zamiast tego przekazuj typename jako ciąg (Pełna przestrzeń nazw, a nie obszar nazw Xaml).

+0

Jest to jedno z możliwych rozwiązań, ale bardzo chciałbym powiązać z właściwością typu "Type". Wiesz, jak normalnie robią to DataTemplates. Poza tym, kiedy jestem w XAML, używanie przestrzeni nazw CLR w jednym miejscu i używanie przestrzeni nazw XML w drugim nie jest proste. Wiem, że to działa na zasadzie obejścia, ale chciałbym znaleźć idealną odpowiedź. –