Ana Sayfa | English Blog | Seminer TV | Dil Cookie Sil  Blog'u Mail ile takip et!       
Daron Yöndem - June, 2009
bir yazılımcının tasarıları...
 Tuesday, June 30, 2009

Windows 7 ile 7 Saat

Bugün sizlere yepyeni ve taptaze bir etkinlik duyuruyorum :) Biliyorsunuz Windows 7'nin RTM (Üretime Hazır Sürüm) haline gelmesine şurada çok kısa bir zaman kaldı. Bu zamanı doğru bir şekilde değerlendirebilmek ve hem yazılımcılar hem de sistem yöneticileri olarak bu değişimi hazırlanabilmek adına dolu dolu bir etkinlik düzenledik.

Etkinliğimizin en güzel yanı sistem yöneticileri ve yazılımcılar için ayrı ayrı paralel oturumların bulunuyor olması. Açıkçası ben içerikle ilgili çok heyecanlıyım. İtiraf etmek gerekirse etkinlikteki bazı sürpriz hediyeler eminim ki sizi de heyecanlandıracaktır :) Neyse daha fazla ip ucu vermeden gelin sizinle içeriğimizi paylaşiyim.

BT Uzmanları Yazılım Geliştiriciler
10.00-10.30 Windows 7'ye Genel Bakış, Sürümler, Language Packs
10.45-11.30 DirectAccess
11.45-12.30 Applocker
13.30-14.00 BitLocker & BitLocker To Go
14.15-14.45 Problem Steps Recorder & UAC Enhancements
15.00-15.30 Search Federation
15.45-16.15 BranchCache
16.30.17.00 Windows XP Mode
10.00-11.00 MultiTouch ve Ink Uygulamaları Geliştirmek
11.15-12.30 Ribbon kontrolünü nasıl kullanırız?
13.30-14.30 Sensor ve Location API’ları nasıl kullanılır?
14.45-15.15 Uygulama uyumluluğu
15.15-16.00 Taskbar ile gelen yenilikler
16.15-17.00 XPS ve Doküman yazdırma yenilikleri

Benimle beraber sevgili Baki Onur Okutucu, Burak Selim Şenyurt, Mesut Aladağ, Muammer Benzeş'in de konuşmacı olacağı etkinlik 11 Temmuz, Cumartesi günü Microsoft Istanbul ofisinde gerçekleşecek. Microsoft Istanbul ofisi haritası için buraya tıklayabilirsiniz. Katılım için www.inetatr.org adresinden kayıt olmayı unutmayın.

Tuesday, June 30, 2009 6:43:11 PM (GTB Standard Time, UTC+02:00)  #    Comments [9]   Windows 7  | 
 Sunday, June 28, 2009

Dün çok ilginç bir gün geçirdik hep beraber :) Sabah 08.30 gibi aldığım telefonda Yıldız Teknik Üniversitesi'nde bir trafo arızası nedeniyle elektriğin olmadığı bilgisi "Organik Yazılım Günü"müzün organikliğine biraz daha doğallık kattı :) Bir, iki saat içerisinde sorunun giderileceği ümidi ile üniversitede toplaştık. Bir sonraki haber öğlen bir gibi elektriğin geleceğiydi :) Eh biz de en azından "Açık Kaynak Lisans Modelleri" üzerine konuşmak üzere açık bir bahçede :) sohbete daldık.

Açık kaynak lisans modelleri üzerine bir sohbet...
Açık kaynak lisans modelleri üzerine bir sohbet...

Sohbetimiz açık kaynak lisans modellerinden başladı ve apayrı konulara doğru ilerledi. İtiraf etmek gerekirse ben böyle sohbet bazlı bir etkinlik hayaline sahiptim :) Hangi konsept ile ne şekilde yapılması gerektiğini düşünürken böyle bir vesile pek aklıma gelmemişti. Herkese katılımından dolay çok teşekkür ediyorum.

Peki devamında ne mi yaptık? Sohbetimiz yaklaşık öğlen bire kadar sürdü :) Israrlı bir şekilde elektrikler gelmeyince Beşiktaş'a bir yemeğe indik. Yemek sonrası üniversiteye hala elektriklerin gelmediği bilgisini alınca Starbucks Beşiktaş'ta sohbete devam ettik ve yavaş yavaş oradan da 15.00 gibi dağıldık diyebilirim.

Katılan herkese çok teşekkür ediyorum. Her anlamda ilginç bir gün oldu. Özellikle şehir dışından gelen katılımcılarımızdan elektrik idaresi adına özür diliyorum! Sanırım "elektrik kesintisi" nedeniyle format değiştiren nadir etkinliklerden olduk. Yaz içerisinde planladığımız esas içerik ile etkinliğimizi tekrar edeceğiz. Bu sefer ricam herkes yanında birer kalem pil getirsin :) seri bağlayıp bir şeyler yaparız belki zor durumlarda :)

Hepinize tekrar teşekkürler...

Unutmadan :) Efsanevi fuji yağmurlarında yıkanmış fuji elmaları için Burak'a ÇOK teşekkürler :)

Sunday, June 28, 2009 2:20:48 PM (GTB Standard Time, UTC+02:00)  #    Comments [15]    | 
 Monday, June 22, 2009

Bu yazımızda Silverlight Toolkit ile beraber gelen AutoCompleteBox kontrolünü inceleyeceğiz. Bilgisayarınızda Silverlight Toolkit yükledikten sonra Toolkit içerisindeki tüm kontrolleri Visual Studio içerisinde Toolbox'ta görebilirsiniz. Ayrıca Toolkit DLL'lerini referans alarak kontrolleri elle XAML sayfalarınıza ekleme şansınız da var. Örneğin AutoCompleteBox kontrolünü inceleyecek olursak; bir sayfaya söz konusu kontrolü eklemek için ilk olarak Toolkit Assembly'lerinden System.Windows.Controls.Input Assembly'sini projenize referans almanız sonrasında da System.Windows.Controls NameSpace'i altından kontrolü bulmanız gerekecektir.

[XAML]

<UserControl xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input" x:Class="SilverlightApplication54.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <input:AutoCompleteBox></input:AutoCompleteBox>

    </Grid>

</UserControl>

Yukarıda gördüğünüz basit örnek içerisinde XAML root elementimizde gerekli input XML namespace'i tanımlanmış durumda. Böylece artık input namespace'i üzerinden AutoCompleteBox kontrolümüzü kullanabiliriz. Daha önce de bahsettiğim üzere eğer Visual Studio'nun Toolbox'ından söz konusu kontrolü kod tarafına sürükle bırak tekniği ile yerleştirirseniz zaten tüm bu işlemleri otomatik olarak gerçekleştirilebiliyor.

Haydi veriye bağlayalım!

Kontrolümüzü artık sahneye yerleştirdiğimize göre hemen çalıştırmak ve sonucu görmek isteyeceğiz fakat onun öncesinde tabi ki bir veri bağlantısı yapmamız şart. Kullanıcılar kutuya birşey yazarken AutoComplete kısmında neler gösterilecek?

[XAML]

<UserControl xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input" x:Class="SilverlightApplication54.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <input:AutoCompleteBox x:Name="myAutoCompleteBox" VerticalAlignment="Top"></input:AutoCompleteBox>

    </Grid>

</UserControl>

[VB]

    Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim Secenekler() As String = {"Silverlight", "Silvernight", "Silverfight", "SilverMonth"}

        myAutoCompleteBox.ItemsSource = Secenekler

    End Sub

[C#]

        void MainPage_Loaded(object sender, RoutedEventArgs e)

        {

            string[] Secenekler = { "Silverlight", "Silvernight", "Silverfight", "SilverMonth" };

            myAutoCompleteBox.ItemsSource = Secenekler;

        }

Yukarıdaki kod içerisinde aslında basit bir şekilde tüm seçenekleri doğrudan AutoCompletebox'ın ItemsSource'una atamış oluyoruz. Sonrasında AutoCompleteBox kendi içerisinde gerekli filtrelemeleri kullanıcılar metin giriş yaptıkça gerçekleştirerek sonucu gösteriyor.

Silverlight içerisinde basit bir AutoCompleteBox
Silverlight içerisinde basit bir AutoCompleteBox

Peki ya web servisinden alacak olsak sonuçları?

Bir önceki örneği incelerken büyük ihtimal ile kendi içinizden "Eee peki kocaman bir listemiz varsa hepsini istemciye mi alacağız?" diye sormuşsunuzdur. Tabi ki hayır. Aslında normal şartlarda yapmamız gereken şey AutoCompleteBox içerisine yazılan kelimeyi sunucuya göndermek ve geri gelen sonucu da AutoCompleteBox'ın AutoComplete bölümünde göstermek. İşte bu işlemi yapabilmek için AutoCompletebox'ın Populating eventını kullanıyoruz.

Örneğimizde Tavsiye adında bir webmethod kullanacağız. Web servisinin yazımı kısmına girmeyeceğim fakat kabaca bahsetmek gerekirse istemciden aranacak kelimeyi alan ve veritabanına gidip uygun AutoComplete seçeneklerini bir list olarak döndüren bir webmethod yeterli olacaktır.

[VB]

    Private Sub myAutoCompleteBox_Populating(ByVal sender As Object, ByVal e As System.Windows.Controls.PopulatingEventArgs) Handles myAutoCompleteBox.Populating

        e.Cancel = True

        Dim servis As New ServiceReference1.Service1Client

        AddHandler servis.TavsiyelerCompleted, AddressOf Servis_TavsiyelerCompleted

        servis.TavsiyelerAsync(myAutoCompleteBox.Text)

    End Sub

[C#]

        public MainPage()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

            myAutoCompleteBox.Populating += new PopulatingEventHandler(myAutoCompleteBox_Populating);

        }

 

        void myAutoCompleteBox_Populating(object sender, PopulatingEventArgs e)

        {

            e.Cancel = true;

            ServiceReference1.Service1Client servis = new ServiceReference1.Service1Client();

            servis.TavsiyelerCompleted += Servis_TavsiyelerCompleted;

            servis.TavsiyelerAsync(myAutoCompleteBox.Text);

        }

Yukarıdaki kodumuzda AutoCompleteBox'ın Populating durumunu yakalayarak hemen e.Cancel değerini True yaparak Populate işlemini iptal ediyoruz. Neden mi? Çünkü Populate işlemi, yani AutoComplete listesinin gösterilmesi işlemi şu anda yapılamaz. İşlemi yapabilmek için bizim Webmethod'umuza parametreyi vermemiz, söz konusu bilginin sunucuya gitmesi ve en önemli cevabın gelmesi gerekiyor! Cevap gelmeden Populate edemeyiz! O nedenle burada hemen Populate işlemini iptal ediyoruz. Sonrasında WebServis'imizden bir kopya alıp Completed durumu da ayrı bir handler'a bağlayıp elimizdeki AutoCompleteBox içerisinde metni Tavsiyeler adındaki Webmethod'umuza gönderiyoruz.

[VB]

    Private Sub Servis_TavsiyelerCompleted(ByVal sender As Object, ByVal e As ServiceReference1.TavsiyelerCompletedEventArgs)

        myAutoCompleteBox.ItemsSource = e.Result

        myAutoCompleteBox.PopulateComplete()

    End Sub

[C#]

        private void Servis_TavsiyelerCompleted(object sender, ServiceReference1.TavsiyelerCompletedEventArgs e)

        {

            myAutoCompleteBox.ItemsSource = e.Result;

            myAutoCompleteBox.PopulateComplete();

        }

Populating durumunda çağırdığımız web servisimizin Completed durumunda artık veri elimizde olduğunda göre doğrudan AutoCompleteBox'ımızın ItemsSource'una verebiliriz. Son olarak tabi ki Populate işlemini bitirdiğimizi de AutoCompleteBox'a belirtmemiz gerek ki hemen sonucu kullanıcıya göstersin.

Arama şeklini nasıl değiştiririz?

Varsayılan ayarları ile AutoCompleteBox'lar kendilerine verilen verinin içinde arama yaparken kullanıcının yazdığı metinile başlayan sonuçları gösterirler. Bunun değiştirmenin yolu AutoCompletebox'ların SearchMode özelliğinde yatıyor.

Veri kaynağında nasıl arama yapılacağını belirleyin.
Veri kaynağında nasıl arama yapılacağını belirleyin.

Peki bu arama sistemleri size yetmedi ve daha da özelleştirmek isterseniz ne yapabilirsiniz? Filtreleme işlemini tamamen ele alma şansınız da var. Bu durum özellikle AutoCompleteBox'a kendi nesne tiplerinizi bağladığınızda çok anlamlı olabilir. Filtreleme esnasından belki de sadece ItemsSource'a verdiğiniz nesnelerin belirli Property'lerine göre ayrı ayrı mantıklarıda aramalar yapılsın isteyebilirsiniz.

[VB]

    Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        myAutoCompleteBox.ItemFilter = AddressOf Arama

    End Sub

 

    Function Arama(ByVal metin As String, ByVal nesne As ServiceReference1.Service1Urun) As Boolean

        If nesne.Adi.Contains(metin) Then

            Return True

        Else

            Return False

        End If

    End Function

[C#]

        public MainPage()

        {

            InitializeComponent();

            myAutoCompleteBox.ItemFilter = Arama;

        }

 

        bool Arama(string metin, ServiceReference1.Service1Urun nesne)

        {

            if (nesne.Adi.Contains(metin)) {

                return true;

            }

            else {

                return false;

            }

        }

Yukarıdaki örneğimizde AutoCompleteBox'ın ItemFilter'ına yeni bir metod atıyoruz. Bu metod parametre olarak o anda filtrelenmek istenen Item'ı, yani benim örneğimde ServiceReference ile beraber gelen tipte bir nesneyi ve filtrelemede kullanılacak metni alıyor. Sonrasında gerekli kontrolleri yaptıktan sonra geriye filtrelediğiniz her bir nesnenin gösterilip gösterilmeyeceğine dair birer Boolean değer döndürmeniz yeterli olacaktır.

Görsel özelleştirmeler...

Makalemizde incelediğimiz tüm özellikleri kullandınız, kendi nesnelerinizi web servisi ile döndürdünüz ve özel bir filtrelemede de eklediniz. Fakat AutoComplete listesinde daha çok detay göstermek istiyorsunuz. Bu durumda gelin AutoCompleteBox'ın ItemTemplate'ini bir inceleyelim.

[XAML]

        <input:AutoCompleteBox x:Name="myAutoCompleteBox" VerticalAlignment="Top">

            <input:AutoCompleteBox.ItemTemplate>

                <DataTemplate>

                    <Grid HorizontalAlignment="Stretch" Width="Auto">

                        <Grid.ColumnDefinitions>

                            <ColumnDefinition Width="250" />

                            <ColumnDefinition Width="250" />

                        </Grid.ColumnDefinitions>

                        <TextBlock Foreground="Red" Grid.Column="0" Text="{Binding Adi}" />

                        <TextBlock Foreground="Red" Grid.Column="1" Text="{Binding Soyadi}" />

                    </Grid>

                </DataTemplate>

            </input:AutoCompleteBox.ItemTemplate>

        </input:AutoCompleteBox>

Kod içerisinde de gördüğünüz üzere AutoCompleteBox'ın ItemTemplate'i aslında kendi içerisinde AutoComplete kısmında tekrar ettiği satırları tanımlıyor. Bu satırların tasarımı aslında arkaplanda bir ListBox'ın Item'larıdır. Bizim örneğimizde ulaşmaya çalıştığımız işlevsellik iki veya daha çok Property'i kullanıcıya gösterirken arka plandaki filtrelememiz ile de bir AutoComplete işlevselliği sağlamak. Zaten hali hazırda AutoCompleteBox'ımıza nesnelerimizi ItemsSource üzerinden aktardığımız için o nesnelerin tüm Property'lerine dair Binding'leri de ItemTemplate içerisinde ayarlayabiliyoruz.

AutoComplete kısmı özelleştirilmiş bir AutoCompleteBox.
AutoComplete kısmı özelleştirilmiş bir AutoCompleteBox.

Taktikler

Eğer herhangi bir AutoCompleteBox'ın ItemsSource'una kendi tanımladığınız tiplerden oluşan listeler verirseniz AutoComplete bölümünde saçma metinler görebilirsiniz. Normal şartlarda bir String Array vs verildiğinde herhangi bir sorun olmuyor fakat kendi tanımladığınız sınıflarda veya web servislerinden Proxy aracılığı ile aldığınız sınıflarda sorun yaşayabilirsiniz. Bu durumu düzeltmenin yolu kendi sınıflarınız için birer ToString extension'ı yazmak olabilir. AutoCompleteBox kendisine verilen tüm nesnelerin üzerinden ToString metodunu çağırarak gelen metni AutoComplete kısmında gösterir. O nedenle siz de kendi sınıflarınızda ToString'i override ederek kendi istediğiniz verileri geri döndürebilirsiniz.

[VB]

Namespace ServiceReference1

    Partial Public Class Service1Urun

        Public Overrides Function ToString() As String

            Return Me.Adi & Me.Soyadi

        End Function

    End Class

End Namespace

[C#]

namespace ServiceReference1

{

    public partial class Service1Urun

    {

        public override string ToString()

        {

            return this.Adi + this.Soyadi;

        }

    }

}

Yukarıdaki örnek kod içerisinde ServiceReference1 adında bir servis referansı ile beraber gelen Service1Urun adındaki nesnenin ToString metodunu override ediyoruz. Artık söz konusu nesneden sonra ToString denildiğinde buradaki Function çalışacak ve nesnenin Adi ile Soyadi Property'lerini birleştirerek geri döndürecek. Böylece AutoCompleteBox da bu ToString'den gelen veriyi gösterebilecek.

Otomatik seçilseler...

AutoComplete listesini gösteriyorsunuz fakat her seferinde kullanıcının bir kayıt seçmek zorunda kalmasını da istemiyorsunuz. Belki de en uygun seçecek hemen seçilebilir şekilde gelse? Nasıl mı?

IsTextCompletionEnabled = True olursa...
IsTextCompletionEnabled = True olursa...

AutoCompleteBox'ın IsTextCompletionEnabled özelliği True yaparsanız yukarıdaki gibi kullanıcılar metin girişi yaparken bir yandan da en uygun seçeneği seçtirebilirsiniz. Böylece anında seçimi onaylayarak kullanıcılar hızlıca işleme devam edebilirler.

Hepinize kolay gelsin.

Monday, June 22, 2009 2:32:11 PM (GTB Standard Time, UTC+02:00)  #    Comments [0]   Silverlight 3.0  | 
 Tuesday, June 16, 2009

Uzun süredir bu anı bekliyordum :) Açık kaynak kodu üreten ve tüketen bir yazılımcı olarak bu konuda bilgi ve birikimlerimizi birbirimiz ile paylaşmanın zamanı geldi. Bu sefer çok daha farklı bir etkinlik beraber olacağız.

Açık kaynak destekçisi herkesi bekliyoruz!

Gördüğünüz üzere bu sefer oturumlarımız yarım saatlik ve konuşmacılarımız da sizin aranızdan kişiler. Eğer siz de benim gibi açık kaynak kodu destekliyor, açık kaynak kodu üretiyor veya tüketiyorsanız hiç fark etmez, hepinizi 27 Haziran'da bu Organik etkinliğe bekliyoruz!

Kayıt olmayı unutmayın : www.inetatr.org

Tuesday, June 16, 2009 6:31:38 PM (GTB Standard Time, UTC+02:00)  #    Comments [14]    | 
 Saturday, June 13, 2009

Silverlight 3.0 ile beraber gelen en ilgi çekici özelliklerden biri 3D desteği. Bu makalemizde Silverlight ile beraber gelen 3D özelliklerine bakmadan önce aslında nasıl bir 3D mantığı ile karşı karşıya olduğumuzu netleştirmekte fayda var. Özellikle Silverlight öncesi WPF ile ilgilnenlerin 3D beklentisi eminim ki çok yüksek olacaktır. Tabi daha öncesinde 3D oyun programlama ile ilgilenenleri liste dışında tutuyoruz :) amacımız zaten oyun tarafı değil.

WPF içerisinde alıştığımız yapıdaki 3D ortamda istediğimiz bir 3D objeyi alarak X, Y ve Z koordinatları ile doğrudan kontrol edebiliyorduk oysa Silverlight tarafındaki 3D desteği biraz daha farklı. Aslında bakılırsa tam olarak 3D desteği olarak da adlandırılmıyor söz konusu mekanizma. Esas bahsedilen özelliğin adı "3D Plane" olarak geçiyor. Peki bunun anlamı nedir? Özetlemek gerekirse aslında Silverlight içerisindeki 3D desteği sadece 3D ortam şeklinde. Yani sadece içerisinde bulunduğunuz ortam 3D :) elinizdeki nesneler 3D değil.

Ne? Nasıl?

Elinizde iki boyutlu bir nesne olduğunu düşünün. Aslında çok basit bir şekilde bir TextBox düşünseniz de yeterli olacaktır. Bir TextBox nesnesi 3D bir nesne midir? Yani kalınlığı var mıdır? Hayır, bir TextBox'ın sadece genişlik ve yüksekliği vardır. İşte bu şekilde 2D nesneleri Silverlight içerisinde artık sanki 3D ortamdaymış gibi gösterebiliyorsunuz. Özünde bahsettiğimiz şey 3D Projection (Yansıtma). Nasıl ki elimizdeki 2D nesnelere RenderTransform'lar uygulayarak iki boyutlu ortamda istediğimiz gibi manipüle edebiliyorduysak artık Silverlight içerisinde tüm görsel nesneleri 3D ortamda da manipüle edebiliyorsunuz. Gelin hemen hızlı bir örnek ile konuya girelim.

Farklı 3D Transform'lar uygulanmış ComboBox'lar.
Farklı 3D Transform'lar uygulanmış ComboBox'lar.

Yukarıdaki ekran görüntüsünde aslında basit bir ComboBox görüyorsunuz fakat söz konusu ComboBox sanki üç boyutlu ortamdaymış gibi döndürülmüş hatta görüntülerden birinde arkasına çevrilmiş. Tüm bu mekanizma animasyonlar dahil çalışırken eldeki nesneleriniz hepsi "arkası döndürüldüğünde" bile kullanılabilir olmaya devam ediyor. Düşünün, tüm bunlar animasyonlar ile birleştirildiğide üç boyutlu ortamda gezen kontroller şeklinde :) ne kadar ilginç arayüzler yaratılabilir.

Kod tarafına geçiş yapalım...

Kod tarafında herhangi bir nesneyi 3D ortamdaymış gibi ekrana yansıtmak istiyorsanız yapmanız gereken söz konusu nesne için hemen bir Projection tanımlamak. Şu an için Silverlight içerisinde sadece PlaneProjection destekleniyor o nedenle tek seçeneğimiz var.

[XAML]

<UserControl x:Class="SilverlightApplication43.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <ComboBox Width="200" Height="20">

            <ComboBox.Projection>

                <PlaneProjection RotationX="208"

                                CenterOfRotationY="1"

                                CenterOfRotationX="1"

                                RotationY="33"

                                GlobalOffsetY="15"

                                GlobalOffsetX="35"/>

            </ComboBox.Projection>

            <ComboBox.Items>

                <ComboBoxItem Content="Seçenek 1"></ComboBoxItem>

                <ComboBoxItem Content="Seçenek 1"></ComboBoxItem>

                <ComboBoxItem Content="Seçenek 1"></ComboBoxItem>

                <ComboBoxItem Content="Seçenek 1"></ComboBoxItem>

                <ComboBoxItem Content="Seçenek 1"></ComboBoxItem>

            </ComboBox.Items>

        </ComboBox>

    </Grid>

</UserControl>

Bir önceki bölümde sizinle paylaştığım ekran görüntüsünde sağ tarafta bulunan ComboBox'ın XAML kodunu yukarıda bulabilirsiniz. Burada önemli olan şey ComboBox'ın Projection özelliğine atanmış olan PlaneProjection nesnesi. Söz konusu PlaneProjection nesnesinin birçok özelliği set edilmiş durumda. İşte biz de makalemizin geri kalanında bu özelliklerin anlamlarına göz atacağız.

RotationX, RotationY, RotationZ

İsimlerinden de anlaşılacağı üzere bu özellikler bir şekilde elimizdeki nesnenin üç boyutlu ortamda kaç derece ile döndürüleceğini belirliyor. Peki X, Y ve Z derken neyi kast ediyorlar? Hemen gözünüzde üç boyutlu bir ortam canlandırın ve söz konusu ortadam ComboBox'ımızı koyalım. ComboBox'ı etrafında döndürebileceğiniz aslında üç adet eksen vardır. Bunlardan birincisi X ekseki. Eğer X ekseki etrafında ComboBox'ı döndürmek istiyorsanız RotationX değerine gerekli açıyı verebilirsiniz. Aynı şekilde Y ve Z eksenleri etrafında da eldeki nesne döndürülecekse rahatlıkla özelliklere de açı değerleri vererek istediğiniz sonucu alabilirsiniz.

Eksenler ve Rotation özellikleri...
Eksenler ve Rotation özellikleri...

Yukarıdaki görsel içerisinde X, Y ve Z eksenlerini ve etraflarında döndürülmüş Button görsellerini inceleyebilirsiniz. Bu noktada dikkat edilmesi gereken detaylardan biri de aslında eksenlerin gerçekten de her nesnenin tam ortasından geçiyor olması. Her görsel nesne kendi X, Y ve Z eksenlerine sahip ve bunlar varsayılan ayarlarda nesnenin tam ortasından geçecek şekilde ayarlanmış. Örneğin X ekseni etrafında 45 derece döndürülen bir Button eksen tam da Button'un ortasından geçtiği için kendi merkezi etrafında dönüyormuş gibi gözüküyor.

İsterseniz eksenlerin yerlerini değiştirme şansınız da var. Tüm eksenler varsayılan ayarları ile nesnelerin tam ortasından geçersen harici Offset değerleri ayarlanabiliyor. Offset değerleri bilindiği üzere 0 ile 1 arasında nesnenin sol üst köşesinden başlayarak sağ alt köşesine gider. Eğer X eksenine 0 Offset değerini verirseniz bu X ekseninin düğmenin en üstüne denk geleceği anlamına gelir. Aynı şekilde eğer 1 değerini verirseniz ekseni düğmenin en alt noktasına itmiş olursunuz.

CenterOfRotation ayarları.
CenterOfRotation ayarları.

Eksenin yerini değiştirmiş olmak artık düğmemizin kendi merkezine göre değilde üst noktasına göre veya alt noktasına göre dönmesini sağlayacaktır. CenterOfRotation olarak geçen bu özelliğin X, Y ve Z Offset değerlerini ayrı ayrı düzenleyebiliyorsunuz. Yukarıdaki görselde de görebileceğiniz üzere aslında X ekseninin Y eksenine göre ne kadar aşağıda veya yukarıda olacağını ayarlıyoruz. O nedenle düğmenizin X eksenin Offset değeri olarak ne kadar uzak olacağına dair bilgiyi CenterOfRotationY değerine atamanız gerekiyor. Aynı şekilde diğer eksenler için de CenterOfrotationX ve CenterOfRotationZ özellikleri de mevcut.

GlobalOffset ve LocalOffset ayarları.

Şu ana kadar eksenleri etrafında nesnelerin ne açı ile döneceklerini ve eksenlerin nesnelerin neresine denk geleceğini ayarladık. Oysa başka dertlerimiz de olabilir. Bunlardan en basiti tüm işlemleri yaparken nesneyi uygulama genelinde Global olarak da posizyonlandırmak isteyebilirsiniz. İşte üç boyutlu ortamda nesnenize Global bir pozisyon vermek istiyorsanız kullanacağınız özellikleri GlobalOffsetX, GlobalOffsetY ve GlobalOffsetZ özellikleri olacaktır. Bu özellikler kesinlikle animasyonunuzu veya nesnenin nasıl döneceğini vs değiştirmez. Nesneyi kendi X, Y ve Z eksenleri ile beraber Global, uygulama bazında ayrı bir X, Y ve Z ekseni üzerinden tekrar pozusyonlandırdığınızı düşünebilirsiniz.

Gelelim animasyonlarınızı en hareketli ve janjanlı hale getirecek olan son özelliğe; LocalOffset! Adından da anlaşılacağı üzere aslınad yine nesnenizi bir şeyden uzaklaştırıyorsunuz fakat bu sefer global anlamda değil. Nesnenin kendi içinde belirlenen açılarla etrafında döndüğü eksenlerden devasa miktarlarda uzaklaştırılarak sanki uzaklardan bir yerden geliyormuş efekti ile ekrana gelmesini sağlayan mekanizma LocalOffset'tir. Düşünün ki nesneniz ekseni etrafında değil de ekseninden 50 piksel uzakta bir dairede dönüyor? Nasıl gözükürdü?

Ekseninden uzaklarda dönen bir Button.
Ekseninden uzaklarda dönen bir Button.

Yukarıdaki görüntüden bir Button'un ekseninden uzakta dönmesi halinde nasıl bir animasyon oluşabileceğini tahmin edebilirsiniz. Offset değerlerini arttırarak ve sadece X değil farklı eksenlerde aynı anda açılar vererek animasyonlar hazırladığınızda nesnelerinizi üç boyutlu ortamda istediğiniz gibi hareketlendirebiliyorsunuz. Tüm bu süreçte Button'un sürekli olarak tıklanabilir kaldığını da hatırlatmak isterim.

Sonuç

Yukarıda bahsettiğimiz tüm özelliklere programatik erişimin haricinde animasyon içerisinde de kullanabileceğiniz kesin. Özellikle unutulmaması gereken nokta Projection'ın tüm nesnelere uygulanabiliyor olması, yani elinizde içi dolu bir StackPanel veya bir DataGrid olabilir ve tüm bunları keni iç elementleri ile beraber rahatlıkla anime edebilirsiniz. Hatta belki StackPanel'inizin içinde bir nesne zaten kendi X, Y, Z koordinatlarına göre üç boyutlu bir animasyonu gerçekleştiriyordur ve siz StackPanel'i de başka bir üç boyutlu animasyona alarak daha ilginç manzaralar da yaratabilirsiniz.

Hepinize kolay gelsin.

Saturday, June 13, 2009 9:31:50 PM (GTB Standard Time, UTC+02:00)  #    Comments [3]   Silverlight 3.0  | 
 Saturday, June 06, 2009

Silverlight ve WPF ile beraber gelen Resource yapıları özellikle web tasarımcıları tarafından alışılmış CSS'in çok daha gelişmiş bir hali şeklinde tanımlanabilir. Sadece görsel anlamda değil aynı anda kontrollerin veya nesnelerin işlevsellik anlamında da birçok özelliklerinin birer stil olarak saklanmasını ve tekrar kullanılmalarının yanı sıra uygulama çerçevesinde merkezi yönetim sağlamasını da düşünürsek Silverlight projelerde Resource kullanımının büyük önemi var.

Maalesef Silverlight 2.0 ile beraber elimizdeki tek seçenek Resource'larımızı App.xaml içerisine yerleştirerek uygulama çapında kullanmaktı. Oysa çoğu zaman harici CSS dosyaları gibi Silverlight tarafında da Resource'larımızı harici dosyalara alarak farklı uygulamalarda da ortak olarak kullanmak istedik. Microsoft bizi dinlemiş olsa gerek ki Silverlight 3.0 ile beraber MergedDictionaries yapısı geliyor.

Nedir bu MergedDictionaries?

MergedDictionary'ler sayesinde harici bir dosyada yer alan ResourceDictionary'lerimizi Silverlight uygulamalarımda yer alan kontrollerinin Resources koleksyonlarına ekleyebiliyoruz. Böylece artık harici dosyalarda Resource'larımızı saklayabiliriz. Peki nasıl yapacağız? Gelin basit bir örnek ile adım adım bu işlemleri nasıl yapabileceğimiz ve ne gibi seçeneklerin bizi beklediğine bir göz atalım.

[XAML / HariciResource.xaml]

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <SolidColorBrush x:Key="OzelRenk" Color="#FF499422"/>

</ResourceDictionary>

Yukarıda gördüğünüz kod harici bir XAML dosyası içerisinde tek başına bulunuyor. Aslında bu dosya harici bir ResourceDictionary tanımlıyor ve içerisinde de sadece bir adet SolidColorBrush var. Bunun gibi farklı Resource tiplerini de bu dosya içerisinde saklayabiliriz. Biz şimdilik kodlarımız kısa ve anlaşılır olsun diye bu basit örnek üzerinden ilerleyelim. OzelRenk adındaki bu Resource'u uygulamanız içerisinde kullanabilmeniz için ilk olarak dosyayı Silverlight projenize eklemeniz gerekiyor. Ekleme işlemi sonrası Solution Explorer içerisinden dosyayı seçtiğinizde Properties panelinde Build Action'ın Content olmasına dikkat edin. Böylece XAML dosyası XAP içerisindeki DLL'inize eklenecektir.

Dosyamızı projemize ekledik fakat hala onu herhangi bir yerde tanımlamadık. Bu noktada iki seçenek var, bunlardan biri söz konusu tanımlamayı App.XAML içerisinde yaparak harici Resource dosyasındaki tüm Resource'ların uygulamada her yerde kullanılabilmesini sağlamak, ikincisi ise sadece UserControl başına harici dosyayı Import etmek. Her iki seçenekte de aynı kod kullanılacağı için gelin App.XAML içerisinde nasıl bir değişiklik yapmamız gerektiğine göz atalım.

[XAML / App.xaml]

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            x:Class="SilverlightApplication14.App"

            >

    <Application.Resources>

 

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="HariciResource.xaml"/>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

 

    </Application.Resources>

</Application>

Gördüğünüz gibi Application.Resources içerisinde bir MergedDictionaries serisi yaratarak içerisine de harici ResourceDictionary'imizi koyuyoruz. Böylece artık harici dosyamızdaki tüm Resource'lar rahatlıkla uygulama genelinde kullanılabilecek.

[XAML / UserControl1.xaml]

<UserControl x:Class="SilverlightApplication14.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <Rectangle Fill="{StaticResource OzelRenk}" Stroke="#FF000000" Height="64" Margin="74,75,159,0" VerticalAlignment="Top"/>

 

    </Grid>

</UserControl>

Yukarıda gördüğünüz basit UserControl içerisinde bir Rectangle bulunuyor. Bu Rectangle aslında bizim biraz önce uygulamamıza eklediğimiz harici Resource dosyası içerisindeki bir kaynağı kullanıyor. Sadece buradaki koda baktığımızda aslında söz konusu Resource'ların App.xaml içerisinde olması ile harici dosyada olması arasında bir fark yok.

Ya harici projelerdeki Resource dosyaları?

Şu ana kadarki örneğimizde Resource dosyası kendi projemiz içerisindeydi. Oysa belki de Resource'larınızı harici Silverlight Class Library'ler içerisinde tutuyor olabilirsiniz. İşte böyle bir durumda da doğrudan söz konusu DLL'i referans alarak onun içerisindeki bir Resource Dictionary'yi projenizde kullanmak isteyeceğiniz kesin. Bu gibi bir durumda değişen tek şey hedeflediğiniz XAML dosyasının adresi yani Source.

[XAML / App.xaml]

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            x:Class="SilverlightApplication16.App"

            >

    <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="SilverlightClassLibrary1;component/HariciResource.xaml"/>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>

</Application>

Yukarıdaki şekli ile örneğimizi incelediğinizde unutmayın ki projemize referans aldığımız ve Assembly adı SilverlightClassLibrary1 olan bir DLL bulunuyor. Söz konusu DLL içerisinde yine bir önceki örnekte kullandığımız HariciResource.xaml dosyası bulunuyor. App.xaml dosyamızdaki tek farklılık HariciResource.xaml dosyasına ulaşırken kullandığımız yol tanımı.

Böylece Resource'larınızı harici DLL'lerde tutabilir ve istediğiniz projeye referans alarak kullanabilirsiniz. Veya projelerinizde Static Resource isimleri kullanarak yer geldiğinde sadece referansı değiştirerek tüm projenin görselliğinin değişmesini sağlayabilirsiniz.

Hepinize kolay gelsin.

Saturday, June 06, 2009 9:16:29 PM (GTB Standard Time, UTC+02:00)  #    Comments [2]   Silverlight 3.0  | 
 Friday, June 05, 2009

Bazı yazılımsal işlemleri özel donanım desteği her zaman performansı arttırır. Aslında bunun çok basit bir nedeni var özünde. Yapacağımız yazılımsal işleve özel bir donanım kullanıyor olmanın yanı sıra özellikle bugün bilgisayarlarımızda normal şartlarda söz konusu işlemi yapmakla yükümlü birimi başka işlemler için serbest bırakıyor olmamız da önemli bir nokta. Neden mi bahsediyorum? Örneğin bilgisayarlarımızdaki ekran kartları! Hatırlayanlarınız vardır :) Eskiden oyunlardaki 3D hesaplamaları vs bilgisayarlarımızın normal işlemcileri üstlenirdi. Sonrasında ekran kartlarında GPU denilen bir birim ortaya çıktı ve 3D dahil birçok görsel hesaplamalayı işlemcilerimizin üzerinden aldı. Böylece hem işlemcilerimiz rahatladı :) diğer işlemler ile çok daha rahat ilgilenir oldular hem de söz konusu 3D işlemleri yapmaya özel tasarlanmış bir donanım artık söz konusu işlemlerden sorumlu olduğu için çok daha yüksek performans alınabildi.

Silverlight ile alakası nedir?

Aslına bakarsak Silverlight içerisinde de bolca görsel işlem yapıyoruz. En basit örnek olarak bir video oynatmayı bile ele alabiliriz. Biz videomuzu bir MediaElement ile sahneye koyduğumuzda video dosyasının orijinal yükseklik ve genişliği ile hiç ilgilenmeyiz. Oysa arkaplanda oynatılacak olan video uygun şekillerde MediaElement içerisine yerleştirilir ve aslında gerçek zamanlı olarak videonun içindeki her kare resim tekrar boyutlandırılır. Özellikle Full HD gibi yoğun data içeren yüksek çözünürlüklü videolarda aslında ciddi bir işlem gücü gerekiyor ve şu ana kadar da bu işlemleri Silverlight tarafında bilgisayarımızın işlemcisi üstleniyordu.

Oysa zaten bugün hali hazırda neredeyse her bilgisayarda en kötüsünden bir GPU yok mu? Neredeyse GPU'suz bilgisayar satın alamaz hale geldik. Tabi bu güzel bir gelişme ama bu gelişmeden Silverlight'ın da faydalanması gerek! İşte Silverlight 3.0 ile beraber "GPU Acceleration" denilen GPU (donanım) destekli işlemler ile uygulamanın hızlandırılması artık mümkün.

Video uygulamalarında Hardware Acceleration...

Hemen basit bir şekilde elimizdeki High Definition bir videoyu oynatan Silverlight uygulaması hazırlayalım. Ekrana bir MediaElement koyarak Source özelliğini ayarlamamız yeterli olacaktır.

[XAML]

<UserControl x:Class="SilverlightApplication10.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <MediaElement Source="Wildlife.wmv" />

    </Grid>

</UserControl>

Yukarıdaki XAML kodu ile uygulamamızı çalıştırdığımda herhangi bir sorun yok. Video rahatlıkla oynuyor fakat kendi makinemde yaptığım testte CPU kullanımı %25 civarında geziyor. Acaba sadece bu videonun en azından tekrar boyutlandırma işini işlemciden alıp GPU'ya aktarsak işlemci kullanımı ne kadar düşer?

Silverlight içerisinde varsayılan ayarları ile GPU kullanımı gelmiyor. GPU kullanımını bir uygulamada açmak için söz konusu uygulamanın HTML sayfa içerisine yerleştirildiği OBJECT taglarına ekstra bir parametre geçmeniz gerekiyor.

[HTML]

        <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">

            <param name="source" value="ClientBin/SilverlightApplication10.xap"/>

            <param name="onerror" value="onSilverlightError" />

            <param name="background" value="white" />

            <param name="minRuntimeVersion" value="3.0.40307.0" />

            <param name="autoUpgrade" value="true" />

            <param name="EnableGPUAcceleration" value="true" />

            <a href="http://go.microsoft.com/fwlink/?LinkID=141205" style="text-decoration: none;">

                 <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>

            </a>

        </object>

Yukarıdaki HTML kodları arasında da görebileceğiniz üzere artık Silverlight uygulamamızı gösterecek olan OBJECT taglarına ek olarak bir de EnableGPUAcceleration parametresi veriyor ve söz konusu değeri de True olarak set ediyoruz. Böylece artık Silverlight uygulamamız GPU desteğine kavuşacak fakat ek olarak uygulama içerisinde hangi kontrollerin GPU tarafından oluşturulması gerektiğini de belirtmemiz gerekiyor. Bunun için kontrol bazında CacheMode ayarlanıyor. Şu anda sadece CacheMode olarak BitmapCache destekliyor. BitmapCache'in tam olarak ne yaptığına bir sonraki örneğimizde göz atacağız. Şimdilik MediaElement'imize nasıl BitmapCach uygulanır onu görelim.

[XAML]

<UserControl x:Class="SilverlightApplication10.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <MediaElement Source="Bear.wmv" >

            <MediaElement.CacheMode>

                <BitmapCache/>

            </MediaElement.CacheMode>

        </MediaElement>

    </Grid>

</UserControl>

Silverlight içerisinde tüm nesnelerin artık bir CacheMode özelliği var. Bu özelliğe bir BitmapCache vererek donanım desteğini arkanıza alabilirsiniz. Yukarıdaki örneği donanım desteği ile çalıştırdığımda benim makinemde CPU kullanımı %25'ten %20'lere indi. Tabi bu yaptığınız işleme ve GPU'nuzun gücüne göre değişecektir.

Silverlight'ta CPU kullanımı farkları.
Silverlight'ta CPU kullanımı farkları.

Unutmadan birkaç detaya göz atalım. Silverlight'ın donanım desteğini arkasına alabilmesi için hedef makinede DirectX9.0c yüklü olması gerekiyor. Eğer söz konusu yükleme makinede yok ise yine eski tarz yazılım bazlı sisteme geri dönülüyor. Mac ortamı için ekstra bir şart daha var; maalesef Mac ortamında donanım desteği sadece tam ekran modunda çalışabiliyor.

Şu an için GPU'ya aktarılan işlemler Transform, Rectangular Clipping ve Blending işlemleri. İleriki sürümlerde farklı işlemlerin de GPU tarafına aktarılabilmesine dair eklentiler vaatler arasında.

BitmapCache nedir?

Aslında BitmapCache çok dikkatli yaklaşılması gereken bir teknik. Örneğin her noktada BitmapCache kullanmaya başlarsak aslında performans kazanalım derken kaybetmemize de neden olabilir.

Bildiğiniz üzere Silverlight içerisinde sürekli olarak ekran vektörel olarak çiziliyor. Bazı durumlarda özellikle hızlı animasyonlarda anime ettiğiniz nesnenin görsel olarak sürekli yeniden çizilmesini istemeyebilirsiniz. Oysa o görsel vektörel bir tekrar çizme sürecinden geçirmek yerine daha önce çizilmiş vektörel sonucu bitmap olarak kullanmak performans noktasında ciddi katkı sağlayabilir.

BitmapCache'in performansa faydası.
BitmapCache'in performansa faydası.

Yukarıdaki grafik testini yaparken çok sayıda notka içeren bir vektörel çizim kullandım. Söz konusu vektörel çizim XAML kodunu makaleye yapıştırmıyorum çünkü hem anlamsız hem de gereğinden uzun :) Vektörel çizimi bir animasyon ile ekranda sürekli olarak büyütüp küçülttüm. Bu süreçte Normal şartlarda Silverlight her karede vektörel çizim vektör verisinden tekrar çizdi. BitmapCache ile GPU desteğini açtığımda ise ilk başta çizilen vektörel datadan bir Bitmap kopya alıp artık Bitmap koyayı büyütüp küçültmeye başladı. Aradaki %10'a yakın performans farkını yukarıdaki görebilirsiniz.

BitmapCache Mantığı.
BitmapCache Mantığı.

Tabi unutmamak gerek ki vektörel yerine bitmap işlemler yaptırdığınızda sistemin ilk aldığı bitmap içerisinde kalite geçerli olacaktır ve nesne büyüdükçe tekrar çizilmediği için normal animasyonlardaki kalite elde edilemeyecektir. Fakat bazı durumlarda bu kalite farkının kullanıcı tarafından algılanması pek mümkün olmayabiliyor. İşte böyle durumlarda BitmapCache hayat kurtarabilir.

Neresi Cache'den geliyor? Neresi gelmiyor?

BitmapCache ayarlarını yaptıktan sonra uygulamanızı test ederken gerçekten ekranda nerelerin Cache'lenip Cache'lenmediğini görmek isterseniz yine HTML OBJECT tagına geri dönüp aşağıdaki parametreyi ekleyebilirsiniz. Böylece artık Silverlight uygulamanız çalışırken Cache'lenmeye yerleri ayrı renklerde gösterecektir.

[HTML]

        <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">

            <param name="source" value="ClientBin/SilverlightApplication12.xap"/>

            <param name="onerror" value="onSilverlightError" />

            <param name="background" value="white" />

            <param name="minRuntimeVersion" value="3.0.40307.0" />

            <param name="autoUpgrade" value="true" />

            <param name="EnableGPUAcceleration" value="true" />

            <param name="EnableCacheVisualization" value="true" />

            <a href="http://go.microsoft.com/fwlink/?LinkID=141205" style="text-decoration: none;">

                 <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>

            </a>

        </object>

Hepinize kolay gelsin.

Friday, June 05, 2009 7:08:08 PM (GTB Standard Time, UTC+02:00)  #    Comments [0]   Silverlight 3.0  | 
 Thursday, June 04, 2009

Bir web sitesi içerisinde birden çok Silverlight uygulamasını beraber kullanıyor olabilirsiniz veya belki de aynı web sitesinde sizin hazırladığınız bir Silverlight uygulamasının yanı sıra bir de başka bir yazılımcının/tasarımcının hazırladığı Silverlight uygulaması bulunuyordur. Peki hiç bu iki uygulamanın birbirinden haberdar olabileceğini ve karşılıklı haberleşebileceklerini düşündünüz mü?

Eminim ki hemen aklınıza gelecek çözüm sunucu tarafından uygulamaları birbiri ile konuşturmak olacaktır oysa artık Silverlight 3.0 ile beraber LocalMessaging denilen ve Named Pipes mantığına çok benzeyen bir sistem ile uygulamaları birbirleri ile haberleşebiliyorlar. Birden çok Silverlight uygulaması ister aynı sayfada, ister tarayıcınızın ayrı tablarında ister farklı tarayıcılarda olsun aynı makinede çalıştıkları sürece birbirleri ile sunucudan bağımsız olarak tamamen istemci tarafında konuşabiliyorlar.

Neler yapılabilir?

Aslında bu yapı ile bir çok farklı senaryo düşünülebilir. Her iki veya üç Silverlight uygulamasını da sizin hazırladığınızı düşünürsek belki de uygulamalar arasına interaksyon sağlayabilirsiniz. Kullanıcının bir uygulamada yaptığı değişiklik bir diğerinde de bazı farklı mekanizmaları tetikleyebilir.

Bir diğer senaryoda ise belki de hiç kodlarına sahip olmadığınız bir Silverlight uygulaması ile sizin uygulamanızın birbiri ile istemci tarafında konuşması gerektiğinde ortak belirlenen bir kanal üzerinden uygulamaların haberleşmesini sağlayarak entegrasyon sağlayabilirsiniz. Daha belki de benim aklıma gelmeyen birçok farklı mekanizma mümkün olabilir.

Mutfağa geçelim....

Teorik kısmı atlattıktan sonra gelin biraz da LocalMessaging sistemini nasıl kullanabileceğimize göz atalım. Özünde LocalMessaging yapısı Named Pipes mantığı ile işliyor. Yani iki tarafın bir biri ile haberleşmesi için bir kanala ihtiyacınız var. Kanallar String tipinde isimler ile birbirinden ayırt ediliyor. Kanal ismine göre dinleyici uygulama söz konusu kanalı dinlerken veri gönderecek uygulama da yine aynı kanal ismini kullanarak veri gönderme işlemini yapabiliyor.

Yapacağımız örnekte uygulamalarımızdan birinde yer alan düğmeye bastığımızda diğerinde bir animasyon çalıştıracağız. Bu sistemin gerçekleşebilmesi için iki numaralı uygulama "MesajKanali" adındaki bir kanalı dinlemeye başlarken aynı kanala bir numaralı uygulama ise iki numaralı uygulamanın anlayabileceği bir mesaj gönderecek. Gelin ilk olarak mesajı gönderecek olan uygulama ile işimize başlayalım.

[XAML]

<UserControl x:Class="SilverlightApplication6.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <Grid x:Name="LayoutRoot" Background="White">

        <Button Content="Mesaj Gönder" x:Name="btnGonder" Click="btnGonder_Click"/>

    </Grid>

</UserControl>

Uygulamamızın ekranında basit bir düğme bulunuyor. Söz konusu düğmeye basıldığında mesaj gönderme işlemini başlatacağız.

[VB]

    Private Sub btnGonder_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        Dim MesajGonderici As New Messaging.LocalMessageSender("MesajKanali", "localhost")

        MesajGonderici.SendAsync("BASLA")

    End Sub

[C#]

        private void btnGonder_Click(object sender, RoutedEventArgs e)

        {

            System.Windows.Messaging.LocalMessageSender MesajGonderici =

                new System.Windows.Messaging.LocalMessageSender("MesajKanali", "localhost");

            MesajGonderici.SendAsync("BASLA");

        }

Yukarıda gördüğünüz kod içerisindeki ilk satırda mesaj gönderebilmek için bir LocalMessageSender yaratıyoruz. Söz konusu sınıfın constructor'ı bizden kanal ismini ve mesajın gönderileceği domainin adını istiyor. Bizim kullanacağımız kanalı ismi şimdilik "MesajKanali" olacak. Kanalimizi ve mesaj göndericimizi yarattığımıza göre artık söz konusu mesaj göndericinin SendAsync metodu ile mesajımızı bu kanalı dinleyen herkese gönderebiliriz. SendAsync metodunun sonundaki Async uzantısından da anlayabileceğiniz üzere işlem asenkron olarak yapılacak. Yani mesaj gönderdildikten sonra durumdan haberdar olmak istiyorsanız LocalMessageSender'ın SendCompleted event'ını yakalamanız gerekiyor.

Eğer gönderdiğiniz mesaj herhangi bir hedef domain'e bakılmaksızın söz konusu kanalı dinleyen herkese gönderilsin istiyorsanız aşağıdaki şekilde ikinci parametre olarak Global değerini de verebilirsiniz.

[VB]

        Dim MesajGonderici As New Messaging.LocalMessageSender("MesajKanali", System.Windows.Messaging.LocalMessageSender.Global)

Gördüğünüz gibi mesaj gönderme işlemi epey basit. Maalesef ki mesajlar sadece String tipinde olabiliyor. Eğer .NET objeleri göndermek isterseniz uygun XML Serializer'ları kullanabilirsiniz. Örneğimizde biz "BASLA" mesajını gönderiyoruz. Tabi bu mesajın alıcı tarafında da alındıktan sonra bir anlam ifade etmesi gerekecek. Onun için de dinleyici tarafında uygun kodu birazdan yazacağız.

Dinleyici nerede?

Sıra geldi dinleyici uygulamayı hazırlamaya. Dinleyici uygulamamızda basit bir animasyon olacak ve kendisine diğer uygulamadan uygun mesaj gönderildiği anda bu animasyon başlatılacak. Siz örneklerinizde farklı senaryolar uygulayabilirsiniz.

[XAML]

<UserControl x:Class="SilverlightApplication7.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300">

    <UserControl.Resources>

        <Storyboard x:Name="Animasyon">

            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"

                                          Storyboard.TargetName="rectangle"

                                          Storyboard.TargetProperty="(UIElement.Opacity)">

                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>

                <SplineDoubleKeyFrame KeyTime="00:00:01" Value="1" KeySpline="1,0,1,1"/>

                <SplineDoubleKeyFrame KeyTime="00:00:02" Value="0" KeySpline="0,1,1,1"/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="White">

        <Rectangle Height="100" Width="100" Fill="Red" Opacity="0" x:Name="rectangle"/>

    </Grid>

</UserControl>

Yukarıda XAML'ını gördüğünüz uygulama Dinleyici uygulamamız. İçerisinde bir Rectangle ve basit bir de animasyon bulunuyor. Artık Dinleyici uygulamamız içerisinde de bir dinlayici nesnesi tanımlayıp mesajın gönderileceği kanalı dinlemeye başlamanın zamanı geldi.

[VB]

    Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim Gonderebilenler As New List(Of String)

        Gonderebilenler.Add("localhost")

        Dim MesajDinleyici As New Messaging.LocalMessageReceiver("MesajKanali", Messaging.ReceiverNameScope.Domain, Gonderebilenler)

        AddHandler MesajDinleyici.MessageReceived, AddressOf MesajDinleyici_MessageReceived

        MesajDinleyici.Listen()

    End Sub

[C#]

        public MainPage()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

        }

 

        void MainPage_Loaded(object sender, RoutedEventArgs e)

        {

            List<String> Gonderebilenler = new List<string>();

            Gonderebilenler.Add("localhost");

            System.Windows.Messaging.LocalMessageReceiver MesajDinleyici =

                new System.Windows.Messaging.LocalMessageReceiver("MesajKanali",

                    System.Windows.Messaging.ReceiverNameScope.Domain, Gonderebilenler);

            MesajDinleyici.MessageReceived += new EventHandler<System.Windows.Messaging.MessageReceivedEventArgs>(MesajDinleyici_MessageReceived);

            MesajDinleyici.Listen();

        }

Uygulamamız açıldığı gibi hemen işlemlere başlıyoruz. İlk olarak dinlemek istediğimiz kanalların bulunduğu domain'lerin bir listesini yaratmamız gerek. Bunun için bir String listesi yeterli olacaktır. Bu liste içerisinde dinleyeceğiniz kanalın hangi domain'den geleceğini belirlemiş oluyorsunuz. Aksi halde herhangi bir domain'den yola çıkan ve aynı kanal ismini kullanan uygulamalar ile çakışmanız olası.

Elimizde dinlenecek domain listedi Gonderebilenler değişkeninde bulunduktan sonra MesajDinleyici'mizi yaratabiliriz. LocalMessageReceiver sınıfından aldığımız değişkenimizi yaratırken toplam üç parametre vermemiz gerek. Bunlardan ilki dinleyeceğimiz kanalı String adı, ikincisi ise dinleme işlemini hangi çapta yapmak istediğimiz. Global anlamda dinleyerek Gonderebilenler listemizdeki tüm alan adlarını dinleyebileceğimiz gibi Domain çapından ilerleyerek sadece bulunduğumuz alan adını dinleyebiliriz. Eğer sadece bulunduğunuz alan adını dinleyecekseniz aslında Gonderebilenler listesine gerek yok ama yine de örneğimizde eksik olmasın diye bir parametremiz Nothing/Null şeklinde geçmeyelim istedim.

Bir sonraki adımda MesajDinleyici nesnemizim MessageReceived event'ını da ayrı bir event-listener'a bağlamamız gerekiyor ki mesaj geldiğinde durumdan haberdar olabilelim. Tabi son olarak LocalMessageReceiver'ın Listen metodunu da çağırıyor ve dinleme işlemini başlatıyoruz.

[VB]

    Private Sub MesajDinleyici_MessageReceived(ByVal sender As Object, ByVal e As System.Windows.Messaging.MessageReceivedEventArgs)

        If e.Message = "BASLA" Then Animasyon.Begin()

    End Sub

[C#]

        void MesajDinleyici_MessageReceived(object sender, System.Windows.Messaging.MessageReceivedEventArgs e)

        {

            if (e.Message == "BASLA") Animasyon.Begin();

        }

Dinleme işlemine bağladığımız event-listener içerisinden yer alan MessageReceivedEventArgs üzerinden Message Property'si ile gelen mesajı alabilirsiniz. Bu mesajı farklı şekilerde Parse ederek anlam kazandırabileceğiniz gibi basit bir şekilde komut anlamında String karşılaştırma yaparak da yapacağınız işlemleri belirleyebilirsiniz. Biz örneğimizde BASLA mesajı geldiğinde elimizdeki animasyonu çalıştırıyoruz.

Bu noktadan sonra eğer Dinleyici uygulama hemen, anında mesajı gönderen kişiye bir cevap vermek isterse MessageReceivedEventArgs ile beraber gelen Response özelliğini kullanabilir. Böylece belki gelen komut için bir cevap belki de başka bir bilgi gönderilebilir.

[VB]

    Private Sub MesajDinleyici_MessageReceived(ByVal sender As Object, ByVal e As System.Windows.Messaging.MessageReceivedEventArgs)

        If e.Message = "BASLA" Then Animasyon.Begin()

        e.Response = "OLDU"

    End Sub

[C#]

        void MesajDinleyici_MessageReceived(object sender, System.Windows.Messaging.MessageReceivedEventArgs e)

        {

            if (e.Message == "BASLA") Animasyon.Begin();

            e.Response="OLDU";

        }

Burada tanımlanan Response değişkeni içerisinde bilgi otomatik olarak mesajı gönderen uygulamayı geri gidecektir. Geri giden bu veriyi mesajı gönderen uygulamanın yakalayabilmesi için kesinlikle kendi içerisinde LocalMessageSender'ın SendCompleted event'ını dinlemesi gerekir. Söz konusu event'ın SendCompletedEventArgs parametresinin Response Property'si dinleyiciden gönderilen mesajı içerecektir. Mesajın göndericisine ait örnek kodu aşağıda inceleyebilirsiniz.

[VB]

    Private Sub btnGonder_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        Dim MesajGonderici As New Messaging.LocalMessageSender("MesajKanali", "localhost")

        AddHandler MesajGonderici.SendCompleted, AddressOf x_SendCompleted

        MesajGonderici.SendAsync("BASLA")

    End Sub

 

    Private Sub x_SendCompleted(ByVal sender As Object, ByVal e As System.Windows.Messaging.SendCompletedEventArgs)

        If e.Response = "OLDU" Then MessageBox.Show("olmuş!")

    End Sub

[C#]

        void MesajGonderici_SendCompleted(object sender, System.Windows.Messaging.SendCompletedEventArgs e)

        {

            if (e.Response == "OLDU")

            {

                MessageBox.Show("olmuş!");

            }

        }

 

        private void btnGonder_Click(object sender, RoutedEventArgs e)

        {

            System.Windows.Messaging.LocalMessageSender MesajGonderici =

                new System.Windows.Messaging.LocalMessageSender("MesajKanali", "localhost");

            MesajGonderici.SendCompleted += new EventHandler<System.Windows.Messaging.SendCompletedEventArgs>(MesajGonderici_SendCompleted);

            MesajGonderici.SendAsync("BASLA");

        }

Böylece Silverlight uygulamaları tarafında aynı makinede çalışan, ister aynı tarayıcıda olsun, ister olmasın farklı domainlerdeki uygulamaların istenen güvenlik kısıları ile birbiriyle konuşabilmesini sağlamış olduk. Hem de tüm bunları tamamen istemci tarafında sunucumuzu hiç yormadan hallettik.

Hepinize kolay gelsin.

Thursday, June 04, 2009 4:11:59 PM (GTB Standard Time, UTC+02:00)  #    Comments [0]   Silverlight 3.0  | 
 Wednesday, June 03, 2009

Web uygulamalarında olsun Windows uygulamalarında olsun belki de en sık başvurduğumzu şey MessageBox'tır :) Sürekli bir yerlerde uyarılar gösterir ve en basit şekilde kullanıcıya bir mesaj iletmek için MessageBox yapısını kullanırız. Web ortamında da JavaScript alert imdadımıza yetişir. Peki ya Silverlight içerisinde?

"Silverlight içinde de MessageBox var!" dediğinizi duyar gibiyim :) ama MessageBox'ın biraz modası geçmedi mi artık? Şöyle daha güzel şeyler yapsak? Bir de üstüne yeri geldiğinde sadece bir mesaj değil de belki Web'deki PopUp'lar tadında farklı işlevsellikler içerebilen ekranlar da açmak istesek ne yapacağız? Tek tek UserControl'ler hazırlayıp sahnede uygun gerlerde göstermekle uğraşamayacak kadar basit bir işlevsellikten bahsediyoruz aslında. Tamam, daha fazla uzatmayacağım...

Karşınızda : "ChildWindow"!

Herhangi bir Silverlight projesine Solution Explorer içerisinde sağ tıklayarak "Add New Item" menüsüne geçtiğinizde karşınıza gelecek seçenekler arasına artık "Silverlight Child Window" da eklendi.

Yeni bir nesne tipi : Child Window
Yeni bir nesne tipi : Child Window

Silverlight içerisinde "UserControl" haricinde yapılara pek alışık değildik :) Silverlight 3.0 ile beraber karşımıza farklı şeyler çıkıyor. Bunlardan biri de System.Windows.Controls.dll'i altında bulunan ChildWindow nesnesi. Basit bir şekilde projenize bir ChildWindow eklediğinizde zaten ChildWindow'un otomatik olarak belirli standart bir tasarım ile geldiğini de göreceksiniz. Bu tasarım tabi ki Expression Blend ile açılıp değiştirilebilecek bir tasarım. Kontrolün XAML kısmına bakarsanız iki tane Button göreceksiniz, söz konusu buttonların Click durumlarında da arka planda ChildWindow'un DialogResult özelliği set ediliyor.

[VB]

    Private Sub OKButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles OKButton.Click

        Me.DialogResult = True

    End Sub

 

    Private Sub CancelButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles CancelButton.Click

        Me.DialogResult = False

    End Sub

[C#]

        private void OKButton_Click(object sender, RoutedEventArgs e)

        {

            this.DialogResult = true;

        }

 

        private void CancelButton_Click(object sender, RoutedEventArgs e)

        {

            this.DialogResult = false;

        }

Tahmin edeceğiniz üzere ChildWindow'un kullanımı epey kolay. ChildWindow'unuzu herhangi bir UserControl'den bir Instance'ını alarak Show metodu ile çağırabiliyor veya aynı şekilde Close metodu ile de uzaktan kapatabiliyorsunuz. Örneğin aşağıda gördüğünüz kod herhangi bir UserControl içerisinde biraz önce yarattığımız ChildWindow'u çağıran kodun ta kendisi.

[VB]

        Dim Yeni As New ChildWindow1

        Yeni.Show()

[C#]

            ChildWindow1 Yeni = new ChildWindow1();

            Yeni.Show();

Sanırım daha basit olamazdı. Tabi söz konusu ChildWindow'u çağırdıktan sonra kapandığından da haberdar olup hatta kapanmadan önce en son aldığı DialogResult değerin de elde etmek gerekir. Bunun için ChildWindow'un Closed event'ını yakalamanız yeterli olacaktır.

[VB]

   Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim Yeni As New ChildWindow1

        AddHandler Yeni.Closed, AddressOf yeni_Closed

        Yeni.Show()

    End Sub

 

    Private Sub yeni_Closed(ByVal sender As Object, ByVal e As System.EventArgs)

        Dim Sonuc As Boolean = CType(sender, ChildWindow1).DialogResult

    End Sub

[C#]

        void MainPage_Loaded(object sender, RoutedEventArgs e)

        {

            ChildWindow1 Yeni = new ChildWindow1();

            Yeni.Closed += new EventHandler(Yeni_Closed);

            Yeni.Show();

        }

 

        void Yeni_Closed(object sender, EventArgs e)

        {

            Nullable<Boolean> Sonuc = ((ChildWindow1)sender).DialogResult;

        }

ChildWindow'u Show etmeden önce Closed eventını bir event-listener'a bağlarsanız sonrasında ChildWindow kapatıldığında rahatlıkla durumdan haberdar olabilirsiniz. Closed event'ı çalıştırıldığı anda sender olarak gelen objeyi kendi ChildWindow'umuza cast ederek DialogResult özelliğini alabiliyoruz. Eğer "Bana sadece Boolean sonuç döndürnek yetmez" diyorsanız aslında çözüm basit. Kendi tanımladığınız ChildWindow'un arkasında sınıfta yeni Property'ler tanımlayabilir ve bunları istediğiniz tipte yaratabilirsiniz. Böylece Closed event'ına gelen sender da aslında sizin tanımladığınız nesne olacağı için istediğiniz Property'sine erişebilirsiniz. Eğer ChildWindow kapanmadan önce sizin tanımladığınız Property'ye bir bilgi aktarırsa bu bilginin rahatlıkla ana UserControl'e gelebileceği anlamına gelir.

ChildWindow karşınızda.
ChildWindow karşınızda.

Gelin hızlı ve teorik bir örnek yapalım. Hazırlayacağımız yeni ChildWindow bizden bir Integer parametre alsın. Bu parametreyi aldıktan sonra zaten ChildWindow kendi içerisinde istediğini yapar. İsterse kendi içine konabilecek bir TextBlock'a bile yazdırabilir. (Uyarı mesajı olabilirdi bu parametre) Ama biz farklı birşey yapacağız. ChildWindow'un OK düğmesine basıldığında kendisine daha önce verilen sayıyıyı ikiyle çarpıp geri verecek, Cancel düğmesine basılınca ise ikiye bölecek. Böylece yeni bir ChildWindow yaratırken nasıl parametre geçebileceğimizi ve söz konusu parametre üzerinde ChildWindow değişiklik yaptıktan sonra parametreyi nasıl geri alabileceğimizi görmüş oluruz.

[VB]

Partial Public Class ChildWindow1

    Inherits ChildWindow

 

    Public Sub New(ByVal deger As Integer)

        InitializeComponent()

        Me.Deger = deger

    End Sub

 

    Private PDeger As Integer

    Public Property Deger() As Integer

        Get

            Return PDeger

        End Get

        Set(ByVal value As Integer)

            PDeger = value

        End Set

    End Property

 

    Private Sub OKButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles OKButton.Click

        Me.Deger *= 2

        Me.DialogResult = True

    End Sub

 

    Private Sub CancelButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles CancelButton.Click

        Me.Deger /= 2

        Me.DialogResult = False

    End Sub

 

End Class

[C#]

namespace SilverlightApplication5

{

    public partial class ChildWindow1 : ChildWindow

    {

        public ChildWindow1(int deger)

        {

            InitializeComponent();

            this.Deger = deger;

        }

 

        public int Deger { get; set; }

 

        private void OKButton_Click(object sender, RoutedEventArgs e)

        {

            this.Deger = this.Deger * 2;

            this.DialogResult = true;

        }

 

        private void CancelButton_Click(object sender, RoutedEventArgs e)

        {

            this.Deger = this.Deger / 2;

            this.DialogResult = false;

        }

    }

}

Yukarıda kalın yazılı olarak gördüğünüz yerler bizim eklememiz gereken satırlar. Hemen ChildWindow'umuzun Constructor'ı ile işe başlayalım. Artık elimizdeki Constructor bir parametre istiyor. Bu parametre ChildWindow'a aktarılacak olan veriyi temsil edecek. Bizim örneğimizde Integer olabilir fakat siz farklı nesne tiplerini ve hatta kendi tanımladığınız nesne tiplerini de parametre olarak geçebilirsiniz. Sonrasında söz konusu parametreyle gelen değişkeni elde saklayabilmek için bir de Property tanımlıyoruz, hatta constructor çalıştığında da kendisine gelen veriyi Property'e atamasını sağlıyoruz.

Artık verimizi rahat rahat elimize aldığımıza göre Ok ve Cancel düğmelerinde söz konusu veriyi istediğimiz gibi değiştirebiliriz. Farklı senaryolarda ChildWindow içerisinde koyduğunuz farklı kontrollerden gelen verilere göre bu şekilde farklı Property'leri değiştirebilirsiniz. Peki söz konusu ChildWindow'u çalıştıran UserControl buradaki Property'ye nasıl ulaşacak? Aslında çok kolay.

[VB]

    Private Sub yeni_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles yeni.Closed

        Dim Sonuc = CType(sender, ChildWindow1).Deger

    End Sub

[C#]

        void Yeni_Closed(object sender, EventArgs e)

        {

            int Sonuc = ((ChildWindow1)sender).Deger;

        }

Gördüğünüz gibi ChildWindow kapandıktan sonra nasıl DialogResult değerini aldıysak aynı şekilde kendi tanımladığımız Property'lere de ulaşabiliyoruz. Ne de olsa sender'ı kendi tanımladığımız ChildWindow1 sınıfına cast ediyoruz. İşte bir ChildWindow'a parametre gönderip farklı veriler alabilmek bu kadar kolay.

Hmm iyiymiş...

Kesinlikle muhteşem! İster özel Property'ler tanımlayın ister ChildWindow'un constructor'larını değiştirip farklı bilgiler atayarak onları göstermesini sağlayın ChildWindow'lar kullanım açısından çok pratik ve şık. Otomatik olarak kendi kendileri ekranın geri kalanını yarı şeffaf bir perde ile kapatıp kullanılamaz hale getirmeler ve hoş bir animasyon ile ekrana gelip gitmeleri gerçekten iş uygulamalarında genel kullanıma çok uygun. Fakat tabi ki özel durumlarda, kullanıcı deneyiminin ve tasarımın daha öne çıktığı senaryolarda kesinlikle ChildWindow'ların özelleştirilmesi gerekiyor.

Aklıma gelmişken :) Ben bir projede ChildWindow'ları sadece Loading animasyonları göstermek için kullandım. Gerçekten pratik oluyor. Loading mesajını verirken istediğimiz şey zaten kullanıcının başka birşey yapamaması. Tabi bu amaçla yaratılan bir ChildWindow'un Close vs gibi düğmelerinin tasarımdan Blend aracılığı ile kaldırılması gerekiyor. Arka planda yükleme işlemi bittiğinde siz ChildWindow'un Close metodu ile pencereyi ekrandan kaldırabiliyorsunuz. Eh pencerenin için de bir de Indeterminate state'te ProgressBar varsa zaten yeme de yanında yat :)

Hepinize kolay gelsin...

Wednesday, June 03, 2009 9:38:50 PM (GTB Standard Time, UTC+02:00)  #    Comments [2]   Silverlight 3.0  | 

Arama Motoru Optimizasyonu konusu bazı durumlarda çok önemli bir hal alabiliyor. Bu konuya ciddi yatırım yapan kişiler / kurumsal bulmak hiç de zor değil. İşte tam da bu konuda Microsoft tarafından IIS7 için Search Engine Optimization Toolkit yayınlandı. Söz konusu Toolkit'i IIS7 üzerine aşağıdaki adresten indirerek bilgisayarınıza kurabilirsiniz.

Search Engine Optimization Toolkit (X86)
Search Engine Optimization Toolkit (X64)

Peki neler var içinde?

Toolkit içerisinde araçlara hızlı olarak ulaşmak için hemen IIS Manager'a açarak çalışan bir sitenizin özelliklerinin vs ayarlandığı ana sayfayı açabilirsiniz. Artık ASP.NET ve IIS gibi tabların yanı sıra bir de "Search Engine Optimization" adında tab ile karşılaşıyor olmanız gerekir. Söz konusu tab altında üç farklı komut göreceksiniz.

IIS Manager içerisinde yeni seçenekler.
IIS Manager içerisinde yeni seçenekler.

Gelin şimdi bu bölümlerin ne işe yaradıklarını tek tek inceleyerek bu yeni Toolkit ile neler yapabileceğimize bir göz atalım. İlk olarak "Site Analysis" adındaki kısım ile başlayalım ve hali hazırdaki bir sitemizin analizini yaptıralım.

Site Analysis

Bu bölüme girdiğinizde karşınıza boş bir ekran gelecek. Burada önemli olan IIS Manager'ın en sağında menüde nasıl komutlar ile karşılaşıyor olmamız. Komutların ilki hemen dikkati çekiyor "New Analysis" diyor. Hemen yeni bir analiz yaratmak için düğmeye basarak devam ediyoruz. Analiz için yeni bir isim verdikten sonra hemen analizi başlatabilirsiniz fakat unutmayın; IIS hemen sitenizdeki tüm linkleri takip ederek tüm sayfaları incelemeye başlayacak. Bu süreç sitenizdeki link yoğunluğuna göre çok uzun sürebilir ve bilgisayarınız tüm işlemci gücünü kullanacaktır. Yeni bir analiz yaratırken karşınıza çıkan ekranda "Maximum Number Of Links" ayarı ile kaç adet linkin incelenebileceğini ve "Maximum Download Size per Link" ile de link başına analiz için söz konusu adreslerden ne kadar verinin alınabileceğini sınırlayabilirsiniz.

SEO Toolkit'ten ilk rapor.
SEO Toolkit'ten ilk rapor.

Analiz bittiğinde ilk karşılaştığınız rapor en sık karşılaşılan hataların bir listesini oluşturuyor. Bu hataların bazıların sadece performans kaybına neden olurken bazıları ise doğrudan arama motorlarına ait optimizasyon ayarları ile ilgili. Taradığım siteyi itiraf etmeyeceğim :) fakat 3000 tane hedefi bozuk link olmasına şaşırmadım desem yalan olur.

Tabi eğer amacınız hataları düzeltmek ise yukarıdaki rapor pek anlamlı değil. Bu raporun haricinde Toolkit isterseniz en çok hata olan sayfaların bir listesini de verebiliyor. Sayfa sayfa tek tek raporu inceleyebilir ve her sayfada en çok kullanılan kelimelerin listelerini dahi alabilirsiniz. Performans noktasında ise en yavaş açılan sayfalarınızı listeleyen Toolkit belki de projenize hangi noktalarda sorun olduğunu bulmanıza da yardımcı olabilir.

Robots Exlusion

Robots.txt dosyası arama motoru optimizasyonu ile ilgilenen herkesin aşina olduğu dosyalardan biri. Bu dosya içerisinde kabaca arama motorunun web sitenizde hangi adreslere gitmesini istediğini istediğinizi veya aynı şekilde hangi adreslere gitmemesini istediğiniz belirtebilirsiniz. İşte bu dosyaya IIS Manager içerisinde de artık pratik bir şekilde yaratabilirsiniz. Hatta en güzel de bir önceki adımda oluşturduğumuz Web Analizi'nde yakalanan adresler üzerinden de bu ayarları yapabiliyor olmamız.

Robots.txt dosyasını IIS Manager üzerinden yaratalım.
Robots.txt dosyasını IIS Manager üzerinden yaratalım.

Sitemaps and SiteMap Indexes

SiteMap mantığı zaten bildiğimiz bir mantık. Normal şartlarda benim kişisel tavsiyem SiteMap dosyalarının özellikle veritabanına bağlı projelerde doğrudan veritabanından bilgi alınarak oluşturulması hatta bu bilginin yine otomatik olarak Robots.txt içerisine de eklenmesi. Böylece robots.txt içerisinde SiteMap'in adresi bulunursa arama motoru da SiteMap üzerinden ilerleyebilecektir. Tabi tüm bu yapıyı kodlamak yerine veya belki de farklı teknolojiler ile hazırlanmış sitelerle uğraşmak yerine IIS Manager üzerinden de yapabilirsiniz. Yine Robots.txt tarafında olduğu gibi burada da bir SiteMap yaratırken bir önceki adımda sitenin analizinde bulunan URL'leri kullanma şansınız oluyor. Son olarak söz konusu SiteMap'i de Robots'a eklediğiniz düşünürsek herşey tamamdır.

Sonuç

Aslına bakarsanız hem SiteMap hem de Robots.txt kısmı aslında çok da öne çıkan özellikler değiller. Zaten bu özellikler site analizine de bağlı olmasa neredeyse hiçbir anlamı kalmayacak. Fakat Site Analyse özelliği gerçekten güzel. Bu gibi analiz yapan ücretsiz araçlar bulmak mümkün fakat benim gördüğüm kadarı ile Toolkit'in kalitesini yakalayan araçlar genelde ücretli olarak satılıyor. Bu kapsam Toolkit hemen çalışmakta olan herhangi bir IIS'e yüklenerek optimizasyonu girişebilir bir ortam sağlıyor.

Hepinize kolay gelsin.

Tuesday, June 02, 2009 11:07:19 PM (GTB Standard Time, UTC+02:00)  #    Comments [0]   IIS 7.0  | 
 Monday, June 01, 2009

Dün bana gelen sorulardan birini cevaplamak üzerine hızlı bir makale yazmaya karar verdim :) Gelen soruya doğrudan girmeden önce senaryoya bir bakalım. Elimizde bir istemci var ve bu istemci sunucu üzerindeki web servislerini kullanıyor. Söz konusu web servisleri IIS üzerine yerleştirilmiş durumda. Oysa bizim istediğimiz şey bir anda bu web servislerini IIS'ten bağımsız hale getirmek. Örneğin bize ait başka bir sunucu uygulaması, özünde bir Windows uygulaması olsun ve söz konusu uygulama bu web servislerini sunmak ile sorumlu olsun. Ne dersiniz?

Nasıl olacak ki?

Web servislerine genel olarak IIS üzerinden sunulan sayfalar gözü ile bakarız oysa WCF ile bu manzara biraz değişiyor. WCF mantığında aslında web servisi diye birşey yok, sadece Servis var. Söz konusu servisi ister WEB ister başka bir şey üzerinden veya daha doğru bir tanımlama ile başka bir protokol ile yayınlayabilirsiniz. Yayınlanan protokolün mantığı vs aslında servisin iç yapısı ile alakasızdır! İşte WCF bunu sağlıyor. O nedenle ilk olarak varsa yazmış olduğumuz eski ASMX'leri birer WCF servisi haline dönüştürelim.

Bu dönüştürme işlemini yaparken projenize "Add New Item" diyerek yeni bir WCF servisi eklemelisiniz. Fakat projeniz ne projesi olacak? Yani eğer bu servisimizi bir web sitesi üzerinden sunmak istiyorsak tabi ki elimizde bir ASP.NET projesi olmalı ve o projeye WCF servisimizi eklemeliyiz. Oysa bizim istediğimiz şey bu Servis'in bir Windows uygulaması tarafından sunulması. O zaman ilk olarak elimizde bir Winforms veya WPF projesi bulunması gerekiyor, sonrasında bu projeye "Add New Item" diyerek bir Service ekleyebiliriz.

Projemizde servisimiz bizi bekliyor...
Projemizde servisimiz bizi bekliyor...

Yukarıda projemizin Servis eklenmiş halini görebilirsiniz. Projemize otomatik olarak bir Interface ve bir de Implentasyon kodları eklenmiş durumda. Eğer ASP.NET tarafında WCF servisleri kullandı iseniz bu noktada birşey dikkatinizi çekecektir. SVC uzantılı bir dosyamız yok! Çünkü ona ihtiyacımız yok :) Bu servis IIS tarafından sunulmayacak, bunu kendi programımız ile biz sunacağız o nedenle SVC gibi bir "placeholder" dosyaya ihtiyacımız yok.

Bu noktadan sonra WCF ile klasik web servisleri arasındaki farklardan en önemlisi bir Interface ve bir de Implementasyon dosyasının ayrı ayrı bulunuyor olması. Makalemizin amacı WCF anlatmak olmadığı için bu farkın detaylarına girmeyeceğiz fakat burada Interface'in servisin dışa sunduğu imzası olduğunu düşünebilirsiniz. Biz basit bir servis hazırlayarak devam edelim.

[VB]

Imports System.ServiceModel

 

<ServiceContract()> _

Public Interface IService1

 

    <OperationContract()> _

    Function Topla(ByVal x As Integer, ByVal y As Integer)

 

End Interface

[C#]

namespace WindowsFormsApplication1

{

    [ServiceContract]

    public interface IService1

    {

        [OperationContract]

        int Topla(int x, int y);

    }

}

Servisimiz şimdilik iki sayı alıp toplayacak. Yukarıda gördüğünüz Interface dosyası içerisinde servisin fonksiyonu ile ilgili bir implementasyon bulunmuyor. Sadece servisin uygulayacağı Intefrace'i yazdık. Sıra geldi şimdi de gerekli kodları yazacağımız diğer VB/C# dosyamıza geçmeye.

[VB]

Public Class Service1

    Implements IService1

 

    Public Function Topla(ByVal x As Integer, ByVal y As Integer) As Object Implements IService1.Topla

        Return x + y

    End Function

End Class

[C#]

namespace WindowsFormsApplication1

{

    public class Service1 : IService1

    {

        public int Topla(int x, int y)

        {

            return x + y;

        }

    }

}

Diğer dosyamızda artık çalışacak olan kodumuzu yazıyoruz. Burada gördüğünüz metodun ve kodun klasik web servislerinde yazdığınız koddan pek bir farkı yok. Genelde eski servislerinizden kodunuz alıp kopyala-yapıştır metodu ile WCF'e rahatlıkla taşıyabilirsiniz.

Artık WCF servisimiz hazırladığımız Winforms uygulaması içerisinde hazır olduğuna göre sıra geldi bu servisi nasıl başkalarına sunacağımza bakmaya. Bunun için hızlıca kullanabileceğimiz özel bir sınıf bulunuyor.

[VB]

Public Class Form1

 

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim ServisSunucu As New ServiceModel.ServiceHost(GetType(Service1))

        ServisSunucu.Open()

    End Sub

End Class

[C#]

namespace WindowsFormsApplication1

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

 

        private void button1_Click(object sender, EventArgs e)

        {

            System.ServiceModel.ServiceHost ServisSunucu = new System.ServiceModel.ServiceHost(typeof(Service1));

            ServisSunucu.Open();

        }

    }

}

ServiceHost nesnesi parametre olarak yaratacağı servisin tipini ister. Bizim daha önce yazdığımız servisin tipini kendisine iletmemiz ile beraber aslında bir servis sunucusu yaratmış oluyoruz. Söz konusu sunucuyu Open metodu ile başlatabilir veya Close metodu ile kapatabiliriz. Uygun şekilde bu komutları sunucuda çalışacak olan Winforms veya WPF uygulamamızda farklı düğmelere bağlayabilirsiniz.

Peki servisi başkaları nasıl kullanacak?

Servisi ekledik, çalıştırdık ama adresi nedir? Yani başkaları nasıl kullanacak? Daha önce de bahsettiğim gibi aslında bir WCF servisinin hangi Endpoint (çıkış noktası/adres) ile yayınlanacağı ve hangi protokol ile sunulacağı gibi ayarların yapılması gerekiyor. Yine maalesef ki bu ayarların detaylarına bu makalede inmeyeceğiz fakat biraz şanslıyız çünkü zaten varsayılan ayarları ile WCF servisleri wsHttpBinding kullanıyor. Yani HTTP üzerinden çıkıyorlar. Bizim de istediğimiz eski web servisleri gibi bu servislerin de HTTP üzerinden ulaşılabiliyor olmasıydı. Fakat çıkış noktası konusunda ufak bir ayar yapmamız şart.

Çıkış noktası veya servisimizin HTTP adresi ile ilgili ayar aslında uygulamanın içindeki App.config dosyasında saklanıyor. Bu dosya eğer Solution Explorer içerisinde gözükmüyorsa doğrudan "Show all files" düğmesine tıklayarak görünür hale getirebilirsiniz.

App.Config dosyası karşınızda.
App.Config dosyası karşınızda.

App.Config dosyasını açtığınızda farklı ayarlar ile karşılaşacaksınız. Bu ayarlar içerisinde bir adres dikkatinizi çekecektir.

[App.Config]

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

    <system.serviceModel>

        <behaviors>

            <serviceBehaviors>

                <behavior name="WindowsApplication1.Service1Behavior">

                    <serviceMetadata httpGetEnabled="true" />

                    <serviceDebug includeExceptionDetailInFaults="false" />

                </behavior>

            </serviceBehaviors>

        </behaviors>

        <services>

            <service behaviorConfiguration="WindowsApplication1.Service1Behavior"

                name="WindowsApplication1.Service1">

                <endpoint address="" binding="wsHttpBinding" contract="WindowsApplication1.IService1">

                    <identity>

                        <dns value="localhost" />

                    </identity>

                </endpoint>

                <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

                <host>

                    <baseAddresses>

                        <add baseAddress="http://localhost:8732/Design_Time_Addresses/WindowsApplication1/Service1/" />

                    </baseAddresses>

                </host>

            </service>

        </services>

    </system.serviceModel>

</configuration>

Yukarıda gördüğünüz adresi değiştirmeniz halinde uygulamanız söz konusu adresi dinlemeye başlayacaktır. Tabi bu işlemi özellikle root adreslerde denerseniz uygulamanızın Admin hakları ile çalışmasına da dikkat etmeniz gerek. Örneğin aşağıdaki şekilde adresimizi değiştirelim ve sonrasında da bu adres üzerinden servisimizi başka bir uygulama ile kullanmayı deneyelim.

[App.Config]

<host>

  <baseAddresses>

    <add baseAddress="http://localhost/benimservisim" />

  </baseAddresses>

</host>

Basit bir şekilde bu uygulamayı admin hakları ile başlattıktan sonra yeni bir proje yaratarak "Add Service Reference" dediğinizde bu adresi girmeniz servise ulaşmanız için yeterli olacaktır. Unutmayın servisi host edecek uygulamanızın admin hakları ile başlamasının yanı sıra servisin ServiceHost üzerinden Open metodu ile çalıştırılmış olması da gerekiyor.

Normal bir servisi kullanmaktan farkı yok!
Normal bir servisi kullanmaktan farkı yok!

Gördüğünüz gibi yarattığımız başka bir projede rahatlıkla bir önceki uygulamamızda çalışan servisi referans olarak alabiliyoruz. Tabi bu referansa ait endpoint'i de kod ile set etmekte fayda var.

Bundan sonrası için servisinizi kullanırken normalden farklı bir mekanizma söz konusu değil. Her zamanki gibi servisinizi IIS üzerinden sunuluyormuş gibi kullanabilirsiniz. Hatta belki makalemizin devamında WCF'i biraz daha araştırarak bu servisin farklı Binding'ler ile nasıl sunulabileceğine de bakabilir ve birkaç saniye içerisinde aynı servisin hem HTTP hem de TCP üzerinden sunulmasını sağlayabilirsiniz.

Hepinize kolay gelsin.

Monday, June 01, 2009 3:47:48 PM (GTB Standard Time, UTC+02:00)  #    Comments [5]   WCF  | 

Haziran ayı ile beraber yaz dönemi geldi. Tatile gidemeyenler olarak :) gelin bu dönemi yenilenme/gelişme dönemi olarak değerlendirelim :) Bu ayı "Silverlight Öğrenme Ayı" ilan ediyorum ;) ve 12 Seminerden oluşan Silverlight WebCast serimi duyuruyorum!

Silverlight'a Giriş - 8 Haziran, 21.00
Bu webinerde Silverlight nedir sorusuna cevap verirken mimari yapısına bakarak Silverlight dünyasına bir giriş yapacağız. Silverlgiht projeleri nasıl oluşturulur,nasıl yayınlanır, XAML nedir? ve "Neden Silverlight" gibi soruların cevapları da bu webinerde.
https://www.livemeeting.com/cc/usergroups/join?id=GFRH8M&role=attend

Silverlight ile Animasyonlar - 9 Haziran, 21.00
Animasyon mantığını kavrayacağımız bu webinerde ilk olarak Expression Blend arayüzüne bakarak animasyonlarının Blend tarafında nasıl oluşturulabildiğine göz atacağız. Sonra bu animasyonların programatik olarak yaratılmasını ve kontrollerini inceleyerek Silverlight tarafında bir animasyon uzmanı olacağız.
https://www.livemeeting.com/cc/usergroups/join?id=6GRJPM&role=attend

Layout Kontrolleri ve Dinamik Arayüzler, 10 Haziran, 21.00
Silverlight içerisinde istediğiniz görsel arayüzü oluşturabilmek için arayüz oluşturma esnasından kullanabileceğiniz Layout kontrollerini çok iyi tanımanız gerekir. Diğer yandan dinamik olarak tüm nesneleri yaratbilmeniz, bu nesnelerin birbiri ile konuşabilmesi ve projenizi doğru biçimde UserControl yapıları ile şekillendirmeniz de şart. İşte bu webinerde bu konulara değineceğiz.
https://www.livemeeting.com/cc/usergroups/join?id=PBRM8P&role=attend

Kontrol Şablonları ve VisualStateManager, 11 Haziran, 21.00
Standart kontroller arasında sıkışmayın ve kontrollerinizi tamamen baştan yaratın. Hatta bu tasarımları tekrar ve tekrar kullanın. Bu webinerde silverlight kontrollerinin şablonlarının değiştirilmesini, Template Binding mekanizmasını ve visual State Manager'ın kullanımını Custom State yapıları ile beraber inceleyeceğiz.
https://www.livemeeting.com/cc/usergroups/join?id=R8JSQH&role=attend

Silverlight içerisinde Veri Erişimi, 12 Haziran, 21.00
İster klasik web servisleri kullanın, ister WCF servisleri veya ister Socket üzerinden haberleşin hepsinin de kendine göre avantaj ve dezavantajları var. Gelin hepsinin de Silverlight tarafındaki detaylarını beraber inceleyip Silverlight uygulamalarımızı veri ile tanıştıralım.
https://www.livemeeting.com/cc/usergroups/join?id=GH9T5W&role=attend

Offline/Online/Desktop çalışma yapıları, 15 Haziran, 21.00
Silverlight 3.0 ile beraber uygulamalarınızı masaüstüne birkaç tıklama ile taşıyabilirsiniz. Peki hem masaüstünde hem webde hem online hem offline çalışırken tüm bu sistemi, verilerinizi nasıl kontrol edeceksiniz. Bu webinerde farklı çalışma yapılarını göz atarken verilerimizi IsolatedStorage içerisinde saklayacağız.
https://www.livemeeting.com/cc/usergroups/join?id=QTZH9R&role=attend

İş Uygulamaları Yaparken Silverlight, 16 Haziran, 21.00
Animasyonlar hoştur, güzeldir ama herşey değildir. DataGrid'ler, Comboboxlar, Calendar'lar bir araya girdiğinde hepsinden bir iş uygulaması arayüzü yaratıp kontrol etmeye ne dersiniz? Bu webinerde hep beraber bir iş uygulamasının ekranını hazırlayacağız.
https://www.livemeeting.com/cc/usergroups/join?id=BGN6BT&role=attend

RIA Services, 17 Haziran, 21.00
Farklı bir bakış açısı ile Silverlight uygulamalarınızdan veritabanına erişimine çocuk oyuncağına dönüştürmek ister misiniz? RIA Services sadece verinin taşınmasını değil aynı anda kendi kontrolleri ile düzenlenmesini de kolaylaştırıyor. Nasıl olacağını merak ediyorsanız bekleriz.
https://www.livemeeting.com/cc/usergroups/join?id=P9KQ6B&role=attend

Silverlight projelerinde SEO, 18 Haziran, 21.00
Arama motorlarına dair optimizasyonların çoğumuz için önemli. Silverlight projelerinde özellikle asenkron veri erişimi nedeniyle söz konusu veriye arama motorları ulaşamıyor. Peki ya bir çözümü varsa? Tabi ki o çözümü paylaşma vakti.
https://www.livemeeting.com/cc/usergroups/join?id=Q6H2G7&role=attend

Video Aşkına! Canlı/Cansız Yayın, 19 Haziran, 21.00
İnternette televizyoz izlemek veya cansız kayıtları yayınlamak, amacınız ne olursa olsun video konusunda Silverlight çok güçlü. Bu webinerde Silverlight ile canlı yayın, Media Services ayarları, Adaptive Streaming, cansız yayın ve Video Player hazırlanması gibi konulara değineceğiz.
https://www.livemeeting.com/cc/usergroups/join?id=BNPK36&role=attend

DeepZoom Uygulamaları, 22 Haziran, 21.00
Yaklaştıkça daha çok detay görmek isteyenlerin tarzı DeepZoom uygulamalarını hızlı bir şekilde geliştirmenin yanı sıra sıfırdan DeepZoom uygulamalarını yazmak, uygulamaları özelleştirmek ve veri bağlantısı yaparak dinamik DeepZoom projeleri yaratmak isteyenler bu webineri kaçırmasın.
https://www.livemeeting.com/cc/usergroups/join?id=BZFCH9&role=attend

Tarayıcı Entegrasyonu, 23 Haziran, 21.00
Silverlight ile tarayıcı arasındaki ilişkiyi inceleyeceğimiz bu webinerde Silverlight tarafındaki C#/VB kodunuz ile DOM entegrasyonunun yanı sıra tam ekran uygulamaları, ön yükleme ekranlarını ve RunTime yükleme ekranlarını özelleştirmeye göz atacağız.
https://www.livemeeting.com/cc/usergroups/join?id=3GZKMH&role=attend

Webinerlerle ilgili güncel gelişmeleri ve hatırlatmaları isterseniz twitter üzerinden beni takip ederek edinebilirsiniz. Herkese faydalı olması dileği ile ;)

P.S.: Unutmadan :) Mümkünse bu seriyi bloglarınızda, üye olduğunuz mail gruplarda paylaşabilirseniz çok sevinirim. "Silverlight'ı nereden öğrenebiliriz" şeklinde özellikle Anadolu'nun farklı köşelerinden mailler alıyorum. 12 seminerlik bu seriyi düzenlememin en büyük nedeni de budur. En azından bu sefer "haberimiz yoktu" durumlarının oluşmaması için duyuru konusunda desteğini rica ediyorum. Ne kadar çok kişi faydalanırsa o kadar mutluluk verci olur benim için, hepimiz için.

Monday, June 01, 2009 10:03:54 AM (GTB Standard Time, UTC+02:00)  #    Comments [21]   Silverlight 2.0 | Silverlight 3.0  | 
Copyright © 2010 Daron Yöndem. Tüm hakları saklıdır.