Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

WPF - Sayfa(Page) Kavramı, Navigasyon İşlemleri ve XBAP

Perşembe, 4 Ekim 2007 05:20 by bsenyurt
Yıllardır ister büyük çaplı ister küçük çaplı olsun pek çok proje web tabanlı olarak geliştirilmektedir. Projelerde bu tip bir seçime gitmenin en büyük nedenlerinden biriside web uygulamalarındaki dağıtım modelinin(Deployment Model) Windows tabanlı olanlara göre çok daha kolay olmasıdır. Her ne kadar .Net 2.0 ile birlikte gelen ClickOnce veya daha öncesinden beri var olan ActiveX gibi dağıtımı kolaylaştırabilecek teknolojiler var olsada bunlar Web uygulamalarına olan yönelimi azaltamamıştır. Sonuç itibariyle web tabanlı uygulamalarda, yazılan parçaları yorumlayacak bir tarayıcı penceresinin(Browser) olması yeterlidir. Geriye kalan, söz konusu tarayıcı pencerelerinin yorumlayacağı HTML içeriklerinin oluşturulmasıdır. Bu amaçlada son derece gelişmiş sunucu(Server-Side) veya istemci taraflı(Client-Side) uygulama geliştirme modelleri mevcuttur. Asp.Net bu modellerden yanlızca birisidir. Ancak web tabanlı uygulamalarda tarayıcı tarafından bakıldığında dağıtım dışında sağlanan başka avantajlarda vardır. Örneğin, tarayıcı penceresi yardımıyla uygulama(Application) alanı içerisinde bir sayfadan diğerine geçmek bir başka deyişle navigasyon işlemleri ile dolaşmak çok kolaydır. Bu tip bir kullanım kolaylığını Windows uygulamalarına kazandırmak ekstra kodlamayı gerekirmektedir.

Microsoft, .Net Framework 3.0 ile birlikte Windows uygulamalarının tarayıcı pencereleri içerisinde çalıştırılabilmesini sağlayacak bir yenilik getirmektedir. Kısacası XBAP(XAML Browser Applications) olarak adlandırılan bu modelde, kısıtlamaları ile birlikte bir WPF(Windows Prensetation Foundation) uygulamasını bir tarayıcı penceresinde açmak mümkündür. Yazı dizimizde bu konuyada değiniyor olacağız. Ama öncesinde bunların temelini oluşturan sayfa(Page) kavramını anlamak gerekmektedir.

WPF uygulamalarını sayfa tabanlı(Page-Based) olacak şekilde tasarlayabilmekteyiz. Burada sayfadan kasıt Page tipinden bir nesnedir. WPF mimarisinde sayfa tabanlı uygulamalarda kendi içlerinde iki ana parçaya ayrılmaktadır. Bunlardan birincisi XBAP uygulamaları, diğeri ise kendi başına çalışan(Stand-Alone) uygulamalardır. Sayfalar(Pages) aslında daha önceki makalelerimizde de incelediğimiz Window tipine benzetilebilir. Lakin arada çok önemli bir fark vardır. Window tipi temel olarak bir taşıyıcı(Container) görevini üstelenebilmektedir. Bu sebeptende ContentControl tipinden türetilmiştir. Ne varki Page tipi doğrudan FrameworkElement tipinden türemektedir. Dolayısıyla Page tiplerinin kullanılabilmesi için bunu servis edecek bir sunucuya(Host) ihtiyaç vardır. Söz konusu özet bilgilere göre sayfa bazlı(Page-Based) uygulamaları aşağıdaki gibi kategorize edebiliriz.

Standalone (Kendi başına
çalışan uygulamalar)
NavigationWindow içerisinde kullanılabilen sayfalar.
Bir pencerede(Window) yer alan Frame veya Frame' ler içerisinde kullanılabilen sayfalar.
Başka bir sayfa(Page) içerisindeki Frame veya Frame' lerde kullanılabilen sayfalar.
XBAP(Xaml Browser Applications) Uygulamaları Internet Explorer veya destek veren başka bir tarayıcı(Browser) üzerinde çalışabilen sayfalar. Lightweight olarakta adlandırılan basit web tabanlı dağıtım modeli için uygun bir yapı sunmaktadır.

Sayfa tabanlı(Page-Based) uygulamalarda kullanılan genel tipler aşağıdaki sınıf diagramında(Class Diagram) görüldüğü gibidir.

Yukarıdaki sınıf diagramında sayfa-tabanlı(Page-Based) uygulamalarda başrol oynayan sınıflardan(class) bazıları yer almaktadır. Window sınıfı daha önceki windows programlamada yer alan Form sınıfının karşılığı olarak düşünülebilir. Bir içerik kontrolüdür(ContentControl). Bu nedenle kendi içerisinde başka elementleride barındırmaktadır. Söz gelimi Window içerisinde bir Frame tanımlanıp bu Frame içerisinde de farklı sayfalar(Page) yer alabilir. Frame tipi aslında bir sayfa içerisinde bağımsız bir parça olaraktanda düşünülebilir. Frame' leri bir Page veya Windows elementi içerisinde kullanabiliriz. Temel görevleri aslında web uygulamalarından bilinen benzeri ile aynıdır. Bir başka deyişle taşıyıcı kontrol içerisinde başka sayfaların(Page) gösterilebilmesini sağlamaktadır. Bu taşıyıcı özelliği nedeni ilede tahmin edileceği gibi ContentControl sınıfından türemektedir. NavigationWindow, içerisinde Page elementlerini içerebilen bir tiptir. Varsayılan olarak Page elementi içeren bir XAML içeriği code-behind dosyası ile birlikte çalıştırıldığında çalışma zamanında otomatik olarak bir NavigationWindow nesnesi örneklenmektedir. NavigationWindow nesneleri çalışma zamanında dinamik olaraktanda örneklenebilir ve sayfa içeriklerini göstermesi sağlanabilir.

Bu kısa teorik bilgilerden sonra dilerseniz basit örnekler yardımıyla konuyu daha iyi anlamaya çalışalım. Eğer aynı pencere üzerinde yer alacak ve aralarında geçişler yapılabilecek sayfalardan bahsediyorsak doğal olaraktan bunların arasında dolaşabilmek gerekmektedir. Dolaşma işlemleri için kullanılabilecek en basit kontrol Hyperlink bileşenidir. Bu bileşenin NavigateUri özelliğinden yararlanılarak başka bir sayfaya geçilmesi, aynı sayfa içerisinde veya başka bir sayfa içerisinde yer alan bir noktaya gidilmesi(burada anchor benzeri bir kullanımdan bahsediyoruz), başka bir NavigationWindow içerisinde bir sayfaya ve hatta var olan geçerli bir Url adresine gidilmesi sağlanabilir. Dolayısıyla ilk örneğimizde bu durumu analiz etmeye çalışıyor olacağız. Bu amaçla Visual Studio 2008 Beta 2 sürümünde yeni bir WPF uygulaması açıp XAML içerikleri başlangıçta aşağıdaki gibi olan iki sayfa(Page) tasarlamamız yeterlidir.

NOT : Page tiplerini bir WPF uygulamasına eklerken öğelerden(Item) yararlanılabilir. Bunun için projeye sağ tıklayıp Add New Item penceresinden yada, doğrudan sağ tıklayınca çıkan menülerden Add->Page ile gerçekleştirebiliriz.

MainPage.xaml

<Page x:Class="PageKullanimi.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Ana Sayfa" WindowHeight="250" WindowWidth="400" WindowTitle="Azon Giriş Sayfası" Loaded="Page_Loaded">
    <Page.Background>
        <ImageBrush ImageSource="Iceberg.jpg" Opacity="0.4"/>
    </Page.Background>
    <Grid>
        <Label Foreground="Black" FontSize="15" FontWeight="Bold" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top" Width="113" Margin="9,21,0,0">
            <Hyperlink NavigateUri="PageX.xaml" ToolTip="Bir sonraki sayfaya geçmenizi sağlar">Sonraki Sayfa</Hyperlink>
        </Label>
        <Button Name="btnBilgiSayfasi" Background="LightGray" Width="120" Height="30" FontSize="12" FontWeight="Bold" HorizontalAlignment="Left" Margin="9,107,0,0" VerticalAlignment="Top">
            <Hyperlink Foreground="Red" NavigateUri="Page2.xaml">Bilgi Giriş Sayfası</Hyperlink>
        </Button>
        <TextBox Name="txtHosgeldinMesajim" Margin="9,60,33,0" Height="21" VerticalAlignment="Top" />
    </Grid>
</Page>

MainPage içerisinde ilk dikkati çeken noktalardan birisi Page elementi ve özellikleridir. WindowHeight ve WindowWidth özellikleri ile çalışma zamanındaki NavigationWindow penceresinin boyutları set edilmektedir. WindowTitle özelliği ile sayfanın NavigationWindow içerisinde gösterilirken sahip olacağı başlık değeri verilmektedir. Title özelliği ise, navigasyon işlemlerinin yapıldığı Combobox içerisindeki başlık bilgisini belirlemektedir. Aşağıdaki şekil çalışma zamanındaki bir ekran görüntüsü olup bahsedilen özellikleri ifade etmektedir.

NOT : Page sınıfının Window sınıfında olduğu gibi Show veya Hide gibi metodları bulunmamaktadır. Bunun en büyük nedeni Page sınıflarına ait nesne örnekleri aralarında gezinirken navigasyon kontrollerinden yararlanılabilmesidir. Dolayısıyla klasik windows programcılığından bildiğimiz Show veya Hide gibi işlemlere gerek kalmamaktadır.

Bunların dışında MainPage içerisinde Label ve Button kontrollerine ait elementler içerisinde Hyperlink alt elementleri kullanılmıştır. Hyperlink bileşeni bağımsız bir element olarak kullanılamamaktadır. Dolayısıyla Inline-Flow tipinden bir kontroldür. Diğer taraftan bu elementin NavigateUri özelliği ile gidilmek istenen sayfalar belirtilmektedir.

NOT : Hyperlink elementi Page yerine bir Window içerisinde kullanılmak istendiğinde belirtilen adrese otomatik olarak gidilemeyecektir. Böyle bir durumda Window sınıfının RequestNavigate olayının bilinçli olarak ele alınması ve yönlendirme işleminin manuel olarak yapılması gerekmektedir.

Label kontrolünde yer alan Hyperlink elementinde kasıtlı olarak projede yer almayan PageX.xaml' e gidilmeye çalışılmaktadır. Burada amaç olmayan bir adrese gidilmek istendiğinde ne olacağının analiz edilmesidir. Button kontrolü içerisinde yer alan Hyperlink elementinde ise, XAML içeriği aşağıda yer alan Page2 isimli sayfaya gidilmektedir.

Page2.xaml

<Page x:Class="PageKullanimi.Page2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Bilgi Giriş Sayfası" Title="Bilgi Girişi" Loaded="Page_Loaded">
    <Grid>
        <Label FontSize="12" FontWeight="Bold" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="85" Margin="0,24,0,0">Tc Kimlik No</Label>
        <Label FontSize="12" FontWeight="Bold" HorizontalAlignment="Left" Width="85" Margin="0,83,0,0" Height="25" VerticalAlignment="Top">Semt</Label>
        <Label FontSize="12" FontWeight="Bold" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="85" Margin="0,46,0,0">Aranan Hat</Label>
        <Button FontSize="14" Margin="125,117,62,0" Name="btnKontrolSayfasi" Click="btnKontrolSayfasi_Click" Height="24" VerticalAlignment="Top">
            Kontrol Sayfası
        </Button>
        <TextBox Name="txtTcNo" Height="21" Margin="120,27,60,0" VerticalAlignment="Top" />
        <TextBox Name="txtArananHat" Height ="21" Margin="120,57,60,0" VerticalAlignment="Top" />
        <TextBox Name="txtSemt" Height ="21" Margin="120,89,60,0" VerticalAlignment="Top" />
    </Grid>
</Page>

İlk olarak örneği çalışma zamanında test ederek işe başlayalım. Uygulamanın yürütülmeye başlaması halinde MainPage.xaml sayfasının örneklenmesi için App.xaml' e ait Application elementi içerisindeki StartupUri özelliğinin değeri MainPage.xaml olarak ayarlanmıştır. Aşağıdaki Flash görselinde ilk çalışma hali gösterilmektedir.

Dikkat edilecek olursa çalışma zamanında(Run-Time) otomatik olarak bir navigasyon çubuğu oluşturulmuştur. İlk aşamada bu çubuk üzerindeki düğmeler pasiftir. Aktif olmaları için sayfalar arasında geçiş yapılması gerekmektedir. Sayfalar arası geçiş yapıldıktan sonra navigasyon düğmeleri ile ileri ve geri yönlü hareketler yapılabilir. Bununla birlikte Combobox kontrolünden yararlanılaraktanda diğer sayfalara daha kolay bir şekilde geçiş yapılmasıda sağlanabilir. Bu örnekte PageX.xaml sayfasına geçiş yapılmasını sağlayan Label kontrolüne basılmamıştır. Bu yapıldığı takdirde söz konusu sayfa olmadığı için aşağıdaki ekran görüntüsünde yer alan bir çalışma zamanı istisnası(exception) alınacaktır.

Görüldüğü gibi basit bir IOException alınmıştır. Elbetteki programatik olarak uygulamayı tasarlarken olmayan sayfalara gidilmemesini sağlamak geliştiricinin görevidir. Lakin NavigateUri ile var olan bir sayfa dışında geçerli bir URL adresinede gidilebilmesi sağlanabilmektedir. Bir başka deyişle var olan bir web sayfasını çalışma zamanında oluşturulan NavigationWindow içerisinde bir sayfa olarak göstermek mümkündür. Bu gibi durumlarda gidilmek istenen URL veya Sayfa bilgisinin geçerli olmaması halinde programın istem dışı bir şekilde sonlanmasının önüne geçmek için Application nesnesinin NavigationFailed olayını ele almak ve içerisinde istisna bilgisini kontrollü bir şekilde yakalamak en doğru yaklaşım olacaktır. Yukarıdaki örnekte bunu uygulamak istediğimiz App.xaml ve App.xaml.cs içeriklerinin aşağıdaki gibi tasarlanması yeterlidir.

App.xaml;

<Application x:Class="PageKullanimi.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainPage.xaml" NavigationFailed="Application_NavigationFailed">
    <Application.Resources>
    </Application.Resources>
</Application>

App.xaml.cs;

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Windows;

namespace PageKullanimi
{
    public partial class App : Application
    {
        private void Application_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
        {
            if (e.Exception != null)
            {
                MessageBox.Show(e.Uri.ToString() + " adresi bulunamadı");
                e.Handled = true;
            }
        }
    }
}

Örnekte basit olması açısında sadece hataya neden olsan sayfanın Uri bilgisi bir MessageBox içerisinde gösterilmektedir. Olay metoduna gelen NavigationFailedEventArgs tipinden e parametresinin Handled özelliğine true değeri atanmasının sebebi hatanın kontrollü bir şekilde ele alındığının belirtilmesidir. Aksi durumda program yine hata sonrası, kullanıcıya oluşan hatanın gönderilip gönderilmeyeceğini soran hepimizin yakından tanıdığı mesaj kutusu ile sonlandırılacaktır. Burada yakalanan hatalar çok doğal olarak başka amaçlarlada değerlendirilebilir. Örneğin Log' lanarak, oluşan hatalar ile ilişkili genel istatistik ve analizlerin yapılması sağlanabilir.

Hyperlink kontrolünü kullanarak web sayfalarınada gidilebildiğinden bahsetmiştik. Bunun dışında bir sayfa içerisinde yer alan herhangibir konuma gidilmeside sağlanabilirki bu durum parçalı navigasyon(Fragment Navigation) olarak adlandırılmaktadır. Şimdi bu iki kullanım şeklini ele alacağımız bir örnek üzerinden ilerleyelim. Bu amaçla projemize Page3.xaml ve Page4.xaml sayfalarını aşağıdaki içerikleri ile eklediğimizi düşünebiliriz.

Page3.xaml;

<Page x:Class="PageKullanimi.Page3" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowTitle="Doğrulama Sayfası" Title="Doğrulama" WindowHeight="250" WindowWidth="400">
    <Grid>
        <Button Name="btnCSHarpNedir" FontSize="14" FontWeight="Bold" Height="25" Margin="20,36,30,0" VerticalAlignment="Top">
            <TextBlock>
                <Hyperlink Foreground="Red" NavigateUri="http://www.csharpnedir.com">C#Nedir?</Hyperlink>Sayfasına Gider.
            </TextBlock>
        </Button>
        <Button Name="btnIceberg" FontSize="14" FontWeight="Bold" Height="25" VerticalAlignment="Top" Margin="20,90,30,0">
            <TextBlock>
                <Hyperlink NavigateUri="Page4.xaml#txtBilgi" Foreground="Red">Iceberg</Hyperlink>
            </TextBlock>
        </Button>
    </Grid>
</Page>

Sayfanın içerisinde kullanılan iki adet Hyperlink kontrolü bulunmaktadır. Bunlardan birisi C#Nedir? sitesine, diğeri ise Page4.xaml sayfası içerisinde txtBilgi isimli bileşenin olduğu yere yönlendirme yapmaktadır. İkinci navigasyon işlemi aslında parçalı navigasyon(Fragment Navigation) işlemi için bir örnektir. Öyleki Page4.xaml sayfası içerisinde yer alan txtBilgi isimli kontrol sayfanın alt kısmında yer almaktadır. Bu nedenle sayfa içerisindeki bu yere navigasyon işlemi ile gidilmesi ve odaklanılması mümkün olabilmektedir.

Page4.xaml;

<Page x:Class="PageKullanimi.Page4" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Antartika Iceberg" WindowTitle="Iceberg">
    <Grid>
        <ScrollViewer VerticalScrollBarVisibility="Visible">
            <Canvas Height="299" Width="284">
                <Image Canvas.Left="18" Canvas.Top="51" Height="104" Name="image1" Width="150" Source="Iceberg.jpg" />
                <Label Canvas.Left="18" Canvas.Top="26" Height="23" Name="label1" Width="120">Antartika Hakkında</Label>
                <Label Canvas.Left="18" Canvas.Top="166" Height="23" Name="label2" Width="120">
                    <Hyperlink NavigateUri="Page4.xaml#txtBilgi">Genel Bilgi</Hyperlink>
                </Label>
                <TextBox Height="25" Name="txtBilgi" Canvas.Left="18" Canvas.Top="274" Width="249" />
            </Canvas>
        </ScrollViewer>
    </Grid>
</Page>

Page4.xaml içerisinde de bu sayfadaki txtBilgi kontrolüne odaklanılmasını sağlayan bir Hyperlink bileşeni bulunmaktadır. Dikkat edilecek olursa parçalı navigasyon işlemlerinde NavigateUri özelliğinde # işaretinden sonra bir bilgi yer almaktadır. Bu bilgi gidilmek istenen kontrolün Name özelliğinin(Property) değeridir. Örneğin çalışma zamanındaki işleyişi aşağıdaki Flash animasyonunda olduğu gibidir.

Görüldüğü gibi Page3.xaml içerisinde yer alan düğme kontrollerine basıldığında C#Nedir? sayfasına veya Page4.xaml içerisindeki txtBilgi isimli TextBox kontrolünün olduğu yere gidilmektedir. Herhangibir internet veya intranet adresine gidilmesini sağlayan teknikte mutlaka hata kontrolü yapılmalıdır. Diğer taraftan bir web sayfasına gidildiğinde sayfada görünen kısım klasik windows programlamadan tanıdığımız WebBrowser kontrolünün sunduğu ortama benzemektedir. Bu sebepten dolayı elde edilen sayfa içerisinde arama yapmak, dinamik kod çalıştırmak gibi işlemler yapılamamaktadır.

NOT : Parçalı navigasyon(Fragment Navigation) işleminin olabilmesi için, söz konusu sayfa içerisinde aşağı yukarı hareket edilebilmesi bir başka deyişle scrolling olması gerekmektedir. Bu amaçla ScrollViewer kontrolünden yararlanılabilir. Söz konusu kontrolün VerticallScorllBarVisibility ve HorizontalScrollBarVisibility özelliklerine ilgili değerler atanarak dikey veya yatay yönde kaydırma çubuklarının(Scroll Bar) gösterilmesi(yada tam tersi) sağlanabilir.

Navigasyon işlemleri istenirse manuel olarak kod tarafından da gerçekleştirilebilir. Bu noktada NavigationWindow içerisinde üst kısımda görünen navigasyon kontrolleri ve menünün yaptığı işlerin kod yardımıylada gerçekleştirilmesi mümkündür. Bunun için NavigationService tipinden ve üyelerinden(Members) yararlanılabilir. Sıradaki örnekte yeni bir sayfaya geçiş işlemini kod ile nasıl yapabileceğimize bakıyor olacağız. Page2.xaml içerisindeki btnKontrolSayfasi isimli Button düğmesine tıklandığında Page3.xaml sayfasına geçilmesini sağlamak için ilgili olay metodunda aşağıdaki kodları yazmak yeterli olacaktır.

private void btnKontrolSayfasi_Click(object sender, RoutedEventArgs e)
{
    this.NavigationService.Navigate(new Page3());
}

NavigationService referansını Button bileşenine ait Click olay metodu içerisine yakaladıktan sonra Navigate fonksiyonuna parametre oalrak Page3 tipinin yeni bir nesne örneği verilmektedir. Böylece Page3 nesne örneği oluşturulup NavigationWindow içerisinde gösterilmesi sağlanmaktadır. Buna göre örneğin çalışma zamanındaki durumu aşağıdaki Flash görselindeki gibi olacaktır.

İstenirse çalışma zamanında yeni bir NavigationWindow oluşturulması ve bir sayfanın bu örnek üzerinde açılması sağlanabilir. Aşağıdaki örnek kod parçası ile, düğmeye basıldığı zaman Page3 isimli sayfanın yeni bir pencere içerisinde açılması sağlanmaktadır.

private void btnKontrolSayfasi_Click(object sender, RoutedEventArgs e)
{
    NavigationWindow nvgWnd = new NavigationWindow();
    Page3 pg3 = new Page3();
    pg3.WindowTitle = "Yeni Pencerede Açılan Doğrulama Sayfası";
    pg3.Title = "Doğrulama(Yeni)";
    pg3.WindowWidth = 300;
    pg3.WindowHeight = 240;
    //pg3.ShowsNavigationUI = false;
    nvgWnd.Content = pg3;
    nvgWnd.Show();
}

İlk olarak yeni bir NavigationWindow nesne örneği oluşturulmaktadır. Sonrasında ise bu pencerede gösterilmek istenen sayfa örneklenir. Sayfanın WindowTilte, Title, WindowWidth, WindowHeight gibi özellikleri set edildikten sonra NavigationWindow nesne örneğinin Content özelliğine oluşturulan Page3 nesne örneği atanır. Son olarak Show metodu ile yeni pencerenin gösterilmesi sağlanmaktadır. Buradaki yorum satırı açılırsa eğer, yeni pencerede navigasyon kontrollerinin gösterilmemesi sağlanmış olur. Uygulamayı bu şekilde test ettiğimizde aşağıdaki Flash görselindeki etkiler görülecektir.

Sayfalar istenirse Frame' ler içerisinde gösterilebilirler. Böylece bir NavigationWindow içerisinde birden fazla Frame kullanılarak birden fazla sayfanın aynı anda gösterilmesi sağlanabilir. Örneğin, içerisinde harici bir web sitesini, uygulamanın kendisi, yardım dökümanını barındıracak şekilde bir pencere geliştirilebilir. Frame tipi kendi içerisinde çeşitli elementler barındırabilmektedir ancak genel kullanım amacı Page tiplerini taşımasıdır. Bu tanımlamalar Frame tipinin webdeki kullanım şeklini tam olarak andırdığınıda göstermektedir. Konuyu daha net anlayabilmek için bir örnek üzerinden ilerlemekte fayda olacağı kanısındayım. Bu amaçla projeye aşağıdaki XAML içeriğine sahip yeni bir pencere(Window) eklediğimizi düşünelim.

<Window x:Class="PageKullanimi.FrameKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Frame Kullanimi" Height="400" Width="300">
    <Grid Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Frame NavigationUIVisibility="Automatic" Grid.Row="0" BorderBrush="Black" BorderThickness="3" Source="Page3.xaml"/>
        <Frame Grid.Row="1" BorderBrush="Gold" BorderThickness="3" Source="Page2.xaml"/>
    </Grid>
</Window>

Frame kullanımını kolaylaştırmak için pencere içerisinde iki satırdan oluşan bir Grid kontrolü konulmuştur. Bu amaçla Grid kontrolü içerisine RowDefinition elementleri ile iki satır eklenmiştir. Hangi Frame' in hangi satırda gösterileceğini belirlemek için Grid.Row elementlerinden yararlanılmaktadır. Her Frame elementinin Source niteliklerine(attribute) atanan değerler ile içlerinde gösterecekleri sayfalar belirlenmektedir. NavigationUIVisibility niteliğine atanan değer ile navigasyon kontrolünün Frame içerisinde gösterilip gösterilmeyeceği veya örnekteki gibi bunun otomatik olarak set edilip edilmeyeceği belirlenebilir. Söz konusu Frame' ler dikkat edilecek olursa Page yerine bir Window elementi içerisinde kullanılmıştır. Örnek yürütüldüğünde aşağıdaki Flash animasyonunda olduğu gibi bir çalışma zamanı sonucu elde edilir.

Flash animasyonundan görülebileceği gibi ilk etapta navigasyon kontrolleri gösterilmemektedir. Ancak Frame' ler içerisinde yer alan sayfalar üzerindeki kontroller yardımıyla hareket edildikten sonra navigasyon kontrolleri görülmektedir. Tabi istenirse NavigationUIVisibility özelliğine Visible değeri atanarak navigasyon kontrolünün başlangıçta çıkmasıda sağlanabilir. Yukarıdaki örnekte meydana gelen işlemler aşağıdaki grafikle daha net anlaşılabilir.

İstenirse sayfalar başka sayfaların içerisinde de kullanılabilir. Böyle bir durumda iç içe sayfalar(Nested-Page) söz konusu olmaktadır. Aşağıdaki XAML içeriğinde bir nested-page tasarımı örneği görülmektedir.

XAML içeriği;

<Page x:Class="PageKullanimi.NestedPageKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Nested Page Orneği" WindowWidth="300" WindowHeight="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="89" />
            <RowDefinition Height="211" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0">
            <Label Foreground="Red" FontSize="14" FontWeight="Bold">Arama</Label>
            <TextBox Text="Aranacak kelimeyi giriniz"/>
            <Button Name="btnAra" Content="Ara" FontSize="12" FontWeight="Bold" Background="Gold" Foreground="Black"/>
        </StackPanel>
        <Frame NavigationUIVisibility="Automatic" Grid.Row="1" BorderBrush="Black" BorderThickness="3" Source="Page3.xaml"/>
    </Grid>
</Page>

Dikkat edilecek olursa Page elementi içerisinde normal elementler dışında birde Frame elementi kullanılmaktadır. Bu element içerisinde Source niteliği ile gösterilecek olan sayfa belirtilmektedir. Böylece çalışan sayfa içerisinde birbirlerine bağlı başka sayfalarında gösterilebilmesi sağlanmaktadır. Olayı aşağıdaki grafik ile daha net kavrayabiliriz.

Uygulamanın çalışma zamanındaki görüntüsü aşağıdaki Flash animasyonunda olduğu gibidir.

Burada dikkat çekici noktalardan biriside Frame içerisinde sayfalar arasında gezinirken navigasyon kontrolünün sayfanın üst kısmında çıkmış olmasıdır. Bu durumda Frame' lerin kendi navigasyon çubuklarına sahip olması sağlanabilir. Bunun için Frame elementinin JournalOwnerShip adı verilen niteliğinden yararlanılır. Bu nitelik Automatic, OwnsJournal ve UsesParentJournal olmak üzere üç farklı değerden birisini alabilir. OwnsJournal değeri seçildiğinde aşağıdaki ekran görüntüsünde olduğu gibi Frame' in navigasyon kontrolü kendi içerisinde çıkacak, sayfanın kendi navigasyon kontrolü ise en üst tarafta yer alacaktır. Doğal olarak bunlar birbirleriylede karışmayacak şekilde çalışmaktadır.

UsesParentJournal değerinin seçilmesi halinde ise, Frame elementinin navigasyon kontrolü bu örneğin ilk versiyonunda olduğu gibi üst tarafta yer alacaktır. Aslında burada analiz edilmesi gereken bir durum daha vardır. Buda sayfanın kendisinin navigasyon işlemlerine sahip olması halidir. Yani aşağıdaki grafikteki gibi bir senaryo olduğunu düşünelim.

Dikkat edileceği üzere Frame içerisinden başka iki sayfaya daha geçilebilmektedir. Diğer taraftan sayfanın kendiside(yani Frame' ide içeren Page) Page2.xaml sayfasına geçiş yapabilmektedir. Bu tip bir durumda UsesParentJournal değerinin seçilmesi özellikle XBAP uygulamalarında oldukça işe yarayacaktır. Söz konusu kullanımda sayfalar çok fazla parçalandığından zaman zaman takibin zorlaştığıda görülmektedir. Dolayısıyla Nested-Page tekniğinin bir alternatif olarak bilinmesinde ve uygun vakkalar bulunduğunda ele alınmasında yarar vardır.

Yazımızın içerisinde belirttiğimiz gibi navigasyon işlemlerini manuel olarak kod tarafından da yapabiliriz. Burada önemli olan nokta NavigationService özelliği ile elde edilecek referans olacaktır.Bununla birlikte bir sayfanın navigasyon süreci içerisinde talep edilmesi halinde neler olduğunun bilinmesinde de yarar vardır. Bir başka deyişle, navigasyon sürecindeki yaşam döngüsünü bilmekte yarar vardır. Temelde süreç bir sayfanın talep edilmesi ile başlar. Bu talep işlemi basit bir Hyperlink kontrolü ile olabileceği gibi, NavigationService sınıfının Navigate metodu ilede olabilir. Sonrasında talep edilen sayfanın yeri tespit edilir. Tahmin edileceği üzere burada bir sorun olması halinde çalışma zamanı istisnası oluşacaktır. Sayfa yeri tespit edildikten sonra bilgileri getirilir. Örneğin talep edilen sayfa bir internet sayfası ise download işlemi gerçekleşir. Bu arada eğer sayfanın indirilmesi sırasında ilişkili kaynaklar var ise bunlarında getirilmeside söz konusudur. Takip eden adımda sayfa için bir ayrıştırma(parsing) işlemi uygulanır ve sayfanın nesne ağacı(Object Tree) oluşturulur. Bu işlemi takiben sayfaya ait Initialized ve Loaded olaylarıda sırasıyla tetiklenir. Son aşamada sayfa Render işlemine tabi tutulur ve gösterilir. Bu noktada eğer parçalı navigasyon(Fragment Navigation) işlemi yapılmışsa ilgili kontrole gidilmesi sağlanır. Aşağıdaki temsili grafik bu işleyişi kısaca özetlemektedir.

Birde bu süreç içerisinde tetiklenen bazı olaylar söz konusudur. Bu olaylar Application, Frame, NavigationWindow yada NavigationService' in kendisi tarafından ele alınabilir. Daha çok tercih edilen, söz konusu olayları Application nesnesi seviyesinde ele almaktır. Böylece uygulama içerisinde yer alabilecek tüm sayfalar için ortak bir noktada olayların kontrol edilebilmesi sağlanmış olur. Burada bahsedilen olaylar Navigating, Navigated, NavigationProgress, LoadCompleted, FragmentNavigation, NavigationStopped ve NavigationFailed' dır. Bu olayların işleyiş sırası aşağıdaki şekildeki gibidir.

İlk olarak süreç bir sayfanın talep edilmesi ile başlar. Sonrasında ise ilgili olaylar tetiklenir. Aşağıdaki tabloda söz konusu olaylar ile ilişkili bilgiler verilmektedir.

Olay(Event) Olay Hakkında Kısa Bilgi
Navigating Yeni bir navigasyon talebi geldiğinde çalışan olaydır.
Navigated Navigasyon başlamıştır ve talep edilen sayfa ile ilgili bilgiler gelmektedir. Ancak sayfanın tamamı henüz gelmemiştir.
NavigationProgress Bu olaya ilişkin metod yazıldığında, sayfanın yüklenmesi tamamlanıncaya kadar bilgilendirme yapılması sağlanabilir. Örneğin sayfanın yüzde olarak kaçının tamamlandığı bilgisi gösterilebilir. Aslında bu olay klasik windows programlamadaki BackgroundWorker kontrolünün ProgressChange olayınınkine benzer bir görev üstlenmektedir. NavigationProgress olayı, talep edilen sayfa ile ilgili her 1Kb bilgi geldiğinde tetiklenmektedir.
LoadCompleted Talep edilen sayfanın ayrıştırma(Parse) işlemi tamamlanmıştır. Ancak ilgili sayfanın Initialized ve Loaded olayları henüz çalışmamıştır.
FragmentNavigation Eğer parçalı navigasyon(Fragment Navigation) söz konusu ise bu olay tetiklenir. Bu olay ilgili kontrole doğru gidilirken çalışmaktadır.
NavigationStopped NavigationService sınıfının static StopLoading metoduna yapılan çağrı sonucu tetiklenen olaydır. Bazı durumlarda uzun süren navigasyon işlemlerinde(örneğin bir web sayfası talebinde) kullanıcı tarafından işlemin durdurulması istenebilir. Böyle bir durumda StopLoading metoduna başvurulması halinde bu olay tetiklenmektedir. (StopLoading' i çalışma zamanında çağırmak son derece kolaydır. Nitekim WPF mimarisinde navigasyon işlemleri asenkron olarak yürütülmektedir.)
NavigationFailed Navigasyon işlemi sırasında bir hata oluşursa tetiklenen olaydır. Bu olay içerisinde oluşan hata ile ilişkili detaylı bilgiye ulaşılabilir ve söz konusu bilgiler örneğin loglama amacı ile kayıt altına alınabilir yada kullanıcı farklı bir şekilde yönlendirilerek uygulamanın sağlıklı bir şekilde çalışması sağlanabilir.

Doğal olarak bu olayların tetiklenmesi belirli koşullara bağlıdır. Söz gelimi bir sayfaya doğru navigasyon işleminin başlatılabilmesi için Hyperlink kontrolünün NavigateUri özelliğinden yada NavigationService sınıfının Navigate metodundan yararlanılır. Navigate metodunun aşırı yüklenmiş olan versiyonları kullanılarak yukarıda bahsi geçen olaylara veri aktarımıda söz konusu olabilmektedir. Söz gelimi navigasyonun başladığı sürenin gönderilip ilgili olaylarda güncel süre ile arasındaki fark hesap edilerek işlemlerin ne kadar sürdüğü ortalama olarak tespit edilebililir. Yada navigasyon işleminin başlatıldığı sayfanın içerisinde bulunduğu NavigationWindow referansının gönderilmesi sağlanarak, Application seviyesindeki olaylarda ele alınması sağlanabilir.

Burada manuel olarak navigasyon işlemleri adına ismini sıkça duyduğumuz NavigationService sınıfının başka güçlü metodlarıda vardır. Söz gelimi navigasyon sürecinde yer alan sayfalar arasında ileri veya geri doğru gidilebilmesini sağlamak amacıyla GoBack ve GoForward metodlarından yararlanılabilir. Yanlız bu metodlar yardımıyla hareket edilirken herhangibir sebeple hedef sayfa bulunamassa çalışma zamanında InvalidOperationException alınır. Bunun önüne geçmek içinse CanGoBack ve CanGoForward özelliklerinin değerlerine bakılabilir. Bu konu ile ilişkili bir örneği denemenizi şiddetle tavsiye ederim. (Yazımızı çok fazla uzatacağından bu tarz bir örneği şu an için geliştirmeyeceğiz. Bu konuyu başka bir makalede detaylı bir şekilde incelemeye çalışcağız.)

XBAP Hakkında

Gelelim sayfalar ile ilgili bir diğer konuya. Yazımızın başında sayfa bazlı(Page-Based) uygulamaların geliştirilmesinde kullanılan modellerden bahsederken XBAP(Xaml Browser Applications) tekniğinede değinmiştik. XBAP uygulamalarında sayfalar tarayıcı pencerede(browser) gösterilmektedir. Şu an için Internet Exploere 6.0 ve üstü ile Mozilla Firefox' un son sürümünde XBAP uygulamaları çalıştırılabilmektedir. (En azından makaleyi yazdığım sıralarda bu tarayıcılar ile yapılan testlere göre...)

Varsayılan olarak XBAP uygulamalarında bazı kısıtlamalar(Restrictions) vardır. Bunlar tahmin edileceği gibi Code Access Security kurallarının uygulanmasınında bir sonucudur. Örneğin XBAP uygulamalarında istemci taraflı olaraktan dosyalara yazmak, veritabanlarına bağlanmak, registry işlemleri yapmak yada başka pencereleri tarayıcı penceresi içerisinde popup şekline açmak varsayılan olarak yasaklanmıştır. Bu tarz ihtiyaçların olması durumunda WPF uygulamasını normal bir windows uygulaması olarak yazmak ve ClickOnce gibi bir dağıtım modeli ile yaymak daha doğru bir yaklaşımdır. Yinede kısıtlama(Restriction) ayarları ile proje özellikleri üzerinden oynanarak istenirse söz konusu işlemlerin yapılması sağlanabilir.

XBAP uygulamaları varsayılan olarak istemci bilgisayardaki tarayıcı penceresine ait tampona(Cache) atılırlar. Bir başka deyişle ilk talep edildiklerinde istemci bilgisayara indirilirler(Download). Burada bir yükleme(install) işlemi söz konusu değildir. Fakat yine proje ayaları ile oynayaraktan uygulamanın istemciye install edilmesi sağlanabilir. Diğer taraftan install edilmemesinin avantajları vardır. Öyleki, uygulamadaki değişiklikler istemci tarafından otomatik olarak algılanıp son sürümün herhangibir sorgu penceresine gerek kalmadan indirilmesi ve çalıştırılması söz konusudur.

Çok doğal olarak XBAP uygulamalarının çalışabilmesi için indirildikleri bilgisayar sisteminde Microsoft .Net Framework 3.0 sürümünün yüklü olması gerekmektedir. Eğer yüklü değilse ilgili uygulama çalıştırıldığında .Net Framework 3.0 indirilmeye çalışılacaktır.

Visual Studio 2008 Beta 2 ile bir XBAP uygulaması oluşturmak son derece basittir. Tek yapılması gereken proje şablonlarından aşağıdaki resimde görüldüğü gibi WPF Browser Application öğesini seçmektir.

Bu işlemin sonucunda içerisinde varsayılan olarak bir sayfa(Page) içeren bir uygulama oluşturulur. Test olması amacıyla uygulamaya ikinci bir sayfa(Page) daha ekleyip bu sayfalar arasında Hyperlink ile geçişler yapmaya çalıştığımızı düşünelim. Bu amaçla Page1.xaml ve Page2.xaml içeriklerinin aşağıdaki gibi olduğunu düşünelim.

Page1.xaml;

<Page x:Class="MerhabaXBAP.Page1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Ana Sayfa" WindowTitle="Giriş Sayfası" Background="LightGray" WindowHeight="250" WindowWidth="250" VerticalAlignment="Top" HorizontalAlignment="Left">
    <Grid>
        <Label Height="33" HorizontalAlignment="Left" Name="label1" VerticalAlignment="Top" Width="120" FontSize="14" FontWeight="Bold">
            <Hyperlink NavigateUri="Page2.xaml">Sayfa 2</Hyperlink>
        </Label>
    </Grid>
</Page>

Page2.xaml;

<Page x:Class="MerhabaXBAP.Page2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="İkinci Sayfa" WindowTitle="Sayfa 2" Background="Gold" Loaded="Page_Loaded" VerticalAlignment="Top" HorizontalAlignment="Left" WindowHeight="250" WindowWidth="250">
    <Grid>
        <Label Height="35" Name="label1" VerticalAlignment="Top" FontSize="14" FontWeight="Bold" HorizontalAlignment="Left" Width="120">
            <Hyperlink NavigateUri="Page1.xaml">Sayfa 1</Hyperlink>
        </Label>
    </Grid>
</Page>

Uygulama çalıştırıldığında sayfaların WindowTitle niteliklerine atanan değerler otomatik olarak Tab'  da ve tarayıcı penceresinin başlık kısmında görülecektir. Diğer taraftan burada navigasyon kontrolleri oluşturulmayacak, otomatik olarak tarayıcının navigasyon kontrolleri işin içerisine dahil edilecektir. Şimdi güvenlik ile ilişkili kısıtlamaları test etmek amacıyla Page2.xaml sayfasının Loaded olayını yükleyelim ve içerisine aşağıdaki kodları yazdığımızı düşünelim.

Page2.xaml.cs;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;

namespace MerhabaXBAP
{
    public partial class Page2 : Page
    {
        public Page2()
        {
            InitializeComponent();
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            using (FileStream stream = new FileStream("C:\\Test.txt", FileMode.Append, FileAccess.Write))
            {
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.WriteLine(DateTime.Now.ToString() + " ek bilgi");
                }
            }
        }
    }
}

Bu durumda uygulamayı çalıştırıp Page2 isimli sayfaya geçmeye çalıştığımızda SecurityException tipinden bir istisna aldığımızı görürüz. Aşağıdaki ekran görüntüsünde bu durum ifade edilmektedir.

Daha öncedende belirttiğimiz gibi bu istisnanın sebebi varsayılan Code Access Security ayarlarıdır. Hata mesajındanda görüleceği gibi FileIOPermission yetkisi olmadığından söz konusu kodlar çalışmayıp istisna vermiştir. Ancak proje özelliklerine girip ilgili izni(Permission) aşağıdaki şekilde görüldüğü gibi vererek dosyaya yazma işleminin gerçekleştirilebilmesi sağlanabilir.

Bu ayarlardan sonra uygulama tekrardan test edilirse Test.txt isimli dosyanın C dizini altında oluşturulduğu ve içerisine bilgi yazıldığı görülür. Buradaki seçeneklerden bir diğeri olan This is a full trust application seçilerek, istenirse tüm kısıtlamaların ortadan kaldırılması ve istemci tarafından yürütülebilmesi sağlanabilir. Ancak bu XBAP uygulamalarının birincil amacı değildir.

Bir XBAP uygulamasını dağıtırken(Deploye) uygulamaya ait exe, manifesto ve xbap dosyalarının üçünün birden istemci uygulamaya kopyalanması yeterli bir seçenektir. Elbette söz konusu dosyalar bir intranet sisteminde ortak bir yola(Path) konularaktanda istemcilerin buradan başlatma işlemini gerçekleştirmesi sağlanabilir. Sonuçta varsayılan olarak uygulama, istemci bilgisayarın tarayıcı programının kullandığın ön bellek alanına indirilecektir. Yukarıda geliştirimiş olduğumuz XBAP örneğine baktığımızda Debug klasörü altında aşağıdaki şekilde görülen dosyaların oluşturulduğunu görürüz.

Burada yer alan dosyalardan exe uzantılı olan, XBAP uygulamasının derlenmiş halini içermektedir. Bir başka deyişle assembly' ın kendisidir e ILDASM, Metadata, manifesto gibi bilgileri içermektedir. Ne varki söz konusu exe tek başına çalıştırılabilen bir dosya değildir. Bu sebepten üzerinde çift tıklandığında hiç bir etkileşim olmayacaktır. Asıl çalıştırıcı dosya XBAP uzantılı olan XML dosyasıdır. Bu dosyanın içeriğinde, uygulamanın giriş noktasını işaret eden bilgi vardır. Açıkçası bu dosya çalıştırıldığında istemci bilgisayardaki varsayılan tarayıcı uygulaması devreye girecek ve exe yürütülmeye başlanacaktır. XBAP uzantılı dosyası içerisinde aynı zamanda program yazılırken üretilen dijital imzada yer almaktadır. Bu imza özellikle güncelleme işlemlerinde önem kazanmaktadır. (Elbette istenirse uygulamanın proje özelliklerine gidilip Signing kısmında başka bir imza üretilip kullanılabilir.) Manifesto dosyasında ise, uygulamanın çalışması için gerekli olan diğer programlara ait bilgiler yer almaktadır. Söz gelimi uygulamanın çalışması için gerekli .Net Framework versiyonu, bağlı olan diğer assmebly(Class Library gibi)' lar veya uygulamadaki kodların neler yapabileceğini belirten izin(Permission) yetkileri gibi bilgiler yer almaktadır. Ne varki dağıtım işlemi özellikle ilgili uygulamalarda güncellemeler yapıldığı takdirde tekrarlanmak zorunda olabilir. Bu vakka ClickOnce ile ilgilide olduğundan ve makalemizin konusunu aştığından bu yazı dizimizde ele alınmayacaktır. Umuyorumki ilerleyen zamanlarda ele alabiliriz.

Bu makalemizde WPF(Windows Presentation Foundation) ile birlikte hayatımıza giren yeni kavramlardan birisi olan sayfa-tabanlı(Page-based) uygulamaları tanımaya çalıştık. Konunun detayları için sizlere 1000 sayfalık Pro WPF: Windows Presentation Foundation in .NET 3.0 kitabı tavsiye ederim.

Böylece geldik uzun bir makalemizin daha sonuna. Bu cümleye kadar sabırla okuduğunu için son teşekkür ederim. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Tags:   ,
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF Temel Animasyon İşlemleri - 2

Pazartesi, 1 Ekim 2007 19:35 by bsenyurt
Bir önceki makalemizde Windows Prensetation Foundation(WPF) uygulamalarında animasyon işlemlerinin temel animasyon tipleri(Basic Animation Types) yardımıyla nasıl gerçekleştirilebileceğini incelemeye başlamıştık. Bu makalemizde animasyon işlemleri üzerindeki yönetimin biraz daha fazla olmasını sağlamak için farklı teknikleri göz önüne alıyor olacağız. Her zaman olduğu gibi konuyu daha iyi kavrayabilmek adına örnekler üzerinden ilerlemekte fayda olduğu kanısındayım. Dilerseniz hiç vakit kaybetmeden ilk örneğimiz ile başalayım. Hatırlanacağı üzere, WPF uygulamalarında bileşenlerin rotasyon işlemleri için RotateTransform, ScaleTransform ve benzeri tiplerin kullanıldığını görmüştük. Bu tip transformasyon işlemlerini animasyon tipleri ile birlikte kullanmak isteyebiliriz. Söz gelimi bir Button kontrolünün 3 saniye içerisinde kendi ekseninde 360 derece dönmesini ve bu sırada boyutlarınında 2 katına çıkarak tekrardan eski haline dönmesini istediğimizi düşünelim. Bu tip bir animasyon işleminin Buttton kontrolünün üzerine mouse ile gelindiğinde başlatılmasını ve 3 saniyelik zaman dilimi içerisinde mouse ile kontrolün terk edilmesi halinde de durmasınıda sağlayabiliriz. Burada temel animasyon tiplerinden olan DoubleAnimation oldukça işe yarayacaktır. Herşeyden önce animasyon tipinin RotateTransform ve ScaleTransform tiplerindeki uygun özellikleri kontrol edecek şekilde ayarlanması gerekir. Bu senaryoyu gerçekleştirmek için XAML içeriği aşağıda verilen bir pencere(Window) oluşturulması yeterli olacaktır.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.RotateAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Rotate ve Sacle Animasyon" Height="220" Width="289">
    <Grid>
        <Button Name="btnMerhaba" Width="75" Height="40" Content="Merhaba" Background="Gold" Foreground="Black" FontSize="14" FontWeight="Bold">
            <Button.LayoutTransform>
                <TransformGroup>
                    <RotateTransform x:Name="RTrans" Angle="0"/>
                    <ScaleTransform x:Name="STrans" CenterX="0" CenterY="0" ScaleX="1" ScaleY="1"/>
                </TransformGroup>
            </Button.LayoutTransform>
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard Name="strBrd">
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="RTrans" Storyboard.TargetProperty="Angle" To="360" Duration="0:0:3"/>
                                <DoubleAnimation Storyboard.TargetName="STrans" Storyboard.TargetProperty="ScaleX" To="2.5" Duration="0:0:1.5"/>
                                <DoubleAnimation Storyboard.TargetName="STrans" Storyboard.TargetProperty="ScaleY" To="2.5" Duration="0:0:1.5"/>
                                <DoubleAnimation Storyboard.TargetName="STrans" Storyboard.TargetProperty="ScaleX" To="1" Duration="0:0:3"/>
                                <DoubleAnimation Storyboard.TargetName="STrans" Storyboard.TargetProperty="ScaleY" To="1" Duration="0:0:3"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
                <EventTrigger RoutedEvent="Button.MouseLeave">

                    <EventTrigger.Actions>
                        <StopStoryboard BeginStoryboardName="strBrd"/>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Button.Triggers>
        </Button>
    </Grid>
</Window>

Burada dikkat edilmesi gereken noktalardan birisi, RotateTransform ve ScaleTransform elementlerinin DoubleAnimation tiplerinde kullanılabilmesi için x:Name nitelikleri ile isimlendirilmiş olmalarıdır. Diğer taraftan RotateTransform, Button bileşenin verilen açı kadar döndürülmesini sağlamaktadır. Buna göre ilk DoubleAnimation elementi 3 saniyelik zaman çizgisi(TimeLine) içerisinde açının değerinin 360 derece olmasını sağlamaktadır. Sonrasında gelen dört DoubleAnimation tipi ise ilk 1.5 saniyelik zaman dilimi içerisinde ScaleTransform elementinin ScaleX ve ScaleY değerlerini önce iki buçuk katına çıkarmakta sonrasında ise tekrar eski haline döndürmektedir. Söz konusu animasyon işlemleri MouseEnter olayı tetiklendiğinde başlatılmaktadır. Diğer taraftan MouseLeave olayı tetiklendiğinde, bir başka deyişle mouse ile Button kontrolü üzerinden çıkıldığında animasyon işlemi durdurulmaktadır. Durdurma işlemi için StopStroyboard elementi kullanılır. Bu elementin hangi animasyon işlemini durduracağını belirtmek için BeginStroyboardName niteliğine(attribute) ilgili değerin atanması gerekir. Örneğimizde bu değer, BeginStroyboard elementindeki Name niteliğinin değeri olan strBrd' dir. Buna göre istersek birden fazla Storyboard' un olduğu bir senaryoda Name niteliğinden yararlanarak hangisinin durdurulacağını, duraksatılacağını(Pause) yada çıkartılacağını(Remove) belirleyebiliriz.

NOT: WPF(Windows Presentation Foundation) mimarisinde farklı Storyboard tipleri vardır.

  • BeginStoryboard : Storyboard' un başlatılmasını sağlar.
  • PauseStoryboard : Storyboard duraksatılır.
  • ResumeStoryboard : Duraksatılan storyboard kaldığı yerden devam eder.
  • RemoveStoryboard : Storyboard kaldırılır.
  • SetStoryboardSpeedRatio : Storyboard içerisinde animasyonların hız oranı değiştirilebilir.
  • SkipStoryboardToFill : Eğer Stroyboard içerisinde tanımlanmış bir Fill periyodu varsa animasyonun otomatik olarak buraya atlaması sağlanır.

Örneği yürüttüğümüzde çalışma zamanında(Run-time) aşağıdaki Flash görselinde yer alan etkileri izleyebiliriz. (Flash dosyasının boyutu 264 Kb olduğundan yüklenmesi zaman alabilir. Dosyanın boyutunun küçük olması için kalitesi düşürülmüştür. Bu nedenle kitap animasyon hareketi sırasında iz bırakmaktadır. Gerçek uygulamada elbetteki böyle bir iz yoktur.)

Benzer işlemleri kod tarafında yapmakta oldukça kolaydır. Bunun için aşağıdaki kodlara ve XAML içeriğine sahip pencereyi ele alabiliriz.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaRotateAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kodla Rotate Animasyon" Height="250" Width="325">
    <Grid>
        <Button Name="btnMerhaba" Width="75" Height="40" Content="Merhaba" Background="Black" Foreground="Gold" FontSize="14" FontWeight="Bold">
            <Button.LayoutTransform>
                <TransformGroup>
                    <RotateTransform x:Name="RTrans" Angle="0"/>
                    <ScaleTransform x:Name="STrans" CenterX="0" CenterY="0" ScaleX="1" ScaleY="1"/>
                </TransformGroup>
            </Button.LayoutTransform>
        </Button>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation; // Animasyon tiplerinin yer aldığı isim alanıdır(Namespace)

namespace AnimasyonIslemleri
{
    public partial class KodlaRotateAnimasyon : Window
    {
        private void AnimasyonuOlustur()
        {
            // Storyboard oluşturulur
            Storyboard strBrd = new Storyboard();

            // Birinci DoubleAnimation tipi oluşturulur.
            // İlk parametre To değeridir ve burada 360 dereceyi işaret etmektedir. İkinci parametre ise Duration değeridir.
            DoubleAnimation dblAni1 = new DoubleAnimation(360, new Duration(TimeSpan.FromSeconds(3)));
            // Animasyon tipinin RTrans adına sahip elementin AngleProperty özelliğine uygulanacağı belirtilir.
            Storyboard.SetTargetName(dblAni1, "RTrans");
            Storyboard.SetTargetProperty(dblAni1, new PropertyPath(RotateTransform.AngleProperty));
            // Animasyon tipi Storyboard' a eklenir.
            strBrd.Children.Add(dblAni1);

            // İkinci DoubleAnimation tipi oluşturulur.
            // İlk parametre To değeridir ve burada 2.5 katını işaret etmektedir. İkinci parametre ise Duration değeridir. Buna göre zaman çizgisinin ilk 1.5 saniyesi içerisinde Button bileşeninin ScaleX değeri 2.5 kat artmaktadır.
            DoubleAnimation dblAni2 = new DoubleAnimation(2.5, new Duration(TimeSpan.FromSeconds(1.5)));
            // Animayonun STrans isimli ScaleTransform elementi içerisindeki ScaleX özelliğine uygulanacağı belirtilir.
            Storyboard.SetTargetName(dblAni2, "STrans");
            Storyboard.SetTargetProperty(dblAni2, new PropertyPath(ScaleTransform.ScaleXProperty));
            // Animasyon tipi Storyboard' a eklenir.
            strBrd.Children.Add(dblAni2);

            // Üçüncü DoubleAnimation tipi oluşturulur.
            // İlk parametre To değeridir ve burada 2.5 katını işaret etmektedir. İkinci parametre ise Duration değeridir. Buna göre zaman çizgisinin ilk 1.5 saniyesi içerisinde Button bileşeninin ScaleY değeri 2.5 kat artmaktadır.
            DoubleAnimation dblAni3 = new DoubleAnimation(2.5, new Duration(TimeSpan.FromSeconds(1.5)));
            // Animayonun STrans isimli ScaleTransform elementi içerisindeki ScaleY özelliğine uygulanacağı belirtilir.
            Storyboard.SetTargetName(dblAni3, "STrans");
            Storyboard.SetTargetProperty(dblAni3, new PropertyPath(ScaleTransform.ScaleYProperty));
            // Animasyon tipi Storyboard' a eklenir.
            strBrd.Children.Add(dblAni3);

            // Zaman çizgisinin 3ncü saniyesine gelindiğinde Button kontrolünün ScaleX değeri ilk haline döner.
            DoubleAnimation dblAni4 = new DoubleAnimation(1, new Duration(TimeSpan.FromSeconds(3)));
            Storyboard.SetTargetName(dblAni4, "STrans");
            Storyboard.SetTargetProperty(dblAni4, new PropertyPath(ScaleTransform.ScaleXProperty));
            // Animasyon tipi Storyboard' a eklenir.
            strBrd.Children.Add(dblAni4);

            // Zaman çizgisinin 3ncü saniyesine gelindiğinde Button kontrolünün ScaleY değeri ilk haline döner.
            DoubleAnimation dblAni5 = new DoubleAnimation(1, new Duration(TimeSpan.FromSeconds(3)));
            Storyboard.SetTargetName(dblAni5, "STrans");
            Storyboard.SetTargetProperty(dblAni5, new PropertyPath(ScaleTransform.ScaleYProperty));
            // Animasyon tipi Storyboard' a eklenir.
            strBrd.Children.Add(dblAni5);
   
            // Mouse ile Button alanı üzerinde gelindiğinde animasyon başlatılır.
            btnMerhaba.MouseEnter += delegate(object sender, MouseEventArgs e)
                                                    {
                                                        /* İkinci parametre Storyboard' un kontrol edilebileceğini gösterir. Bir başka deyişle programatik olarak animasyonun durdurulması, duraksatılması ve benzeri işlemlerin yapılabilmesi sağlanır. */
                                                        strBrd.Begin(this,true);
                                                    };

            btnMerhaba.MouseLeave += delegate(object sender, MouseEventArgs e)
                                                    {   
                                                        // Storyboard' un başlattığı animasyon çıkartılır. Dolayısıyla Button açısal konum ve büyüklük olarak ilk değerlerine döner.
                                                        strBrd.Stop(this);
                                                    };
        }

        public KodlaRotateAnimasyon()
        {
            InitializeComponent();
            AnimasyonuOlustur();
        }
    }
}

Kod yapısında dikkat edilmesi gereken önemli noktalardan birisi, MouseLeave olayının gerçekleşmesi halinde var olan amimasyonun durdurulması(Stop) işleminin Storyboard nesne örneğine ait Stop metodu ile gerçekleştirimiş olmasıdır. Lakin burada Stop metodunun işe yarayabilmesi için animasyon Begin metodu ile başlatılırken ikinci parametrenin true olarak verilmesi şarttır. Bu parametrenin true olarak verilmesi halinde yanlızca Stop metodu değil, Pause, Resume, Remove gibi yönetsel fonksiyonelliklerinde kullanılabilir hale gelmesi sağlanmaktadır.

Animasyon işlemlerinde zaman çizgileri (Timeline) içerisinde yer alan KeyFrame' lerin yönetilmesi ve bu sayede daha güçlü hareketlerin sağlanabilmeside mümkündür. Bu amaçla WPF yapısı içerisine 3 temel KeyFrame tipi konulmuştur. Bunlar LinearDoubleKeyFrame, DiscreteDoubleKeyFrame, SplineDoubleKeyFrame tipleridir. Söz konusu tiplerin ne şekilde kullanıldıklarını örnekler ile incelediğimizde konuyu daha rahat kavrayabiliriz. Sıradaki örneğimizde LinearDoubleKeyFrame tipini kullanıyor olacağız. Bu amaçla ilk olarak aşağıdaki XAML çıktısını içeren bir pencere (Window) hazırladığımızı düşünelim.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.MerhabaKeyFrameAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Merhaba Key Frame Animasyon" Height="300" Width="300">
    <Canvas>
        <Rectangle Name="Dortgen" Canvas.Top="0" Canvas.Left ="100" Height="125" Width="93">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="Rectangle.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard RepeatBehavior="Forever" AutoReverse="True">
                                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Dortgen" Storyboard.TargetProperty="(Canvas.Top)">
                                    <LinearDoubleKeyFrame Value="160" KeyTime="0:0:2"/>
                                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:4"/>
                                    <LinearDoubleKeyFrame Value="100" KeyTime="0:0:6"/>
                                    <LinearDoubleKeyFrame Value="40" KeyTime="0:0:8"/>
                                    <LinearDoubleKeyFrame Value="80" KeyTime="0:0:10"/>
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>
    </Canvas>
</Window>

Dikkat edileceği üzere örnekte kullanılan LienarDoubleKeyFrame elementleri DoubleAnimationUsingKeyFrames elementi içerisinde yer almaktadır. Buna göre KeyFrame tiplerinin TipAdıUsingKeyFrames notasyonu ile tanımlanmış tipler içerisinde olması gerektiği ortadadır. Peki burada yer alan LinearDoubleKeyFrame elementleri ne işe yaramaktadır?

Value özelliği ile tahmin edileceği gibi DoubleAnimationUsingKeyFrames elementinde belirtilen kontrol ve ilgili özelliğin yeni değeri belirlenir. KeyTime niteliği ilede, özelliğin zaman çizelgesi içerisinde belirtilen anda değerini alması sağlanır. Bir başka deyişle örnekte kullanılan Dortgen isimli Rectangle elementinin pencerenin(Window) üst kısmından olan uzaklığı, zaman çizelgesi(timeline) içerisinde Frame noktalarında farklı değerlere set edilmektedir. Aslında durumu daha iyi kavramak için pencerenin çalışma zamanındaki çıktısına bakmakta fayda vardır. Aşağıdaki Flash animasyonunda bu durum işaret edilmektedir.(Flash dosyasının boyutu 322 Kb olduğundan yüklenmesi zaman alabilir. Dosyanın boyutunun küçük olması için kalitesi düşürülmüştür. Bu nedenle kitap animasyon hareketi sırasında iz bırakmaktadır. Gerçek uygulamada elbetteki böyle bir iz yoktur.)

Görüldüğü gibi dörtgenimiz zaman çizelgesinin 2nci saniyesinde pencerenin üst kenarının 160 piksel, 4ncü saniyesinde tekrardan 0 piksel, 6ncı saniyesinde 100 piksel, 8nci saniyesinde 40 piksel, 10ncu saniyesinde ise 80 piksel uzağına gelmektedir. Sonrasında ise bu animasyon hareketini tersine doğru yapmaktadır. Bunun için Storyboard elementinin AutoReverse özelliğine true değerinin atanması yeterli olmaktadır. Durumu aşağıdaki şekil ile daha net bir şekilde anlayabiliriz.

Aynı örneği kod yardımıylada gerçekleştirebiliriz. Bunun için aşağıdaki XAML içeriğine ve kodlarına sahip bir pencere(Window) geliştirmemiz yeterli olacaktır.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaMerhabaKeyFrameAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kodla Merhaba KeyFrame Animasyon" Height="241" Width="218">
    <Canvas x:Name="Bolge">
        <Rectangle Name="Dortgen" Canvas.Top="0" Canvas.Left="65" Height="65" Width="55">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
        </Rectangle>
    </Canvas>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace AnimasyonIslemleri
{
    public partial class KodlaMerhabaKeyFrameAnimasyon : Window
    {
        private void AnimasyonuHazirla()
        {
            // DoubleAnimationKeyFrames nesnesi örneklenir.
            DoubleAnimationUsingKeyFrames dblKeyFrames = new DoubleAnimationUsingKeyFrames();
            // Animasyon tipi, kontrol ve özelliğini ilişkilendirecek olan StoryBoard oluşturulur.
            Storyboard strBrd = new Storyboard();
            // Animasyon sona erdiğinde aynı rotadan geriye doğru dönmesi için AutoReverse özelliğine true değeri atanır
            strBrd.AutoReverse = true;
            // Animasyonun sürekli olması için RepeatBehavior özelliğine Forever değeri atanır.
            strBrd.RepeatBehavior = RepeatBehavior.Forever;
            // Hedef kontrol olarak Rectangle tipinden olan Dortgen seçilir
            Storyboard.SetTargetName(dblKeyFrames, "Dortgen");
            // Animasyon değerlerinin uygulanacağı özellikl olarak Canvas' ın Top özelliği seçilir.
            Storyboard.SetTargetProperty(dblKeyFrames, new PropertyPath(Canvas.TopProperty));
            // KeyFrame değerleri belirlenir.
            // İlk 1 saniyede Canvas dikeylemesine 120 pikselinci uzaklığa gelecektir
            dblKeyFrames.KeyFrames.Add(new LinearDoubleKeyFrame(120, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 1))));
            // 2nci saniyede Canvas dikeylemesine 0 pikselinci uzaklığa gelecektir
            dblKeyFrames.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 2))));
            // 3ncü saniyede Canvas dikeylemesine 100 pikselinci uzaklığa gelecektir
            dblKeyFrames.KeyFrames.Add(new LinearDoubleKeyFrame(100, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 3))));
            // 4ncü saniyede Canvas dikeylemesine 50 pikselinci uzaklığa gelecektir
            dblKeyFrames.KeyFrames.Add(new LinearDoubleKeyFrame(50, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 4))));
            // 5nci saniyede Canvas dikeylemesine 75 pikselinci uzaklığa gelecektir.
            dblKeyFrames.KeyFrames.Add(new LinearDoubleKeyFrame(75, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 5))));
            // 6nci saniyede Canvas dikeylemesine 25 pikselinci uzaklığa gelecektir.
            dblKeyFrames.KeyFrames.Add(new LinearDoubleKeyFrame(25, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 6))));
            // Animasyon tipi StoryBoard nesnesine eklenir
            strBrd.Children.Add(dblKeyFrames);
            // Animasyonun, Mouse ile Dortgen in üzerine gelindiğinde başlaması sağlanır.
            Dortgen.MouseEnter += delegate(object sender, MouseEventArgs e)
                                                {
                                                    strBrd.Begin(this);
                                                };
        }
        public KodlaMerhabaKeyFrameAnimasyon()
        {
            InitializeComponent();
            AnimasyonuHazirla();
        }
    }
}

Kod tarafında dikkat edilmesi gereken noktalardan birisi LinearDoubleKeyFrame nesne örneklerinin DoubleAnimationUsingKeyFrames tipinin KeyFrames koleksiyonunda tutuluyor olmasıdır. KeyFrames özelliği üzerinden çağırılan Add metodu ile ilgili nesnelerin koleksiyona eklenmesi sağlanır. Oldukça eğlenceli değil mi? Gelin LinearDoubleKeyKeyFrame nesnelerinin kullanıldığı başka bir örnek ile devam edelim. Bu yeni örnekte bir öncekinden farklı olarak Canvas' ın Top ve Left özelliklerini rastegele oranlarda değiştiriyor olacağız. İşte örnek XAML içeriği ve kodlarımız.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaKeyFrameAnimasyon2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="KodlaKeyFrameAnimasyon2" Height="400" Width="400" Background="LightBlue">
    <Canvas x:Name="alan">
        <Rectangle Name="dortgenim" Canvas.Top="0" Canvas.Left="0" Height="65" Width="55">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
        </Rectangle>
    </Canvas>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace AnimasyonIslemleri
{
    public partial class KodlaKeyFrameAnimasyon2 : Window
    {
        Random rnd = new Random();
        Storyboard strBrd = new Storyboard();

        public void AnimasyonuYurut()
        {
            double randomT = rnd.Next(1, 250);
            double randomL = rnd.Next(1, 250);
            Title=String.Format("İlk nokta {0}:{1} İkinci Nokta {2}:{3}",randomT.ToString(),randomL.ToString(),(randomT - 25).ToString(),(randomL - 25).ToString());

            strBrd.AutoReverse = false;
            strBrd.RepeatBehavior = new RepeatBehavior(1);

            DoubleAnimationUsingKeyFrames keyFrm1 = new DoubleAnimationUsingKeyFrames();
            keyFrm1.KeyFrames.Add(new LinearDoubleKeyFrame(randomT, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 3))));
            keyFrm1.KeyFrames.Add(new LinearDoubleKeyFrame(randomT-25, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 5))));

            Storyboard.SetTargetName(keyFrm1, "dortgenim");
            Storyboard.SetTargetProperty(keyFrm1, new PropertyPath(Canvas.TopProperty));
            strBrd.Children.Add(keyFrm1);
   
            DoubleAnimationUsingKeyFrames keyFrm2 = new DoubleAnimationUsingKeyFrames();
            keyFrm2.KeyFrames.Add(new LinearDoubleKeyFrame(randomL, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 3))));
            keyFrm2.KeyFrames.Add(new LinearDoubleKeyFrame(randomL-25, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 5))));
       
            Storyboard.SetTargetName(keyFrm2, "dortgenim");
            Storyboard.SetTargetProperty(keyFrm2, new PropertyPath(Canvas.LeftProperty));
            strBrd.Children.Add(keyFrm2);
       
            strBrd.Begin(this);
        }
   
        void dortgenim_MouseEnter(object sender, MouseEventArgs e)
        {
            AnimasyonuYurut();
        }

        public KodlaKeyFrameAnimasyon2()
        {
            InitializeComponent();
            dortgenim.MouseEnter += new MouseEventHandler(dortgenim_MouseEnter);
        }
    }
}

Tahmin edileceği üzere dortgenin Top ve Left özelliklerini animayon içerisinde kullanmak istediğimizden iki farklı DoubleAnimationUsingKeyFrames nesne örneği kullanılması gerekmektedir. Bu nesne örnekleride kendi içlerinde farklı LinearDoubleKeyFrame nesnelerine sahiptir. Örneğin çalışma zamanındaki(run-time) çıktısı aşağıdaki Flash görselindeki gibi olacaktır. (Flash dosyasının boyutu 454 Kb olduğundan yüklenmesi zaman alabilir. Dosyanın boyutunun küçük olması için kalitesi düşürülmüştür. Bu nedenle kitap animasyon hareketi sırasında iz bırakmaktadır. Gerçek uygulamada elbetteki böyle bir iz yoktur.)

LinearDoubleKeyFrame tipi söz konusu değerlere ulaşılırken akıcı bir görselliğin olmasını sağlar. Bunun dışında DiscreteDoubleKeyFrame tipi kullanılaraktan nesnenin belirtilen animasyonda bir sonraki değerine sıçrayarak geçmesi sağlanabilir. Aslında bu durumu anlamanın en iyi yolu örnek bir senaryo üzerinde ilerlemektir. Bu amaçla aşağıdaki XAML çıktısına sahip bir pencere(Window) tasarladığımızı düşünelim.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.DiscreteKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="DiscreteDoubleKeyFrame ile Animasyon" Height="400" Width="400">
    <Canvas>
        <Rectangle Name="Dortgen" Canvas.Top="0" Canvas.Left="0" Height="75" Width="55">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="Window.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Dortgen" Storyboard.TargetProperty="(Canvas.Top)">
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:1" Value="150"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:2" Value="30"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:3" Value="120"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="60"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:5" Value="90"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:6" Value="75"/>
                                </DoubleAnimationUsingKeyFrames>
                                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Dortgen" Storyboard.TargetProperty="(Canvas.Left)">
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:1.5" Value="150"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:2.5" Value="30"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:3.5" Value="120"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:4.5" Value="60"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:5.5" Value="90"/>
                                    <DiscreteDoubleKeyFrame KeyTime="0:0:6.5" Value="75"/>
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>
    </Canvas>
</Window>

Örnekte iki adet DoubleAnimationUsingKeyFrames elementi kullanılmıştır. Bunlardan birisi Canvas tipinin Top özelliği üzerinde diğeri ise Left özelliği üzerinden animasyon işlemlerinin yürütülmesini olanaklı kılmaktadır. Her biri kendi içerisinde birden fazla DiscreteDoubleKeyFrame tipi içermektedir. DiscreteDoubleKeyFrame elementleri KeyTime niteliğinde belirtilen anda, animasyon uygulanan kontrolün ilgili özelliğinin value niteliği ile belirtilen değerde olmasını sağlamaktadır. Söz gelimi animasyondaki zaman çizelgesinin 2nci saniyesinde Top değeri 30 piksel, 1.5nci saniyesinde Left değeri 150 piksel olacak şekilde belirlenmektedir. Daha öncede belirtildiği gibi kontrolün söz konusu koordinat noktalarına ulaşması sırasında akıcı bir efekt yerine sıçrama(Jump) efekti uygulanmaktadır. Her halde bir yerden bir yere ışınlanmanın WPF' cesinin bu olduğunu söyleyebiliriz. Bu durumu aşağıdaki Flash görselinden daha net bir şekilde analiz edebiliriz.

Elbetteki aynı örneği kod yardımıylada gerçekleştirebiliriz. Bunun için aşağıdaki XAML içeriğine ve kodlara sahip bir pencere hazırlamamız yeterli olacaktır.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaDiscreteKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kod ile Discrete Kullanimi" Height="300" Width="300">
    <Canvas>
        <Rectangle Name="Dortgen" Canvas.Top="0" Canvas.Left="0" Height="75" Width="55">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
        </Rectangle>
    </Canvas>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation; // Animasyon tiplerini içeren isim alanıdır

namespace AnimasyonIslemleri
{
    public partial class KodlaDiscreteKullanimi : Window
    {
        private void AnimasyonuHazirla()
        {
            // Storyboard nesnesi örneklenir
            Storyboard strBrd = new Storyboard();
            // Animasyonun sürekli tekrar edeceği belirtilir
            strBrd.RepeatBehavior = RepeatBehavior.Forever;
            // Animasyonun zaman çizgisini tamamladığında aynı yoldan geriye dönmesi sağlanır
            strBrd.AutoReverse = true;

            // Dortgen nesnesinde Canvas' ın Top özelliği üzerinde Frame bazlı animasyon yapılmasını sağlayacak şekilde DoubleAnimationUsingKeyFrames nesnesi örneklenir
            DoubleAnimationUsingKeyFrames dblA1 = new DoubleAnimationUsingKeyFrames();
            Storyboard.SetTargetName(dblA1, "Dortgen");
            Storyboard.SetTargetProperty(dblA1, new PropertyPath(Canvas.TopProperty));
   
            // Frame' lerdeki Top özelliğinin değerleri belirtilir. Bunun için DiscreteDoubleKeyFrame nesneleri örneklenerek KeyFrames koleksiyonuna eklenir.
            dblA1.KeyFrames.Add(new DiscreteDoubleKeyFrame(150, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 1))));
            dblA1.KeyFrames.Add(new DiscreteDoubleKeyFrame(30, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 2))));
            dblA1.KeyFrames.Add(new DiscreteDoubleKeyFrame(120, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 3))));
            dblA1.KeyFrames.Add(new DiscreteDoubleKeyFrame(60, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 4))));
            dblA1.KeyFrames.Add(new DiscreteDoubleKeyFrame(90, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 5))));
            dblA1.KeyFrames.Add(new DiscreteDoubleKeyFrame(75, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 6))));
            // DoubleAnimationUsingKeyFrames nesne örneği Storyboard' a alt element olarak eklenir.
            strBrd.Children.Add(dblA1);
   
            DoubleAnimationUsingKeyFrames dblA2 = new DoubleAnimationUsingKeyFrames();
            Storyboard.SetTargetName(dblA2, "Dortgen");
            Storyboard.SetTargetProperty(dblA2, new PropertyPath(Canvas.LeftProperty));
           
            dblA2.KeyFrames.Add(new DiscreteDoubleKeyFrame(150, KeyTime.FromTimeSpan(new TimeSpan(0,0, 0, 1,500))));
            dblA2.KeyFrames.Add(new DiscreteDoubleKeyFrame(30, KeyTime.FromTimeSpan(new TimeSpan(0, 0,0, 2,500))));
            dblA2.KeyFrames.Add(new DiscreteDoubleKeyFrame(120, KeyTime.FromTimeSpan(new TimeSpan(0, 0,0, 3,500))));
            dblA2.KeyFrames.Add(new DiscreteDoubleKeyFrame(60, KeyTime.FromTimeSpan(new TimeSpan(0, 0,0, 4,500))));
            dblA2.KeyFrames.Add(new DiscreteDoubleKeyFrame(90, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0,5,500))));
            dblA2.KeyFrames.Add(new DiscreteDoubleKeyFrame(75, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0,6,500))));
            strBrd.Children.Add(dblA2);
   
            // Pencere yüklendikten sonra
            Loaded += delegate(object sender, RoutedEventArgs e)
                            {
                                // Animasyon başlatılır
                                strBrd.Begin(this);
                            };
       
        }
        public KodlaDiscreteKullanimi()
        {
            InitializeComponent();
            AnimasyonuHazirla();
        }
    }
}

Frame tabanlı animasyon işlemlerinde LinearDoubleKeyFrame ve DiscreteDoubleKeyFrame tipleri dışında kullanılabilen bir diğer sınıfta SplineDoubleKeyFrame' dir. Bu tip sayesinde, animasyon işlemi sırasındaki ileri ve geri yönlü hareketlerde, hızlanma ve yavaşlama oranları daha net bir şekilde belirlenebilir. Söz konusu hızlanma verilerini belirlemek için, sadece 0.0 ile 1.0 arasında değerler alabilen KeySpline özelliğinden yararlanılır. Aşağıdaki XAML içeriğine sahip örnekte SplienDoubleKeyFrame tipi kullanılmaktadır.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.SplineKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Spline Kullanimi" Height="422" Width="181">
    <Canvas>
        <Rectangle Name="Dortgen" Canvas.Top="0" Canvas.Left="0" Height="75" Width="55">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="Window.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Dortgen" Storyboard.TargetProperty="(Canvas.Top)">
                                    <SplineDoubleKeyFrame KeyTime="0:0:3" Value="200" KeySpline="0.3,0.0 0.9,0.3"/>
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>
    </Canvas>
</Window>

Bu örneğin çalışma zamanındaki çıktısı aşağıdaki Flash animasyonunda olduğu gibidir.(Flash dosyasının boyutu 298 Kb olduğundan yüklenmesi zaman alabilir.)

Dikkat edilecek olursa düşey düzlemde hareket eden resim 3 saniyelik zaman dilimi içerisinde pencerenin üst noktasından 200 piksel aşağıya gelmektedir. Ancak bu hareketi sırasında KeySpline niteliğinde belirtilen değerler nedeni ile düşey olarak hızlanarak ve ters yönde de yavaşlayarak ilerlemektedir. KeySpline niteliğindeki değerler ile oynayarak daha farklı etkiler de elde edilebilir. Hatta bu değerler ile oynayarak farklı etkilerin nasıl oluşturulabileceğini incelemenizi şiddetle tavsiye ederim. Aynı örnek kod yardımıylada aşağıdaki gibi geliştirilebilir.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaSplineKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kod ile Spline Kullanimi" Height="397" Width="181">
    <Canvas>
        <Rectangle Name="Dortgen" Canvas.Top="0" Canvas.Left="0" Height="75" Width="55">
            <Rectangle.Fill>
                <ImageBrush ImageSource="Kitap.jpg"/>
            </Rectangle.Fill>
        </Rectangle>
    </Canvas>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation; // Animasyon tiplerini içeren isim alanıdır

namespace AnimasyonIslemleri
{
    public partial class KodlaSplineKullanimi : Window
    {
        private void AnimasyonuHazirla()
        {
            // Storyboard nesnesi örneklenir, animasyonun tekrarlı ve geriye dönüşlü olacak şekilde çalışacağı belirlenir
            Storyboard strBrd = new Storyboard();
            strBrd.RepeatBehavior = RepeatBehavior.Forever;
            strBrd.AutoReverse = true;

            DoubleAnimationUsingKeyFrames dblA = new DoubleAnimationUsingKeyFrames();
   
            // SplineDoubleKeyFrame nesnesi eklenir. Üçüncü parametre ile hızlanma ve yavaşlama değerleri belirlenir.
            dblA.KeyFrames.Add(new SplineDoubleKeyFrame(200, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 3)), new KeySpline(0.3, 0, 0.9, 0.3)));
            // Animasyonun Dortgen isimli nesneye ve içinde bulunduğu Canvas' ın Top özelliğine uygulanacağı belirlenir
            Storyboard.SetTargetName(dblA, "Dortgen");
            Storyboard.SetTargetProperty(dblA, new PropertyPath(Canvas.TopProperty));
   
            // Animasyon nesnesi Storyboard' a alt element olarak eklenir
            strBrd.Children.Add(dblA);
       
            // Pencerenin yüklenmesi tamamlandıktan sonra
            Loaded += delegate(object sender, RoutedEventArgs e)
                            {
                                // Animasyon başlatılır
                                strBrd.Begin(this);
                            };
        }
        public KodlaSplineKullanimi()
        {   
            InitializeComponent();
            AnimasyonuHazirla();
        }
    }
}

Bu örneklerdende anlaşılacağı üzere farklı veri tipleri için yazılmış olan Frame bazlı animasyon tipleri olduğunu söyleyebiliriz. Geliştirilen örnekler double tipi ile çalışan özellikler üzerinde Frame bazlı animasyonlar geliştirilebilmesi için DoubleAnimationUsingKeyFrames tipini kullanmaktadır. Ancak bu tip dışında ColorAnimationUsingKeyFrames, PointAnimationUsingKeyFrames, ByteAnimationUsingKeyFrames vb... sınıflarda yer almaktadır. Tüm bu Frame bazlı animasyon tipleri LinearTipAdıKeyFrame, DiscreteTipAdıKeyFrame ve SplineTipAdıKeyFrame sınıflarına ait nesne örnekleri ile çalışabilmektedir. Tabiki bu durum her veri türü için geçerli değildir. Söz gelimi StringAnimationKeyFrames tipi sadece DiscreteStringKeyFrame sınıfına ait nesne örneklerini taşıyabilir. Dolayısıyla enterpoasyon(Interpolation) efektleri gerçekleştirilemez.

Sıradaki örnekte bir Button kontrolünün Text özelliğinin içeriğini Frame bazlı animasyon işleminde nasıl ele alabileceğimizi inceleyeceğiz. Bu amaçla XAML içeriği aşağıdaki gibi olan bir pencere tasarladığımızı düşünelim.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.StringKeyFrameAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="StringKeyFrameAnimasyon" Height="149" Width="278">
    <Grid>
        <Button Name="btnMerhaba" Width="140" Height="40" FontSize="12" FontWeight="Bold">
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard RepeatBehavior="Forever">
                                <StringAnimationUsingKeyFrames Storyboard.TargetName="btnMerhaba" Storyboard.TargetProperty="Content" Duration="0:0:6">
                                <!-- Eğer Duration süresi belirtilmesse DiscreteStringKeyFrame elementlerinden sonuncusu gösterilmiyor. Sonuncusununda gösterilmesi için Duration süresi son DiscreteStringKeyFrame' inkinden fazla verilmelidir.-->
                                    <DiscreteStringKeyFrame Value="WPF" KeyTime="0:0:1"/>
                                    <DiscreteStringKeyFrame Value="ile" KeyTime="0:0:2"/>
                                    <DiscreteStringKeyFrame Value="Temel" KeyTime="0:0:3"/>
                                    <DiscreteStringKeyFrame Value="Animasyon" KeyTime="0:0:4"/>
                                    <DiscreteStringKeyFrame Value="İşlemleri" KeyTime="0:0:5"/>
                                </StringAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Button.Triggers>
        </Button>
    </Grid>
</Window>

Bu örneğin çalışma zamanındaki işleyişi aşağıdaki Flash görselinde olduğu gibidir. DiscreteStringKeyFrame elementlerine ait Value nitelikleri tahmin edileceği üzere, animasyonun bağlandığı kontrolün ilgili özelliğindeki string bilginin t anında ne olacağını belirtmekte kullanılmaktadır. KeyTime niteliğine verilen değerler ile söz konusu t anları belirtilir.

Elbetteki bu örnekteki animasyon etkisi kod yardımıylada geliştirilebilir. Aşağıdaki kod parçalarında bu duruma bir örnek verilmektedir.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaStringKeyFrameAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="KodlaStringKeyFrameAnimasyon" Height="144" Width="300">
    <Grid>
        <Button Name="btnMerhaba" Width="140" Height="40" FontSize="12" FontWeight="Bold"/>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace AnimasyonIslemleri
{
    public partial class KodlaStringKeyFrameAnimasyon : Window
    {
        string slogan = "CSharp az keyword ile çok iş yapmamızı sağlayan bir dildir";

        private void AnimasyonuHazirla()
        {
            Storyboard strBrd = new Storyboard();
            strBrd.RepeatBehavior = RepeatBehavior.Forever;
   
            StringAnimationUsingKeyFrames strA = new StringAnimationUsingKeyFrames();
            string[] kelimeler = slogan.Split(' ');
            strBrd.Duration = TimeSpan.FromSeconds(kelimeler.Length + 1);
            for(int i=1;i<=kelimeler.Length;i++)
            {
                strA.KeyFrames.Add(new DiscreteStringKeyFrame(kelimeler[i-1],KeyTime.FromTimeSpan(TimeSpan.FromSeconds(i))));
            }
            Storyboard.SetTargetName(strA, "btnMerhaba");
            Storyboard.SetTargetProperty(strA, new PropertyPath(Button.ContentProperty));
            strBrd.Children.Add(strA);
            btnMerhaba.MouseEnter += delegate(object sender, MouseEventArgs e)
                                                    {
                                                        strBrd.Begin(this);
                                                    };
        }
        public KodlaStringKeyFrameAnimasyon()
        {
            InitializeComponent();
            AnimasyonuHazirla();
        }
    }
}

Bu kez, bir slogan cümle içerisindeki kelimeler boşluk karakterine göre Split metodu ile ayrıştırılmakta ve elde edilen string dizisindeki her bir kelime için DiscreteStringKeyFrame temelli, saniyede bir artan animasyon uygulanmaktadır. Bu örnek kod parçasının çalışma zamanındaki çıktısı ise aşağıdaki Flash görselindeki gibi olacaktır.

Makalemizde son olarak bir bileşenin belirli bir rota üzerinde hareket etmesinin animasyon teknikleri ile nasıl gerçekleştirilebileceğini incelemeye çalışacağız. Önceki makalelerimizden hatırlayacağınız gibi, Path ve PathGeometry tiplerini kullanarak birbirlerine bağlı yayların çizilmesi mümkündür. Bu tip bir elementin oluşturacağı rotayı takip eden bir bileşen söz konusu olduğunda, animasyon tiplerinden TipAdıAnimationUsingPath isimlendirmesi ile tanımlananlardan yararlanbiliriz. Örneğin bir PathGeometry ile tanımlanan rotada hareket edecek bir nesnenin içinde bulunduğu Canvas' ın Top ve Left özellikleri söz konusu ise DoubleAnimationUsingPath tipini kullanabiliriz. Aşağıdaki XAML çıktısında bu durum örneklenmeye çalışılmaktadır.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.PathFrameKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="PathFrameKullanimi" Height="300" Width="300">
    <Window.Resources>
        <PathGeometry x:Key="YolGeometrisi">
            <PathFigure>
                <BezierSegment Point1="0,0" Point2="10,120" Point3="70,40"/>
                <ArcSegment Point="150,125" SweepDirection="Clockwise" Size="50,50" IsLargeArc="True" RotationAngle="60"/>
                <ArcSegment Point="200,200" SweepDirection="Counterclockwise" Size="50,50" IsLargeArc="True" RotationAngle="60"/>
            </PathFigure>
        </PathGeometry>
    </Window.Resources>
    <Canvas>
        <Path Data="{StaticResource YolGeometrisi}" Stroke="LightGray" StrokeThickness="2"/>
            <Rectangle Height="65" Name="Dortgen" Width="55">
                <Rectangle.Fill>
                    <ImageBrush ImageSource="Kitap.jpg" />
                </Rectangle.Fill>
                <Rectangle.Triggers>
                    <EventTrigger RoutedEvent="Rectangle.MouseEnter">
                        <BeginStoryboard>
                            <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                                <DoubleAnimationUsingPath Storyboard.TargetName="Dortgen" Storyboard.TargetProperty="(Canvas.Top)" PathGeometry="{StaticResource YolGeometrisi}" Source="Y" Duration="0:0:6" />
                                <DoubleAnimationUsingPath Storyboard.TargetName="Dortgen" Storyboard.TargetProperty="(Canvas.Left)" PathGeometry="{StaticResource YolGeometrisi}" Source="X" Duration="0:0:6" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Rectangle.Triggers>
            </Rectangle>
        </Canvas>
</Window>

Söz konusu örnekte, Storyboard elementi içerisinde DoubleAnimationUsingPath alt elementi kullanılmaktadır. Bu elementlerden iki adet tanımlanmasının sebebi, Dortgen isimli nesnenin Top ve Left özelliklerinin ayrı ayrı değiştirilmesi gerekliliğidir. DoubleAnimationUsingPath tipinde yer alan niteliklerden Storyboard.TargetName ile animasyonun hangi kontrole uygulanacağı belirtilir ki bu örnekte söz konusu kontrol Dortgen isimli Rectangle dır. Storyboard.TargetProperty nitelikleri ise Rectangle' ın içerisinde bulunduğu Canvas' ın Top ve Left özelliklerinin değerleri üzerinde hareketlendirmeler yapılacağını belirtir. PathGeometry nitelikleri tahmin edileceği üzere rota bilgisini içeren pencere kaynağını (Windows Resource) işaret etmektedir. MSDN kaynakları Resource olarak yapılan bu tip tanımlamaların animasyon işlemlerinde performans yönünden olumlu katkı yapacağını belirtmektedir. Static kaynak olarak tanımlanan YolGeometrisi isimli veri kümesi yardımıyla Path elementinin rota bilgiside verilmektedir. Bunun içinde Path elementinin Data niteliğine değer ataması yapılmıştır. Bu örneğin çalışma zamanındaki çıktısı aşağıdaki Flash animasyonunda olduğu gibidir. (Flash dosyasının boyutu 352 kb olduğundan yüklenmesi zaman alabilir.)

Görüldüğü gibi WPF mimarisinde gerek XAML tarafında gerekse kod tarafında animasyon işlemleri için son derece etkili yollar kullanılabilmektedir. Bence burada önemli olan noktalardan biriside işaretleme dili içerisinde bu tip gelişmiş fonksiyonelliklerin geliştirilebilmesidir. İlerleyen makalelerimizde WPF ile ilgili farklı konularada değinmeye çalışacağız. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF Temel Animasyon İşlemleri - 1

Çarşamba, 26 Eylül 2007 19:03 by bsenyurt
Windows uygulamalarında kullanıcılara görsel bir şölen sunmak için animasyon işlemlerinden yararlanılır. Ne varki söz konusu animasyonları gerçekleştirebilmek amacıyla zorlu olan bazı süreçlerin aşılması gerekmektedir. Burada aslında, GDI+(Graphics Device Interface) alt yapısını(Infrastructure) daha etkili kullanmak adına daha çok ve daha karmaşık kodların yazılması, form üzerinde animasyonun zaman çizelgesine göre Timer bileşenleri ile Interval özelliği ve Tick olayı(event) gibi üyelerlerle uğraşılması ve hatta yeri geldiğinde ana iş parçasının(Main Thread) bozabileceği durumların önüne geçmek için özel tedbirler alınması söz konusudur. Hele birde işin içerisine üç boyutlu(3D) nesneler girdiğinde ve bunların üzerinde kullanıcı etkileşimli olayların tetiklenebilmesi de istendiğinde programıcının geliştirme süreci hem zorlaşacak hemde uzayacaktır. Tabiki günümüzde bu tip işlemleri gerçekleştirmek için ele alına pek çok kolaylaştırıcı yazılım vardır. Ancak bir olaya .Net Windows programcısı olarak bakıyor olacağız. Durumun zorluğunu daha net anlayabilmek adına, basit olarak form üzerinde yer alan bir Button kontrolünün yükseklik(Height) ve genişlik(Width) özelliklerinin sürekli olarak belirli oranlarda büyüyüp küçüldüğünü düşünelim. Bunu Windows programlamada nasıl yapmamız gerektiğini hayal edin. Her şeyden önce sürenin söz konusu olduğu bu senaryoda bir Timer bileşenin var olması ve Tick olayının uygun şekilde ele alınması(Handle) gerekmektedir. Oysaki WPF (Windows Presentation Foundation) mimarisinde bu ve benzeri animasyon işlemleri çok daha kolay, hızlı ve güçlü bir şekilde gerçekleştirilebilmektedir. İşte bu makalemizde söz konusu durumu incemeleye başlayacak ve WPF mimarisinde temel animasyon işlemlerine değineceğiz. WPF mimarisinin söz konusu animasyon geliştirme işlerini nasıl kolaylaştırdığını görmek adına aşağıda XAML(eXtensible Application Markup Language) çıktısı aşağıda görülen örnek ile hızlı bir şekilde başlamakta yarar olacağı kanısındayım.

<Window x:Class="AnimasyonIslemleri.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Merhaba Animasyon (DoubleAnimation)" Height="300" Width="300">
    <Grid>
        <Button Name="btnMerhaba" Width="100" Height="40" Content="Merhaba" FontSize="12" Foreground="Blue" FontWeight="Bold">
            <Button.Background>
                <LinearGradientBrush>
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Color="Red" Offset="0.5"/>
                        <GradientStop Color="White" Offset="0.5"/>
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Button.Background>
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="btnMerhaba" Storyboard.TargetProperty="Width" By="5" RepeatBehavior="Forever" From="100" To="150" Duration="0:0:1" AutoReverse="True"/>
                                <DoubleAnimation Storyboard.TargetName="btnMerhaba" Storyboard.TargetProperty="Height" From="40" To="60" Duration="0:0:1" RepeatBehavior="Forever" AutoReverse="True"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Button.Triggers>
        </Button>
    </Grid>
</Window>

XAML içeriğinde yer alan elementler ve nitelikleri(attributes) hakkında konuşmadan önce bu pencerenin çalışma zamanına bir bakalım. Aşağıdaki Flash animasyonunda çalışma zamanındaki(run-time) durum görülmektedir. Dikkat edilecek olursa pencere(Window) üzerindeki Button kontrolünün Width ve Height özelliklerinde genişlemeler olmaktadır. Dikkat çekici bir diğer noktada animasyonun, mouse ile Button kontrolü üzerine gelindiğinde başladığıdır. Bununla birlikte söz konusu aminasyon sürekli olarak devam etmektedir.

Gelelim XAML içeriğinde neler yapıldığına. Daha önceki makalelerimizden de hatırlanacağı gibi Button bileşeninin arka planında LinearGradientBrush elementi ile geçişli renklerden oluşan bir dolgu efekti kullanılmaktadır. Button elementinin içerisinde animasyon özelliklerinin belirtildiği yer Button.Triggers alt elementi ile başlamaktadır. Hemen altta yer alan EventTrigger elementinin RoutedEvent niteliğine atanan değer ile animasyonun hangi olayın tetiklenmesi sonrası başlatılacağı belirtilmektedir. Örneğimizde MouseEnter olayı bu amaçla kullanılmaktadır. Burada olay adı belirtilirken tipAdı.Özellikadı (örneğin Button.MouseEnter gibi) şeklinde olmasına dikkat edilmelidir. Aksi durumda bir istisna(Exception) alınması muhtemeldir. Dolayısıyla temel anlamda animasyonların başlatılabilmesi için olaylardan yararlanılabildiğini ve bunların ilgili kontrollere birer tetikleyici(Trigger) ile bağlandığını söyleyebiliriz.

NOT : Bir kontrolün animasyon işlemlerinde kullanılabilmesi için IAnimatable arayüzünü uyarlamış(Implement) olması gerekmektedir. Söz gelimi Button bileşeninin sınıf diagramındaki(Class Diagram) görüntüsüne bakıldığında IAnimatable arayüzünü uyarladığı açık bir şekilde görülebilir.

Animasyon işlemlerinde önemli olan noktalardan birisi kontrolün veya şeklin hangi özelliğinin değerinin, ne şekilde değiştirileceğidir. İlk örnekte Button kontrolünün Width ve Height özelliklerinin değerlerinin her ikisi birden değiştirilmektedir. Peki WPF mimarisinde hangi kontrolün hangi özelliğinin değerinin, ne şekilde değiştirileceği nasıl belirlenmektedir? İşte bu noktada devreye StoryBoard tipi ve alt elementleri girmektedir.

NOT : StoryBoard tipi hangi kontrolün hangi özelliği üzerinde nasıl bir animasyon uygulanacağını belirleyip bunu başlatmak ve belirli bir tetikleyici(Trigger) olaya bağlamak için kullanılan önemli bir sınıftır.

Örnek XAML içeriğinde dikkat edilecek olursa StoryBoard elementi altında iki adet DoubleAnimation elementi tanımlanmıştır.

NOT : WPF içerisinde yer alan temel animasyon tipleri(Basic Animation Types) uygun veri türleri ile çalışacak şekilde tasarlanmışlardır. Bu tiplerin adları DoubleAnimation, ColorAnimation, PointAnimation, VectorAnimation örneklerinde olduğu gibi TürAdıAnimation kelimelerinden oluşmaktadır. Animasyon tipleride IAnimatable arayüzünü uyarlamaktadır. Söz gelimi örnekte kullanılan DoubleAnimation tipinin sınıf diagramındaki(Class Diagram) görüntüsü aşağıdaki gibidir.

Ayrıca söz konusu animasyon tipleri Animatable isimli abstract sınıfdan da türemektedir. Buna göre ilgili abstract sınıfı ve arayüzü kullanarak kendi animasyon tiplerimizde yazabiliriz. WPF içerisinde temel animasyon tipleri dışında yer alan animasyon tipleride vardır. ColorAnimationUsingKeyFrames gibi. Bu tipleri ilerleyen makalelerimizde incelemeye çalışıyor olacağız.

Burada animasyon tipinin kullanımı ve özellikleri hakkında biraz konuşmakta yarar vardır. Örnekte yer alan Button kontrolünün animasyon etkisi oluşturan özellikleri Width ve Height üyeleridir. Bu özellikler Double tipinden değerler alabilmektedir. Bu nedenle StoryBoard içerisinde tanımlanan animasyon tipleri DoubleAnimation tipindendir. Dolayısıyla animasyon işleminde kullanılacak olan kontrol özelliği için, uygun olan animasyon tipinin seçilmesi gerekmektedir. DoubleAnimation tipi içerisinde oldukça önemli nitelik(attribute) tanımlamaları yer almaktadır. Bunlardan StoryBoard.TargetName niteliği ile animasyonun uygulanacağı kontrol seçilmektedir ki örnekte bu btnMerhaba isimli Button kontrolüdür. Diğer taraftan StoryBoard.TargetProperty niteliği ile kontrolün hangi özelliğinin animasyon işleminde kullanılacağı belirtilir. From niteliği ile hedef özelliğin hangi değerden başlayacağı, To niteliği ilede hedef özelliğin ilgili değerinin hangi değere kadar gelebileceği belirtilir.

Bir başka deyişle Button kontrolünün genişliği 100 değerinden 150 değerine gelecekse From niteliğine 100, To niteliğine ise 150 değerleri atanır. Burada istenirse By niteliği kullanılarak değerin artış oranıda belirtilebilir. Çok doğal olarak animasyonun ne kadar süre devam edeceği bilgisinin de belirtilmesi gerekmektedir. Bu amaçla DoubleAnimation tipinin Duration niteliği kullanılmaktadır. Duration niteliği temelde saat:dakika:saniye şeklinde bir değer almaktadır. Buna göre örnek XAML içeriğinde yer alan 0:0:1 değeri animasyondaki zaman çizgisinin (Timeline) 1 saniye olacağını belirtmektedir. RepeatBehavior niteliği ile animasyonun tekrar sayısı belirtilir. Örnekte verilen Forever değerine göre animasyon sürekli olarak devam edecektir. Son olarak AutoReverse değerine true atanarak, 1 saniyelik animasyon sona erdiğinde işlemlerin tekrar başlangıç haline tersten bir animasyon ile gitmesi sağlanmaktadır. Bu nedenle Flash animasyonunda görüldüğü gibi kontrol büyüdükten sonra aynı animasyon etkisi ile tersten küçülmüş ve ilk haline gelmiştir.

İlk örnekte söz konusu animasyon işlemleri için XAML elementlerinden ve niteliklerinden(attributes) yararlanılmıştır. Çok doğal olarak kod ile dinamik olarak aynı etkiler oluşturulabilir. Sıradaki örneğimizde kod tarafında animasyon işlemlerinin nasıl yapılabileceğini incelemeye çalışacağız. Bu amaçla penceremize(Window) ait XAML içeriğini ve kodlarını aşağıdaki gibi hazırladığımızı düşünelim.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaMerhabaAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kodla Merhaba Animasyon" Height="300" Width="300">
    <Grid>
        <Button Name="btnMerhaba" Width="100" Height="50" Foreground="Gold" Background="Black" Content="Merhaba" FontSize="14" FontWeight="Bold">
        </Button>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation; // Animasyon tiplerini barındıran isim alanıdır

namespace AnimasyonIslemleri
{
    public partial class KodlaMerhabaAnimasyon : Window
    {
        private void AnimasyonuHazirla()
        {
            // Button kontrolünün Width özelliği üzerinde animasyon işlemi yaptıracağımızdan ve Width özelliği Double tipinden değerler aldığından DoubleAnimation tipi kullanılmaktadır.
            // İlk parametre From
            // İkinci parametre To
            // Üçüncüd parametre animasyonun süresi
            DoubleAnimation dblAnmtr = new DoubleAnimation(100, 150, new Duration(new TimeSpan(0, 0, 0, 3)));
            // Animasyonun tekrar sayısı belirtilir.
            dblAnmtr.RepeatBehavior = new RepeatBehavior(2);

            // Yeni bir StoryBoard oluşturulur
            Storyboard strBrd = new Storyboard();
            // DoubleAnimation tipi bu storyBoard' a eklenir
            strBrd.Children.Add(dblAnmtr);
            // Hedef kontrol belirlenir
            Storyboard.SetTargetName(dblAnmtr, btnMerhaba.Name);
            // Animasyon işleminde Button kontrolünün hangi özelliğinin değiştirileceği belirtilir.
            Storyboard.SetTargetProperty(dblAnmtr,new PropertyPath(Button.WidthProperty));
            // Animasyon Begin metodu ile Button üzerine Mouse ile gelindiğinde başlatılır
            btnMerhaba.MouseEnter += delegate(object sender, MouseEventArgs e)
                                                    {    
                                                        strBrd.Begin(this);
                                                    };
        }
   
        public KodlaMerhabaAnimasyon()
        {
            InitializeComponent();
            AnimasyonuHazirla();
        }
    }
}

Bu kez animasyon işlemleri kod tarafında gerçekleştirilmektedir. Örnekte ilk olarak DoubleAnimation tipinden bir örnek oluşturulmuş ve yapıcı metodu içerisinde bazı ilk değerlerin (From, To, Duration gibi) verilmesi sağlanmıştır. Sonrasında ise RepatBehavior özelliği ile tekrar sayısı belirtilmektedir. Bu örnekte bir öncekinden farklı olarak Forever değeri yerine 2 kullanılmıştır. Buna göre söz konusu animasyon sadece iki kere tekrar edecektir. Sonrasında ise her zamanki gibi animasyonu, kontrol ve kontrolün özelliği ile ilişkilendirecek StoryBoard nesne örneği oluşturulur. Animasyonun başlatılması için yine kontrolün MouseEnter olayı göz önüne alınmıştır. Animasyon işlemi için StoryBoard nesne örneğinin Begin metodunu kullanmak yeterlidir. Ancak bu metodun uygun olay için tetiklenmesini sağlamak gerekmektedir. Bu amaçlada isimsiz bir metod(anonymous method) yazılarak hem Button kontrolünün MouseEnter olayı tanımlanmış hemde gerçekleştiğinde işletilmesi istenen kodlar belirlenmiştir. Örneğin çalışma zamanındaki çıktısı aşağıdaki Flash animasyonunda olduğu gibidir.

Gelelim bir diğer örneğe. Önceki örneklerde Button kontrolonün boyutlarını ele alan animasyon tipi kullanılmıştır. Sıradaki örnekte ise ColorAnimation tipi başrolü oynamaktadır. Tahmin edileceği gibi bu tip, bir rengin başka bir renge dönüşümünün animasyon olarak gerçekleştirilmesini sağlamaktadır. Örneğin XAML içeriği aşağıdaki gibidir.

<Window x:Class="AnimasyonIslemleri.RenkliAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Renkli Animasyon(ColorAnimation)" Height="300" Width="300">
    <Grid>
        <Button Name="btnMerhaba" Foreground="White" Content="Merhaba" FontSize="12" FontWeight="Bold" Width="100" Height="50">
            <Button.Background>
                <SolidColorBrush x:Name="DolguRengi" Color="Red"/>
            </Button.Background>
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetName="DolguRengi" Storyboard.TargetProperty="(SolidColorBrush.Color)" From="Red" To="Blue" AutoReverse="True" Duration="0:0:6" RepeatBehavior="Forever"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Button.Triggers>
        </Button>
    </Grid>
</Window>

ColorAnimation tip olarak kullanıldığında kontrollerin renk değeri alabilen özelliklerinin animasyon işlemlerinde kullanılabileceği anlaşılmaktadır. Örnekte Button kontrolünün arka plan dolgusu animasyon içerisine katılmaktadır. Bu nedenle ColorAnimation kontrolünün ele alacağı kontrol ve özellik DoubleAnimation tipinin kullanıldığı örneklerdekinden biraz daha farklıdır. Dikkat edilecek olursa SolidColorBrush elementi içerisinde DolguRengi ismi x:Name niteliği yardımıyla tanımlanmıştır. Bu isim StoryBoard.TargetName niteliğinde kullanılmaktadır. Bir başka deyişle StroyBoard tipinin XAML içeriğindeki SolidColorBrush elementini bulması kolaylaştırılmaktadır. Bunun en büyük nedenlerinden birisi SolidColorBrush elementinin Button elementinin altında kalmış olması ve Button tipine isim ile erişilebiliyor olmasına rağlem aynı işin söz konusu SolidColorBrush için sağlanamıyor olmasıdır. Diğer taraftan animasyonda kullanılacak özellik değeride StoryBoard.TargetProperty elementi içerisinde ele alınırken (SolidColorBrush.Color) yazım stili kullanılmaktadır. Animasyona göre, Button kontrolünün arka plan rengi kırmızından maviye doğru değişecek ve sonrasında tekrardan maviden kırmızıya dönecektir(AutoReverse=true olduğu için). Animasyondaki zaman çizelgesinin(Timeline) süresi 6 saniyedir. Aşağıdaki Flash animasyonunda uygulamanın çalışma zamanındaki(Run-time) görüntüsü yer almaktadır.

Aynı etki aşağıdaki kod parçası yardımıyla dinamik olaraktan da gerçekleştirilebilir.

XAML İçeriği;

<Window x:Class="AnimasyonIslemleri.KodlaRenkliAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="KodlaRenkliAnimasyon" Height="300" Width="300">
    <Grid>
        <Button Name="btnMerhaba" Width="100" Height="50" Content="Merhaba" Foreground="White" FontSize="14" FontWeight="Bold">
            <Button.Background>
                <SolidColorBrush x:Name="firca" Color="Red"/>
            </Button.Background>
        </Button>
    </Grid>
</Window>

Kod İçeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation; // Animasyon tiplerini içeren isim alanıdır(Namespace)

namespace AnimasyonIslemleri
{
    public partial class KodlaRenkliAnimasyon : Window
    {
        private void AnimasyonuAyarla()
        {
            // Animasyon tipi oluşturulur.
            // İlk parametre başlangıç rengini işaret eder (From)
            // İkinci parametre bitiş rengini işaret eder (To)
            // Üçüncü parametre zaman çizgisinin süresidir
            ColorAnimation clrAnmtr = new ColorAnimation(Colors.Red, Colors.Blue, new Duration(new TimeSpan(0, 0, 6)));
            // Animasyonun sürekli tekrar edeceği belirtilir
            clrAnmtr.RepeatBehavior = RepeatBehavior.Forever;
   
            // Animasyonu kontrol ve özelliği ile ilişkilendirecek StoryBoard nesnesi örneklenir.
            Storyboard strBrd = new Storyboard();
            strBrd.Children.Add(clrAnmtr);
            Storyboard.SetTargetName(clrAnmtr, "firca"); // SolidColorBrush kontrolünün x:name niteliğinin değeri ile animasyon uygulanacak kontrol belirtilmiş olur.
            Storyboard.SetTargetProperty(clrAnmtr, new PropertyPath(SolidColorBrush.ColorProperty)); // SolidColorBrush kontrolünün Color özelliği üzerinde animasyonun uygulanacağı belirtilir.

            // Animasyon yine MouseEnter olay metodunda Begin metodu ile başlatılır.
            btnMerhaba.MouseEnter += delegate(object sender, MouseEventArgs e)
                                                    {
                                                        strBrd.Begin(this);
                                                    };
        }
        public KodlaRenkliAnimasyon()
        {
            InitializeComponent();
            AnimasyonuAyarla();
        }
    }
}

Yukarıdaki kodun yer aldığı pencere çalıştırıldığında, bir önceki örnekteki ile aynı etkinin oluştuğu görülecektir.

Yazımıza PointAnimation tipini ele alacağımız son bir örnek ile devam edelim. PointAnimation tipi adındanda anlaşılacağı üzere Point tipinden değerler alabilen kontrol özellikleri(Control Property) üzerinde animasyon işlemleri gerçekleştirilebilmesi amacıyla kullanılır. Örnek olarak aşağıdaki XAML içeriğini ele alabiliriz.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.NoktaAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Nokta Animasyon" Height="300" Width="300">
    <Grid>
        <Path Name="Daire" Fill="Red" Stroke="Black" StrokeThickness="1">
            <Path.Data>
                <EllipseGeometry x:Name="daireGeo" Center="15,15" RadiusX="10" RadiusY="10" />
            </Path.Data>
            <Path.Triggers>
                <EventTrigger RoutedEvent="Path.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <PointAnimation Storyboard.TargetName="daireGeo" Storyboard.TargetProperty="Center" From="15,15" To="285,15" Duration="0:0:1.75" AutoReverse="true" RepeatBehavior="Forever"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Path.Triggers>
        </Path>
    </Grid>
</Window>

Burada örnek olarak Path tipinden bir şekil(Shape) kullanılmış ve EllipsGeometry tipinden yararlanılarak bir daire oluşturulmuştur. Burada Point tipinden değer alan üye Center özelliğidir. Bu anlamda PointAnimation tipinin hedef aldığı kontrol aslında EllipseGeometry nesnesidir. Bu işaretlemeyi yapabilmek için yine x:Name niteliğinden yararlanılmıştır. Diğer taraftan EllipsGeometry nesne örneğinin Center özelliği animasyon işlemine tabi tutulacağından StoryBoard.TargetProperty niteliğine Center değeri verilmiştir. PointAnimation tipi basit olarak kontrolün bir zaman çizgisi(Timeline) içerisinde belirli bir koordinata doğru hareket etmesini sağlamaktadır. Buna göre örnekteki daire şekli, 15:15 noktasından 285:15 noktasına doğru 1.75 saniyelik zaman dilimi içerisinde hareket edecek ve AutoReverse özelliğine true değerinin atanması nedeniylede bu süre sonunda tekrardan geriye ilk konumuna gidecektir. Tahmin edileceği üzere RepeatBehavior niteliğinin değerinin true olması bu işlemi sürekli kılacaktır. Burada diğer örneklerden farklı olarak animasyon işleminin tetiklendiği olay olarak Path.Loaded seçilmiştir. Buna göre Path, söz konusu pencere(Window) üzerine yüklendikten sonra animasyon işlemi doğrudan başlayacaktır. Örneğin çalışma zamanındaki görüntüsü aşağıdaki Flash görselinde olduğu gibidir.

Aynı işleyişi kod tarafında gerçekleştirmek istersek aşağıdaki satırları kullanmamız yeterli olacaktır.

XAML içeriği;

<Window x:Class="AnimasyonIslemleri.KodlaNoktaAnimasyon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kodla Nokta Animasyon (PointAnimation)" Height="300" Width="300">
    <Grid>
        <Path Name="Daire" Fill="Red" Stroke="Black" StrokeThickness="1">
            <Path.Data>
                <EllipseGeometry x:Name="daireGeo" Center="15,15" RadiusX="10" RadiusY="10" />
            </Path.Data>
        </Path>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation; // Animasyon tiplerini içeren isim alanıdır

namespace AnimasyonIslemleri
{
    public partial class KodlaNoktaAnimasyon : Window
    {
        private void AnimasyonuBaslat()
        {
            // Nokta animasyonu için gerekli tip oluşturulur
            // İlk parametre başlangıç noktası koordinatlarıdır From
            // İkinci parametre bitiş noktası koordinatlarıdır To
            // Üçündü parametre zaman çizelgesinin süresidir Duration
            PointAnimation pntAnmtr = new PointAnimation(new Point(15, 15), new Point(285, 15), new Duration(new TimeSpan(0, 0, 0,1,75)));

            // Animasyonu kontrol ve özelliği ile ilişkilendirecek olan StoryBoard oluşturulur
            Storyboard strBrd = new Storyboard();
            // Animasyon tipi StoryBoard' a eklenir
            strBrd.Children.Add(pntAnmtr);
            // Animasyonun sonundan tekrardan geriye doğru gidileceği belirtilir
            strBrd.AutoReverse = true;
            // Animasyonun sürekli devam edeceği belirtilir
            strBrd.RepeatBehavior = RepeatBehavior.Forever;
            // Animasyonun uygulanacağı EllipsGeometry tipi seçilir. Buradaki ikinci parametre XAML tarafındaki x:Name niteliğinin değeridir
            Storyboard.SetTargetName(pntAnmtr,"daireGeo");
            // Animasyonun uygulanacağı özellik seçilir.
            Storyboard.SetTargetProperty(pntAnmtr, new PropertyPath(EllipseGeometry.CenterProperty));
            // Animasyonun, Daire isimli Path yüklendikten sonra başlatılması için Loaded olay metodu yüklenir.
            Daire.Loaded += delegate(object sender, RoutedEventArgs e)
                                        {
                                            strBrd.Begin(this);
                                        };
        }
        public KodlaNoktaAnimasyon()
        {
            InitializeComponent();
            AnimasyonuBaslat();
        }
    }
}

WPF içerisinde kullanılan temel animasyon tipleri(Basic Animation Types) sadece yazımızda bahsetiklerimiz ile sınırlı değildir. System.Windows.Media.Animation isim alanında(Namespace) yer alan diğer animasyon tiplerinin listesi aşağıdaki gibidir.

  • ByteAnimation
  • DecimalAnimation
  • Int16Animation
  • Int32Animation
  • Int64Animation
  • Point3DAnimation
  • QuaternionAnimation
  • Rotation3DAnimation
  • RectAnimation
  • SingleAnimation
  • SizeAnimation
  • TicknessAnimation
  • Vector3DAnimation
  • VectorAnimation

Görüldüğü gibi kontrollerin pek çok farklı tipteki özelliği için yazılmış temel animasyon tipleri vardır. Animasyon ile ilgili işlemler bu makalede ele aldıklarımız ile sınırlı değildir elbeteki. 3 boyutlu (3D) animasyon, KeyFrame' lerin kullanımı ve dahası da var. Animasyon işlemleri ile ilgili bu ilk yazımızda temel animasyon tiplerinin tanımaya ve onları anlamaya çalıştık. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF - Grafik İşlemlerinde Fırçaların(Brushes) Kullanımı

Salı, 18 Eylül 2007 20:08 by bsenyurt
Windows Presentation Foundation(WPF) ile ilgili bir önceki makalemizde, iki boyutlu(2D) grafiklerin çizilmesi amacıyla kullanılan fırçaları(Brushes) incelemeye çalışmıştık. Bu makalemizde ise iki boyutlu şekilleri(Shapes) araştırıyor olacağız. Vektörel grafiklerde şekillerin(Shapes) büyük önemi vardır. Nitekim temel şekiller kullanılarak asıl resimler ve görüntüler kolaylıkla elde edilebilir. Bir CAD uygulamasının karmaşık çizelgelerinden, eğlenceli çocuk programlarında kullanılan vektörel grafiklere kadar pek çok alanda temel şekiller yeterli olmaktadır. Söz gelimi bir şehrin imar planlamasında kullanılacak bir programda iki boyutlu olarak düşünüldüğünde dörtgenler, daireler, elipsler, poligonlar ve düz çizgiler evlerin, yolların, arsaların, parkların ifade edilmesi için yeterlidir. Senaryolar arttırılabilir ve daha geniş alanlarda düşünülebilir. Ancak temel olarak gereken şekiller bellidir. WPF kendi bünyesinde iki boyutlu çizimlerin gerçekleştirilebilmesi amacıyla aşağıda belirtilen şekilleri(Shapes) sunmaktadır.

  • Ellips : Bu tip yardımıyla içi dolu veya boş tam daire yada elipslerin çizilmesi mümkündür.
  • Line : Düz çizgilerin çizilmesini sağlayan tiptir. Başlangıç ve bitiş koordinatları düz çizginin çizilmesi için yeterlidir.
  • Rectangle : Dört köşeli şekillerin çizilmesinde kullanılan tiptir. İçi boş veya dolu dikdörtgen yada kare gibi şekillerin çizilebilmesini sağlar.
  • Polygon : N sayıda köşeden oluşan poligonların çizilmesinde kullanılır. Bir üçgen olabileceği gibi bir çokgen de olabilir. Diğer taraftan düzgün köşeli olmayan bir poligonda oluşturulabilir. Ayrıca poligonların içi boş veya dolu olacak şekilde oluşturulabilmesi de mümkündür.
  • Polyline : Birbirlerine bitiş noktalarından bağlı bir başka deyişle uç uca eklenmiş düz çizgilerin(Line) çizilmesini sağlayan tiptir.
  • Path : Birbirlerine son noktalarından bağlı olan düz çizgi veya eğri (Curve) gibi toplu şekillerin çizidirilmesini sağlayan tiptir. Farklı şekillerin bir arada kullanılabilmesini sağlamak için geometri(Geometry) tiplerinden yararlanır.

WPF, XAML(eXtensible Application Markup Language) tabanlı bir ortam sunduğundan, grafiksel şekillerin tasarım zamanında element bazlı olarak geliştirilmeleri ve sonuçlarının görülmesi mümkündür. GDI+ mimarisinde aynı durum düşünüldüğünde sonuçların ancak çalışma zamanında(run-time) elde edilebildiği unutulmamalıdır. Bu nedenle WPF bize büyük avantaj sağlamaktadır.

Bu kısa bilgilerden sonra örneklerimizi geliştirerek şekilleri daha yakından tanımaya çalışalabiliriz. Her zamanki gibi örneklerimizi geliştirirken Visual Studio 2008 Beta 2 sürümünden yararlanıyor olacağız. Bu nedenle, final sürümünde özellikle IDE bazlı bazı değişiklikler olma ihtimali olduğunu baştan belirtelim. İlk örneğimizde Ellips tipinden yararlanıyor olacağız. Bu amaçla Window nesnemizin XAML içeriğini aşağıdaki gibi tasarladığımızı düşünelim.

<Window x:Class="GrafiklerleCalismak.Elipsler" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Elipslerin Kullanımı" Height="300" Width="300">
    <Grid>
        <Ellipse Fill="DarkRed" Width="40" Height="150" Stroke="Coral" StrokeThickness="3"/>
        <Ellipse Fill="Red" Width="150" Height="40" Stroke="Black" StrokeThickness="3" Opacity="0.75"/>
        <Ellipse Width="40" Height="40" Fill="Gold" Stroke="Black" StrokeThickness="3"/>
        <Ellipse Width="10" Height="10" Stroke="DarkBlue" StrokeThickness="2" HorizontalAlignment="Left" Margin="64,56,0,0" VerticalAlignment="Top" />
        <Ellipse Height="25" HorizontalAlignment="Right" Margin="0,56,64,0" Stroke="DarkBlue" StrokeThickness="2" VerticalAlignment="Top" Width="25" />
        <Ellipse Height="50" HorizontalAlignment="Right" Margin="0,0,64,56" Stroke="DarkBlue" StrokeThickness="2" VerticalAlignment="Bottom" Width="50" />
    </Grid>
</Window>

Bu örnekte altı adet elips çizdirilmektedir. Ellips tipinin Fill niteliği(attribute) yardımıyla dolgu deseni belirtilebilir. Bunun dışında width ve height nitelikleri eşit oldukları takdirde tam bir dairenin çizilmesi söz konusudur. Diğer hallerde ise, yatay doğrultuda veya dikey doğrultuda uzayan bir elips oluşumu söz konusu olmaktadır. Kenar çizgilerini renk ve kalınlık olarak belirlemek amacıyla Stroke ve StrokeTickness niteliklerine değer atamaları yapılmaktadır. Stroke niteliği tahmin edileceği üzere geçerli bir fırça(Brush) ile eşleştirilebilir. Buda çizginin dolu bir renk dışında desenli olabileceği hatta içinde resim barındırabileceği anlamına gelmektedir.

NOT : Stroke ve StrokeTickness nitelikleri diğer şekillerde de ye almaktadır. Bu nedenle tüm şekillerin çizgilerinin olabileceğini söyleyebiliriz.

Yukarıdaki XAML içeriğini Visual Studio 2008 Beta 2 ortamındaki çıktısı aşağıdaki gibi olacaktır.

Şekillerin bu biçimde yatay veya dikey düzlemlerde oluşturulması haricinde, açısal olaraktanda yerleştirilmesi istenebilir. Bunu bir elips üzerinde GDI+ ile gerçekleştirmek oldukça zor ve zahmetlidir. Oysaki WPF içerisinde yer alan Transform tipleri kullanılarak bu işlemler son derece kolay bir şekilde gerçekleştirilebilir.

NOT : Transoform’ dan kasıt, şeklin açısal olarak konumunun değiştirilebilmesi, büyüklüğünün ayarlanabilmesi, kendi ekseni üzerinde veya farklı bir orjine göre döndürülebilmesi, şeklin x veya y düzlemleri üzerinde yer değiştirmesi yada eğilip bükülmesi gibi aksiyonlardır.

İkinci örneğimizde bu durum incelenmektedir. Yeni penceremizin XAML içeriğinin aşağıdaki gibi olduğunu düşünelim.

<Window x:Class="GrafiklerleCalismak.ElipsTransform" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="ElipsTransform" Height="300" Width="300" Loaded="Window_Loaded">
    <Grid>
        <Ellipse Width="125" Height="40" Stroke="Red" StrokeThickness="2"/>
        <Ellipse Width="40" Height="125" Stroke="Black" StrokeThickness="2"/>
        <Ellipse Width="40" Height="125" Stroke="Red" StrokeThickness="2">
            <Ellipse.LayoutTransform>
                <RotateTransform Angle="45"/>
            </Ellipse.LayoutTransform>
        </Ellipse>
        <Ellipse Width="40" Height="125" Stroke="Black" StrokeThickness="2">
            <Ellipse.LayoutTransform>
                <RotateTransform Angle="135"/>
            </Ellipse.LayoutTransform>
        </Ellipse>
    </Grid>
</Window>

Burada dikkat edilmesi gereken en önemli nokta, Ellips.LayoutTransform elementinin içeriğidir. Bu elementin altında yer alan RotateTransform elementi içerisinde Angle niteliği ile bir açı değeri belirtilmektedir. Bu açı değeri, şeklin x,y eksenine göre farklı bir derecede döndürülmesini sağlamaktadır. Örnek XAML içeriğinde 45 derece ve 135 derecelik açılar ile döndürülmüş iki elips yer almaktadır. Bu içeriğin tasarım zamanındaki çıktısı aşağıdaki gibi olacaktır.

Atomu WPF ile daha kolay çizebildiğimizi söyleyebiliriz. Bu tip dönüştürme(Transform) işlemleri sadece RotateTransform ile sınırlı değildir. Yazımızın ilerleyen kısımlarında diğer Transform modellerinede kısaca değinmeye çalışacağız. Çok doğal olarak rotasyonların programatik olarak gerçekleştirilmesi gereken vakkalar olacaktır. Yukarıdaki atom çizelgesininin benzerini kod tarafında oluşturmak istersek, element ve niteliklerin karşılığı olan uygun sınıf(class) ve özellikleri(property) kullanmak yeterli olacaktır. Üçüncü örneğimizde bu durum incelenmektedir. Bu amaçla yeni penceremizin XAML ve kod içeriğini aşağıdaki gibi tasarladığımızı düşünelim.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodlaElipsTransform" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Kod Yardımıyla Elips Transform" Height="300" Width="300">
    <Grid Name="grdEllips">
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodlaElipsTransform : Window
    {
        private void ElipsCiz(Ellipse elps, int width, int height, int angle,Color color)
        {
            elps.Width = width;
            elps.Height = height;
            elps.Stroke = new SolidColorBrush(color);
            elps.StrokeThickness = 2;
            elps.LayoutTransform = new RotateTransform(angle);
            grdEllips.Children.Add(elps);
        }
        private void Cizdir()
        {
            ElipsCiz(new Ellipse(), 125, 40, 270,Colors.Red);
            ElipsCiz(new Ellipse(), 40, 125, 90,Colors.Gold);
            ElipsCiz(new Ellipse(), 125, 40, 45, Colors.DarkBlue);
            ElipsCiz(new Ellipse(), 125, 40, 135,Colors.Lavender);
        }

        public KodlaElipsTransform()
        {
            InitializeComponent();
            Cizdir();
        }
    }
}

Bir kaçtane elipsi farklı renk, çizgi, çizgi kalınlığı ve açıda çizdirmek istediğimizden yardımcı olacak ElipsCiz isimli bir metod tasarlanmıştır. Bu metod, parametre olarak gelen Ellips nesne örneğini alıp, genişlik(Width), yükseklik(Height), Çizgi rengi(Stroke), Çizgi kalınlığı(StrokTickness) ve rotasyon için gerekli açı(Angle) değerlerini set etmektedir. Dikkat edilecek olursa, rotasyon işlemi için RotateTransform tipine ait bir nesne örneklenmekte ve yapıcı metoda(Constructor) açı değeri verilmektedir. Sonrasında ise bu nesne örneği, Ellips nesne örneğinin LayoutTransform özelliğine atanmaktadır. Söz konusu kod parçası yürütüldüğünde çalışma zamanında(run-time) aşağıdakine benzer bir ekran görtüntüsü ile karşılaşılır.

Yine kod yardımıyla elips çizdirmeye örnek olması açısından aşağıdaki pencerede(Window) göz önüne alınabilir.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodYardimiylaElips" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="KodYardimiylaElips" Height="300" Width="300">
    <Grid>
        <Grid Name="grdTahta" Margin="0,74,0,8" Background="Gold" />
        <Button Height="23" HorizontalAlignment="Left" Margin="10,20,0,0" Name="btnCiz" VerticalAlignment="Top" Width="75" Click="btnCiz_Click">Çizdir</Button>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaElips : Window
    {
        private void ElipsCiz()
        {
            grdTahta.Children.Clear();
            Random rnd=new Random();
            for (int i = 0; i < 3; i++)
            {
                Ellipse elps = new Ellipse();
                elps.Width = rnd.Next(50, 100);
                elps.Height = rnd.Next(50, 100);
                elps.Stroke = new SolidColorBrush(Colors.Black);
                elps.StrokeThickness = rnd.Next(1, 5);
                grdTahta.Children.Add(elps);
            }
        }
   
        public KodYardimiylaElips()
        {
            InitializeComponent();
        }
   
        private void btnCiz_Click(object sender, RoutedEventArgs e)
        {
            ElipsCiz();
        }
    }
}

Bu kez bir düğmeye basılması ile rastgele üretilen değerlere göre elipslerin çizdirilmesi sağlanmaktadır. Bu amaçla 50 ile 100 arasında rastgele genişlik ve yükselik değerleri elde edebilmek için meşhur Random sınıfına ait nesne örneğinden ve Next metodundan yararlanılmaktadır. Çoğu zaman oygun programlamada şekilsel olarak bazı oyun karakterlerinin saha üzerinde rastgele konumlarda çıkması istenebilir. İşte bu noktada Random sınıfına ait metodlar ve WPF ile gelen yeni şekil çizme teknikleri işimizi oldukça kolaylaştırmaktadır.

Kod yardımıyla gerçekleştirilen örneklerde dikkat edilmesi gereken noktalardan birisi, oluşturulan şekillerin mutlaka bir taşıyıcıya eklenmiş olmalarıdır. Söz gelimi yukarıdaki örneklerde Ellips nesne örnekleri Grid bileşenine alt element olarak, Children özelliğinin Add metodundan yararlanılarak eklenmektedir. Son pencereye(Window) ilişkin olarak aşağıdaki ekran görüntüsünde çalışma zamanında(run-time) oluşabilecek bir örnek çıktı yer almaktadır.

Sıradaki örneğimizde poligonların nasıl çizilebileceğini incelemeye çalışacağız. Poligonlar, çok sayıda köşeden oluşabilen ve kapalı olarak tasarlanabilen şekillerdir. Poligonlar sayesinde basit bir üçgen çizilebileceği gibi bir onaltıgen’ de çizilebilir. Yada düzensiz bir kapalı şekil oluşturulabilir. Burada önemli olan köşe noktalarının belirlenmesidir. Köşe noktalarının belirlenmesinde Point tipinden yararlanılır. Point tipi x ve y koordinatlarını bünyesinde taşımaktadır. Polygon tipi, köşe noktalarını Points isimli bir koleksiyonda taşımaktadır. Şimdi aşağıdaki XAML içeriğini göz önüne alalım.

<Window x:Class="GrafiklerleCalismak.PolygonKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Polygon Kullanımı" Height="300" Width="403">
    <Grid>
        <Polygon Fill="Gold" FillRule="Nonzero" Stroke="Black" StrokeThickness="2">
            <Polygon.Points>
                <Point X="20" Y="20" />
                <Point X="120" Y="20" />
                <Point X="120" Y="120" />
                <Point X="220" Y="120" />
                <Point X="220" Y="220" />
            </Polygon.Points>
        </Polygon>
        <Polygon Fill="LightSkyBlue" FillRule="Nonzero" Stroke="Black" StrokeThickness="2">
            <Polygon.Points>
                <Point X="20" Y="20"/>
                <Point X="120" Y="20"/>
                <Point X="120" Y="120"/>
                <Point X="220" Y="120"/>
                <Point X="220" Y="220"/>
            </Polygon.Points>
            <Polygon.LayoutTransform>
                <RotateTransform Angle="-45"/>
            </Polygon.LayoutTransform>
        </Polygon>
    </Grid>
</Window>

Örnekte iki adet Polygon elementi tanımlanmıştır. Bunlardan ikincisine -45 derecelik bir açısal döndürme işlemi uygulanmıştır. Her iki Polygon nesne örneğinin köşe noktaları Polygon.Points alt elementi(child element) içerisinde yer alan Point alt elementleri ile belirtilmektedir. Polygon nesnelerinin kenar çizgileri Stroke ve StrokTickness niteliklerine atanan değerler yardımıyla set edilmiştir. Diğer taraftan Polygon’ ların dolgu rengi Fill özelliklerine atanan standart fırça(Brush) renkleri ile ayarlanmaktadır. Söz konusu XAML içeriğinin Visual Studio 2008 Beta 2 ortamındaki tasarım zamanı(design-time) çıktısı ise aşağıdaki gibi olacaktır.

Bir poligon kod yardımıylada oluşturulabilir. Sıradaki örneğimizde bir üçgenin kod yardımıyla oluşturulması ve düğmeye basılaraktan onbeşer derecelik artan açılar ile döndürülmesi örneklenmektedir. Bu amaçla yeni penceremize(Window) ait XAML ve kod içeriklerini aşağıdaki gibi tasarladığımızı düşünelim.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodYardimiylaPolygonKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Kod Yardımıyla Polygon" Height="300" Width="300">
    <Grid Name="grdTahta">
        <Button Height="23" HorizontalAlignment="Left" Margin="8,19,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click">Çevir</Button>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Timers;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaPolygonKullanimi : Window
    {
        Polygon plgy;
        int sayac = 1;

        private void Cizdir()
        {
            plgy = new Polygon();
            plgy.Points.Add(new Point(50, 50));
            plgy.Points.Add(new Point(150, 50));
            plgy.Points.Add(new Point(150, 150));
            plgy.Stroke = new SolidColorBrush(Colors.LightSalmon);
            plgy.StrokeThickness = 2;
            grdTahta.Children.Add(plgy);
        }

        public KodYardimiylaPolygonKullanimi()
        {
            InitializeComponent();
            Cizdir();
        }
   
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            plgy.LayoutTransform = new RotateTransform(sayac*15);
            sayac++;
        }
    }
}

Penceremizin yapıcı metodu(constructor) içerisinde Cizdir metodu ile bir Polygon nesne örneği oluşturulmakta ve Grid kontrolünün alt elementi olarak eklenmektedir. Polygon nesne örneği bir üçgeni temsil edeceğinden Points koleksiyonunda sadece üç nokta(Point) eklemesi yapılmıştır. Döndürme işleminin gerçekleştirildiği yer düğmenin Click olay(event) metodudur. Burada dikkat edilecek olursa yine LayoutTransform özelliğine bir değer ataması yapılmaktadır. Bu atama sırasında yeni bir RotateTransform nesnesi örneklenmekte ve parametre olarak artan bir açı değer verilmektedir. Uygulama test edildiğinde çalışma zamanında aşağıdaki Flash animasyonunda yer alan çıktı elde edilecektir. (Flash dosyasını görebilmek için Flasy Player’ ın sisteminizde yüklü olması gerekmektedir.)

Şimdiki örneğimizde düz çizgileri nasıl çizdirebileceğimizi incelemeye çalışacağız. Düz çizgi için Line tipi kullanılmaktadır. Bir çizgi için belkide en önemli özellikler Stroke, StrokeTickness, X1, X2, Y1 ve Y2’ dir. X ve Y özellikleri yardımıyla çizginin başlangıç ve bitiş noktaları belirlenir. Örneğin aşağıdaki XAML içeriğini ele alalım.

<Window x:Class="GrafiklerleCalismak.LineKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Line Kullanimi" Height="211" Width="237">
    <Grid>
        <Line Stroke="RosyBrown" StrokeThickness="2" X1="10" Y1="10" X2="50" Y2="50"/>
        <Line Stroke="Brown" StrokeThickness="3" X1="50" Y1="50" X2="100" Y2="50"/>
        <Line Stroke="BurlyWood" StrokeThickness="4" X1="100" Y1="50" X2="50" Y2="140"/>
        <Line Stroke="CadetBlue" StrokeThickness="5" X1="50" Y1="140" X2="180" Y2="50"/>
    </Grid>
</Window>

Bu içeriğin tasarım zamanındaki(design-time) çıktısı aşağıdaki gibi olacaktır.

Dikkat edileceği üzere farklı kalınlık, renk ve lokasyonlarda yer alan çizgiler elde edilmektedir. Çizgiler özellikle kod tarafındada zaman zaman ele alınırlar. Söz gelimi bir harita üzerinden bir şehirden bir şehire doğru olabilecek hava yolu rotalarının belirlenmesi amacıyla çizgilerden yararlanılabilir. Bunu çok basit olarak sembolize etmek amacıyla aşağıdaki XAML ve kod içeriğini göz önüne alabiliriz.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodYardimiylaLineKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Kod Yardımıyla Line Kullanımı" Height="249" Width="422" WindowStyle="SingleBorderWindow">
    <Grid Name="grdTahta">
        <Image Name="imgHarita" MouseDown="imgHarita_MouseDown" MouseUp="imgHarita_MouseUp" Source="map_world_destination.gif" />
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaLineKullanimi : Window
    {
        Point basilanNokta;

        public KodYardimiylaLineKullanimi()
        {
            InitializeComponent();
        }

        private void imgHarita_MouseDown(object sender, MouseButtonEventArgs e)
        {
            basilanNokta=e.GetPosition(grdTahta);
        }

        private void imgHarita_MouseUp(object sender, MouseButtonEventArgs e)
        {
            Line yol = new Line();
            yol.Stroke = new SolidColorBrush(Colors.Red);
            yol.StrokeThickness = 2;
            yol.X1 = basilanNokta.X;
            yol.Y1 = basilanNokta.Y;
            Point bitisNoktasi = e.GetPosition(grdTahta);
            yol.X2 = bitisNoktasi.X;
            yol.Y2 = bitisNoktasi.Y;
            grdTahta.Children.Add(yol);
        }       
    }
}

Bu örnekte haritayı göstermesi için bir Image kontrolü kullanılmaktadır. Image kontrolünün source özelliğine atanan değer ile arka plan dünya haritası olarak gösterilmektedir. Kullanıcılar çalışma zamanında mouse’ un tuşuna basıp bir noktadan başka bir noktaya gittiklerinde ve mouse’ un tuşunu bıraktıklarında Line nesne örneği oluşturulmaktadır. Bunun için Image kontrolü üzerinde mouse tuşuna basılma ve bırakılma anlarının yakalanması gerekir. Söz konusu anlar aşina olduğumuz MouseDown ve MouseUp olay metodlarında yakalanırlar. GDI+ ile aynı işlemleri nasıl yaptığımızı hatırlarsak eğer, kontrolün MouseDown olayına gelen parametre ile X ve Y değerlerini ayır ayrı aldığımızı görürüz. Burada durum biraz daha farklıdır. Nitekim MouseUp ve MouseDown olay metodlarında yer alan MouseButtonEventArgs parametresi, mouse tuşuna basıldığında o noktadaki X ve Y koordinatlarını yeni bir Point tipi olarak geriye döndürmektedir. GetPosition metodu X ve Y koordinatları, üzerinden alınmak istenen kontrolüde parametre olarak alır. Örnekte bu parametreye Grid kontrolünün referansı verilmiştir. Dolayısıyla Grid kontrolündeki X ve Y değerleri elde edilebilmektedir.

Mouse üzerinde basılan tuş bırakıldığında ise çizginin çizilme işlemi gerçekleştirilmektedir. Line nesne örneğinin X1 ve Y1 değerleri tahmin edileceği gibi, MouseDown içerisinde yer alan GetPosition ile elde edilen basilanNokta değişkeninden gelmektedir. Çizginin son noktası ise bu kez MouseUp olay metodu içerisindeki GetPosition çağrısı ile alınmakta ve Line nesne örneğinin X2 ve Y2 değerlerine aktarılmaktadır. Son olarak oluşturulan çizgi, Grid bileşenine alt element olarak eklenmektedir. Bu ekleme işleminin bize getirdiği önemli bir avantaj vardır. Yine GDI+ ile Windows programlama yaptığımızı düşünecek olursak, aynı senaryoda ekrana çizgiler çizdirmek için Graphics nesnesninin DrawLine metodundan yararlanıldığını görürüz. Ne varki bu metod hep son çizginin çizilmesine öncekilerin ise kaybolmasına neden olmaktadır. Oysaki WPF mimarisinde şekiller bir taşıyıcının alt elementi olarak eklendiklerinden son çizilen şekilden öncekiler ekrandan kaybolmamaktadır. Bu durumu GDI+ ile Windows programlamada gerçeklemek için WPF’tekine benzer bir mantık ile hareket edilmekte ve ekranda duran çizgilerin sürekli hatırlanması için koleksiyonalardan yararlanılması gerekmektedir. Yukarıdaki kodun çalışma zamanındaki ekran çıktısı aşağıdaki Flash animasyonunda olduğu gibidir.(Flash dosyasını görebilmek için Flasy Player’ ın sisteminizde yüklü olması gerekmektedir.)

Bu örneği daha da geliştirmeye çalışmanızı öneririm. Oldukça fazla eksiği var. Söz gelimi, mouse tuşuna basıp sürüklerken farklı renkte bir çizginin çıkartılarak gidilen rotanın gösterilmesi sağlanabilir. Mouse bırakıldığında ise asıl rengini alan rota ortaya çıkar. Üstelik örnek kodda sağ tuşa veya sol tuş kontrolü yapılmamıştır. Belkide klavyeden tuş kombinasyonları katarak sadece düz çizgi değil eğrilerin çizdirilmesinide sağlayabiliriz. Bu örneğin geliştirilmesini siz değerli okurlarıma bırakıyorum.

Gelelim Path bileşenine. Bu tip aslında kendi içerisinde birden fazla düz çizgi veya eğriyi barındırabilecek şekilde tasarlanmıştır. Temelde şekiller birbirleriyle uç uca eklenecen şekilde yeni bir grafik oluşturmaktadırlar. Dilerseniz örnek üzerinden ilerleyerek konuyu daha iyi anlamaya çalışalım. Bu amaçla aşağıdaki gibi bir XAML içeriği oluşturduğumuzu göz önüne alalım.

<Window x:Class="GrafiklerleCalismak.PathKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White"
Title="Path Kullanimi(Birbirlerine Bağlı Farklı Şekiller)" Height="261" Width="321">
    <Grid>
        <Path Stroke="Black" StrokeThickness="2">
            <Path.Data>
                <PathGeometry>
                    <PathFigure>
                        <BezierSegment Point1="0,0" Point2="10,120" Point3="70,40"/>
                        <ArcSegment SweepDirection="Clockwise" Point="150,125" Size="50,50" IsLargeArc="True" RotationAngle="60"/>
                        <LineSegment Point="240,40"/>
                        <QuadraticBezierSegment Point1="35,135" Point2="75,75"/>
                        <PolyLineSegment>
                            <PolyLineSegment.Points>
                                <Point X="10" Y="10"/>
                                <Point X="50" Y="75"/>
                            </PolyLineSegment.Points>
                        </PolyLineSegment>
                        <PolyBezierSegment>
                            <PolyBezierSegment.Points>
                                <Point X="100" Y="100"/>
                                <Point X="150" Y="150"/>
                                <Point X="75" Y="180"/>
                            </PolyBezierSegment.Points>
                        </PolyBezierSegment>
                    </PathFigure>
                </PathGeometry>
            </Path.Data>
        </Path>
    </Grid>
</Window>

Path elementi içerisinde farklı şekillerin uç uca eklenmesi işini geometri tipleri üstlenmektedir. Bu geometri tipleride kendi içlerinde segmentler halinde şekilleri barındırmaktadır. Kullanılabilecek olan geometri tipleri aşağıdaki gibidir.

Örnekte söz konusu geometri tiplerinden PathGeometry kullanılmaktadır. PathGeometry elementinin altında yer alan tüm şekiller Segment anahtar kelimesi ile bitmektedir. Buna göre, BezierSegment ile başlayan şekiller dizisi sırasıyla, ArcSegment, LineSegment, QuadraticBezierSegment, PolyLineSegment ve PolyBezierSegment ile devam eder. Tüm bu alt elementleri farklı nitelikleri ile değişik çizgilerin oluşturulması sağlanmaktadır. BezierSegment elementinin nitekikleri sayesinde üç noktadan bükülmüş bir eğri çizimi yapılabilmektedir. ArcSegment elementinin nitelikleri ile, başlangıç koordiantları, genişlik yükseklik değerleri, eğilme açısı ve saat yönü yada saatin ters yönünde çizilecek yaylar oluşturulabilmektedir. Burada PolyLineSegment’ i altında belirtilen noktalar uç uca bağlı düz çizgilerin oluşturulmasını sağlarken, PolyBezierSegment elementi altındaki noktalarda, uç uca eklenmiş eğrilerin oluşturulmasını sağlamaktadır. Örneğin tasarım zamanındaki çıktısı aşağıdaki gibi olacaktır.

Dikkat edilecek olursa, tüm çizgiler ister eğri ister düz olsunlar, uç uca eklenerekten birleştirilmiştir. Bu nedenle Path tipini örneğin harita gibi arka planlarda yolların birleştirilmesi amacıyla kullanabiliriz.

Bu yazımızda son olarak basit anlamda dönüştürme(Transform) işlemlerine bakıyor olacağız. Transform denilince aklımıza gelmesi gerekenler bir şeklin yön, büyüklük, düzlemsel koordinat gibi değerlerinin değiştirilmesidir. Bu sayede bir şekli herhangibir açıda döndürebilir, ebatlarını ayarlayabilir yada herhangibir düzlem üzerinde öteleyebiliriz. Transform işlemlerinde beş farklı tip rol oynamaktadır. Bu tipler aşağıdaki gibidir.

  • RotateTransform : Şeklin belirtilen bir açıda, kendi ekseninde yada belirtilen orijine göre farklı bir eksende döndürülmesini sağlamak için kullanılır. Söz gelimi bir şeklin farklı açılardan gösterilmesinin sağlanmasında önemli rol oynamaktadır.
  • ScaleTransform : Şeklin ebatlarının eşit oranda yada farklı oranlarda arttırılması yada azaltılmasında kullanılır. Örneğin Zoom işlemlerinde bu tip çok faydalı olacaktır.
  • SkewTransform : Şeklin bükülmesini yada eğilmesini sağlamak amacıyla kullanılan tiptir.
  • TranslateTransform : Şeklin x veya y düzlemleri üzerinde farklı noktalara ötelenmesi amacıyla kullanılan tiptir.
  • MatrixTransform : Resim işlemede önemli bir yere sahip olan matris algoritmalarının iki boyutlu şekiller üzerinde de uygulanabilmesini sağlayan tiptir. Diğer Transform tipleri ile gerçekleştirilmesi zor olan dönüştürmelerde kullanılmaktadır. Bu tipi ilerleyen yazılarımızda ele almaya çalışacağız.

Şimdi basit bir örnek ile RotateTransform ve ScaleTransform tiplerinin nasıl kullanılabileceğini incelemeye çalışalım. Bu amaçla XAML ve kod içeriklerini aşağıdaki gibi geliştirdiğimizi düşünelim.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.TransformKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Transform Kullanımı" Height="292" Width="330">
    <Grid Name="grdTahta" Width="309" Height="253">
        <Rectangle Fill="Gold" Stroke="Black" StrokeThickness="2" Name="dortgen" Width="100" Height="40" />
        <Slider Minimum="1" Maximum="5" ValueChanged="sldScale_ValueChanged" Height="21" Margin="110,14,89,0" Name="sldScale" VerticalAlignment="Top" />
        <Label Height="23" HorizontalAlignment="Left" Margin="0,12,0,0" Name="label1" VerticalAlignment="Top" Width="92">Scale Transform</Label>
        <Slider Height="21" Margin="110,49,89,0" Maximum="360" Minimum="0" Name="sldRotate" ValueChanged="sldRotate_ValueChanged" VerticalAlignment="Top" />
        <Label Height="23" HorizontalAlignment="Left" Margin="0,47,0,0" Name="label2" VerticalAlignment="Top" Width="92">Rotate Transform</Label>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class TransformKullanimi : Window
    {
        public TransformKullanimi()
        {
            InitializeComponent();
        }

        private void sldScale_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            TransformYap();
        }

        private void sldRotate_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            TransformYap();
        }

        private void TransformYap()
        {
            if (sldRotate != null
                && sldScale != null)
            {
                TransformGroup grp = new TransformGroup();
                grp.Children.Add(new ScaleTransform(sldScale.Value, sldScale.Value));
                grp.Children.Add(new RotateTransform(sldRotate.Value));
                dortgen.LayoutTransform = grp;
            }
        }
    }
}

Kullanıcı Slider kontrollerindeki çubuğu hareket ettirdikçe ekran üzerindeki dörtgenin kendi ekseni üzerinde dönmesi veya büyüklüğünün değişmesi amaçlanmaktadır. Bu, aynı şekle birden fazla dönüştürme işleminin uygulanmasını gerektirmektedir. Bir başka deyişle Rectangle nesne örneğinin LayoutTransform özelliğine hem ScaleTransform hemde RotateTransform nesne örneklerinin atanması gerekmektedir. Bunun için TransformGroup adı verilen nesne örneklerinden yararlanılır. TransformGroup tipi, sahip olduğu Children özelliği ile sunduğu koleksiyon içerisinde farklı Transform tiplerini taşıyabilmektedir. Dolayısıyla TransformYap metodu içerisinde bu şekilde bir kodlama yapılmaktadır. Uygulamanın çalışma zamanındaki görüntüsü aşağıdaki Flash animasyonunda olduğu gibidir.(Flash dosyasını görebilmek için Flasy Player’ ın sisteminizde yüklü olması gerekmektedir. Dosya boyutu 220 Kb olduğundan yüklenmesi zaman alabilir.)

Böylece geldik bir makalemizin daha sonuna. Bu makalemizde WPF(Windows Presentation Foundation) uygulamalarında iki boyutlu grafik(2D Graphics) işlemlerinde kullanılabilecek şekilleri ele almaya çalıştık. Son olarak basit bir transform işleminin örnek bir dörtgen üzerinde nasıl gerçekleştirileceğine değindik. Buradaki bilgilerden yola çıkarak çok daha kolay grafik işlemleri gerçekleştirebileceğimizi ve eski Windows programlamadaki GDI+ ile zorlandığımız vakkaları daha etkin bir biçimde yapabileceğimizi görmüş bulunuyoruz. İlerleyen makalelerimizde WPF ile ilişkili başka konularada değiniyor olacağız.Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF - Grafik İşlemlerinde Şekillerin(Shapes) Kullanımı

Salı, 18 Eylül 2007 20:02 by bsenyurt
Windows Presentation Foundation(WPF) ile ilgili bir önceki makalemizde, iki boyutlu(2D) grafiklerin çizilmesi amacıyla kullanılan fırçaları(Brushes) incelemeye çalışmıştık. Bu makalemizde ise iki boyutlu şekilleri(Shapes) araştırıyor olacağız. Vektörel grafiklerde şekillerin(Shapes) büyük önemi vardır. Nitekim temel şekiller kullanılarak asıl resimler ve görüntüler kolaylıkla elde edilebilir. Bir CAD uygulamasının karmaşık çizelgelerinden, eğlenceli çocuk programlarında kullanılan vektörel grafiklere kadar pek çok alanda temel şekiller yeterli olmaktadır. Söz gelimi bir şehrin imar planlamasında kullanılacak bir programda iki boyutlu olarak düşünüldüğünde dörtgenler, daireler, elipsler, poligonlar ve düz çizgiler evlerin, yolların, arsaların, parkların ifade edilmesi için yeterlidir. Senaryolar arttırılabilir ve daha geniş alanlarda düşünülebilir. Ancak temel olarak gereken şekiller bellidir. WPF kendi bünyesinde iki boyutlu çizimlerin gerçekleştirilebilmesi amacıyla aşağıda belirtilen şekilleri(Shapes) sunmaktadır.

  • Ellips : Bu tip yardımıyla içi dolu veya boş tam daire yada elipslerin çizilmesi mümkündür.
  • Line : Düz çizgilerin çizilmesini sağlayan tiptir. Başlangıç ve bitiş koordinatları düz çizginin çizilmesi için yeterlidir.
  • Rectangle : Dört köşeli şekillerin çizilmesinde kullanılan tiptir. İçi boş veya dolu dikdörtgen yada kare gibi şekillerin çizilebilmesini sağlar.
  • Polygon : N sayıda köşeden oluşan poligonların çizilmesinde kullanılır. Bir üçgen olabileceği gibi bir çokgen de olabilir. Diğer taraftan düzgün köşeli olmayan bir poligonda oluşturulabilir. Ayrıca poligonların içi boş veya dolu olacak şekilde oluşturulabilmesi de mümkündür.
  • Polyline : Birbirlerine bitiş noktalarından bağlı bir başka deyişle uç uca eklenmiş düz çizgilerin(Line) çizilmesini sağlayan tiptir.
  • Path : Birbirlerine son noktalarından bağlı olan düz çizgi veya eğri (Curve) gibi toplu şekillerin çizidirilmesini sağlayan tiptir. Farklı şekillerin bir arada kullanılabilmesini sağlamak için geometri(Geometry) tiplerinden yararlanır.

WPF, XAML(eXtensible Application Markup Language) tabanlı bir ortam sunduğundan, grafiksel şekillerin tasarım zamanında element bazlı olarak geliştirilmeleri ve sonuçlarının görülmesi mümkündür. GDI+ mimarisinde aynı durum düşünüldüğünde sonuçların ancak çalışma zamanında(run-time) elde edilebildiği unutulmamalıdır. Bu nedenle WPF bize büyük avantaj sağlamaktadır.

Bu kısa bilgilerden sonra örneklerimizi geliştirerek şekilleri daha yakından tanımaya çalışalabiliriz. Her zamanki gibi örneklerimizi geliştirirken Visual Studio 2008 Beta 2 sürümünden yararlanıyor olacağız. Bu nedenle, final sürümünde özellikle IDE bazlı bazı değişiklikler olma ihtimali olduğunu baştan belirtelim. İlk örneğimizde Ellips tipinden yararlanıyor olacağız. Bu amaçla Window nesnemizin XAML içeriğini aşağıdaki gibi tasarladığımızı düşünelim.

<Window x:Class="GrafiklerleCalismak.Elipsler" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Elipslerin Kullanımı" Height="300" Width="300">
    <Grid>
        <Ellipse Fill="DarkRed" Width="40" Height="150" Stroke="Coral" StrokeThickness="3"/>
        <Ellipse Fill="Red" Width="150" Height="40" Stroke="Black" StrokeThickness="3" Opacity="0.75"/>
        <Ellipse Width="40" Height="40" Fill="Gold" Stroke="Black" StrokeThickness="3"/>
        <Ellipse Width="10" Height="10" Stroke="DarkBlue" StrokeThickness="2" HorizontalAlignment="Left" Margin="64,56,0,0" VerticalAlignment="Top" />
        <Ellipse Height="25" HorizontalAlignment="Right" Margin="0,56,64,0" Stroke="DarkBlue" StrokeThickness="2" VerticalAlignment="Top" Width="25" />
        <Ellipse Height="50" HorizontalAlignment="Right" Margin="0,0,64,56" Stroke="DarkBlue" StrokeThickness="2" VerticalAlignment="Bottom" Width="50" />
    </Grid>
</Window>

Bu örnekte altı adet elips çizdirilmektedir. Ellips tipinin Fill niteliği(attribute) yardımıyla dolgu deseni belirtilebilir. Bunun dışında width ve height nitelikleri eşit oldukları takdirde tam bir dairenin çizilmesi söz konusudur. Diğer hallerde ise, yatay doğrultuda veya dikey doğrultuda uzayan bir elips oluşumu söz konusu olmaktadır. Kenar çizgilerini renk ve kalınlık olarak belirlemek amacıyla Stroke ve StrokeTickness niteliklerine değer atamaları yapılmaktadır. Stroke niteliği tahmin edileceği üzere geçerli bir fırça(Brush) ile eşleştirilebilir. Buda çizginin dolu bir renk dışında desenli olabileceği hatta içinde resim barındırabileceği anlamına gelmektedir.

NOT : Stroke ve StrokeTickness nitelikleri diğer şekillerde de ye almaktadır. Bu nedenle tüm şekillerin çizgilerinin olabileceğini söyleyebiliriz.

Yukarıdaki XAML içeriğini Visual Studio 2008 Beta 2 ortamındaki çıktısı aşağıdaki gibi olacaktır.

Şekillerin bu biçimde yatay veya dikey düzlemlerde oluşturulması haricinde, açısal olaraktanda yerleştirilmesi istenebilir. Bunu bir elips üzerinde GDI+ ile gerçekleştirmek oldukça zor ve zahmetlidir. Oysaki WPF içerisinde yer alan Transform tipleri kullanılarak bu işlemler son derece kolay bir şekilde gerçekleştirilebilir.

NOT : Transoform’ dan kasıt, şeklin açısal olarak konumunun değiştirilebilmesi, büyüklüğünün ayarlanabilmesi, kendi ekseni üzerinde veya farklı bir orjine göre döndürülebilmesi, şeklin x veya y düzlemleri üzerinde yer değiştirmesi yada eğilip bükülmesi gibi aksiyonlardır.

İkinci örneğimizde bu durum incelenmektedir. Yeni penceremizin XAML içeriğinin aşağıdaki gibi olduğunu düşünelim.

<Window x:Class="GrafiklerleCalismak.ElipsTransform" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="ElipsTransform" Height="300" Width="300" Loaded="Window_Loaded">
    <Grid>
        <Ellipse Width="125" Height="40" Stroke="Red" StrokeThickness="2"/>
        <Ellipse Width="40" Height="125" Stroke="Black" StrokeThickness="2"/>
        <Ellipse Width="40" Height="125" Stroke="Red" StrokeThickness="2">
            <Ellipse.LayoutTransform>
                <RotateTransform Angle="45"/>
            </Ellipse.LayoutTransform>
        </Ellipse>
        <Ellipse Width="40" Height="125" Stroke="Black" StrokeThickness="2">
            <Ellipse.LayoutTransform>
                <RotateTransform Angle="135"/>
            </Ellipse.LayoutTransform>
        </Ellipse>
    </Grid>
</Window>

Burada dikkat edilmesi gereken en önemli nokta, Ellips.LayoutTransform elementinin içeriğidir. Bu elementin altında yer alan RotateTransform elementi içerisinde Angle niteliği ile bir açı değeri belirtilmektedir. Bu açı değeri, şeklin x,y eksenine göre farklı bir derecede döndürülmesini sağlamaktadır. Örnek XAML içeriğinde 45 derece ve 135 derecelik açılar ile döndürülmüş iki elips yer almaktadır. Bu içeriğin tasarım zamanındaki çıktısı aşağıdaki gibi olacaktır.

Atomu WPF ile daha kolay çizebildiğimizi söyleyebiliriz. Bu tip dönüştürme(Transform) işlemleri sadece RotateTransform ile sınırlı değildir. Yazımızın ilerleyen kısımlarında diğer Transform modellerinede kısaca değinmeye çalışacağız. Çok doğal olarak rotasyonların programatik olarak gerçekleştirilmesi gereken vakkalar olacaktır. Yukarıdaki atom çizelgesininin benzerini kod tarafında oluşturmak istersek, element ve niteliklerin karşılığı olan uygun sınıf(class) ve özellikleri(property) kullanmak yeterli olacaktır. Üçüncü örneğimizde bu durum incelenmektedir. Bu amaçla yeni penceremizin XAML ve kod içeriğini aşağıdaki gibi tasarladığımızı düşünelim.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodlaElipsTransform" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Kod Yardımıyla Elips Transform" Height="300" Width="300">
    <Grid Name="grdEllips">
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodlaElipsTransform : Window
    {
        private void ElipsCiz(Ellipse elps, int width, int height, int angle,Color color)
        {
            elps.Width = width;
            elps.Height = height;
            elps.Stroke = new SolidColorBrush(color);
            elps.StrokeThickness = 2;
            elps.LayoutTransform = new RotateTransform(angle);
            grdEllips.Children.Add(elps);
        }
        private void Cizdir()
        {
            ElipsCiz(new Ellipse(), 125, 40, 270,Colors.Red);
            ElipsCiz(new Ellipse(), 40, 125, 90,Colors.Gold);
            ElipsCiz(new Ellipse(), 125, 40, 45, Colors.DarkBlue);
            ElipsCiz(new Ellipse(), 125, 40, 135,Colors.Lavender);
        }

        public KodlaElipsTransform()
        {
            InitializeComponent();
            Cizdir();
        }
    }
}

Bir kaçtane elipsi farklı renk, çizgi, çizgi kalınlığı ve açıda çizdirmek istediğimizden yardımcı olacak ElipsCiz isimli bir metod tasarlanmıştır. Bu metod, parametre olarak gelen Ellips nesne örneğini alıp, genişlik(Width), yükseklik(Height), Çizgi rengi(Stroke), Çizgi kalınlığı(StrokTickness) ve rotasyon için gerekli açı(Angle) değerlerini set etmektedir. Dikkat edilecek olursa, rotasyon işlemi için RotateTransform tipine ait bir nesne örneklenmekte ve yapıcı metoda(Constructor) açı değeri verilmektedir. Sonrasında ise bu nesne örneği, Ellips nesne örneğinin LayoutTransform özelliğine atanmaktadır. Söz konusu kod parçası yürütüldüğünde çalışma zamanında(run-time) aşağıdakine benzer bir ekran görtüntüsü ile karşılaşılır.

Yine kod yardımıyla elips çizdirmeye örnek olması açısından aşağıdaki pencerede(Window) göz önüne alınabilir.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodYardimiylaElips" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="KodYardimiylaElips" Height="300" Width="300">
    <Grid>
        <Grid Name="grdTahta" Margin="0,74,0,8" Background="Gold" />
        <Button Height="23" HorizontalAlignment="Left" Margin="10,20,0,0" Name="btnCiz" VerticalAlignment="Top" Width="75" Click="btnCiz_Click">Çizdir</Button>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaElips : Window
    {
        private void ElipsCiz()
        {
            grdTahta.Children.Clear();
            Random rnd=new Random();
            for (int i = 0; i < 3; i++)
            {
                Ellipse elps = new Ellipse();
                elps.Width = rnd.Next(50, 100);
                elps.Height = rnd.Next(50, 100);
                elps.Stroke = new SolidColorBrush(Colors.Black);
                elps.StrokeThickness = rnd.Next(1, 5);
                grdTahta.Children.Add(elps);
            }
        }
   
        public KodYardimiylaElips()
        {
            InitializeComponent();
        }
   
        private void btnCiz_Click(object sender, RoutedEventArgs e)
        {
            ElipsCiz();
        }
    }
}

Bu kez bir düğmeye basılması ile rastgele üretilen değerlere göre elipslerin çizdirilmesi sağlanmaktadır. Bu amaçla 50 ile 100 arasında rastgele genişlik ve yükselik değerleri elde edebilmek için meşhur Random sınıfına ait nesne örneğinden ve Next metodundan yararlanılmaktadır. Çoğu zaman oygun programlamada şekilsel olarak bazı oyun karakterlerinin saha üzerinde rastgele konumlarda çıkması istenebilir. İşte bu noktada Random sınıfına ait metodlar ve WPF ile gelen yeni şekil çizme teknikleri işimizi oldukça kolaylaştırmaktadır.

Kod yardımıyla gerçekleştirilen örneklerde dikkat edilmesi gereken noktalardan birisi, oluşturulan şekillerin mutlaka bir taşıyıcıya eklenmiş olmalarıdır. Söz gelimi yukarıdaki örneklerde Ellips nesne örnekleri Grid bileşenine alt element olarak, Children özelliğinin Add metodundan yararlanılarak eklenmektedir. Son pencereye(Window) ilişkin olarak aşağıdaki ekran görüntüsünde çalışma zamanında(run-time) oluşabilecek bir örnek çıktı yer almaktadır.

Sıradaki örneğimizde poligonların nasıl çizilebileceğini incelemeye çalışacağız. Poligonlar, çok sayıda köşeden oluşabilen ve kapalı olarak tasarlanabilen şekillerdir. Poligonlar sayesinde basit bir üçgen çizilebileceği gibi bir onaltıgen’ de çizilebilir. Yada düzensiz bir kapalı şekil oluşturulabilir. Burada önemli olan köşe noktalarının belirlenmesidir. Köşe noktalarının belirlenmesinde Point tipinden yararlanılır. Point tipi x ve y koordinatlarını bünyesinde taşımaktadır. Polygon tipi, köşe noktalarını Points isimli bir koleksiyonda taşımaktadır. Şimdi aşağıdaki XAML içeriğini göz önüne alalım.

<Window x:Class="GrafiklerleCalismak.PolygonKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Polygon Kullanımı" Height="300" Width="403">
    <Grid>
        <Polygon Fill="Gold" FillRule="Nonzero" Stroke="Black" StrokeThickness="2">
            <Polygon.Points>
                <Point X="20" Y="20" />
                <Point X="120" Y="20" />
                <Point X="120" Y="120" />
                <Point X="220" Y="120" />
                <Point X="220" Y="220" />
            </Polygon.Points>
        </Polygon>
        <Polygon Fill="LightSkyBlue" FillRule="Nonzero" Stroke="Black" StrokeThickness="2">
            <Polygon.Points>
                <Point X="20" Y="20"/>
                <Point X="120" Y="20"/>
                <Point X="120" Y="120"/>
                <Point X="220" Y="120"/>
                <Point X="220" Y="220"/>
            </Polygon.Points>
            <Polygon.LayoutTransform>
                <RotateTransform Angle="-45"/>
            </Polygon.LayoutTransform>
        </Polygon>
    </Grid>
</Window>

Örnekte iki adet Polygon elementi tanımlanmıştır. Bunlardan ikincisine -45 derecelik bir açısal döndürme işlemi uygulanmıştır. Her iki Polygon nesne örneğinin köşe noktaları Polygon.Points alt elementi(child element) içerisinde yer alan Point alt elementleri ile belirtilmektedir. Polygon nesnelerinin kenar çizgileri Stroke ve StrokTickness niteliklerine atanan değerler yardımıyla set edilmiştir. Diğer taraftan Polygon’ ların dolgu rengi Fill özelliklerine atanan standart fırça(Brush) renkleri ile ayarlanmaktadır. Söz konusu XAML içeriğinin Visual Studio 2008 Beta 2 ortamındaki tasarım zamanı(design-time) çıktısı ise aşağıdaki gibi olacaktır.

Bir poligon kod yardımıylada oluşturulabilir. Sıradaki örneğimizde bir üçgenin kod yardımıyla oluşturulması ve düğmeye basılaraktan onbeşer derecelik artan açılar ile döndürülmesi örneklenmektedir. Bu amaçla yeni penceremize(Window) ait XAML ve kod içeriklerini aşağıdaki gibi tasarladığımızı düşünelim.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodYardimiylaPolygonKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Kod Yardımıyla Polygon" Height="300" Width="300">
    <Grid Name="grdTahta">
        <Button Height="23" HorizontalAlignment="Left" Margin="8,19,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click">Çevir</Button>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Timers;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaPolygonKullanimi : Window
    {
        Polygon plgy;
        int sayac = 1;

        private void Cizdir()
        {
            plgy = new Polygon();
            plgy.Points.Add(new Point(50, 50));
            plgy.Points.Add(new Point(150, 50));
            plgy.Points.Add(new Point(150, 150));
            plgy.Stroke = new SolidColorBrush(Colors.LightSalmon);
            plgy.StrokeThickness = 2;
            grdTahta.Children.Add(plgy);
        }

        public KodYardimiylaPolygonKullanimi()
        {
            InitializeComponent();
            Cizdir();
        }
   
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            plgy.LayoutTransform = new RotateTransform(sayac*15);
            sayac++;
        }
    }
}

Penceremizin yapıcı metodu(constructor) içerisinde Cizdir metodu ile bir Polygon nesne örneği oluşturulmakta ve Grid kontrolünün alt elementi olarak eklenmektedir. Polygon nesne örneği bir üçgeni temsil edeceğinden Points koleksiyonunda sadece üç nokta(Point) eklemesi yapılmıştır. Döndürme işleminin gerçekleştirildiği yer düğmenin Click olay(event) metodudur. Burada dikkat edilecek olursa yine LayoutTransform özelliğine bir değer ataması yapılmaktadır. Bu atama sırasında yeni bir RotateTransform nesnesi örneklenmekte ve parametre olarak artan bir açı değer verilmektedir. Uygulama test edildiğinde çalışma zamanında aşağıdaki Flash animasyonunda yer alan çıktı elde edilecektir. (Flash dosyasını görebilmek için Flasy Player’ ın sisteminizde yüklü olması gerekmektedir.)

Şimdiki örneğimizde düz çizgileri nasıl çizdirebileceğimizi incelemeye çalışacağız. Düz çizgi için Line tipi kullanılmaktadır. Bir çizgi için belkide en önemli özellikler Stroke, StrokeTickness, X1, X2, Y1 ve Y2’ dir. X ve Y özellikleri yardımıyla çizginin başlangıç ve bitiş noktaları belirlenir. Örneğin aşağıdaki XAML içeriğini ele alalım.

<Window x:Class="GrafiklerleCalismak.LineKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Line Kullanimi" Height="211" Width="237">
    <Grid>
        <Line Stroke="RosyBrown" StrokeThickness="2" X1="10" Y1="10" X2="50" Y2="50"/>
        <Line Stroke="Brown" StrokeThickness="3" X1="50" Y1="50" X2="100" Y2="50"/>
        <Line Stroke="BurlyWood" StrokeThickness="4" X1="100" Y1="50" X2="50" Y2="140"/>
        <Line Stroke="CadetBlue" StrokeThickness="5" X1="50" Y1="140" X2="180" Y2="50"/>
    </Grid>
</Window>

Bu içeriğin tasarım zamanındaki(design-time) çıktısı aşağıdaki gibi olacaktır.

Dikkat edileceği üzere farklı kalınlık, renk ve lokasyonlarda yer alan çizgiler elde edilmektedir. Çizgiler özellikle kod tarafındada zaman zaman ele alınırlar. Söz gelimi bir harita üzerinden bir şehirden bir şehire doğru olabilecek hava yolu rotalarının belirlenmesi amacıyla çizgilerden yararlanılabilir. Bunu çok basit olarak sembolize etmek amacıyla aşağıdaki XAML ve kod içeriğini göz önüne alabiliriz.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.KodYardimiylaLineKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Kod Yardımıyla Line Kullanımı" Height="249" Width="422" WindowStyle="SingleBorderWindow">
    <Grid Name="grdTahta">
        <Image Name="imgHarita" MouseDown="imgHarita_MouseDown" MouseUp="imgHarita_MouseUp" Source="map_world_destination.gif" />
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaLineKullanimi : Window
    {
        Point basilanNokta;

        public KodYardimiylaLineKullanimi()
        {
            InitializeComponent();
        }

        private void imgHarita_MouseDown(object sender, MouseButtonEventArgs e)
        {
            basilanNokta=e.GetPosition(grdTahta);
        }

        private void imgHarita_MouseUp(object sender, MouseButtonEventArgs e)
        {
            Line yol = new Line();
            yol.Stroke = new SolidColorBrush(Colors.Red);
            yol.StrokeThickness = 2;
            yol.X1 = basilanNokta.X;
            yol.Y1 = basilanNokta.Y;
            Point bitisNoktasi = e.GetPosition(grdTahta);
            yol.X2 = bitisNoktasi.X;
            yol.Y2 = bitisNoktasi.Y;
            grdTahta.Children.Add(yol);
        }       
    }
}

Bu örnekte haritayı göstermesi için bir Image kontrolü kullanılmaktadır. Image kontrolünün source özelliğine atanan değer ile arka plan dünya haritası olarak gösterilmektedir. Kullanıcılar çalışma zamanında mouse’ un tuşuna basıp bir noktadan başka bir noktaya gittiklerinde ve mouse’ un tuşunu bıraktıklarında Line nesne örneği oluşturulmaktadır. Bunun için Image kontrolü üzerinde mouse tuşuna basılma ve bırakılma anlarının yakalanması gerekir. Söz konusu anlar aşina olduğumuz MouseDown ve MouseUp olay metodlarında yakalanırlar. GDI+ ile aynı işlemleri nasıl yaptığımızı hatırlarsak eğer, kontrolün MouseDown olayına gelen parametre ile X ve Y değerlerini ayır ayrı aldığımızı görürüz. Burada durum biraz daha farklıdır. Nitekim MouseUp ve MouseDown olay metodlarında yer alan MouseButtonEventArgs parametresi, mouse tuşuna basıldığında o noktadaki X ve Y koordinatlarını yeni bir Point tipi olarak geriye döndürmektedir. GetPosition metodu X ve Y koordinatları, üzerinden alınmak istenen kontrolüde parametre olarak alır. Örnekte bu parametreye Grid kontrolünün referansı verilmiştir. Dolayısıyla Grid kontrolündeki X ve Y değerleri elde edilebilmektedir.

Mouse üzerinde basılan tuş bırakıldığında ise çizginin çizilme işlemi gerçekleştirilmektedir. Line nesne örneğinin X1 ve Y1 değerleri tahmin edileceği gibi, MouseDown içerisinde yer alan GetPosition ile elde edilen basilanNokta değişkeninden gelmektedir. Çizginin son noktası ise bu kez MouseUp olay metodu içerisindeki GetPosition çağrısı ile alınmakta ve Line nesne örneğinin X2 ve Y2 değerlerine aktarılmaktadır. Son olarak oluşturulan çizgi, Grid bileşenine alt element olarak eklenmektedir. Bu ekleme işleminin bize getirdiği önemli bir avantaj vardır. Yine GDI+ ile Windows programlama yaptığımızı düşünecek olursak, aynı senaryoda ekrana çizgiler çizdirmek için Graphics nesnesninin DrawLine metodundan yararlanıldığını görürüz. Ne varki bu metod hep son çizginin çizilmesine öncekilerin ise kaybolmasına neden olmaktadır. Oysaki WPF mimarisinde şekiller bir taşıyıcının alt elementi olarak eklendiklerinden son çizilen şekilden öncekiler ekrandan kaybolmamaktadır. Bu durumu GDI+ ile Windows programlamada gerçeklemek için WPF’tekine benzer bir mantık ile hareket edilmekte ve ekranda duran çizgilerin sürekli hatırlanması için koleksiyonalardan yararlanılması gerekmektedir. Yukarıdaki kodun çalışma zamanındaki ekran çıktısı aşağıdaki Flash animasyonunda olduğu gibidir.(Flash dosyasını görebilmek için Flasy Player’ ın sisteminizde yüklü olması gerekmektedir.)

Bu örneği daha da geliştirmeye çalışmanızı öneririm. Oldukça fazla eksiği var. Söz gelimi, mouse tuşuna basıp sürüklerken farklı renkte bir çizginin çıkartılarak gidilen rotanın gösterilmesi sağlanabilir. Mouse bırakıldığında ise asıl rengini alan rota ortaya çıkar. Üstelik örnek kodda sağ tuşa veya sol tuş kontrolü yapılmamıştır. Belkide klavyeden tuş kombinasyonları katarak sadece düz çizgi değil eğrilerin çizdirilmesinide sağlayabiliriz. Bu örneğin geliştirilmesini siz değerli okurlarıma bırakıyorum.

Gelelim Path bileşenine. Bu tip aslında kendi içerisinde birden fazla düz çizgi veya eğriyi barındırabilecek şekilde tasarlanmıştır. Temelde şekiller birbirleriyle uç uca eklenecen şekilde yeni bir grafik oluşturmaktadırlar. Dilerseniz örnek üzerinden ilerleyerek konuyu daha iyi anlamaya çalışalım. Bu amaçla aşağıdaki gibi bir XAML içeriği oluşturduğumuzu göz önüne alalım.

<Window x:Class="GrafiklerleCalismak.PathKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White"
Title="Path Kullanimi(Birbirlerine Bağlı Farklı Şekiller)" Height="261" Width="321">
    <Grid>
        <Path Stroke="Black" StrokeThickness="2">
            <Path.Data>
                <PathGeometry>
                    <PathFigure>
                        <BezierSegment Point1="0,0" Point2="10,120" Point3="70,40"/>
                        <ArcSegment SweepDirection="Clockwise" Point="150,125" Size="50,50" IsLargeArc="True" RotationAngle="60"/>
                        <LineSegment Point="240,40"/>
                        <QuadraticBezierSegment Point1="35,135" Point2="75,75"/>
                        <PolyLineSegment>
                            <PolyLineSegment.Points>
                                <Point X="10" Y="10"/>
                                <Point X="50" Y="75"/>
                            </PolyLineSegment.Points>
                        </PolyLineSegment>
                        <PolyBezierSegment>
                            <PolyBezierSegment.Points>
                                <Point X="100" Y="100"/>
                                <Point X="150" Y="150"/>
                                <Point X="75" Y="180"/>
                            </PolyBezierSegment.Points>
                        </PolyBezierSegment>
                    </PathFigure>
                </PathGeometry>
            </Path.Data>
        </Path>
    </Grid>
</Window>

Path elementi içerisinde farklı şekillerin uç uca eklenmesi işini geometri tipleri üstlenmektedir. Bu geometri tipleride kendi içlerinde segmentler halinde şekilleri barındırmaktadır. Kullanılabilecek olan geometri tipleri aşağıdaki gibidir.

Örnekte söz konusu geometri tiplerinden PathGeometry kullanılmaktadır. PathGeometry elementinin altında yer alan tüm şekiller Segment anahtar kelimesi ile bitmektedir. Buna göre, BezierSegment ile başlayan şekiller dizisi sırasıyla, ArcSegment, LineSegment, QuadraticBezierSegment, PolyLineSegment ve PolyBezierSegment ile devam eder. Tüm bu alt elementleri farklı nitelikleri ile değişik çizgilerin oluşturulması sağlanmaktadır. BezierSegment elementinin nitekikleri sayesinde üç noktadan bükülmüş bir eğri çizimi yapılabilmektedir. ArcSegment elementinin nitelikleri ile, başlangıç koordiantları, genişlik yükseklik değerleri, eğilme açısı ve saat yönü yada saatin ters yönünde çizilecek yaylar oluşturulabilmektedir. Burada PolyLineSegment’ i altında belirtilen noktalar uç uca bağlı düz çizgilerin oluşturulmasını sağlarken, PolyBezierSegment elementi altındaki noktalarda, uç uca eklenmiş eğrilerin oluşturulmasını sağlamaktadır. Örneğin tasarım zamanındaki çıktısı aşağıdaki gibi olacaktır.

Dikkat edilecek olursa, tüm çizgiler ister eğri ister düz olsunlar, uç uca eklenerekten birleştirilmiştir. Bu nedenle Path tipini örneğin harita gibi arka planlarda yolların birleştirilmesi amacıyla kullanabiliriz.

Bu yazımızda son olarak basit anlamda dönüştürme(Transform) işlemlerine bakıyor olacağız. Transform denilince aklımıza gelmesi gerekenler bir şeklin yön, büyüklük, düzlemsel koordinat gibi değerlerinin değiştirilmesidir. Bu sayede bir şekli herhangibir açıda döndürebilir, ebatlarını ayarlayabilir yada herhangibir düzlem üzerinde öteleyebiliriz. Transform işlemlerinde beş farklı tip rol oynamaktadır. Bu tipler aşağıdaki gibidir.

  • RotateTransform : Şeklin belirtilen bir açıda, kendi ekseninde yada belirtilen orijine göre farklı bir eksende döndürülmesini sağlamak için kullanılır. Söz gelimi bir şeklin farklı açılardan gösterilmesinin sağlanmasında önemli rol oynamaktadır.
  • ScaleTransform : Şeklin ebatlarının eşit oranda yada farklı oranlarda arttırılması yada azaltılmasında kullanılır. Örneğin Zoom işlemlerinde bu tip çok faydalı olacaktır.
  • SkewTransform : Şeklin bükülmesini yada eğilmesini sağlamak amacıyla kullanılan tiptir.
  • TranslateTransform : Şeklin x veya y düzlemleri üzerinde farklı noktalara ötelenmesi amacıyla kullanılan tiptir.
  • MatrixTransform : Resim işlemede önemli bir yere sahip olan matris algoritmalarının iki boyutlu şekiller üzerinde de uygulanabilmesini sağlayan tiptir. Diğer Transform tipleri ile gerçekleştirilmesi zor olan dönüştürmelerde kullanılmaktadır. Bu tipi ilerleyen yazılarımızda ele almaya çalışacağız.

Şimdi basit bir örnek ile RotateTransform ve ScaleTransform tiplerinin nasıl kullanılabileceğini incelemeye çalışalım. Bu amaçla XAML ve kod içeriklerini aşağıdaki gibi geliştirdiğimizi düşünelim.

XAML içeriği;

<Window x:Class="GrafiklerleCalismak.TransformKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Transform Kullanımı" Height="292" Width="330">
    <Grid Name="grdTahta" Width="309" Height="253">
        <Rectangle Fill="Gold" Stroke="Black" StrokeThickness="2" Name="dortgen" Width="100" Height="40" />
        <Slider Minimum="1" Maximum="5" ValueChanged="sldScale_ValueChanged" Height="21" Margin="110,14,89,0" Name="sldScale" VerticalAlignment="Top" />
        <Label Height="23" HorizontalAlignment="Left" Margin="0,12,0,0" Name="label1" VerticalAlignment="Top" Width="92">Scale Transform</Label>
        <Slider Height="21" Margin="110,49,89,0" Maximum="360" Minimum="0" Name="sldRotate" ValueChanged="sldRotate_ValueChanged" VerticalAlignment="Top" />
        <Label Height="23" HorizontalAlignment="Left" Margin="0,47,0,0" Name="label2" VerticalAlignment="Top" Width="92">Rotate Transform</Label>
    </Grid>
</Window>

Kod içeriği;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class TransformKullanimi : Window
    {
        public TransformKullanimi()
        {
            InitializeComponent();
        }

        private void sldScale_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            TransformYap();
        }

        private void sldRotate_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            TransformYap();
        }

        private void TransformYap()
        {
            if (sldRotate != null
                && sldScale != null)
            {
                TransformGroup grp = new TransformGroup();
                grp.Children.Add(new ScaleTransform(sldScale.Value, sldScale.Value));
                grp.Children.Add(new RotateTransform(sldRotate.Value));
                dortgen.LayoutTransform = grp;
            }
        }
    }
}

Kullanıcı Slider kontrollerindeki çubuğu hareket ettirdikçe ekran üzerindeki dörtgenin kendi ekseni üzerinde dönmesi veya büyüklüğünün değişmesi amaçlanmaktadır. Bu, aynı şekle birden fazla dönüştürme işleminin uygulanmasını gerektirmektedir. Bir başka deyişle Rectangle nesne örneğinin LayoutTransform özelliğine hem ScaleTransform hemde RotateTransform nesne örneklerinin atanması gerekmektedir. Bunun için TransformGroup adı verilen nesne örneklerinden yararlanılır. TransformGroup tipi, sahip olduğu Children özelliği ile sunduğu koleksiyon içerisinde farklı Transform tiplerini taşıyabilmektedir. Dolayısıyla TransformYap metodu içerisinde bu şekilde bir kodlama yapılmaktadır. Uygulamanın çalışma zamanındaki görüntüsü aşağıdaki Flash animasyonunda olduğu gibidir.(Flash dosyasını görebilmek için Flasy Player’ ın sisteminizde yüklü olması gerekmektedir. Dosya boyutu 220 Kb olduğundan yüklenmesi zaman alabilir.)

Böylece geldik bir makalemizin daha sonuna. Bu makalemizde WPF(Windows Presentation Foundation) uygulamalarında iki boyutlu grafik(2D Graphics) işlemlerinde kullanılabilecek şekilleri ele almaya çalıştık. Son olarak basit bir transform işleminin örnek bir dörtgen üzerinde nasıl gerçekleştirileceğine değindik. Buradaki bilgilerden yola çıkarak çok daha kolay grafik işlemleri gerçekleştirebileceğimizi ve eski Windows programlamadaki GDI+ ile zorlandığımız vakkaları daha etkin bir biçimde yapabileceğimizi görmüş bulunuyoruz. İlerleyen makalelerimizde WPF ile ilişkili başka konularada değiniyor olacağız.Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF - Grafik İşlemlerinde Fırçaların(Brushes) Kullanımı

Salı, 11 Eylül 2007 19:47 by bsenyurt
Windows uygulamalarında özellikle .Net tarafında vektörel grafik işlemleri için çoğunlukla GDI+ alt yapısı(infrastructure) kullanılmaktadır. Ancak Windows Presentation Foundation(WPF) mimarisinde öne çıkan özelliklerden biriside grafik anlamdaki yeteneklerin son derece geliştirilmiş olmasıdır. Kişisel görüşüm grafik yönündeki özelliklerin gelişmesi dışında bu tekniklerin element bazında uygulanabiliyor olması son derece önemli ve yerindedir. Yani XAML tarafında grafikler ile daha kolay çalışabilme imkanının gelmiş olması önemlidir. Bu yazımız ile birlikte WPF içerisindeki grafiksel öğeleri incelemeye başlıyor olacağız. Grafik konusu oldukça geniş olduğundan bir iki yazı dizisi halinde incelememiz çok daha yerinde olacaktır. Hepimizin GDI+ alt yapısından aşina olduğumuz teknikler aslında WPF içinde geçerlidir. Ancak önemli olan temel bazı kavramlar vardır. Bu kavramlar grafik işlemlerinde önemli bir yere sahiptir. Söz konusu kavramlar ve açıklamaları aşağıda maddeler halinde sunulmaktadır.

  • Brushes: Bir alanın farklı biçimlerde boyanabilmesi için fırçalara ihtiyaç vardır. Burada tek renk tonlaması(Solid), gradyan tonlamalar(Gradients), değişik tipteki desenler(Patterns) veya resimler(Image) bir şeklin boyanmasında kullanılabilir. Hatta var olan Windows bileşenlerini, görsel materyalleri(örneğin video dosyalarını) şekillerin içerisini doldurmak amacıyla kullanabiliriz. Windows Presentation Foundation içersinde bu anlamda tasarlanmış pek çok hazır Brush tipi yer almaktadır. LinearGradientBrush yada RadialGradientBrush tipleri bunlara örnek olarak verilebilir.
  • Shapes: Grafik uygulamalarının olmassa olmaz parçalarından birisi de şekillerdir. Kare, dikdörtgen, daire, elips, yay, çizgi vb... bu anlamda göz önüne alınabilir. WPF, iki boyutlu (2D) şekilleri doğrudan destekleyen tipler içermektedir.
  • Transformations: Grafik öğelerinin ekran üzerinde döndürülmelerinin(Rotation), ebatlarının ayarlanmasının(Scaling) sağlanması ile ilgili konuları kapsar. Söz gelimi bir karenin kendi ekseninde dönmesi(Rotation) ve büyüklüğünün ayarlanması bu konuya örnek olarak verilebilir.
  • Imaging: Bitmap resimlerin farklı formatlara dönüştürülmesi veya resimler üzerinde bilinen bazı efektlerin uygulanması gibi konuları kapsar. Söz gelimi bir resmin daha bulanık gösterilmesinin sağlanması bu konuya örnek olarak verilebilir.
  • Animations: Grafik nesneleri üzerinde bazı animasyonların yapılabilmesi için gereken teknikleri içeren kavramdır.

İzleyen örneklerimizi yine Visual Studio 2008 Beta 2 sürümünde tasarladığımızı baştan belirtelim. Bu nedenle final sürümünde değişen bazı kullanım kolaylıkları olabilir. Elbette artık relase edilmiş olan WPF içinde IDE bazlı bir değişiklik olmayacağını söyleyebiliriz. Dilerseniz basit bir örnek ile başlayalım. İlk olarak aşağıda XAML(eXtensible Application Markup Language) içeriği belirlenen bir pencere(Window) geliştirelim.

Merhaba Fırçalar;

<Window x:Class="GrafiklerleCalismak.MerhabaFircalar" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="Fill, Transparant Fill, SolidFillBrush Kullanımı" Height="300" Width="300">
<Grid>
    <Rectangle Fill="Goldenrod">
         <Rectangle.Height>45</Rectangle.Height>
         <Rectangle.Width>45</Rectangle.Width>
    </Rectangle>
    <Rectangle Fill="#99FFCC66" Width="40" HorizontalAlignment="Left" Margin="94,81,0,81" />
    <Ellipse Margin="78,130,54,42">
        <Ellipse.Fill>
            <SolidColorBrush Color="BlueViolet" Opacity="0.6"/>
        </Ellipse.Fill>
    </Ellipse>
    <Rectangle HorizontalAlignment="Right" Width="82" Margin="0,58,54,0" Height="45" VerticalAlignment="Top">
        <Rectangle.Fill>
            <SolidColorBrush>
                <SolidColorBrush.Color>
                    <Color A="90" R ="50" G="50" B="150"/>
                </SolidColorBrush.Color>
            </SolidColorBrush>
        </Rectangle.Fill>
    </Rectangle>
</Grid>
</Window>

Bu örneğin tasarım zamanındaki(Design Time) çıktısıda aşağıdaki gibi olacaktır.

Bu örnekte dört farklı şekil yer almaktadır. İlk dörtgenimiz Fill niteliğinde bilinen bir renk tanımlaması ile oluşturulmuş Rectangle nesne örneğidir. Bu dörtgen aslında SolidColorBrush için en temel örnektir. SolidColorBrush, bir şeklin içini boşluk bırakmayacak şekilde doldurmak üzere tasarlanmış bir tiptir. Örnekte bu açık bir şekilde belirtilmesede Fill niteliğine atanan renk değeri bir SolidColorBrush oluşumunu sağlamıştır.

<Rectangle Fill="Goldenrod">

İkinci dörtgende ise Fill niteliğine hexadecimal olarak değer ataması aşağıdaki gibi gerçekleştirilmiştir.

<Rectangle Fill="#99FFCC66" Width="40" HorizontalAlignment="Left" Margin="94,81,0,81" />

Yanlız burada dikkat edilmesi gereken bir nokta vardır. Bu da ilk iki hanededeki değerlerdir. Bu değerler 00 ile FF arasında olabilien alfa (Alpha) değeridir. Bir başka deyişle şeklin saydamlığının(Transparancy) belirlenmesi için kullanılır. 00 olması tam saydamlık anlamına gelir.

Üçüncü dörtgenin çizimi sırasında SolidColorBrush elementi açık bir şekilde aşağıdaki gibi kullanılmıştır.

<Rectangle.Fill>
            <SolidColorBrush>
                <SolidColorBrush.Color>
                    <Color A="90" R ="50" G="50" B="150"/>

Bu kullanıma göre dolgu renginin belirlenmesinde RedGreenBlue kombinasyonuna ve saydamlık içinde Alpha değerine bakılmaktadır. Bu değerler Color elementi içerisinde birer nitelik(attribute) yardımıyla belirlenmektedir.  SolidColorBrush elementinin kullanılması halinde, transformasyon işlemlerinin gereçekleştirilmesi dahada kolay olmaktadır. Bu sebepten transformasyon işlemlerinin söz konusu olduğu durumlarda SolidColorBrush elementinin açık bir şekilde örnekteki gibi kullanılması önerilmektedir. Son olarak ilk örneğimizde dörtgenden farklı olarak bir elips(Ellipse) şekli çizdirilmiştir.

<Ellipse.Fill>
            <SolidColorBrush Color="BlueViolet" Opacity="0.6"/>

Elipsin dolgu rengini belirlemek içinse Ellipse.Fill elementi altında SolidColorBrush isimli alt element(Child Element) kullanılmıştır. Color niteliğine atanan değer ile fırça rengi ve Opacity niteliğine atanan değer ilede şeffaflık değeri bildirimiştir. Bu örnekteki işlemlerin çoğu kod tarafında da gerçekleştirilebilir. Söz gelimi elipsin çizimi ve doldurulmasını aşağıdaki kodlar yardımıyla sağlayabiliriz.

public partial class MerhabaFircalarKodIle : Window
{
    private void SekilleriCiz()
    {
        Ellipse elips = new Ellipse();
        elips.Margin = new Thickness(78, 130, 54, 42);
        SolidColorBrush firca = new SolidColorBrush(Colors.BlueViolet);
        firca.Opacity = 0.6;
        elips.Fill = firca;
        grdAlan.Children.Add(elips);
    }
    public MerhabaFircalarKodIle()
    {
        InitializeComponent();
        SekilleriCiz();
    }
}

Elbette bu kez sonuçlar tasarım zamanında(Design Time) değil, çalışma zamanında(Run Time) görülebilecektir. Aşağıdaki ekran görüntüsünde bu durum gösterilmektedir. Buradan şu sonuca bir kez daha varabiliriz; XAML deki element ve niteliklerin karşılıkları kod tarafında sınıf ve özelliklere denk gelmektedir.

Gradyan Dolgular (Gradient Brush);

İlk örnekte basit olarak SolidColorBrush kullanımına değinmeye çalıştık. Dolgu işlemlerinde popüler olan kullanımlardan biriside Gradyan efektlerin verilmesidir. Bu renkler arasında yumuşak geçişlerin oluşmasına ve bu sayede daha güzel dolguların görünmesine neden olmaktadır. Böyle bir amacı gerçekleştirmek için WPF içerisinde çeşitli tipler yer almaktadır. Sıradaki örneğimizde bu durumu incelemeye çalışacağız. Bu amaçla aşağıdaki XAML içeriğini göz önüne alabiliriz.

<Window x:Class="GrafiklerleCalismak.LinearGradientFirca" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="LinearGradientBrush ve RadialGradientBrush Kullanımı" Height="304" Width="362">
<Grid>
    <Rectangle Margin="16,14,21,0" Height="69" VerticalAlignment="Top">
        <Rectangle.Fill>
            <LinearGradientBrush>
                <GradientStop Color="Blue" Offset="0"/>
                <GradientStop Color="Red" Offset="0.25"/>
                <GradientStop Color="White" Offset="0.5"/>
                <GradientStop Color="Black" Offset="0.75"/>
                <GradientStop Color="Gold" Offset="1"/>
            </LinearGradientBrush>
        </Rectangle.Fill>
    </Rectangle>
    <Rectangle Margin="96,101,101,18">
        <Rectangle.Fill>
            <RadialGradientBrush GradientOrigin="0.1,0.75">
                <RadialGradientBrush.GradientStops>
                    <GradientStop Color="Gold" Offset="0"/>
                    <GradientStop Color="Black" Offset="0.50"/>
                    <GradientStop Color="DarkSlateGray" Offset="1"/>
                </RadialGradientBrush.GradientStops>
            </RadialGradientBrush>
        </Rectangle.Fill>
    </Rectangle>
</Grid>
</Window>

Bu pencerenin tasarım zamanındaki(Design Time) çıktısı aşağıdaki gibi olacaktır.

Burada üst taraftaki dörtgen içeriği LienarGradientBrush ile, alttaki dörtgen ise RadialGradientBrush fırçaları ile doldurulmuştur. Her iki fırçanında ortak noktalarına bakıldığında farklı renkler ve geçiş noktalarının belirlenmesi amacıyla GraidentStop elementlerine başvurulduğu görülmektedir. Bu elementler renkleri ve bunların geçiş yerlerinin neresi olduğunu belirtmek amacıyla kullanılmaktadır. GradientStop tipine ait nesne örnekleri, GradientStopCollections adı verilen özel bir koleksiyonda tutulmaktadır. Diğer taraftan ikinci şelilde odak noktası dikkat edileceği üzere tam merkez değildir. Bunun belirlenmesi için RadialGradientBrush elementinin GradientOrigin niteliğinden(attribute) yararlanılmaktadır.

Kod Tarafından Fırça(Shape) Kullanımı;

Şu ana kadar geliştirilen örneklerde şekillerin oluşturulması ve içlerinin doldurulması gibi işlemlerde çoğunlukla XAML elementlerinden ve niteliklerinden yararlanılmıştır. Bu geliştirici açısından tasarım zamanı(Design Time) için büyük bir esnekliktir. Nitekim işlemlerin sonuçları anında Visual Studio arabirimi üzerinde görülebilmektedir. Diğer taraftan bazı durumlarda çalışma zamanında dinamik olarak söz konusu şekillerin çizilmesi ve doldurulması istenebilir. Örneğin .Net ile yazılmış grafik uygulamalarında bu mutlaka olması gereken bir özelliktir. Sıradaki örnekte basit olarak bir dörtgenin LinearGradientBrush sınıfı ile kod tarafındaki tiplerden ve üyelerden yararlanılarak nasıl doldurulacağı örneklenmektedir. Bu amaçla yeni bir Window arkasında aşağıdaki kodlar kullanılmıştır.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GrafiklerleCalismak
{
    public partial class KodYardimiylaGradient : Window
    {
        private void LinearGradientDortgenCiz()
        {
            // LinearGradientBrush nesne örneği oluşturulur.
            LinearGradientBrush fircam = new LinearGradientBrush();

            // C# 3.0 ile gelen Object Initializers kullanılmıştır.
            GradientStopCollection noktalar = new GradientStopCollection() { new GradientStop(Colors.WhiteSmoke, 0), new GradientStop(Colors.RosyBrown, 0.25), new GradientStop(Colors.Salmon, 0.50), new GradientStop(Colors.Silver, 0.75), new GradientStop(Colors.Gold, 0.1) };
            // Gradient Stop noktaları set edilir
            fircam.GradientStops = noktalar;
            fircam.Opacity = 0.80; // Saydamlık değeri ayarlanır

            fircam.Freeze(); //Nesnenin değiştirilemeyeceğini belirtir. Bu metod çoğunlukla performans açısından kullanılır.

            // Dörtgen nesnesi örneklenir.
            Rectangle dortgen = new Rectangle();
            // Dörtgenin genişlik ve yükseklik değerleri belirlenir.
            dortgen.Width = 200;
            dortgen.Height = 50;       
            dortgen.Margin = new Thickness(0, 0, 0, 0); // Sağ, sol kenar uzaklıkları belirlenir. Buna göre şekil formun tam ortasında olacaktır.
            dortgen.Fill = fircam; // Dortgenin içinin fircam isimli LinearGradientBrush sınıfının değerleri ile doldurulacağı belirlenir.

            grdAlan.Children.Add(dortgen); // Dortgen nesnesi Grid alanı içerisine eklenir.
        }
        public KodYardimiylaGradient()
        {
            InitializeComponent();
            LinearGradientDortgenCiz();
        }
    }
}

Dikkat edilecek olursa XAML elementlerinin tip karşılıkları kullanılarak istenen sonuçlar elde edilebilmektedir. LinearGradientBursh ile örneklenen fırçanın renk tonlarını ve geçiş noktalarını belirlemek için GradientStopCollection koleksiyonuna ait bir nesne örneklenmiştir.

NOT : Bu örnekleme işlemi sırasında C# 3.0 ile birlikte gelen nesne başlatıcılarından(Object Initializers) yararlanılmıştır. Böylece koleksiyona ait nesneyi örneklediğimiz satırda içerisinde olmasını istediğimiz GradientStop nesne örnekleride belirtilebilmiştir. Nesne başlatıcıları sadece koleksiyonlarda değil tiplerin örneklenmesi işlemlerinde de kullanılabilmektedir.

İlerleyen satırlarda fırçanın saydamlık değeri Opacity özelliği ile belirtilmektedir. Freeze metodu özel olarak performansı arttrmak adına kullanılması MSDN dökümanlarında tavsiye edilen bir metoddur. Freeze metodu ile ilgili olarak dikkat edilmesi gereken noktalardan biriside bu metod çağrısından sonra fırça özelliklerinin değiştirilemediğidir. Dortgen nesnesi Rectangle sınıfı yardımıyla oluşturulmaktadır. Dortgenin içinin hangi fırça ile doldurulacağı ise Fill özelliğine atanan değer ile belirlenir. Son olarak, dörtgenin Grid içerisinde gösterilmesini sağlamak için Children özelliğine Add metodu ile Rectangle nesne örneğinin eklenmesi sağlanmıştır. Bu işlemlere göre söz konusu pencerenin çalışma zamanındaki çıktısı aşağıdaki gibidir.

Resimleri Fırçalar ile Kullanmak;

Çok doğal olarak bir şeklin içinin resimler ile doldurulması ve farklı bir taban deseni oluşturulması istenebilir. Bunun için WPF içerisinde ImageBrush tipi kullanılmaktadır. Aşağıdaki XAML içeriğinde, dörtgenin içinin örnek bir resim ile doldurulması sağlanmaktadır.

<Window x:Class="GrafiklerleCalismak.ImageBrushKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="ImageBrushKullanimi" Height="300" Width="300">
<Grid>
    <Rectangle Margin="52,48,54,55">
        <Rectangle.Fill>
            <ImageBrush ImageSource="ArkaPlan.png" TileMode="Tile" Viewport="0,0,0.1,0.1"/>
        </Rectangle.Fill>
    </Rectangle>
</Grid>
</Window>

Bu örneğin tasarım zamanındaki(Design Time) çıktısı aşağıdaki gibi olacaktır. Örnekte ArkaPlan.png isimli bir resim kullanılmıştır.

ImageBrush elementinin dikkate değer nitelikleri ImageSource, TileMode ve ViewPort' dur. ImageSource ile tahmin edileceği gibi içerik resmi belirlenmektedir. TileMode ile resmin şekil içerisine nasıl döşeneceği belirtilir. ViewPort niteliğine atanan dört sayısal değer bulunmaktadır. Bunlardan ilk ikisi konumu, son ikiside şekil içerisinde kaç adet resim gösterileceğini belirtir. Yukarıdaki örnek göz önüne alındığında 0.1,0.1 değerlerinin verilmesi ile 10X10 resmin şekil içerisinde yer aldığı görülmektedir. TileMode değerlerinin farklı şekillerde ayarlanmasının sonuçları aşağıdakine benzerdir.

TileMode Değerine Göre Dolgu Çeşitleri

FlipX FlipY
FlipXY None

WPF Bileşenlerini Fırça ile Kullanmak;

Var olan resimleri şekillerin içerisini boyamak amacıyla kullanabileceğimiz gibi, WPF deki pek çok bileşenide dolgu efekti olarak ele alabiliriz. Bunun için VisualBrush tipinin kullanılması yeterlidir. Söz gelimi bir dörtgenin içeriğinin Button nesneleri ile doldurulmasını istediğimizi düşünelim. Bunu gerçekleştirmek için aşağıdaki XAML çıktısı göz önüne alınabilir.

<Window x:Class="GrafiklerleCalismak.VisualBrushKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="VisualBrush Kullanimi" Height="300" Width="300">
<Grid>
    <Rectangle Margin="40,50,42,50">
        <Rectangle.Fill>
            <VisualBrush TileMode="Tile" Viewport="0,0,0.2,0.2">
                <VisualBrush.Visual>
                    <Grid>
                        <Button Content="Selam!"/>
                    </Grid>
                </VisualBrush.Visual>
            </VisualBrush>
        </Rectangle.Fill>
    </Rectangle>
</Grid>
</Window>

Bu sayfanın tasarım zamanındaki(Design Time) çıktısı aşağıdaki gibidir.

WPF kontrollerinin dolgu efekti olarak kullanılabilmesini sağlayan, VisualBrush elementinin VisualBrush.Visual alt elementidir. Bu elementin içerisinde bilinen WPF taşıyıcıları(Containers) kullanılabilir. Örnekte bir Grid kontrolü ve içerisidende tek bir Button nesnesi kullanılmıştır. Tabi ViewPort niteliğinde belirtilen değerlere göre Button kontrolünün dörtgen içerisindeki yerleşim sayısı değişecektir. Aynen ImageBrush tipinde olduğu gibi TileMode niteliğinin değerine göre yerleşimler aşağıdaki gibi farklı biçimlerde olabilir.

TileMode Değerine Göre Dolgu Çeşitleri
FlipX FlipY
FlipXY None

Sistem Renklerinin Fırçalar ile Kullanımı;

Windows işletim sisteminde kullanılan pek çok görünüm vardır. Örneğin masaüstü rengi, aktif olan pencerelerin bar kısımlarındaki renkler ve aralarındaki geçişler vb. Çoğu zaman Windows uygulaması içerisindeki bazı dolguların, sistemdeki hazır dolgulardan alınması istenebilir. Böyle bir durumda kullanıcı, makinesindeki sistem renklerini değiştirdiğinde Windows uygulaması içerisindeki şekillerde buna uygun bir biçimde güncellenebilecektir. Bunu gerçekleştirmek için SystemColors tipi kullanılır. Aşağıdaki XAML içeriğinde bu durum irdelenmektedir.

<Window x:Class="GrafiklerleCalismak.SystemColorsKullanimi" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="White" Title="SystemColors Kullanimi" Height="300" Width="300">
<Grid>
    <Rectangle Fill="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" Margin="26,31,106,0" Height="86" VerticalAlignment="Top" />
</Grid>
</Window>

Burada Fill niteliğine özel bir atama gerçekleştirilmiştir. DynamicResource ifadesini takip eden kısımda SystemColors.WindowBrushKey ile dolgu rengi, Windows işletim sistemi üzerinden alınmaktadır. Buna göre örneğin benim sistemimdeki çıktı aşağıdaki gibidir.

SystemColors tipi System.Window isim alanı(Namespace) altında yer almakta olan static bir sınıftır ve aşağıdaki gibi pek çok hazır değere sahiptir.

NOT : Hatırlanacağı gibi static sınıflar(class) örneklenemeyen, türetilemeyen, sadece static üyeler içeren bir tiptir. Normal sınıflara göre daha hızlı çalıştıklarından duruma göre performans amacıyla tercih edilebilirler.

Burada dikkat edilmesi gereken noktalardan birisi tüm bu değerlerin Fill işlemlerinde kullanılmadığıdır. Bir başka deyişle Fill özelliği, buradaki static üyelerden her birini kabul etmez. Özellikle sistem tarafında set edilmiş olan ve Key anahtar kelimesi ile bitenleri tercih etmek gerekmektedir.

Drawing ile Fırça Kullanımı;

Dilersek bir şeklin içini çizerekte doldurabiliriz. Söz gelimi videoları, metinleri, başka şekilleri çizim amacıyla dolgularda ele alabiliriz. Bu oldukça geniş bir konu olmasına rağmen bir örnek yapmadan geçmenin doğru olmayacağı kanısındayım. Bu tip bir işlemde DrawingBrush tipi esas olan noktadır. DrawingBrush tipi ile oluşturulan fırçalar söz konusu alanları, GemoetryDrawing, ImageDrawing, GlpyhRunDrawing, VideoDrawing, DrawingGroup gibi tipler yardımıyla farklı şekillerde doldurabilirler. Söz gelimi bir metnin içerisinde geometrik şekiller gösterilmesini(GeometryDrawing ile), yada bir elips içerisinde bir video oynatılmasını(VideoDrawing ile) sağlayabiliriz. Aşağıdaki örnek kod parçasında, bir elips içerisinde intro.wmv isimli videonun oynatılmasını sağlamak amacıyla yazılmış ifadeler yer almaktadır.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace GrafiklerleCalismak
{
    public partial class DrawingBrushKullanimi : Window
    {
        private void VideoCiz()
        {
            // Elips oluşturulur
            Ellipse elips = new Ellipse();
            // Yükseklik ve genişlik belirtilir.
            elips.Width = 250;
            elips.Height = 75;
       
            // Video dosyasını oynatacak bir MediaPlayer nesnesi örneklenir.
            MediaPlayer oynatici = new MediaPlayer();
            // intro.wmv dosyasının açılması sağlanır.
            oynatici.Open(new Uri("..\\..\\intro.wmv",UriKind.Relative));
   
            // DrawingBrush' ın kullanacağı VideoDrawing nesnesi örneklenir.
            VideoDrawing vd=new VideoDrawing();
            // Oynatıcı set edilir.
            vd.Player=oynatici;
            // Videonun oynayacağı alan belirlenir. Örnekte bu alan elips' inki ile aynı tutulmuştur.
            vd.Rect = new Rect(0, 0, 250, 75);
   
            // Fırça oluşturulur ve Drawing özelliğine VideoDrawing nesne örneği aktarılır
            DrawingBrush fircam = new DrawingBrush();
            fircam.Drawing = vd;
       
            // Elipsin için dolduracak nesne örneği belirlenir.
            elips.Fill = fircam;
       
            // Elips Grid içerisine eklenir
            grdAlan.Children.Add(elips);
   
            // Video oynatılır.
            oynatici.Play();
        }

        public DrawingBrushKullanimi()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                VideoCiz();
            }
            catch (Exception excp)
            {
                MessageBox.Show(excp.Message);
            }
        }
    }
}

Uygulama test edildiğinde aşağıdaki Flash videosunda görüldüğü gibi düğmeye basıldığında ilgili videonun elips içerisinde oynadığı görülür. (Flash animasyonunun oynatılabilmesi için sisteminizde Flash Player' ın yüklü olması gerekebilir.)

Video efektlerini ilerleyen yazılarımızda incelemeye devam ediyor olacağız. Şimdilik yazımızın sonuna geldik. Bu yazımızda temel olarak grafik işlemlerinde şekillerin fırçalar(Brushes) yardımıyla nasıl doldurulabileceğinin belirlenmesinde kullanılan tipleri, ağırlıklı olarak XAML içerisinde ele almaya çalıştık. Bir sonraki makalemizde grafik işlemlerinde kullanılabilecek şekilleri(Shapes) incelemeye çalışıyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF - Veriye Bağlanmak(Data Binding)

Pazartesi, 3 Eylül 2007 17:42 by bsenyurt

Bu gün geliştirdiğimiz programların çoğu veri(Data) ile ilişkili kaynakları kullanmaktadır. Özellikle büyük ölçekli pek çok proje tipi içerisinde mutlaka verilerin kullanılması söz konusudur. Veriler kimi zaman müşteri bilgilerini, kimiz zaman ürün bilgilerini, kimi zamanda uygulamaya ait konfigurasyon bilgilerini vb... tutar. Verilerin çoğu zaman veritabanı sistemlerinde, fiziki dosyalarda veya program içerisindeki özel tiplerde saklandıklarını görürüz. Çok doğal olarak bu veri depoları içerisinde tutulan bilgilerin son kullanıcılara gösterilmeside söz konusudur. Bu noktada, geliştirilen uygulamaya bakılmaksızın pek çok veri bağlama tekniği olduğunu söyleyebiliriz. Ama bu yazımızda özellike Windows Presentation Foundation(WPF) uygulamalarında veri bağlama işlemlerinin nasıl yapılabileceğini basit örneklerden hareket ederek incelemeye çalışıyor olacağız. Windows tabanlı programlamada özellikle Visual Studio 2005 ile birlikte veri bağlama işlemlerinin dahada genişletildiğine şahit olduk. Data Source menüsü bunun en güzel örneklerinden birisidir. Hatta XML ve nesne(object) kaynaklarına daha kolay bağlanılmasını sağlayan XmlDataSource ve ObjectDataSource gibi kontrolleri gördük. Aslında veri bağlama(Data Binding) denildiği zaman akla gelmesi gereken; "bir tipin herhangibir üyesinin başka bir tipin sahip olduğu veriye otomatik olarak erişmesidir" diyebiliriz. Form üzerindeki bir metin kutusunun(TextBox) text özelliğinin, veritabanındaki bir alana bağlanması buna verilebilecek basit bir örnektir.

Ne varki WPF mimarisinde durum biraz farklı bir hal almıştır. Özellikle .Net Framework 3.0 ve LINQ(Language Integrated Query) gibi yenilikler, verilerin işleniş ve ele alınış şekillerinide değiştirmektedir. WPF' e kısaca bakıldığında ilk fark edilen noktalardan birisi .Net Framework 2.0 formlarındaki kadar çok kontrolün Toolbox sekmesine gelmediğidir. Dahası, Visual Studio 2005 uygulama geliştirme ortamından hatırladığımız pek çok veri kontrolü burada yer almamaktadır. Üstelik bizleri bekleyen sayısız yeni kontrol bulunmaktadır. Peki bir WPF uygulamasında, pencere(Window) üzerindeki kontrollerin çeşitli özelliklerinin verilere olan bağlantılarını nasıl gerçekleştirebiliriz? İşte bu yazımızda araştıracağımız ve örnekleyeceğimiz konular bunlar olacaktır.

WPF uygulamlarında veri kaynaklarına bağlanabilmek amacıyla kullanılan iki temel sağlayıcı bulunmaktadır. Bunlardan birisi XML kaynaklarına bağlanma işlemini gerçekkleştirmemizi sağlayan XmlDataProvider bileşenidir. Diğeri ise, herhangibir .Net nesnesine (.Net Object) bağlanmamızı kolaylaştıran ObjectDataProvider bileşenidir. (Bunların dışında özellikle bağlantısız katman nesneleri ilede kullanılan DataContext özelliğide veri bağlama işlemlerinde kullanılmaktadır.) Bu bileşenler sayesinde XAML içerisinden ilgili kaynaklara bağlanma, tek yönlü ve çift yönlü olarak veri transfer etme işlemlerini gerçekleştirebiliriz. Bu tiplerin kullanımını gösteren basit örneklerimize geçmeden önce veri bağlama işlemine basit ve genel bir bakış atmakta yarar var. Bu amaçla Visual Studio 2008 Beta 2 sürümünde yeni bir WPF uygulaması açıyor ve Window1 penceresinin XAML içeriğini aşağıdaki gibi kodluyoruz.

<Window x:Class="DataBindIslemleri.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Bir kontrol niteliğini başka bir kontrodekine bağlamak" Height="169" Width="275" WindowStartupLocation="CenterScreen" Name="wndBasitBaglama">
    <Grid>
        <Button Background="#FFFFCC66" ClickMode="Release" Height="23" HorizontalAlignment="Left" Margin="10,16,0,0" Name="btnGiris" VerticalAlignment="Top" Width="75" Click="btnGiris_Click">Giriş</Button>
        <TextBox Background="Black" Foreground="{Binding ElementName=btnGiris, Path=Background}" Margin="10,47,44,54" Name="txtSifre" />
    </Grid>
</Window>

Bu ilk örneğimizde penceremiz üzerinde bir Button ve birde TextBox kontrolümüz bulunmaktadır. Dikkat edilmesi gereken nokta TextBox kontrolünün ön plan renginin, Button kontrolünün arka plan rengine bağlanmış olmasıdır. Bunun için TextBox elementi içerisinde Forground niteliğine bir değer ataması gerçekleştirilmiştir. Binding ile başlayan bu ifade içerisinde ElementName isimli özellik verinin kaynağı olan nesneyi temsil etmektedir. Bu örnekte söz konusu nesne btnGiris isimli Button kontrolüdür. Foreground özelliğine bağlanmasını istediğimiz veri içeriği ise Path tanımlaması ile belirtilmektedir. Buna göre btnGiris isimli veri kaynağındaki Background isimli özelliğin değerinin atanması söz konusudur. Uygulamayı çalıştırıp test ettiğimizde örnek olarak aşağıdakine benzer bir görüntü elde ederiz.

Burada bahsedilen teknik en basit haliyle veri bağlamayı göstermektedir. Şimdi işi biraz daha ilerletip örnek bir XML dökümanı içerisinden, WPF kontrollerine veri bağlama işlemlerini nasıl gerçekleştirebileceğimize bakalım. Aşağıdaki gibi bir XML içeriğimiz olduğunu ve projemizde Urunler.xml adıyla kaydedildiğini göz önüne alabiliriz.

<?xml version="1.0" encoding="utf-8" ?>
<Depo>
    <Urun id="1000">
        <Ad>Ekran Kartı(VGA)</Ad>
        <BirimFiyat>35</BirimFiyat>
        <StokMiktari>100</StokMiktari>
        <Durum>OK.bmp</Durum>
        <Kategori>Yedek Parça</Kategori>
    </Urun>
    <Urun id="1001">
        <Ad>Intel Core Duo İşlemci (CPU)</Ad>
        <BirimFiyat>90</BirimFiyat>
        <StokMiktari>125</StokMiktari>
        <Durum>OK.bmp</Durum>
        <Kategori>Yedek Parça</Kategori>
    </Urun>
    <Urun id="1002">
        <Ad>17Inch LCD Monitor</Ad>
        <BirimFiyat>150</BirimFiyat>
        <StokMiktari>35</StokMiktari>
        <Durum>Warning.bmp</Durum>
        <Kategori>Ekran</Kategori>
    </Urun>
    <Urun id="1003">
        <Ad>250 GB Usb Harddisk</Ad>
        <BirimFiyat>150</BirimFiyat>
        <StokMiktari>90</StokMiktari>
        <Durum>Serious.bmp</Durum>
        <Kategori>Depolama Aygıtı</Kategori>
    </Urun>
    <Urun id="1004">
        <Ad>1 GB Usb Flash Bellek</Ad>
        <BirimFiyat>28</BirimFiyat>
        <StokMiktari>300</StokMiktari>
        <Durum>Warning.bmp</Durum>
        <Kategori>Depolama Aygıtı</Kategori>
    </Urun>
</Depo>

XML dökümanımız içerisinde çeşitli tipte bilgisayar ürünlerine ait bilgiler yer almaktadır. Temel olarak ürünün adı, birim fiyatı, stok miktarı , kategorisi ve durumuna ait bilgisi yer almaktadır. Söz gelimi bu XML veri kümesi içerisinde yer alan her bir ürünün adlarının bir ComboBox kontrolünde gösterilmesini istediğimizi düşünelim. Bu amaçla yine Visual Studio 2008 Beta 2 üzerinde tasarladığımız WPF projemize yeni bir pencere(Window) ekliyor ve XAML(eXtensible Application Markup Language) içeriğini aşağıdaki gibi düzenliyoruz.

<Window x:Class="DataBindIslemleri.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="XmlDataProvider ile Xml verilerine basit bağlanmak" Height="150" Width="290" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.Resources>
            <XmlDataProvider x:Key="UrunlerProvider" Source="Urunler.xml"/>
        </Grid.Resources>
        <ComboBox Height="28" Margin="23,42,60,0" Name="cmbUrunler" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource UrunlerProvider},XPath=/Depo/Urun/Ad}" FontSize="13" FontWeight="Bold" />
        <Label Height="23" HorizontalAlignment="Left" Margin="18,12,0,0" Name="label1" VerticalAlignment="Top" Width="120" FontSize="12" FontWeight="Bold">Ürünler</Label>
    </Grid>
</Window>

Daha öncedende bahsettiğimiz gibi WPF uygulamalarında veri bağlama işlemleri için XmlDataProvider ve ObjectDataProvider tipleri kullanılmaktadır. Bu örnekte yer alan Grid alanı içerisindeki kontrollerin bir XML veri kaynağını kullanacağını belirtmek amacıyla Grid.Resources elementi içerisinde XmlDataProvider tanımlaması yapılmıştır. XmlDataProvider elementi bu örnekte iki önemli nitelik(attribute) kullanmaktadır. Bunlardan birisi x isim alanı altında bulunan Key niteliğidir. Key aslında söz konusu veri kaynağının diğer kontrollerde ele alınmasını sağlamak amacıyla bir isim tanımlaması yapılmasını sağlar. Öyleki UrunlerProvider adı, ComboBox kontrolünde ele alınan Binding ifadesinde kullanılmaktadır. XmlDataProvider elementinin Source niteliğinde ise veri kaynağının yeri işaret edilmektedir. Burada Urunler.xml isimli dosya gösterilmektedir. Source özelliğine internet üzerindeki bir URL adresi atanabileceği gibi, başka bir fiziki lokasyondaki XML dosya yeride verilebilir. ComboBox kontrolünde öğelerin(Items) içeriklerinin aslında XML dosyasındaki Ad elementlerinden geleceğini belirtmek amacıyla aşağıdaki ifade kullanılmıştır.

ItemsSource="{Binding Source={StaticResource UrunlerProvider},XPath=/Depo/Urun/Ad}"

Burada en çok göze çarpan nokta XPath niteliğine atanan değerdir. Tahmin edileceği üzere burada bir XPath ifadesi kullanılmış ve Ad elementine kadar geçişler yapılmıştır. Bu anlamda özellikle XAML tarafında, XML veri kaynaklarının söz konusu olduğu durumlarda XPath ifadelerinin büyük önem taşıdığını ifade edebiliriz. Örneğimizi çalıştırdığımızda aşağıdaki ekran görüntüsü elde edilecektir. Dikkat edilecek olursa Urunler.xml içerisindeki tüm ürünlerin Ad elementlerinin değerleri gelmiştir.

XmlDataProvider tipinin her zaman için harici bir XML veri kümesini işaret etmesine gerek yoktur. XAML içeriğinde yer alan gömülü(Embedded) bir XML veri kümeside bu anlamda kullanılabilir. Üçüncü örneğimizde bu durumu analiz ederek yazımıza devam edelim. Yeni bir pencereyi(Window) aşağıdaki gibi tasarladığımızı düşünelim. Kahramanımız yine bir ComboBox kontrolü olacak.

<Window x:Class="DataBindIslemleri.Window3" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Inline Xml kaynağını kontrollere bağlamak" Height="173" Width="280">
    <Grid>
        <Grid.Resources>
            <XmlDataProvider x:Key="SehirVerisi">
                <x:XData>
                    <Sehirler xmlns="">
                        <Sehir kod="+90216" ad="Istanbul Anadolu"/>
                        <Sehir kod="202" ad="Kahire"/>
                        <Sehir kod="813" ad="Tokyo"/>
                        <Sehir kod="+44171" ad="Londra"/>
                        <Sehir kod="+1718" ad="New York City"/>
                        <Sehir kod="4989" ad="Münih"/>
                    </Sehirler>
                </x:XData>
            </XmlDataProvider>
        </Grid.Resources>
        <ComboBox ItemsSource="{Binding Source={StaticResource SehirVerisi}, XPath=/Sehirler/Sehir/@ad}" Margin="15,57,16,51" FontSize="14" FontWeight="Bold" />
        <Label Height="23" HorizontalAlignment="Left" Margin="15,22,0,0" Name="label1" VerticalAlignment="Top" Width="120">Şehir Telefon Kodları</Label>
    </Grid>
</Window>

Bu sefer XML veri kümesi XAML dökümanı içerisinde yer alan Grid' in kaynağı olarak tanımlanmıştır. Bunun için XmlDataProvider elementi içerisinde x:XData isimli bir alt eleman(Child Element) tanımlanmaktadır. Bu elementin içerisinde ise XML veri kümesi bulunmaktadır. Buradaki XML içeriği istenilen şekilde tasarlanabilir. Önemli olan noktalardan birisi bir önceki örnekte olduğu gibi yine x:Key niteliğinin tanımlanmış olmasıdır. ComboBox kontrolünde Sehir elementi içerisindeki ad niteliklerinin(attributes) değerleri gösterilmektedir. ItemsSource niteliğine atanan ifade içerisinde bağlanılacak veri kaynağı SehirVerisi olarak belirtildikten sonra ad niteliklerinin değerlerinin elde edilmesi için XPath ifadesinde @ işaret kullanılmıştır.(Hatırlayalım; XPath ifadelerinde nitelikleri ele alırken @ işareti kullanılır) Uygulama bu haliyle çalıştırıldığında aşağıdaki ekran görüntüsü ile karşılaşılacaktır.

Görüldüğü gibi ad niteliklerinin değerleri ComboBox kontrolü içerisine alınmıştır. Geliştirdiğimiz son örneğin bir öncekinden tek farkı, harici bir XML veri kümesi kullanmaktansa, gömülü(Embeded) bir XML kaynağının kullanılmasıdır. Pek çok kaynakta özellikle MSDN' de bu tip bir XML içeriği için XML veri adası (XML Data Island) tanımlaması yapılmaktadır. XML veri adaları x:XData elementleri arasında tutulmak zorundadır.

Sıradaki örneğimizde yine bir XML veri kaynağını ele alacağız. Bu sefer diğer örneklerden farklı olarak ComboBox' ın ItemTemplate elementini ele alacağız. Bu sayede ComboBox içerisinde birden fazla veri bağlı kontrolü yan yana göstermemiz mümkün olacaktır. Bu amaçla yeni penceremizi aşağıdaki gibi tasarlamamız yeterlidir.

<Window x:Class="DataBindIslemleri.Window4" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="DataTemplete Yardımıyla Xml verisine bağlanma" Height="154" Width="384">
    <Grid>
        <Grid.Resources>
            <XmlDataProvider x:Key="UrunVerileri" Source="Urunler.xml"/>
        </Grid.Resources>
        <ComboBox Height="40" Margin="21,37,15,0" Name="cmbUrunler" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource UrunVerileri},XPath=Depo/Urun}" FontSize="14" FontWeight="Bold">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock>
                        <Label>
                            <Label.Content>
                                <Binding XPath="Ad"/>
                            </Label.Content>
                        </Label>
                        <Button>
                            <Button.Content>
                                <Binding XPath="StokMiktari"/>
                            </Button.Content>
                        </Button>
                        <Image>
                            <Image.Source>
                                <Binding XPath="Durum"/>
                            </Image.Source>
                        </Image>
                        <Button Name="btnSiparisVer" Content="Sipariş Ver" FontSize="10" FontWeight="Bold" Background="Black" Foreground="Gold"/>
                    </TextBlock>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</Window>

Her zamanki gibi Grid içerisindeki kontrollerin bağlanabileceği veri kaynağı sağlayıcısını XmlDataProvider yardımıyla Grid.Resources elementi içerisinde tanımlamaktayız. Bundan sonra ise ComboBox elementi altında bir ItemTemplate elementi açılmaktadır. Bu element içerisinde yer alan TextBlock elementi altına Label, Button, Image kontrolleri atılmıştır. Hepsinin ortak özelliği, hangi niteliklerine veri bağlayacaksak, bununla ilgili alt elementin açılması ve Binding elementi ile bağlama işleminin gerçekleştirilmesidir. Söz gelimi ürünün Ad elementinin değerini Label kontrolünün Content özelliğine atamak istiyorsak aşağıdaki gibi bir bildirim yapılması yeterlidir.

<Label>
    <Label.Content>
        <Binding XPath="Ad"/>
    </Label.Content>
</Label>

Burada Content elementinin değerinin, Ad isimli elementten alınacağı belirtilmiştir. Yanlız dikkat edilmesi gereken bir nokta vardır. Ad elementi aslında XML ağaç yapısına bakıldığında Depo/Urun üzerinden elde edilebilmektedir. Dolayısıyla burada XPath ifadesinde doğrudan Ad değerinin alınabilmesi için Urun elementlerine ulaşılmış olması gerekmektedir. Bunu sağlamak için ComboBox bileşeninin ItemsSource niteliğindeki XPath ifadesi Depo/Urun şeklinde ayarlanmıştır. Uygulamayı bu haliyle çalıştırdığımızda aşağıdakine benzer bir ekran görüntüsü ile karşılaşırız.

Oldukça etkileyici değil mi? Bir ComboBox' ın her bir öğesi bir taşıyıcı(Container) gibi davranıp birden fazla farklı bileşeni içeriyor ve içerikleri bir XML veri kümesinden geliyor. Bence süper.

Şu ana kadar geliştirdiğimiz örneklerimizde XmlDataProvider tipinden yararlandık ve XML veri kümelerine bağlandık. XML dışındaki veri kaynakları göz önüne alındığında ObjectDataProvider bileşeninin kullanılması söz konusudur. Bu sınıf yardımıyla herhangibir .Net tipine bağlanmak mümkündür. MSDN bu konu ile ilişkili olarak çoğunlukla koleksiyonları örnekleyerek işe başlamaktadır. Bizde dilerseniz geleneği bozmayalım. Şimdiki örneğimizde Urun isimli bir sınıfa ait nesne örneklerini barındıran generic bir List<T> koleksiyonunun veri kaynağı olarak kullanılmasını ele alıyor olacağız. İlk olarak aşağıdaki sınıf diagramında (class diagram) görülen Urun isimli tipi tasarlayarak başlayalım.

public class Urun
{
    public int Id;
    public string Ad;
    public double BirimFiyat;
    public int StokMiktari;
    public bool Durum;
   
    public override string ToString()
    {
        return Id.ToString() + " " + Ad+" "+BirimFiyat.ToString();
    }
}

Urun sınıfı yine bir ürünü tanımlayabilecek bazı public alanlara sahiptir. ToString metodunu ezmemizin(override) sebebi ise, ComboBox kontrolüne bağlandıklarında ne gösterileceğini belirtmektir. Eğer bunu belirtmessek, ComboBox kontrolünde IsimAlaniAdi.TipAdi (Namespace.TypeName) notasyonuna uygun olacak şekilde bir görüntü elde edilir. Gelelim ürünlere ait nesne örneklerini taşıyacak koleksiyon sınıfını tasarlamaya. UrunListesi isimli sınıfımız aşağıdaki gibidir.

public class UrunListesi:List<Urun>
{
    public UrunListesi()
    {
        Add(new Urun() { Id = 1, Ad = "Grafik Kartı", BirimFiyat = 35, StokMiktari = 100,Durum=true });
        Add(new Urun() { Id = 2, Ad = "Monitor", BirimFiyat = 150, StokMiktari = 50, Durum = false });
        Add(new Urun() { Id = 3, Ad = "CPU X86", BirimFiyat = 145, StokMiktari = 150, Durum = true });
        Add(new Urun() { Id = 4, Ad = "USB Bellek", BirimFiyat = 15, StokMiktari = 250, Durum = true });
        Add(new Urun() { Id = 5, Ad = "HDD 250 Gb", BirimFiyat = 250, StokMiktari = 14, Durum = false });
    }
}

UrunListesi sınıfı veri bağlanmasında kullanılacak kaynak bir tip olarak göz önüne alındığından List<Urun> koleksiyonundan türetilmiştir. Daha önceki yazılarımızda da değinildiği gibi C# 3.0 ile gelen yeniliklerden birisi olan nesne başlatıcıları(Object Initializers) kullanılarak Urun nesneleri örneklenmiş bir List koleksiyonuna eklenmiştir. Şimdi UrunListesi sınıfını veri kaynağı olarak kullanacak bir pencereyi(Window) aşağıdaki gibi tasarlayabiliriz. Kahramanımız her zamanki gibi bir ComboBox bileşenidir.

<Window x:Class="DataBindIslemleri.Window5" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Basit ObjectDataProvider Kullanımı" Height="158" Width="290" xmlns:dahili="clr-namespace:DataBindIslemleri">
    <Grid>
        <Grid.Resources>
            <ObjectDataProvider x:Key="UrunVerileri" ObjectType="{x:Type dahili:UrunListesi}" />
        </Grid.Resources>
        <ComboBox FontSize="12" FontWeight="SemiBold" Height="29" Margin="17,44,45,0" Name="comboBox1" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource UrunVerileri}}" />
    </Grid>
</Window>

ObjectDataProvider tipide x:Key isimli bir niteliği kullanarak, veriye bağlanacak kontrollerin kullanabilmesi için ortak bir isim tanımlaması yapmaktadır. XmlDataProvider tipinde Source isimli nitelik ile veri kümesi belirtilirken ObjectDataProvider tipinde bu iş için ObjectType niteliği(attribute) kullanılmaktadır. ObjectType niteliğinde ise UrunListesi isimli bir tipin veri kaynağı olarak kullanılacağı ve bunun, dahili kelimesi ile ifade edilen isim alanında olduğu belirtilmektedir. Peki dahili XML isim alanı nereden tanımlanmıştır? Bunun için Window elementinde bir XML isim alanı (XML Namespace) tanımlaması aşağıdaki gibi yapılmıştır. Burada, clr-namespace ifadesini izeleyen ismin bir CLR isim alanı(Namespace) adı olduğu ve XAML dökümanı içerisinde dahili kısa adı ile ifade edileceği belirtilmektedir.

xmlns:dahili="clr-namespace:DataBindIslemleri"

Dikkat edilecek olursa, tipin içerisinde yer aldığı isim alanı(Namespace) işaret edilmektedir. Buradan şu sonucada varabiliriz. Veri bağlama amacıyla kullanılan tip farklı bir assembly içerisinde, dolayısıyla farklı bir isim alanında bulunuyorsada ilgili XAML içeriğinde kullanılabilir. Farklı bir assembly söz konusu olduğunda aşağıdakine benzer bir tanımlama yeterli olacaktır.

xmlns:dahili="clr-namespace:DataBindIslemleri,assembly=UrunLibrary"

Uygulamayı çalıştırdığımızda aşağıdakine benzer bir ekran görüntüsünü elde ederiz.

Dikkat edilecek olursa ToString metodu içeriği ComboBox kontrollerinde birer öğe olarak görülmektedir.

Veri bağlama işlemlerinde tip olarak DataTable veya DataSet gibi bağlantısız katman nesne örneklerinin kullanılması çok daha yaygındır. Bu tip bir senaryoda DataContext sınıfı ele alınmaktadır. Sıradaki örneğimizde bir DataTable içerisindeki veri kümesinin, WPF kontrollerine nasıl bağlanabileceğini incelemeye çalışacağız. Örnek bir senaryo olarak SQL Server 2005 ile birlikte gelen veritabanlarından birisi olan AdventureWorks ve Product, ProductPhoto, ProductProductPhoto tablolarını göz önüne alabiliriz. Bu tablolar arasındaki ilişki aşağıdaki şekilde olduğu gibidir.

Amacımız resimli ürün bilgilerini bir ListBox kontrolünde gösterebilmek. Bu amaçla ilk olarak Window6 isimli penceremizin kodlarını aşağıdaki gibi geliştirelim.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Data;
using System.Data.SqlClient;

namespace DataBindIslemleri
{
    public partial class Window6 : Window
    {
        string sqlConn = "data source=.;database=AdventureWorks;integrated security=SSPI";
        string sorgu = @"SELECT PRD.ProductID, PRD.Name AS ProductName, PRD.SafetyStockLevel , PRD.StandardCost,PRD.ListPrice, PH.ThumbNailPhoto FROM Production.Product PRD INNER JOIN Production.ProductProductPhoto PPH ON PRD.ProductID = PPH.ProductID INNER JOIN Production.ProductPhoto PH ON PPH.ProductPhotoID = PH.ProductPhotoID";

        private void VeriyiCek()
        {
            DataTable dtUrunler = new DataTable();
            using (SqlConnection conn = new SqlConnection(sqlConn))
            {
                SqlDataAdapter da = new SqlDataAdapter(sorgu, conn);
                da.Fill(dtUrunler);
            }
            DataContext = dtUrunler;
        }

        public Window6()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            lstUrunler.Items.Clear();
            VeriyiCek();
        }
    }
}

Bu kod parçasında üzerinde durulacak olan tek nokta Window sınıfının FrameworkElement sınıfından kalıtımsal olarak devraldığı DataContext özelliğine DataTable nesne örneğinin atanmış olmasıdır. Böylece bir anlamda XAML içerisindeki bileşenlerin bağlanabileceği veri içeriği set edilmiş olur. Buna göre Window6.xaml dosyasının içeriğini aşağıdaki gibi tasarlayabiliriz.

<Window x:Class="DataBindIslemleri.Window6" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window6" Height="273" Width="567" Loaded="Window_Loaded">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="Urunler">
                <StackPanel Orientation="Horizontal">
                    <Label Content="{Binding Path=ProductID}"/>
                    <Label Content="{Binding Path=ProductName}"/>
                    <Label Content="{Binding Path=ListPrice}"/>
                    <Label Content="{Binding Path=SafetyStockLevel}"/>
                    <Image Name="imgPhoto" Source="{Binding Path=ThumbNailPhoto}"/>
                </StackPanel>
            </DataTemplate>
        </Grid.Resources>
        <ListBox Margin="13,28,17,31" Name="lstUrunler" ItemsSource="{Binding}" ItemTemplate="{StaticResource Urunler}"/>
    </Grid>
</Window>

Şimdi burada neler yaptığımıza kısaca bakalım. Öncelikli olarak Grid.Resources elementi içerisinde bir DataTemplete oluşturulmaktadır. Bu veri şablonu, kullanıldığı yerde nasıl bir içerik sunulacağını belirlemektedir. Söz gelimi, Label kontrollerimizin Content özelliklerine yapılan atamalarda DataContext' in işaret ettiği veri kümesindeki alanlar belirlenmektedir. Diğer taraftan Image kontrolünün Source özelliğine yapılan atama ile ThumbNailPhoto alanındaki binary içeriğin bağlanması sağlanmıştır. Peki bu veri şablonunu kim kullanacaktır? Bunun için örnek olarak bir ListBox kontrolü ele alınmaktadır. Bu kontrolde ItemsSource özelliğine sadece Binding atanması, veri kaynağı olarak DataContext içeriğinin ele alınacağını göstermektedir. Diğer taraftan veri şablonunun set edildiği yer ItemTemplate özelliğine yapılan atamadır. Burada atamada Urunler isimli veri şablonunun (Data Template) kullanılacağı belirtilmektedir. Bu durumda uygulama çalıştırıldığında aşağıdakine benzer bir ekran görüntüsü ile karşılaşılır.

Dikkat edilecek olursa ListBox içeriği, DataTable' a yüklenen veriler ile dolmuştur. Her halde buradaki en güzel nokta, ürün resimlerininde ListBox içerisinde gösterilebiliyor olmasıdır. Şimdi bu örneği biraz daha farklılaştıralım. Örneğin ListBox kontrolünde ürün adları gözüküyor olsun. Bunlardan herhangibiri seçildiğindeyse, ürün ile ilgili diğer bilgiler TextBox ve Image kontrollerinde görünüyor olsun. Bunun için yeni bir pencere(Window) ekleyip aşağıdaki gibi tasarlamamız yeterli olacaktır.

<Window x:Class="DataBindIslemleri.Window7" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window7" Height="304" Width="632">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="Urunler">
                <StackPanel Orientation="Horizontal">
                    <Label Content="{Binding Path=ProductName}"/>
                </StackPanel>
            </DataTemplate>
        </Grid.Resources>
        <ListBox Margin="13,19,0,18" Name="lstUrunler" ItemsSource="{Binding}" ItemTemplate="{StaticResource Urunler}" HorizontalAlignment="Left" Width="139" IsSynchronizedWithCurrentItem="True" />
        <Label Height="23" HorizontalAlignment="Left" Margin="176,24,0,0" Name="label1" VerticalAlignment="Top" Width="61">Ürün Adı</Label>
        <TextBox Text="{Binding Path=ProductName}" Height="21" Margin="270,24,191,0" Name="txtProductName" VerticalAlignment="Top"></TextBox>
        <Label Height="23" HorizontalAlignment="Left" Margin="176,50,0,0" Name="label2" VerticalAlignment="Top" Width="120">Birim Fiyatı</Label>
        <TextBox Text="{Binding Path=ListPrice}" Height="21" Margin="270,50,193,0" Name="txtListPrice" VerticalAlignment="Top" />
        <Label Height="23" HorizontalAlignment="Left" Margin="176,77,0,0" Name="label3" VerticalAlignment="Top" Width="120">Stok Seviyesi</Label>
        <TextBox Text="{Binding Path=SafetyStockLevel}" Margin="270,77,193,0" Name="txtSafetyStockLevel" Height="20" VerticalAlignment="Top" />
        <Image Source="{Binding Path=ThumbNailPhoto}" Margin="182,113,195,20" Name="imgPhoto" />
        <Label Content="{Binding Path=ProductID}" Height="49" HorizontalAlignment="Right" Margin="0,24,57,0" Name="lblProductID" VerticalAlignment="Top" Width="103" Foreground="Red" FontSize="16" FontWeight="Bold"/>
    </Grid>
</Window>

Bir önceki örnek ile karşılaştırıldığında önemli olan tek fark ListBox kontrolünün IsSynchronizedWithCurrentItem özelliğinin değerinin true olarak set edilmiş olmasıdır. Eğer bu özelliğe true değerini atamassak, ListBox üzerinde dolaştığımızda, bir başka deyişle başka bir öğeye geçtiğimizde diğer kontrollerin içerikleri DataContext nesnesinden dolmayacaktır. Bu durumda ListBox kontrolünde diğer öğelere tıklasakta hep ilk satırın bilgileri görünecektir. Uygulamamızı bu haliyle çalıştırdığımızda aşağıdakine benzer bir ekran görüntüsü elde ederiz.

Görüldüğü gibi örnek kaydın üzerine ListBox ile gidildiğinde, kontrollerin içerikleride o an DataContext' te üzerinde bulunulan satıra ait değerler olarak değişmiştir.

Bazı durumlarda birbirleriyle ilişkili olan tabloların kullanılmasıda söz konusudur. Özellikle bağlantısız katman(Disconnected Layer) nesneleri göz önüne alındığında bu tip vakkaları karşılamak için DataRelation nesnelerinden yararlanmaktayız. Peki DataRelation örnekleri ile aralarındaki ilişkiler(Relations) ifade edilen tabloları WPF uygulamalarındaki kontrollerimize nasıl bağlayabiliriz? Sıradaki örneğimizde bu durumu ele almaya çalışacağız. Söz gelimi, AdventureWorks veritabanında yer alan ProductSubCategory ve Product tablolarını baz alalım. Bu tablolar aşağıdaki diagramdanda görüleceği gibi birbirlerine ProductSubCategoryID alanları üzerinden bağlıdırlar.

Bu ilişkiyi bağlantısız katmanda temsil edebilmek için DataSet, DataTable ve DataRelation nesnelerine ihtiyaç vardır. Window8 örneğinde, alt kategorilerin gösterildiği bir ComboBox kontrolü ve bu kategoriye bağlı ürünlerin gösterildiği bir ListBox kontrolü bulunmaktadır. Senaryomuza göre ComboBox kontrolünde değişiklik yapılması halinde, seçilen yeni alt kategoriye bağlı ürünlerinde ListBox kontrolünde gösterilmesi istenmektedir. Bu amaçla ilk olarak verinin çekilmesi ve pencerenin DataContext özelliğine gerekli DataTable nesne örneğinin atanması gerekmektedir. Bu sebepten Window8.xaml.cs dosyamızın içeriğini aşağıdaki gibi geliştirebiliriz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Data;
using System.Data.SqlClient;

namespace DataBindIslemleri
{
    public partial class Window8 : Window
    {
        private string conStr = "data source=.;database=AdventureWorks;integrated security=SSPI";
        private string kategoriSorgusu = "Select ProductSubCategoryID,Name From Production.ProductSubCategory";
        private string urunSorgusu = "Select ProductID,ProductSubCategoryID,Name,ListPrice From Production.Product";

        private void VerileriCek()
        {
            using (SqlConnection conn = new SqlConnection(conStr))
            {
                SqlDataAdapter daKategori = new SqlDataAdapter(kategoriSorgusu, conn);
                DataTable dtKategori = new DataTable();
                daKategori.Fill(dtKategori);

                SqlDataAdapter daUrun = new SqlDataAdapter(urunSorgusu, conn);
                DataTable dtUrun = new DataTable();
                daUrun.Fill(dtUrun);

                DataSet ds = new DataSet();
                ds.Tables.Add(dtUrun);
                ds.Tables.Add(dtKategori);
       
                DataRelation iliski = new DataRelation("SubCatToProduct", dtKategori.Columns["ProductSubCategoryID"], dtUrun.Columns["ProductSubCategoryID"]);
                ds.Relations.Add(iliski);
       
                DataContext = dtKategori;
            }
        }
        public Window8()
        {
            InitializeComponent();
            VerileriCek();
        }
    }
}

Burada dikkat edilmesi gereken nokta, DataRelation nesne örneğinin mutlaka belirtilmesi ve DataSet nesne örneğinin Relations koleksiyonuna eklenmesi gerektiğidir. Pencerede kullanılacak olan ComboBox bileşeninin asıl bağlanacağı veri dtKategori tablosu olduğundan DataContext özelliğine bu tablonun değeri aktarılmıştır. Bundan sonra Window8 penceresinin XAML içeriği aşağıdaki gibi hazırlanabilir.

<Window x:Class="DataBindIslemleri.Window8" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window8" Height="300" Width="440">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="KategoriVerisi">
                <StackPanel>
                    <TextBlock Text="{Binding Path=Name}"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="UrunVerisi">
                <StackPanel Orientation="Horizontal">
                    <Label Content="{Binding Path=ProductID}"/>
                    <Label Content="{Binding Path=Name}"/>
                    <Label Content="{Binding Path=ListPrice}"/>
                </StackPanel>
            </DataTemplate>
        </Grid.Resources>
        <ComboBox Height="25" HorizontalAlignment="Left" Margin="12,35,0,0" Name="cmbAltKategori" VerticalAlignment="Top" Width="120" ItemsSource="{Binding}" ItemTemplate="{StaticResource KategoriVerisi}" IsSynchronizedWithCurrentItem="True"/>
        <Label Height="23" HorizontalAlignment="Left" Margin="10,12,0,0" Name="label1" VerticalAlignment="Top" Width="120">Alt Kategori</Label>
        <ListBox Margin="166,35,10,19" Name="lstUrunler" ItemsSource="{Binding SubCatToProduct}" ItemTemplate="{StaticResource UrunVerisi}" />
        <Label Height="23" Margin="166,12,132,0" Name="label2" VerticalAlignment="Top">Urunler</Label>
    </Grid>
</Window>

Grid içerisinde iki adet veri şablonu(Data Templete) kullanılmaktadır. Bunlardan birisi ComboBox, diğeri ise ListBox içindir. ComboBox bileşeni kendi içerisinde alt kategori adlarını göstermektedir. Veri kaynağını ItemsSource özelliği ile {Binding} olarak belirttiğimizden, DataContext içerisinden DataTable kullanılacaktır. Buna göre ItemTemplate' in ulaşacağı KategoriVerisi isimli DataTemplete elementinin içeriğine göre alt kategori adları görünecektir. Diğer tarafan, ComboBox üzerinde gezildikçe DataContext' in işaret ettiği veri kümesi üzerindede hareket edilebilmesini sağlamak istediğimizden IsSynchronizedWithCurrentItem niteliğine true değeri verilmesi şarttır. ListBox kontrolünün ItemsSource özelliğine dikkat edilecek olursa Binding ifadesinden sonra, DataSet' e eklenen DataRelation nesne örneğinin adı yazılmıştır. İşte bu bizim örneğimizin kilit noktasıdır. Bir başka deyişle ListBox kontrolü, içeriğini oluştururken Binding ifadesinde belirtilen ilişki(Relation) üzerinden hareket edecek ve buna UrunVerisi isimli DataTemplete' e göre verilerini yükleyecektir. Uygulamamızı çalıştırdığımızda aşağıdaki ekran görüntüsünde olduğu gibi alt kategori ve buna bağlı ürünlerin başarılı bir şekilde ilişkilendirildiği ve kontrollere bu değişimin yansıtıldığı görülür. Örnekte Shorts alt kategorisi seçilmiş ve buna bağlı olan ürünlerin bilgiside (DataTemplete ile çektiklerimiz) ListBox içerisinde gösterilmiştir.

Buraya kadar geliştirdiğimiz örneklerimizde, WPF uygulamalarında yer alan kontrollerin çeşitli veri kaynaklarına(XML, Database, Object gibi) farklı şekillerde nasıl bağlanabileceklerini incelemeye çalıştık. Özellikle XML bazlı veri kaynakları için XmlDataProvider tipini, nesne(Object) bazlı kaynaklar için ObjectDataProvider tipini kullanmayı, bunlara ek olarak özellikle veritabanı bazlı gerçek bağlantılarda da DataContext tipinden yararlanmayı incelemeye çalıştık. Önceki Windows mantığına göre XAML getirdiği bir takım yenilikler ile, veri bağlama işleminin epey bir değiştiği sonucunuda çıkartmamız mümkün. Konuyla ilişkili detaylı bilgilere ve çok daha güzel örneklere MSDN' den ulaşabilirsiniz. Ayrıca bir önceki makalemizde tanıttığımız kitaplarında çok faydası olacağını söyleyebilirim. Böylece geldik bir makalemizin daha sonuna. İlerleyen makalelerimizde WPF ile uygulamalar geliştirmeye devam ediyor olacağız. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

WPF - Application Nesnesi

Perşembe, 30 Ağustos 2007 17:48 by bsenyurt

Windows Presentation Foundation windows tabanlı uygulama geliştirmeye çok yeni bir yaklaşım getirdi. Tabiri yerindeyse pek çok yenilik ile karşı karşıyayız. İşte bu makalemizde WPF ile geliştirilen windows uygulamalarında çekirdek nesnelerden birisi olan Application tipini incelemeye çalışacağız. Application nesnesi, WPF uygulamalarının çekirdek nesnesidir. Genel olarak bir windows uygulamasının çalıştırılması işletim sistemi tarafından tetiklenen bir aksiyonla gerçekleşir ve bu uygulama Process dahilinde ayrı bir AppDomain içerisinde çalışır. Buna göre her WPF uygulaması çalıştığı yaşam süresi boyunca yanlızca bir Application nesnesine sahip olur. Buda çok doğal olarak, windows uygulaması içerisindeki tüm sayfa(Page) veya pencerelerin(Windows) ortaklaşa kullanacakları global seviyede bir nesnenin söz konusu olması anlamına gelmektedir. Aşağıdaki şekilde Application nesnesinin WPF uygulamasındaki yeri tasvir edilmeye çalışılmaktadır.

Bu, inanıyorumki web uygulaması geliştirenler için tuhaf değildir. Malum web uygulamalarında da Application nesnesi vardır ve özellikle uygulama seviyesinde değişkenlerin tutulması için kullanılır. Aslında bu kavram .Net Framework 3.0 öncesi windows programlamadada vardır. Öyleki hepimiz Main metod içerisinde kullanılan Application sınıfını ve üyelerini az çok biliyoruz. Ne varki, WPF ile geliştirilen uygulamalarda Application nesnesinden yararlanarak daha farklı fonksiyonellikler elde edilebilmektedir. Bunları maddeler halinde aşağıdaki gibi sıralayabiliriz.

  • Uygulamanın yaşam süresi izlenebilir. (Application LifeTime)

  • Komut satırından uygulamaya gönderilen parametreler(Command Arguments) alınıp işlenebilir.

  • Uygulamanın kapatılma süreci ele alınabilir.

  • Ele alınamayan istisnalar(Unhandled Exceptions) için özel durum yönetimleri gerçekleştirilebilir.

  • Uygulama genelinde kullanılabilecek global özellikler(properties) ve kaynaklar(resources) tanımlanabilir ve kullanılabilir.

  • Uygulamanın derleme(build) sürecine ait ayarlar yapılabilir.

  • XBAP(Xaml Browser APplication) uygulamalarında navigasyon süreci izlenebilir.

Application nesnesini işletim sistemi ile uygulama kodu arasındaki bir arayüz olarakda düşünebiliriz. Uygulama içerisindeki tüm pencere(Window) veya sayfaların(Page) aynı Application nesnesine erişebilmesini ve bu nesnenin tekliğini sağlamak için tahmin edileceği üzere Singleton Pattern' e uygun bir üretim sistemi söz konusudur. Bu tek nesne üretim işini Application sınıfının Current özelliği üstlenmektedir. Application nesneleri, uygulamanın bulunduğu bilgisayarda saklandıklarından özellikle kaynak(resource) veya özelliklerin(properties) değerlerinin saklanması gibi durumlarda sunucu(server) gibi kaynaklara erişime gerek kalmamaktadır. Buda bir avantaj olarak görülebilir.

Örneklere başlamadan önce, uygulama yaşam sürecini(Application LifeTime) ele almakta ve değerlendirmekte yarar olacağı kanısındayım. Application nesnesi ile birlikte, bir WPF uygulamasının yaşam sürecini daha iyi kontrol edebilmekteyiz. Öncelikli olarak aşağıdaki temsili resmi göz önüne alalım. Bu şekilde temel olarak bir WPF uygulamasının standart yaşam döngüsü, süreçteki temel olaylar ve çevresel bazı etkiler irdelenmeye çalışılmaktadır.

Herşeyden önce WPF uygulmasının kullanıcı tarafından tetiklenmesi sonrası işletim sistemi tarafından uygulamanın bir AppDomain içerisine açılması söz konusudur. Bundan sonraki süreçte uygulama çeşitli nedenlerle sonlanıncaya kadar ele alabilecek bazı olaylar vardır. Uygulama çalışmaya başladığında Application nesnesinin Startup olayı tetiklenir. Bu olay içerisinde uygulama başlatılırken yapılması istenenler yazılabilir. Örneğin uygulamanın hangi pencere veya sayfasının yükleneceğine burada karar verilebilir. Startup olayını global seviyedeki özellik ve kaynakların saklanması halinde, yüklenecekleri yer olarakda tasarlayabiliriz. Hatta komut satırı parametrelerininde(Command Line Arguments) bu olay içerisinde ele alınması sağlanabilir. Yazımızın ilerleyen kısımlarında bununla ilişkili bir örnek geliştiriyor olacağız.

Gelelim Activated ve Deactivated olaylarına. Activated olayı, WPF uygulaması ilk çalıştırıldığında ve ilk penceresi açıldığında otomatik olarak tetiklenir. Bundan sonra uygulama pasif moda(Deactivate) geçinceye kadar çalışmaz. İşletim sistemi üzerinde çalışan başka uygulamalara yapılan geçişlerde, Deactivated isimli olay tetiklenmektedir. Tahmin edileceği üzere tekrardan WPF uygulamasına dönülmesi sonrasında Activated olayı yeniden tetiklenecektir. Bu tetikleme, taskbar yardımıyla söz konusu WPF uygulamasındaki formlardan herhangibirinin açılması halinde, Alt+Tab tuş kombinasyonu ile yapılan geçişler sonrasında meydana gelmektedir. Deactivated olayında kaynak tüketimi yüksek olan çalışma zamanı nesnelerinin işleyişlerinin duraksatılması yada uyku moduna geçirilmeleri sağlanarak işletim sisteminin daha az yorulması gerçekleştirilebilir. Activated olayı tetiklendiğindede söz konusu kaynakların tekrardan ayağa kaldırılması işlemleri gerçekleştirilebilir. Bu tip bir senaryo elbetteki tartışmaya açık olmalıdır.

NOT : Activated ve Deactivated olayları XBAP(Xaml Browser APplications) uygulamaları tarafından desteklenmemektedir.

Gelelim DispatcherUnhandledException olayına. Şekildende görüleceği üzere uygulama kodu içerisinden çalışma ortamına fırlayacak bir istisnayı Application nesnesninin bu olayı içerisinde ele alabiliriz. Uygulama içerisindeki kodlarda bir exception oluştuğunda(özellikle ele alınmayan-unhandled exceptions) WPF çalışma ortamı standart bir hata dialog penceresi çıkartacak ve hata raporunun gönderilip gönderilmeyeceği sorulacaktır. Ardındanda uygulama sonlandırılacaktır. Ancak, DispatcherUnhandledException olayı içerisine gelen DispatcherUnhandledExceptionEventArgs parametresininin Handled özelliğini kullanarak uygulamanın kapatılması (Eğer mümkünse tabi) engellenebilir.

SessionEnding isimli olay, işletim sisteminden aşağıdaki aktiviteler gerçekleştiğinde tetiklenmektedir.

  • LogOff
  • Windows Shutdown
  • Restart
  • Hibernate

Eğer çalşan WPF uygulamasında kritik işlemler(örneğin halen daha devem eden veya sunucuya bağlı halde iken veritabanı üzerinde transaction bazlı gerçekleşen bir işlem vb. söz konusu olabilir) söz konusu ise, yukarıdaki aktivitelerin gerçekleşmesi halinde sürecin iptal edilmesi arzu edilebilir. Bu nedenle Application sınıfının SessionEnding olayı ele alınır. Bu amaçla, SessionEndingCancelEventArgs parametresi kullanılır. Eğer SessionEnding içerisinde işlem iptali yapılmassa Application sıfının Shutdown metodu çağırılır ve Exit isimli olay tetiklenmiş olur.

NOT : SessionEnding olayı XBAP(Xaml Browser APplications) uygulamları tarafından desteklenmemektedir.

Bir WPF uygulamasını bilinçli olarak sonlandırmak için Application sınıfının Shutdown metodu kullanılabilir. Aslında uygulamanın ana penceresi(Main Window) kapatıldığında, tüm pencereleri kapatıldığında veya yukarıdaki gibi işletim sistemince LogOff, Windows Shutdown, Restart, Hibernate aksiyonları gerçekleştirildiğinde otomatik olarak çalışır. İstenirse bir uygulama içerisindeki ShutDown metodunun hangi durumlarda otomatik olarak çalışacağı ShutdownMode(ShutDownMode enum sabiti tipinden değerler alır) özelliği yardımıyla Application elementi içinden veya uygulama kodundan değiştirilebilir. ShutDownMode enum sabitinin OnLastWindowClose, OnMainWindowClose ve OnExplicitShutDown isminde üç farklı değeri vardır. OnLastWindowClose varsayılan değerdir ve uygulama içerisinde birden fazla pencere olması halinde en sonuncusu kapatıldığında uygulamanın kapanması söz konusudur. Bir başka deyişle uygulamanın kapatılabilmesi için açık olan tüm pencerelerin kapatılması gereklidir. Eğer OnMainWindowClose değeri verilirse, uygulama başladığında etklinleştirilen pencere kapatıldığında uygulama sonlanır. OnExplicitShutDown seçildiğinde ise uygulamanın kapatılması geliştiriciye bırakılmıştır. ShutDown metodu ile yapılan sonlandırma isteklerinde istenirse işletim sistemine bir çıkış kodu(ExitCode) değeri integer tipinden gönderilebilir. Çıkış kodunun değeri, bu uygulamaya bağlı başka uygulamalar tarafından değerli olabilir. ExitCode için varsayılan değer sıfırdır.

Shutdown metoduna yapılan bilinçli çağırdan sonra veya SessionEnding gerçekleştikten sonra Exit isimli olay tetiklenir. Bu olay içerisinde uygulamanın son durumuna ait bilgilerin saklanması gibi işlemler yapılabilir. Özellikle global özellik(Global Properties) ve kaynakların(Resources) saklanma işlemlerinin gerçekleştirilmesi için ideal bir lokasyondur. Bu olay içerisine gelindiğinde uygulamanın kapatılma süreci artık iptal edilememektedir. Exit olayı içerisine gelen ExitEventArgs parametresi yardımıyla, çıkış kodu(Exit Code) değeride yakalanabilir ve buna göre farklı askiyonlar gerçekleştirilebilir.

NOT : Exit olayı XBAP(Xaml Browser APplications) uygulamalarında da yazılabilir. Ancak ExitCode değeri XBAP uygulamalarında görmezden gelinir. Exit olayı bir XBAP uygulamasında örneğin Internet Explorer 7 ile açılmışsa ilgili tab kapatıldığında, tarayıcı(Browser) uygulama kapatıldığında veya başka bir yere navigasyon ile gidildiğinde tetiklenmektedir.

Bu kadar teorik bilgiden sonra birazda pratiğe geçmekte fayda var. Yazımızın bundan sonraki kısımlarında örnekler üzerinden ilerlemeye çalışacağız. Ancak bu kez Visual Studio 2008 Beta 2 sürümünü kullanıyor olacağız. Bu nedenle yazdığımız kodlarda bir değişiklik olmasada, IDE üzerinde final sürümü çıktığında bazı farklılıklar olabileceğini şimdiden söylemek isterim. İlk olarak Visual Studio 2008 Beta 2 de yeni bir WPF uygulaması açarak işe başlayalım. Uygulama açıldığında Application sınıfından türeyen App isimli bir tipin olduğunu göreceğiz. App sınıfının türediği Application sınıfının .Net Framework 3.0 içerisindeki yeri sınıf diagramdan(class diagram) bakıldığında aşağıdaki gibidir.

Uygulamaya ait ayarların hem element hemde kod bazında yönetimi için aşağıdaki şekildende de görüldüğü gibi App.xaml ve App.xaml.cs dosyaları otomatik olarak oluşturulacaktır.

Visual Studio 2008 Beta 2 ile bir olayı element seviyesinde yüklemek son derece kolaydır. Burada intellisense desteğinin tam olarak verildiğini söyleyebiliriz. Örneğin Startup olayını aşağıdaki gibi Application elementi içerisinden seçebiliriz. Aşağıdaki ekran görüntüsündende görüldüğü gibi, Application elementi içerisinde boşluk tuşuna basıldığında kullanılabilecek tüm üyeler çıkacaktır.

Diğer taraftan bir olay yüklenmek istendiğinde (örneğin Startup) aşağıdaki ekran görüntüsünde olduğu gibi geleneksel olarak tab tuşundan yararlanarak ilgili olay metodun otomatik olarak yüklenmesi sağlanabilir.

Bunun sonucunda App.xaml.cs içerisine aşağıdaki olay metodu eklenir.

private void Application_Startup(object sender, StartupEventArgs e)
{

}

Application elementinin içeriği ise aşağıdaki gibi olacaktır.

<Application x:Class="UsingApplicationObjects.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml" Startup="Application_Startup">
    <Application.Resources>
    </Application.Resources>
</Application>

Visual Studio 2008 Beta 2' nin bu yardımlarına değindikten sonra Activated ve Deactivated olaylarını inceleyerek devam edelim. Bu amaçla Application elementi içerisinde aşağıdaki yüklemeler yapılmalıdır.

<Application x:Class="UsingApplicationObjects.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml" Activated="Application_Activated" Deactivated="Application_Deactivated">
    <Application.Resources>
    </Application.Resources>
</Application>

Sonrasında ise App.xaml.cs dosyası içerisinde açılan olay metodları aşağıdaki gibi düzenlenmelidir.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
using System.Diagnostics;

namespace UsingApplicationObjects
{
    public partial class App : Application
    {
        private void Application_Activated(object sender, EventArgs e)
        {   
            Debug.WriteLine("Activated");
        }

        private void Application_Deactivated(object sender, EventArgs e)
        {
            Debug.WriteLine("DeActivated");
        }
    }
}

Test amaçlı bir kod yazıldığı için System.Diagnostics isim alanında bulunan Debug sınıfın WriteLine metodu ile Output penceresine çıktılar verilmektedir. Uygulama ilk çalıştırıldığında ve output penceresine bakıldığında Activated yazdığı görülecektir. Eğer Alt+Tab tuşları veya mouse ile uygulama dışına çıkılırsa output penceresine DeActivated yazdığı görülecektir. Bir başka deyişle Deactivated olayı tetiklenmiştir. Elbette tekrardan uygulamaya dönülürse Activated olayı yeniden tetiklenecektir.

Sıradaki örneğimizde Startup ve Exit olaylarını birlikte incelemeye çalışacağız. Bunun için bize örnek bir senaryo gerekmektedir. Application nesnesinin tüm uygulama için geçerli olabiliecek özellik(Property) ve kaynakları(Resources) saklayabildiğinden bahsetmiştik. Çok doğal olarak bunları uygulamadan çıkarken saklamak ve uygulama açıldığında yeniden yüklemek isteyebiliriz. İşte bu noktada saklanan bilgileri okuma işlemini Startup olayında, yazdırma işlemini ise Exit olayı içerisinde ele almalıyız. Application nesnesi üzerinden bir özellik tanımı yapmak ve değerini vermek son derece kolaydır. Tek yapılması gereken Application sınıfının Properties özelliği ve indeksleyicisinden yararlanmaktır. Aynı durum kaynaklar içinde geçerlidir. Örneğin aşağıdaki ekran görüntüsünde özellik ekleme adımı gösterilmektedir.

Dikkat edilecek olursa Properties özelliği Dictionary bazlı bir koleksiyondur ve object tipinden anahtar-değer(key-value) çiftleri ile çalışmaktadır. Buda kendi tiplerimizi özellik olarak tutabileceğimiz anlamına gelir. Diğer taraftan Resource yüklemek içinde aşağıdaki notasyon kullanılır.

Resources özelliğide aslında ResourceDictionary tipinden bir koleksiyon döndürmektedir. Bu koleksiyonda IDictionary arayüzünü uyarlayan ancak içerisinde Hashtable bazlı çalışan hızlı bir koleksiyondur. (Resource ' ları Application elementi içerisinde ApplicationResource alt elementi içerisindede tanımlayabiliriz. Örneğin pencerelerdeki kontroller için ortak stilleri burada belirleyebiliriz. Kaynak yönetimi konusunada ilerleyen yazılarımızda değinmeye çalışacağım.)

Bu bilgilerden sonra StartUp ve Exit olaylarının bildirimlerini yapıp kodlayarak devam edebiliriz. Örnek senaryomuzda kullanıcı sembolik olarak bir pencere(Window) üzerinden ürün bilgisi girecek ve bu bilgiler uygulama seviyesinde yazılmış bir sınıfa ait örnekte saklanacaktır. Tahmin edileceği üzere tipe ait örnek Application nesnesinin özelliği ile elde edilebilecektir. Urun sınıfını ayrı bir fiziki dosyada projeye ekleyip aşağıdaki gibi geliştirebiliriz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UsingApplicationObjects
{
    class Urun
    {
        public int Id;
        public string Ad;
        public double BirimFiyat;
    }
}

Dikkat edilecek olursa, sınıfın içerisinde özellik(property) veya yapıcı metod(constructor) dahil edilmemiştir. Nitekim burada C# 3.0 ile gelen object initializers tekniğinden yararlanılmak istenmektedir. Bu anlamda Visual Studio 2008 Beta 2' nin C# 3.0 içinde tam bir intellisense desteği verdiğini söylemeliyim. Örneğin Window1 üzerinde yer alan bir Button kontrolün Click olay metodunda Urun sınıfına ait bir nesneyi object initializers tekniği ile oluşturmak istediğimizde bu destek açık bir şekilde görülmektedir.

Window1.xaml.cs dosyasının içeriğini aşağıdaki gibi tasarlayabiliriz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;

namespace UsingApplicationObjects
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            Urun bilgisayar = new Urun() { Id = 1000, Ad = "Bilgisayar", BirimFiyat = 1000 };
            Application.Current.Properties["SonBakilanUrun"] = bilgisayar;
        }
    }
}

Application nesnesi üzerinden o anki uygulama referansına erişmek için Current özelliği kullanılır. Sonrasında ise Properties özelliği üzerinden indeksleyici(indexer) yardımıyla, oluşturulan Urun nesne örneği SonBakilanUrun adıyla kaydedilir. Artık uygulama çalışma zamanında burada oluşturulan Application seviyesindeki özelliğe heryerden erişilebilir. Söz gelimi uygulamadan çıkarken bu özelliklerin içeriğini saklamak istersek App.xaml.cs içerisinde Exit olayını aşağıdaki gibi kodlamamız yeterli olacaktır.

private void Application_Exit(object sender, ExitEventArgs e)
{
    try
    {
        Urun sonUrun = (Urun)Application.Current.Properties["SonBakilanUrun"]; // Properties koleksiyonuna eklenmiş SonBakilanUrun isimli bir anahtar var ise bunun değeri Urun tipinden elde edilir.
        if (sonUrun != null) // Eğer sonUrun nesne örneği null değilse...
        {
            IsolatedStorageFile storageFile = IsolatedStorageFile.GetUserStoreForDomain(); // Bu uygulamanın ve assembly' ın kimlik (Identity) bilgisine göre izole edilmiş kullanıcı odaklı depolama alanının elde edilmesini sağlar.
            IsolatedStorageFileStream stream = new IsolatedStorageFileStream("GlobalAppProperties.txt", FileMode.Create, storageFile); // Bu uygulama için diğerlerinden ayrılmış olan bir alana path' ten bağımsız olacak şekilde GlobalAppProperties.txt dosyasının açılmasını sağlar.
            StreamWriter writer = new StreamWriter(stream); // izole edilmiş alandaki dosya üzerine yazmak için bir StreamWriter kullanılabilir.
            writer.WriteLine(sonUrun.Id.ToString() + "|" + sonUrun.Ad + "|" + sonUrun.BirimFiyat.ToString()); // Bilgiler text dosyasına yazdırılır.
            writer.Close();
            stream.Close();
        }
    }
    catch (Exception exp)
    {
        MessageBox.Show(exp.Message);
    }
}

Tabiki burada illede IsolatedStorageFile kullanılması gerekmemektedir. Bunun yerine ikili(binary) veya xml serileştirmeden yararlanılabilir yada bilinen dosya saklama teknikleri tercih edilebilir. MSDN kaynaklarında bu konu işlenirken genel olarak yukarıdaki IsolatedStorageFile tipinin kullanıldığını söyleyebilirim. Gelelim Application_Startup olay metodunun içerisine. Bu olay metodunu ise aşağıdaki gibi kodladığımızı düşünelim.

private void Application_Startup(object sender, StartupEventArgs e)
{
    try
    {
        IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForDomain();
        IsolatedStorageFileStream stream = new IsolatedStorageFileStream("GlobalAppProperties.txt", FileMode.Open, storage);
        StreamReader reader = new StreamReader(stream);
        while (!reader.EndOfStream)
        {
            string[] keyValue = reader.ReadLine().Split(new char[] { '|' });
            Urun urn = new Urun() { Id = Convert.ToInt32(keyValue[0]), Ad = keyValue[1].ToString(), BirimFiyat = Convert.ToDouble(keyValue[2].ToString()) };
            Application.Current.Properties["SonBakilanUrun"] = urn;
        }
    }
    catch (Exception exp)
    {
        MessageBox.Show(exp.Message);
    }
}

Eğer GlobalAppProperties.txt isimli dosya var ise buradan elde edilen değerlerden yararlanarak Urun nesne örneği oluşturulur ve Application nesnesinin özellikler koleksiyonuna eklenir. Söz gelimi yüklenen bu değeri yine bir Window' un yüklenmesi sırasında ele aldığımızı düşünebiliriz. Bunun için test olması açısından aşağıdaki gibi bir kod yazdığımızı düşünebiliriz.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    Urun urn = (Urun)Application.Current.Properties["SonBakilanUrun"];
    if (urn != null)
        Title = urn.Ad.ToString(); // Bu pencerenin başlığında Urun' un adı gösterilir.
}

Sonuçta uygulama çalıştırıldığında Window ' un başlık kısmında aşağıdakine benzer bir görüntü elde edilir.

NOT : Yapılan testlerde bilgisayarın kapatılıp açılmasından sonra da Application özelliklerinin yazıldığı dosyadan başarılı bir şekilde okunabildiği görülmüştür.

Startup olayını komut satırı parametrelerini almak içinde kullanabiliriz. Burada Startup olay metodundaki StartupEventArgs parametresinin args isimli özelliği string tipinden bir dizi döndürmektedir. Bu özellik komut satırından girilen parametre değerlerini taşımaktadır. Ortam Visual Studio 2008 Beta 2, platformda .Net Framework 3.0 olunca, C# 3.0 ile gelen extension metodlar(örneğin Select, Min, Max, Average, Where, Sum vb...) ve anahtar kelimelerinde doğrudan desteklendiğini görüyoruz. Aşağıdaki ekran görüntüsünde bu durum açık bir şekilde görülmektedir.

Örnek senaryomuzda komut satırında, bir veritabanı bağlantısı açmak için gerekli bazı bilgileri aldığımızı düşünebiliriz. Örneğin bu bilgiler sunucu adı, veritabanı adı, kullanıcı adı ve şifre olabilir. Buna göre komut satırından uygulamaya gönderilebilecek 4 farklı parametre söz konusudur. Bu parametre deseninin aşağıdaki gibi olduğunu farz edelim.

ProgramAdi.exe s:LONDON d:AdventureWorks u:Burak p:1234

Buna göre Application sınıfının Startup olay metoduna aşağıdaki kodları eklediğimizi düşünelim.

if (e.Args.Length == 4)
{
    if (e.Args[0][0] == 's')
        Application.Current.Properties["Sunucu"] = e.Args[0].Substring(2, e.Args[0].Length-2);
    if (e.Args[1][0] == 'd')
        Application.Current.Properties["Veritabani"] = e.Args[1].Substring(2, e.Args[1].Length-2);
    if (e.Args[2][0] == 'u')
        Application.Current.Properties["Kullanici"] = e.Args[2].Substring(2, e.Args[2].Length-2);
    if (e.Args[3][0] == 'p')
        Application.Current.Properties["Sifre"] = e.Args[3].Substring(2, e.Args[3].Length-2);
}

Burada çok daha farklı algoritmalar düşünülebilir. Temel amaç, komut satırından gelecek 4 parametreninde elde edilmesi ve bunlardan var olanların uygulama nesnesinin ilgili özelliklerine set edilmesidir. Bundan sonraki adımımızda, herhangibir pencerenin StatusBar kontrolünde, buradaki bilgilerin içeriğini göstermeye çalışacağız. Söz gelimi Window1 içinde bir StatusBar kontrolünde bu bilgiler gösterilebilir. Üzülerek belirtmeliyimki bu StatusBar bileşenide WPF' den önce bildiğimiz StatusStrip değil. İtiraf etmek gerekirse, StatusBar içerisine kontrolleri atmak için bir süre uğraştım. Sonuçta artık kontrolleri ve içeriklerininde hiyerarşik bir XML yapısında düşünmemiz gerektiğini öğrendim. Buna göre StatusBar içerisinde barındırmak istediğimiz her kontrolü bir StatusBarItem elementi içerisinde göz önüne almalıyız. Dolayısıyla Window1.xaml içeriğini aşağıdaki gibi geliştirmemiz gerekmektedir.

<Window x:Class="UsingApplicationObjects.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="294" Width="452" Loaded="Window_Loaded">
    <Grid>
        <Button Height="23" HorizontalAlignment="Left" Margin="16,57,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click">Button</Button>
        <StatusBar Height="23" Name="stbBilgi" VerticalAlignment="Bottom">
            <StatusBarItem>
                <TextBlock Name="txtSunucu" Text="Sunucu:"></TextBlock>
            </StatusBarItem>
            <StatusBarItem>
                <Separator/>
            </StatusBarItem>
            <StatusBarItem>
                <TextBlock Name="txtVeritabani" Text="Veritabanı:"></TextBlock>
            </StatusBarItem>
            <StatusBarItem>
                <Separator/>
            </StatusBarItem>
            <StatusBarItem>
                <TextBlock Name="txtKullanici" Text="Kullanıcı:"></TextBlock>
            </StatusBarItem>
            <StatusBarItem>
                <Separator/>
            </StatusBarItem>
        </StatusBar>
    </Grid>
</Window>

Dikkat edilecek olursa, her StatusBarItem içerisinde bir kontrol yer almaktadır.TextBlock kontrollerinin Text özelliklerinden yararlanarak gerekli bilgileri gösterebiliriz. Seperator kontrolü ise bu haliyle basit olarak diğer StatusBarItem kontrolleri içerisindeki bileşenlerin arasında bir ayraç görevi üstlenmektedir. (WPF kontrolleri ile ilişkili olarak ilerleyen yazılarımızda detaylı incelemeler yapmayı düşünüyorum.) Window1.xaml.cs dosyasında ise Loaded olay metodunu amacımıza yönelik olarak aşağıdaki gibi kodlayabiliriz.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    Urun urn = (Urun)Application.Current.Properties["SonBakilanUrun"];
    if (urn != null)
        Title = urn.Ad.ToString();

      txtSunucu.Text += Application.Current.Properties["Sunucu"]!=null? Application.Current.Properties["Sunucu"].ToString():"Tanımlı Değil";
      txtVeritabani.Text += Application.Current.Properties["Veritabani"] != null ? Application.Current.Properties["Veritabani"].ToString() : "Tanımlı Değil";
      txtKullanici.Text += Application.Current.Properties["Kullanici"] != null ? Application.Current.Properties["Kullanici"].ToString() : "Tanımlı Değil";
}

Artık testimizi gerçekleştirebiliriz. Parametreleri test edebilmek için proje özelliklerine(Project Properties) gidip Command Line Arguments kısmını aşağıdaki gibi doldurmamız yeterli olacaktır.

Uygulama test edildiğinde aşağıdakine benzer bir sonuç ile karşılaşırız.

Bu son örneğimizde komut satırında WPF(Windows Presentation Foundation) uygulamasına gelen parametreleri nasıl ele alabileceğimizi incelemeye çalıştık. Dilerseniz yeni bir örnekle devam edelim. Bu örneğimizde uygulamanın kapatma sürecini kontrol altına almaya çalışacağız. Daha öncedende belirttiğimiz gibi, işletim sistemi seviyesinde gelebilecek olan kapatma taleplerini uygulama içerisinden geri çevirme şansına sahip olduğumuzu söylemiştik. Bunun için Application nesnesinin SessionEnding olay metodunu ele almamız yeterliydi. SessionEnding olay metodunda kilit nokta SessionEndingEventArgs isimli parametredir. Bu parametre üzerinden erişilen ResonSessionEnding özelliği ResonSessionEnding enum sabiti tipinden bir değer alır. Bu enum sabitinin değerleri Logoff veya Shutdown' dır. SessionEndingEventArgs' ın bir diğer önemli özelliği ise Cancel üyesidir. Bu özelliğe atanan değere göre sürecin iptal edilmesi bir başka deyişle Shutdown veya Logoff' dan vazgeçilmesi sağlanabilir. Şimdi olay metodumuzu aşağıdaki gibi tasarladığımızı düşünelim. (Elbette SessionEnding olayının yüklenmesi için Application elementi içerisine ilgili niteliği eklemeyi unutmamalıyız.)

private void Application_SessionEnding(object sender, SessionEndingCancelEventArgs e)
{
    MessageBoxResult cevap=MessageBox.Show("Bilgisayar " + e.ReasonSessionEnding.ToString() + " nedeniyle kapatılıyor. İptal etmek ister misiniz?", "Kapatma Sorusu", MessageBoxButton.YesNo, MessageBoxImage.Question);
    if (cevap == MessageBoxResult.No)
        e.Cancel = true;
}

Buradaki kod parçasında sembolik olarak kullanıcıya soru sorulmaktadır. Eğer kullanıcı hayır cevabını verirse işletim sisteminin kapanma süreci iptal edilir. Eğer uzun bir süre cevap verilmesse Windows' un standart End Program penceresi karşımıza çıkacaktır. Bu penceredeki progress tamamlandıktan sonra bile No denildiğinde Windows kapatma süreci yine iptal edilecektir. Aşağıdaki ekran görüntüsünde End Program çıktısınında ele alındığı durum gösterilmektedir.

Session_Ending içerisinde daha önceden de bahsedildiği gibi kritik kaynak kaydetme gibi işlemler yapılabilir. Bir başka deyişle uygulamanın son durumunu (Application State) korumak adına çeşitli tedbirler alınabilir.

Bir WPF uygulamasının kapatılması ile ilgili olarak ShutdownMode özelliğinin değerlerine bakıldığından bahsetmiştik. Bu özelliği doğrudan Application elementi içerisinde belirtebileceğimiz gibi kod yardımıylada değiştirebiliriz. Varsayılan hali OnLastWindowsClose olan bu değeri aşağıdaki gibi OnExplicitShutdown olarak değiştirdiğimizde, uygulamanın kapatılabilmesi için Shutdown metodunun çağırılması gerekmektedir.

Şimdi bunu test edelim. Bu amaçla uygulamaya ikinci bir Window daha dahil edilmiş ve ana pencereden Window2 penceresinin açılması sağlanmıştır. Window2 penceresini açma işlemini Window1 üzerindeki bir Button kontrolüne ait Click olay metodu içerisinde aşağıdaki gibi gerçekleştirmekteyiz.

private void btnDigerForm_Click(object sender, RoutedEventArgs e)
{
    Window2 wnd2 = new Window2();
    wnd2.Show();
}

Şimdi uygulamamızı çalıştıralım. Sonradan açılan Window2 kapatıldığında uygulama doğal olarak sonlanmaz. Lakin ana pencere kapatıldığında da uygulama sonlanmaz. Bu iki aksiyonu yaptığımızda ve arka planda çalışan Process' lere baktığımızda uygulamanın gerçektende sonlandırılmadığını görebiliriz. Aşağıdaki ekran görüntüsünde bu durum açık bir şekilde görülmektedir.

Dolayısıyla programdan çıkmak için Shutdown metodunun bilinçli olarak çağırılması gerekmektedir. Bunun için Window1 içerisinden başka bir Button yardımıyla aşağıdaki gibi bir metod çağrısında bulunduğumuzu düşünebiliriz. Dikkat edilecek olursa o anki uygulama nesnesi üzerinden Shutdown metodu çağırılabilmektedir.

private void btnKapat_Click(object sender, RoutedEventArgs e)
{
    Application.Current.Shutdown();
}

Artık uygulamamız başarılı bir şekilde kapatılabilir. Daha önceden de belirtildiği gibi, işletim sistemine bir çıkış koduda gönderilebilir. Bunun için Shutdown metodunun aşırı yüklenmiş versiyonunu kullanmamız ve bir integer değeri parametre olarak vermemiz yeterlidir.

Yazımızda son olarak kod içerisinden fırlatılan istisnaların DispatcherUnhandledException olayında nasıl yorumlanabileceğini inceleyeceğimiz bir örnek geliştireceğiz. Bu amaçla uygulama içerisinden bilinçli olarak bir istisna(exception) nesne örneği fırlatmamız yeterli olacaktır. Window1 içerisinde bu amaçla iki farklı Button kontrolü yerleştirilmiş ve farklı istisna(Exception) nesne örneklerinin fırlatılması sağlanmıştır. Window1.xaml.cs içerisindeki yeni kodlar aşağıdaki gibidir.

private void btnArgumentException_Click(object sender, RoutedEventArgs e)
{
    throw new ArgumentException();
}

private void btnStackOverFlow_Click(object sender, RoutedEventArgs e)
{
    throw new StackOverflowException();
}

Şimdi uygulamayı test edersek .Net Framework 3.0 çalışma zamanının (Run-time) aşağıdaki standart pencereyi çıkarttığını görürüz. Üstelik penceredeki cevabımızdan sonra rapor göndersekte, göndermesekte uygulamanın sonlandığını görürüz.

İstersek bu pencerenin çıkmasını engelleyebiliriz. Bunun için, kod içerisinden ele alınmamış(Unhandled Exceptions) istisnaların ele alınacağını belirtmemiz gerekmektedir. Bu amacı gerçekleştirmek için DispatcherUnhandledException olayında yer alan DispatcherUnhandledExceptionEventArgs parametresinin Handled özelliğine true değerini atamamız yeterlidir. Ancak bu sadece ele alınmamış istisnalar için geçerlidir. Hatta ele alınamayacak cinsten bir istisna olduğunda da bu kontrol yeterli gelmeyebilir. Ne demek istediğimizi biraz daha net anlayabilmek için olay metodlarımızda bir değişiklik yapalım ve ArgumentException istisnasını yakalayacağımız bir try...catch bloğunu aşağıdaki gibi uygulama koduna dahil edelim.

private void btnArgumentException_Click(object sender, RoutedEventArgs e)
{
    try
    {
        throw new ArgumentException();
    }
    catch (Exception excp)
    {
        MessageBox.Show("Bir istisna oluştu");
    }
}

Uygulama bu şekilde test edildiğinde ve ArgumentException istisnası oluşturulduğunda, try...catch bloklarından dolayı .Net Framework' ün çalışma zamanı istisna yönetim mekanizması devreye girerek yukarıda yer alan hata mesaj kutusunu göstermeyecektir. Bunun sebebi hatanın uygulama kodu içerisinde kontrollü bir şekilde try...catch blokları ile ele alınmış(Handled) olmasıdır. Ancak ele alınmamış istisnalar varsa ve bunlar oluştuğunda loglama gibi ek işlemler yapılmak isteniyor ve kurtarılabilirse uygulamanın çalışmasına devam etmesi isteniyorsa DispatcherUnhandledException olayı aşağıdaki şekilde olduğu gibi değiştirilebilir.

private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
    // Burada EventLog dışından bir dosyaya yazdırma işlemleride gerçekleştirilebilir.
    EventLog.WriteEntry("Application", "X Uygulamasında "+DateTime.Now.ToString()+" zamanında "+e.Exception.Message+" hatası alınmıştır.", EventLogEntryType.Error); // e parametresi üzerinden oluşan istisna referansı Exception özelliği ile yakalanabilir.

    e.Handled = true; // Eğer oluşan istisna kurtarılabilecek cinstense, standart hata mesajı kutusunun çıkartılmaması ve uygulamanın çalışmaya devam etmesi sağlanmış olur. Bu özelliğin varsayılan değeri false dur.
}

Bu makalemizde WPF uygulamalarında çekirdek nesnelerden birisi olan Application nesnesini incelemeye çalıştık. Makalemizi sonlandırmadan önce sizlere WPF ile ilişkili faydalı olabilecek bir kaç kitap tavsiye etmek isterim.

Essential Windows Presentation Foundation

Addison Wesley yayınlarının Essential serisinde yer alan kitaplar çoğunlukla içerikleri bakımından ileri seviye konuları dahi içeririler. Bu nedenle bu kitap tam anlamıyla bir başvuru kaynağı olarak düşünülebilir. Zaman zaman okura ağır gelebilecek bir içeriğe sahip olsada kitaplığımızda olması gereken bir kitap.

Pro WPF : Windows Presentation Foundation in .Net 3.0

Şu sıralarda okumakta olduğum bu kitap oldukşa başarılı. Üstelik son çıkan WPF kitaplarından birisi.1000 sayfa olması nedeniyle okuması biraz zaman alıyor :) Ancak içeriğinde WPF ile ilgili hemen her bilgiye ulaşabiliyoruz. Bu kitabı ısrarla tavsiye ederim.

Professional WPF Programming

Diğerlerine göre daha az sayfadan(480 sayfa) oluşan bu kitap içerisinde Expression Blend ile ilgili bir bölümde yer almakta. Çok kısa sürede okunabilecek bir kitap ama bazı yerlerde çok fazla detaya girilmediği için başka kaynaklara bakmayı gerektirebiliyor.

Programming WPF

O'Reilly yayınlarındaki favori yazarım Juval Lowy' ye ait bir kitap olmasada(Onun Programming WCF kitabı bir harika) yeni çıkan bir yayın olması ve 863 sayfalık(kabul edilebilir bir sayfa adedi) içeriğinin bulunması bu kitabın okunması için yeterli. Söz gelimi birinci bölümünü okuduğunuzda, WPF in temel yapısının öğrenmiş ve en büyük elementlerini kavramış oluyorsunuz.

Application nesnesi ile ilgili olarak değinmediğimiz pek çok nokta var. Söz gelimi bu nesneden yararlanarak uygulamadaki tüm pencere yada formları elde edebiliriz. Application' dan türetilen App sınıfı içerisine, tüm uyguladaki nesnelerin erişebileceği üyeler(Metodlar veya Özellikler) koyabiliriz vb. Bu tip konuların ve dahasının araştırılmasını siz değerli okurlarıma bırakıyorum. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Tags:  
Categories:   WPF
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share