DataTemplateSelector : Her item için farklı ItemTemplate!

Merhaba arkadaşlar,

Sizlere geçenlerde bir mesajlaşma uygulaması yazarken karşılaştığım bir
sorunu anlatayım. Kabaca mesajların listelendiği ListBox’ta kullanıcıya gelen
mesajların solda, kullanıcının yazdığı mesajları sağda listelemek istiyordum.
WhatsApp, Facebook, Viber ve diğer aklınıza gelecek bütün uygulamalarda bu
böyle. Gelgelelim ki gelen mesajların solda, giden mesajların sağda listelenmesini bir türlü
becerememiştim. Message sınıfına HorizontalAlignment türünde bir
property eklemeyi bile düşündüm 🙁 Ama bu çok hantal bir çözüm olacaktı. Derken 
DataTemplateSelector adlı çok kullanışlı bir sınıfla
tanıştım.

DataTemplateSelector nedir?
DataTemplateSelector,
herhangi bir listeleme kontrolündeki -bu bir ListBox olabilir- elemanları Property’lerine göre parse edip
o elemana özel bir DataTemplate sunmanızı sağlayan basit ama çok kullanışlı bir
yapıdır. DataTemplateSelector, ContentControl sınıfından türer,
abstract bir sınıftır. Yani biz her ListBox’ımız için DataTemplateSelector’den
türemiş bir başka sınıf yazmak durumundayız.

Benim bu yazıda ele alacağım örneğin sınıf diyagramı:

 Gördüğünüz gibi DataTemplateSelector sınıfı ContentControl’den türüyor. DataTemplateSelector sınıfında odaklanmamız gereken metot SelectTemplate() metodu, virtual bir metot. Parametre olarak object türünde item parametresi alıyor. Bu item parametresi, ListBox’ımızdaki her bir elemanın referansından başka birşey değil. Yazının başında bahsettiğim senaryoda item = Message objesi. Geriye ise DataTemplate döndürüyor.

Bana kalan iş ise DataTemplateSelector’dan türemiş bir sınıf yazıp, SelectTemplate metodunu ezmek.

Yazacağım yeni sınıfa ListBox’ım için kaç tane template olmasını istiyorsam o kadar DataTemplate türünden property eklemem gerekiyor.

Hatırlatmakta fayda var: DataTemplateSelector sınıfı WPF için bulunurken ben Windows Phone için bulamadım, ama Windows Phone için DLL’ini hazırladım :

 DataTemplateSelector.dll (4,5KB) / 8.0 ve üzeri için

Bu kadar bilgi yeterli sanıyorum, şimdi lafı fazla uzatmadan örnek uygulamamıza geçelim. Bu yazımda basitçe ürünlerin listelendiği bir uygulama yapacağız. Ürün sınıfının isim, fiyat, stok gibi alanları olacak. Biz bu sınıftan örnekleri listelerken stoğu 10dan küçük ürünlerin stokta az kaldığını belirten bir yazı ekleyeceğiz.

  1. Ürünler için Product isminde bir sınıf oluşturacağız
  2. Örnek veriler için UrunRepository isminde başka bir sınıf oluşturacağız. Bu sınıfı MainPage.xaml’in DataContext’i olarak kullanacağız.
  3. Daha sonra bu Product sınıfından nesneleri istediğimiz düzende listelemek için bir ProductDataTemplateSelector sınıfı yazacağız.

Product.cs

    public class Product
{
public string ID { get; set; }
public string iconUri { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public int Stock { get; set; }
}

UrunRepository.cs

 public class UrunRepository
{
public ObservableCollection<Product> UrunDataSource { get; set; }
public UrunRepository()
{
this.UrunDataSource = new ObservableCollection<Product>();
this.UrunDataSource.Add(new Product { ID = "1", Name = "Windows Phone'lu cep telefonu", iconUri="Images/goodPhone.jpg", Price = 1900, Stock = 9 });
this.UrunDataSource.Add(new Product { ID = "2", Name = "Diğer cep telefonu", iconUri="Images/badPhone.jpeg", Price = 2800, Stock = 100 });
this.UrunDataSource.Add(new Product { ID = "3", Name = "Su ısıtıcı", iconUri = "Images/kettle.jpg", Price = 40, Stock = 70 });
}
}

UrunRepository sınıfımın UrunDataSource adında, ObservableCollection<Product> türünde bir property’si var. Biz MainPage.xaml’in DataContext’i olarak bu sınıfı gösterip ListBox’ın ItemsSource’ına UrunDataSource isimli property’yi göstereceğiz. Sınıfımın constructor’ında ise gördüğünüz gibi UrunDataSource’u belleğe çıkarıp bu koleksiyona üç adet örnek Product nesnesi ekledim.

Şimdi App.xaml’e iki adet DataTemplate ekleyeceğiz. Bu template’lerden biri stoğu azalan ürünler için olacak.

   <DataTemplate x:Key="NormalUrunTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="300"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Image Source="{Binding iconUri}" Width="100" Height="100" Grid.Column="0" Grid.Row="0"></Image>
<TextBlock Text="{Binding Name}" Grid.Column="1" Grid.Row="0" Foreground="#FF00FFD1"></TextBlock>
<TextBlock Text="Fiyat: " Grid.Column="1" Grid.Row="0" Margin="0,50,0,0" FontWeight="Bold">
<Run Text="{Binding Price}" FontWeight="Normal"></Run>
</TextBlock>
</Grid>
</DataTemplate>


<DataTemplate x:Key="AzKalanUrunTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="300"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Image Source="{Binding iconUri}" Width="100" Height="100" Grid.Column="0" Grid.Row="0"></Image>
<TextBlock Text="{Binding Name}" Grid.Column="1" Grid.Row="0" Foreground="#FF00FFD1"></TextBlock>
<TextBlock Text="Fiyat: " Grid.Column="1" Grid.Row="0" Margin="0,50,0,0" FontWeight="Bold">
<Run Text="{Binding Price}" FontWeight="Normal"></Run>
</TextBlock>
<TextBlock Text="Son " Grid.Column="1" Grid.Row="0" Margin="0,50,0,0" HorizontalAlignment="Right" Foreground="Red" FontWeight="Bold">
<Run Text="{Binding Stock}" Foreground="Red"></Run>
<Run Text="adet!"></Run>
</TextBlock>

</Grid>
</DataTemplate>

App.xaml’e Resource olarak NormalUrunTemplate ve AzalanUrunTemplate olmak üzere iki adet DataTemplate gömdük. Bu template’leri ProductTemplateSelector sınıfında kullanacağız. Dikkat ederseniz AzalanUrunTemplate’inde ekstra olarak Son (stok) adet! değerine sahip bir TextBlock var.

İhtiyacımız olan son sınıf ProductTemplateSelector.

ProductTemplateSelector.cs

    public class ProductTemplateSelector : DataTemplateSelector
{
/*
iki adet DataTemplate türünde Property tanımlıyoruz.
*/
public DataTemplate NormalUrunTemplate { get; set; }
public DataTemplate AzKalanUrunTemplate { get; set; }

public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
/* burada item parametresini Product'a çevirip stok kontrolü yapıyoruz. */

Product urun = item as Product;
if (urun.Stock < 10)
return AzKalanUrunTemplate;
else
return NormalUrunTemplate;
}
}

ProductTemplateSelector sınıfında öncelikle iki adet DataTemplate türünde iki adet property tanımladık.

SelectTemplate() metodunda ise parametre olarak gelen item nesnesini Product türüne çevirip stok kontrolü yaptık. Stok 10dan az ise geriye AzalanUrunTemplate’ini döndürdük.

MainPage.xaml’deki ListBox’ı ayarlamak dışında herşey hazır.

  • Öncelikle MainPage.xaml’in DataContext’ini UrunRepository sınıfı olarak ayarlayacağız.
  • Sonrasında ise ListBox’ımızın ItemTemplate’ini ayarlayacağız.

DataContext oluşturmadan önce MainPage.xaml’e şöyle bir decleration eklememiz lazım:

    xmlns:local=”clr-namespace:PROJEISMI” 


Bu decleration’u datacontext oluşturmak ve ListBox’ımızın ItemTemplate’ine ProductTemplateSelector’ı atamak için yapıyoruz. Ekledikten sonra local bizim projemizin ana dizinini ifade ediyor olacak.

Ekledikten sonra aşağıdaki kod ile sayfamızın DataContext’ini UrunRepository olarak ayarlıyoruz.

    <phone:PhoneApplicationPage.DataContext>
<local:UrunRepository></local:UrunRepository>
</phone:PhoneApplicationPage.DataContext>

Son olarak ta ListBox’ımızın ItemSource’unu UrunDataSource yapıp ItemTemplate’ini ayarlayalım.

<ListBox ItemsSource="{Binding UrunDataSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ProductTemplateSelector Content="{Binding}"
NormalUrunTemplate="{StaticResource NormalUrunTemplate}"
AzKalanUrunTemplate="{StaticResource AzKalanUrunTemplate}"
HorizontalContentAlignment="Stretch" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

ItemsSource’a UrunDataSource’u bind ettik, ItemTemplate için de bir ProductTemplateSelector tanımlayıp NormalUrunTemplate ve AzalanUrunTemplate propertylerine App.xaml’deki değerleri atadık. Buradaki Content, ContentControl sınıfından gelen bir değer, bu ListBox’taki itemlerin herhangi bir şekilde ekranda görünür olması için onun Binding olması gerekiyor.

Projeyi build edip çalıştırdığımızda şöyle bir görüntüyle karşılacağız:

Gördüğünüz gibi stoğu 10dan az olan ürünümüzün yanında stok uyarısı da bulunuyor. Son derece enteresan bir mesaj vermeyi de unutmadım.

Yazının faydalı olması dileğiyle, başka bir yazıda görüşmek üzere.

DataTemplateSelectorSample.zip (267,6KB)

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir