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)

Fazlasını Oku

XAML Binding Converters(Dönüştürücüler)

Merhaba arkadaşlar,

Bu yazımda Windows Store, WPF ve Windows Phone platformlarında uygulama geliştirirken kullanabileceğiniz Converter’lara değineceğim.

Converter nedir
Converter adı üstünde dönüştürme işlemi yapan nesnelerdir.

Converter’lar System.Windows.Data namespace’i altında bulunan IValueConverter arayüzünden türerler.
IValueConverter‘in imza yapısı;

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);

Converterlar neden kullanılır
Sınıfların Propertylerini UI’da kendi istediğimiz formatta görüntülemek için Converter kullanırız. Bir nevi ToString() metodunu ezmek gibi düşünebiliriz.

Üç adımda converter kullanımı:

  1. IValueConver arayüzünden türemiş bir sınıf oluşturulur ve metodlar gerektiği şekilde düzenlenir.
class OrnekConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            //gerekli dönüştürmeler ve return
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException(); //ConvertBack metodu çevrilen değeri eski haline getirmek içindir.
        }
    }

 

2. Oluşturulan converter sınıfını xaml sayfada kullanmak için XAML sayfasına Converter sınıfı StaticResource olarak gömülür.

 xmlns:local="clr-namespace:ProjeIsmi"
 
 <phone:PhoneApplicationPage.Resources>
        <local:OrnekConverter x:Key="ornekConverter1"></local:OrnekConverter>
 </phone:PhoneApplicationPage.Resources>

 

3. UI’da Propertyler görüntülenirken {Binding PropertyIsmi, Converter=ConverterIsmi} şeklinde bind edilir.

<TextBlock Text="{Binding OrnekProperty, Converter={StaticResource ornekConverter1}}"/>

Örnek Uygulama
Bir alışveriş uygulaması geliştirdiğinizi varsayalım. Ürünleri UI’da listelerken ürün ismi, kategorisi ve fiyat bilgileriyle listelediğimizi varsayalım. Ürünleri listelerken doğrudan Fiyat isimli Property’ye bind ettiğimizde
sadece TL cinsinden değeri yazacaktır. Ama sonunda TL ibaresi
olmayacaktır ki bu kesinlikle istemediğimiz bir durum.

İşte bu gibi durumlarda Converter’lar devreye giriyor. Hemen örneğimize başlayalım.

  • Visual Studio’da BindingConverters isimli bir Windows Phone, WPF veya Windows Store projesi oluşturalım
  • Projeye sağ tıklayıp Add -> New Class adımından bir sınıf ekleyelim ve ismine Urun.cs verelim.
  • Kullanacağim Urun.cs sınıfı:
public class Urun
    {
        public string Isim { get; set; }
        public string Kategori { get; set; }
        public int Fiyat { get; set; }

        public static List<Urun> urunGetir()
        {
            List<Urun> urunler = new List<Urun>();
            urunler.Add(new Urun { Isim = "Notebook", Kategori = "Elektronik", Fiyat = 1300 });
            urunler.Add(new Urun { Isim = "Tost Makinesi", Kategori = "Ev eşyaları", Fiyat = 220 });
            urunler.Add(new Urun { Isim = "Parfüm", Kategori = "Kozmetik", Fiyat = 80 });

            return urunler;
        }
    }

Örnek veri kullanmak için urunGetir() isimli bir statik metot tanımladım.

  • Converter sınıfımızı oluşturalım. Projeye sağ tıklayıp Add -> New Class diyelim ve sınıfa FiyatConverter ismini verelim.

FiyatConverter.cs : 

 public class FiyatConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value.ToString() + " ₺";
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

 

Fiyat isimli Property value parametresiyle geliyor biz de onu string’e çevirip sonuna ₺ ekleyip döndürüyoruz.

  • MainPage.xaml’in üstünde şöyle bir decleration eklememiz gerekecek,

 xmlns:local=”clr-namespace:BindingConverters”

local isimli ön etiket bizim projemizin ana dizinini ifade ediyor. Converter sınıfımızı da ana dizine ekleyeceğiz.

MainPage.xaml:

<phone:PhoneApplicationPage.Resources>
        <local:FiyatConverter x:Key="fConverter"></local:FiyatConverter>
</phone:PhoneApplicationPage.Resources>
 
  <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox x:Name="lstUrunler" ItemsSource="{Binding UrunDataSource}">
                <ListBox.ItemContainerStyle>
                    <Style TargetType="ListBoxItem">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="ListBoxItem">
                                    <Grid x:Name="grid" Background="#FF07B063" Height="100" Margin="0,0,0,2">
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="60"></RowDefinition>
                                            <RowDefinition Height="*"></RowDefinition>
                                        </Grid.RowDefinitions>
                                        <TextBlock Grid.Row="0" Text="{Binding Isim}" Margin="12,0,0,0"></TextBlock>
                                        <TextBlock Grid.Row="0" Text="{Binding Fiyat, Converter={StaticResource fConverter}}" HorizontalAlignment="Right"></TextBlock>
                                        <TextBlock Grid.Row="1" Text="{Binding Kategori}" HorizontalAlignment="Right" FontStyle="Italic" Foreground="Black"></TextBlock>
                                    </Grid>
                                </ControlTemplate>
                            </Setter.Value>                            
                        </Setter>
                    </Style>
                </ListBox.ItemContainerStyle>
            </ListBox>
        </Grid>

 

 

  • Örnek verimizi ListBox’a bağlamak için de MainPage.xaml.cs‘i de şöyle yapalım.
 public partial class MainPage : PhoneApplicationPage
    {
        public static List<Urun> UrunDataSource = new List<Urun>(Urun.urunGetir());
        public MainPage()
        {
            InitializeComponent();
            this.DataContext = this;
        }
    }

 

Ve nihayet uygulamayı test ediyoruz.

1

Gördüğünüz gibi Urun sınıfımızın Fiyat propertyleri yalın bir şekilde değil de sonundaki ₺ sembolü ile birlikte görüntüleniyor.

Umarım faydalı olmuştur, bir sonraki yazıda görüşmek üzere.
   Örnek Proje Dosyası (BindingConverters.zip, 181 KB)

Fazlasını Oku