Makaleler, screencast’ler, webinerler, kitaplar, seminerler, eğitimler..
Belli bir uzmanlık alanında bu kadar çok kaynak bulunması, sadece ilk adımlarını
atanlar için değil, o alanda çalışan herkes için gerçekten çok büyük bir nimet.
Özellikle, üniversite döneminde sınavlara hazırlanırken, çalıştığım konularla ilgili
(Endüstri Mühendisliği) internette kaynak araştırması yaptığımda, elle tutulur pek
birşey bulamamış olmam beni çok ama çok şaşırtmıştı. Halbuki, konu yazılım olduğunda,
en ufak bir alt alt alt başlık hakkında dahi, çok fazla yazı, tartışma, görsel materyal
bulabiliyorsunuz. Bunun en büyük sebeplerinden biri belki de, bu dünyada zamanın
biraz hızlı işlemesi. Geriye yaslanıp şöyle bir düşünsenize; uygulama geliştirirken
kullandığımız kütüphaneler, biricik Framework’ümüz 4.0 sürümüne ulaşmış, veritabanı
programcılığının belki de en güçlü ve en modern programlama dili olan C#’ın sağına
4.0 eklenmiş, ailemizin runtime’ı CLR da 4.0 mertebesine yükselenler arasında, hem
de daha 10. yaşını bile doldurmadan!
Gelişim süreci bu kadar kısayken, gelinen noktada, biz yazılım geliştiriciler, Microsoft
tarafından adeta bir bilgi bombardımanına tutuluyoruz. İş böyle olunca da “Tips&Tricks”
tadındaki, konuyu anlatmaktan ziyade konunun ipuçlarına değinen kaynak bulmak biraz
güçleşiyor. Ben de bu defa, bir konuyu anlatmak yerine, konuyla ilgili projelerde
karşıma çıkmış birkaç önemli gördüğüm noktaya değineceğim.
İpucu 1 : Template içindeki nesnelere ait property’lerin, kontrolün property’lerine
bağlanması
Senaryoyu görmeden önce, Window’un Resource’larına bir ControlTemplate nesnesi tanımlayalım.
Örneğin benim tanımladığım template, uygulandığı butonun, köşeleri yuvarlatılmış
kırmızı bir dörtgen olarak çizilmesini sağlamaktadır.
<Window.Resources>
<ControlTemplate x:Key="MyTemplate"
TargetType="{x:Type Button}">
<Grid>
<Rectangle RadiusX="20" RadiusY="20" Fill="Red" Stroke="Black" Margin="0"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Daha sonra da oluşturduğumuz ControlTemplate’i, birkaç butona uygulayalım.
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition
Height="*"/>
<RowDefinition
Height="*"/>
<RowDefinition
Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Margin="5" Template="{StaticResource
MyTemplate}" />
<Button Grid.Row="1" Margin="5" Template="{StaticResource
MyTemplate}" />
<Button Grid.Row="2" Margin="5" Template="{StaticResource
MyTemplate}" />
</Grid>
Window’un son hali aşağıdaki gibi olacaktır.
Dikkat ederseniz, üç butonunda Template’ine, tanımladığımız ControlTemplate nesnesi
verilmiş, bunun sonucu olarak da ekrandaki üç buton da aynı görüntüye sahip olmuştur.
Peki ya, hem aynı template üç butona da uygulanarak hem her butonun farklı bir renkte
görünmesi nasıl sağlanabilir ?
Örneğin butonların herbirine ayrı ayrı arkaplan renkleri atayalım.
<Button Grid.Row="0" Margin="5" Template="{StaticResource MyTemplate}" Background=”Blue”
/>
<Button Grid.Row="1" Margin="5" Template="{StaticResource MyTemplate}" Background=”White”
/>
<Button Grid.Row="2" Margin="5" Template="{StaticResource MyTemplate}" Background=”Black”
/>
Fakat XAML’de bu değişikliğin yapılmasına rağmen, üç butonun da hala kırmızı renkte
olduğunu görünmektedir. Bunun sebebi gayet basittir : ControlTemplate’in içindeki
Rectangle nesnesine Template içinde kırmızı rengi atandığından uygulandığı tüm butonlar
da her zaman kırmızı olarak görünmektedir. Bu durumda rengin, template’in
uygulanması anında, uygulanan buton tarafından belirlenmesi sağlanmalıdır. Çok açıkça
görünmektedir ki bu, XAML tarafında ele alabileceğimiz basit bir Binding işlemidir.
Bir ControlTemplate içindeki nesnenin property’sini ( Rectangle ’ın Fill
property’si), uygulandığı kontrolün bir property’sine ( Button ’ın
Background property’si) bağlamak için ise, XAML’de TemplateBinding
Markup Extension ’ı kullanılır. Kodun son halini aşağıdaki gibi değiştirelim.
<Window.Resources>
<ControlTemplate x:Key="MyTemplate"
TargetType="{x:Type Button}">
<Grid>
<Rectangle RadiusX="20" RadiusY="20" Fill="{TemplateBinding Background}" Stroke="Black"
Margin="0"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Window’un son hali aşağıdaki gibi görünmelidir.
Bu işlem Blend’in arayüzü kullanılarak çok daha kolay bir şekilde yapılabilir. Bunun
için ControlTemplate'in içindeki Rectangle seçilerek Properties penceresinden Fill
property’sinin hemen yanında bulunan küçük kare simgesine tıklanır ve ardından
açılan menüde, Template Binding’in altındaki Background property’si seçilir.
Bu noktada basit bir ayrıntının üzerinden geçmekte fayda var. Dikkat ederseniz,
menüde Template Binding seçeneği altında sadece Background , BorderBrush
, Foreground ve OpacityMask property’leri bulunmaktadır.
Bunun sebebi oldukça basittir. Listede çıkan property’lerin hepsi Brush tipinden
property’lerdir. Peki neden? Çünkü bağlanılmaya çalışılan property, yani Rectangle’ın
Fill property’si Brush tipli bir property idi. Demek ki bağlanılan property’lerin
tipleri aynı olmalıdır.
İpucu 2 : Template içindeki nesnelerin yerleşimlerini sağlamak
Template oluştururken, göz önünde bulundurulması gereken noktalardan bir tanesi
de kontrollerin template içindeki yerleşimleridir (layout). Özellikle, template’in
uygulanacağı kontroller boyutlandırılacaksa, yani tamamı aynı boyutta olmayacaksa,
template içinde yer alan tüm kontrollerin HorizontalAlignment, VerticalAlignment,
Margin, Padding gibi yerleşimle ilgili property’leri gözden geçirilmelidir.
Örneğin aşağıdaki buton template’ini, Blend’in dizayn ekranında, yerleşimle ilgili
property’lere hiç dikkat etmeden hızlı bir şekilde gerçekleştirdiğimde, aşağıdaki
XAML çıktısını elde ettim.
<Window.Resources>
<ControlTemplate x:Key="MyTemplate"
TargetType="{x:Type Button}">
<Border BorderBrush="Black"
BorderThickness="2">
<Grid>
<Ellipse Stroke="{x:Null}" HorizontalAlignment="Left" Margin="8,8,0,8" Width="83">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFE3FF69" Offset="0"/>
<GradientStop Color="#FF92A7B9" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock Margin="111,30,26,23" FontSize="29.333" FontStyle="Italic" Text="My
Button" TextWrapping="Wrap"/>
</Grid>
</Border>
</ControlTemplate>
</Window.Resources>
Şimdi bu template’i ikinci bir butona uygulayıp, bu butonun boyutlarıyla oynayalım.
Boyutlar değiştirildiğinde, template’de yer alan elips’in genişlediği ve yazının
yukarıda kaldığı açıkça görünmektedir. Oluşan durum etüd edildiğinde, şu sonuçlar
ortaya çıkar.
- Elips, dikey eksende boyutunu arttırmaktadır.
- Yazı, kontrolün sol ve üst kenarlarıyla olan mesafesini korumaktadır.
WPF ‘te kontrollerin yerleşimleriyle ilgili durumlar kolaylıkla ele alınabilmektedir.
Bunun için Blend’in Property ekranındaki Layout sekmesindeki property’lerden
faydalanılır. Öncelikle elipsle ilgili durumun ele alınmasına bakalım.
Template içinden elips nesnesini seçerek, Layout sekmesindeki property’lerinden
VerticalAlignment’ı Center olarak değiştirelim.
Fakat bu işlem yapıldığında, elipsin tamamen ekrandan kaybolduğu görünecektir. Bunun
sebebi de elipse sabit bir yükseklik verilmemiş olmasıdır. İlk resimdeki Height
değeri 139px olmasına rağmen yanında Auto yazmaktadır; diğer bir deyişle,
elips dikey olarak kendisini Strech ettiğinden, 139px yüksekliğe sahip
oluncaya kadar genişlemiştir. Fakat VerticalAlignment Center olarak değiştirildiğinde,
artık Strech etmediğinden, sabit bir yükseklik değeri verilmelidir. Bunun
için Height’a da 83px değerini atayalım. Daha sonra aynı işlemleri TextBlock için
de gerçekleştirelim. XAML’in son hali aşağıdaki gibidir.
<Window.Resources>
<ControlTemplate x:Key="MyTemplate"
TargetType="{x:Type Button}">
<Border BorderBrush="Black"
BorderThickness="2">
<Grid>
<Ellipse Stroke="{x:Null}" HorizontalAlignment="Left" Margin="8,8,0,8" Width="83"
VerticalAlignment="Center" Height="83">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFE3FF69" Offset="0"/>
<GradientStop Color="#FF92A7B9" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock Margin="111,30,26,23" FontSize="29.333" FontStyle="Italic" Text="My
Button" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</Grid>
</Border>
</ControlTemplate>
</Window.Resources>
Yerleşim, programlamadan ziyade, tasarımla ilgili olduğundan, Blend kullanan programcılar
,alışık olmadıklarından olsa gerek, genelde bu tarz noktalara özen göstermiyorlar.
Bu yüzden de kontrollerin boyutlandırılması anında yerleşimin bozulmasına çokça
rastlanabiliyor. Sonuç olarak, daha sonra boyutlandırılsın ya da boyutlandırılmasın,
template’in oluşturulması anında template’te yer alan her kontrol için Layout property’lerine
dikkat edilmelidir.
İpucu 3 : Resource yönetimi
Oluşturulan template içinde, Style,Storyboard,Brush gibi birçok farklı
tipten nesneler kullanılıyor olabilir. Bu nesnelerin XAML içerisinde resourcelar
olarak tanımlanması, büyük projelerde çok kolaylık sağlamaktadır. Hemen bu senaryoyu
da basit bir örnek üzerinde birlikte inceleyelim. Örneğin aşağıdaki gibi, iki farklı
kontrol için hazırlanmış iki ayrı template olsun:
<Window.Resources>
<ControlTemplate x:Key="ButtonTemplate"
TargetType="{x:Type Button}">
<Grid>
<Rectangle RadiusX="30" RadiusY="30">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Brown" Offset="0.5"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Text="Custom Button" Foreground="White" FontSize="15" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ButtonTemplate2"
TargetType="{x:Type Button}">
<Grid>
<Rectangle RadiusX="30" RadiusY="30">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Brown" Offset="0.5"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Left" Margin="20,0,0,0"
/>
<TextBlock Text="Custom Button" Margin="10,0,0,0" Foreground="White" FontSize="10"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Bu template’lerin ekranda görüntüsü şu şekilde olacaktır :
Bu iki template arasındaki ortak nokta, kullanılan LinearGradientBrush
nesnelerinin aynı özelliklere sahip olmasıdır; ancak bahsi geçen brush’ın her iki
template içinde de ayrı ayrı tanımlandığı açıkça görünmektedir. Bu durumda en doğru
iş, ilgili brush’ın da resource olarak oluşturulup, her iki template’in ortak kullanmasını
sağlamaktır. Yukarıdaki XAML’in düzenlenmiş hali şu şekilde olmalıdır:
<Window.Resources>
<LinearGradientBrush x:Key="MyBrush" EndPoint="0,1">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Brown" Offset="0.5"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type
Button}">
<Grid>
<Rectangle
RadiusX="30" RadiusY="30" Fill="{StaticResource MyBrush}"/>
<TextBlock
Text="Custom Button" Foreground="White" FontSize="15" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ButtonTemplate2" TargetType="{x:Type
Button}">
<Grid>
<Rectangle
RadiusX="30" RadiusY="30" Fill="{StaticResource MyBrush}"/>
<CheckBox
VerticalAlignment="Center" HorizontalAlignment="Left" Margin="20,0,0,0" />
<TextBlock
Text="Custom Button" Margin="10,0,0,0" Foreground="White" FontSize="10" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Yapılan düzenleme çok basit görünse de, büyük çapta template’lerin yazılması sırasında
genelde gözden kaçabilmektedir. Ayrıca söz konusu olan brush her iki template içinde
kullanılmasa, yani ortak olmasa dahi, kendi başına resource olarak tanımlanması
XAML’in yönetimini kolaylaştırır. Bu sebeple, template içinde yer alan Brush,Storyboard,Style
gibi nesnelerin, kendi başlarına resource olarak tanımlanması çoğu zaman doğru bir
harekettir.
İpucu 4 : Farklı tipten property’ler arasında template binding kurulması
İpucu 1’de de bahsedildiği üzere, TemplateBinding kullanımıyla bir template
içindeki kontrole ait property, template’in uygulandığı kontrolün property’sine
bağlanabilmektedir. Fakat TemplateBinding markup extension’ı kullanımında,
bağlanan property’lerin tipleri aynı olmalıdır. Diğer bir deyişle,
eğer bağlanan property’lerin tipleri farklıysa, bağlama işlemi gerçekleştirilememektedir.
Farklı tipten property’ler arasında Binding yapmaya neden ihtiyaç duyulacağını görmek
için aşağıdaki basit senaryoyu birlikte inceleyelim.
<Window.Resources>
<ControlTemplate x:Key="MyTemplate" TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Margin="5"/>
<TextBlock
HorizontalAlignment="Left" Margin="8,0,0,0" VerticalAlignment="Center" Grid.Column="1"
Text="My Button" TextWrapping="Wrap" FontSize="32" Height="Auto"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Yukarıdaki XAML kodu ile butonun, Image ve TextBlock kontrollerinden
oluşan bir görünüme sahip olması amaçlanmaktadır. Dikkat ederseniz, Image
kontrolünün Source property’sine Template içinde değer atanmamıştır. Çünkü
bu noktada bir değer ataması yapıldığında, template’in kullanıldığı her butonda
aynı resim görünecektir. Eğer her butonda farklı resimler isteniyorsa, Source
değerinin, kullanıldığı anda, template’in uygulandığı kontrole ait bir property’den
alınabilmesi sağlanmalıdır. Kısacası, template binding yapılmalıdır. Fakat Source
property’si ImageSource tipinden olup, ne yazık ki, Button
sınıfında bu tipten bir property bulunmadığından, TemplateBinding markup extension’ını
kullanmak mümkün değildir.
Peki bu durumda ne yapılabilir? Hemen Windows programcılığı bilgilerimizi şöyle
bir gözden geçirelim. Windows Forms uygulamalarında kullanılan tüm kontrollerde
Tag isminde bir property bulunmaktadır ve bu property Object tipinden
olup, kontrolün üzerinde herhangi bir nesnenin referansını saklama imkanı vermektedir.
Acaba aynı property WPF içinde de var mıdır? Tabi ki evet! Ancak unutmayalım ki,
Tag property’si Object , bağlamak istenilen Source ise ImageSource
tipindendir; dolayısıyla TemplateBinding kullanılamamaktadır. Hatta, Blend’in
arayüzünde TemplateBinding seçeneğı tıklanamaz durumdadır.
Buna rağmen XAML’e müdahale edilip, aşağıdaki düzenleme yapılsa dahi sonuç alınmayacaktır.
<Image Source=”{TemplateBinding Tag}” Margin="5"/>
Peki, farklı tipten property’ler arasında template binding kurmanın başka bir yöntemi
yok mu? Tip farklılığı olduğunda bunu çözümleyebilecek bir bağlama tekniği yapılamaz
mı?
Gelelim projelerde karşınıza çıkma ihtimali yüksek olan bu durumun çözümüne.
Tek yapılması gereken, TemplateBinding yerine klasik Binding’
i kullanmaktır. Çünkü TemplateBinding daha optimize edilmiş ve performans
kazandıran bir bağlama tekniği olmasına rağmen tiplerin aynı olması konusunda bir
kısıtlaması söz konusudur. Dolayısıyla bu örnekteki gibi tipler farklı ise, klasik
Binding kullanılmalıdır. Kod üzerinde gerekli düzenlemeyi yapalım.
<Image Source=”{Binding Path=Tag,RelativeSource={RelativeSource TemplatedParent}}”
Margin="5"/>
Relative Source ’un yukarıdaki kullanımında, template ’in uygulanacağı
nesneye Binding yapıldığı belirtilmekte olup, sonuçta her iki yöntem de
template ’in uygulandığı nesneye Binding yapılmasını sağmaktadır. Fakat gerek duyulmadıkça
Relative Source kullanımının tercih edilmemesi gerektiği unutulmamalıdır.
Bu tarz veri bağlamalarında, daha performanslı çalıştığından ötürü, öncelikli tercih
her zaman TemplateBinding’ dir.