Windows Phone

Windows Phone Custom Control Geliştirme

Uygulamalarımızın teknik tasarımını yaparken var olan problemi olabildiğince ufak parçalara ayırmayave her bir problemden sorumlu modüller oluşturmaya çalışırız. Çok basit bir örnek düşünecek olursak, bir filmin posterini, isminive oyuncularının listesini gösteren bir ...

Mehmet Altıparmak |

07.10.2013

 

Uygulamalarımızın teknik tasarımını yaparken var olan problemi olabildiğince ufak parçalara ayırmaya ve her bir problemden sorumlu modüller oluşturmaya çalışırız. Çok basit bir örnek düşünecek olursak, bir filmin posterini, ismini ve oyuncularının listesini gösteren bir sayfamız olsun. Bu sayfayı dümdüz düşünmeden geliştirmeye kalkarsak muhtemelen sayfamıza Image, TextBlock, ListBox kontrolleri ekler, hemen sayfa kodunda film bilgilerini çekeceğimiz REST servisine bağlanır, gelen JSON veriyi yine sayfa kodunda parse eder gerekli string formatlama işlemlerini yapar, model objelerini oluşturur ve en sonunda sayfamıza eklediğimiz controller'e bağlarız. Elbette olması gereken bu değildir. Uygulamalarımızı geliştirirken öncesinde mutlaka şöyle bir durup, elimizdeki problemleri düşünmeli ve alt kırılımlarını çıkarmalıyız.

Aynı sayfayı biraz düşünerek geliştirirsek, yapmamız gereken işlemleri şu şekilde sıralayabiliriz;

  • REST servise yapılacak bir GET isteği,

  • Servisten dönen string tipindeki JSON değeri Model sınıflarına çevirmek,

  • Model sınıflarını sayfamızdaki kontrollere bağlamak.

Bu işlemler için geliştireceğimiz sınıflar da şu şekilde olsun;

  • HttpUtility -> GET isteği yapıp String tipinde sonuç dönsün,

  • ImdbApi -> IMDB üzerinden HttpUtility’i kullanarak film bilgilerini çeksin ve Model objelerini oluştursun.

  • ImdbPoster Custom Control’ü -> ImdbApi’i kullanarak film model objesini çeksin ve arayüz kontrollerinde bilgileri göstersin.

Bu şekilde uygulamamız farklı sorumlulukları bulunan kabaca 3 sınıfa ayrıldı ve detaylar bu sınıfların içerisine saklandı (bknz: abstraction, single responsibility principle). Anasayfa kodumuz artık çok daha temiz ve şöyle bir baktığımızda olup biteni kolayca anlayabiliyoruz.

Gelelim başlığa, başlıkta da belirtildiği gibi bu yazının asıl amacı bir Custom Control geliştirmek. Az önce anlattığım hikâyenin tamamını olmasa da, bir filmin kapak resmini gösteren Custom Control geliştireceğiz.

 

Custom Control Nedir?

Custom Türkçe “özel” anlamına gelmektedir. Custom Control ise Windows Phone SDK tarafından bize sağlanmış Control’lerin (Button, TextBlock, Image) özelleştirilmiş, farklı yetenekler kazandırılmış halleri olarak düşünülebilir. Neden Control diyoruz diye düşünen olursa da bunun sebebi Control sınıfından türeyecek (bknz: Inheritance) olmasıdır diyebiliriz.

 

Geliştirme Adımları

Öncelikle projemize ImdbPoster isminde bir sınıf ekleyelim. Sonrasında ImdbPoster sınıfını Control sınıfından türetelim ki, Control sınıfının yeteneklerine de sahip olsun. Control sınıfının önemli bir özelliği arayüzünün özelleştirilebilir olmasıdır, biz de bu özelliği kullanacağız. Control sınıfından türetilmiş ImdbPoster sınıfımız aşağıdaki gibi olmalı:

[cpp]namespace ImdbFilmPoster
{
    public class ImdbPoster : Control

    {
    //kodumuz buraya yazılacak
    }
    
}[/cpp]

 

ImdbPoster Sınıfının Stilinin(Style) Tanımlanması

Sıra geldi ImdbPoster sınıfının Style’ını tanımlamaya. Custom Control'lerin stillerini tanımlamak için projemiz içerisine öncelikle Themes isminde bir klasör ekliyoruz. (Projeye sağ tıklıyoruz, Add -> New Folder)

Sonrasında oluşturduğumuz Themes klasörüne generic.xaml isminde bir .xaml dosyası ekliyoruz. (Projeye sağ tıklıyoruz, Add -> New Item -> Text File, bu adımda dosya uzantısını .xaml yapmayı unutmayınız.)

Açtığımızda içi boş bir text sayfası ile karşı karşıya kalacağız. Sayfaya ilk olarak Windows Phone uygulamalarında Resource’ları saklamakta kullandığımız ResourceDictionary elementi ekliyoruz. Windows Phone sistemi ResourceDictionary içerisinde bulunan Style elementlerini otomatik olarak bir HashTable/Dictionary’de saklar ve erişimimizi kolaylaştırır.

[code]<ResourceDictionary

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

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

xmlns:imdbFilmPoster="clr-namespace:ImdbFilmPoster">

<Style TargetType="imdbFilmPoster:ImdbPoster">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate>

<Grid>

<ProgressBar x:Name="_progress" />

<Image x:Name="_image" />

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</ResourceDictionary>[/code]

ResourceDictionary elementinin dikkat etmemiz gereken bir niteliği(Attribute) xmlns:imdbFilmPoster. Bu niteliğe verdiğimiz clr-namespace:ImdbFilmPoster değeri sayesinde ImdbFilmPoster namespace’i içerisinde bulunan sınıflara imdbFilmPoster ön-eki ile ulaşabiliyoruz. Sonrasında dikkat etmemiz gereken nokta da ResourceDictionary içerisindeki Style elementi. İşte ImdbPoster sınıfının görünüşünü belirleyecek olan kısım. Style elementinin TargetType niteliği ile bu stilinin hangi sınıfa özel olduğunu belirtiyoruz.

İçerisindeki Setter elementi sayesinde Property niteliği ile belirtilmiş Template özelliğini tanımlıyoruz. Template, Control sınıfının görünümünü belirleyen özelliğidir. Setter.Value içerisinde ise bir ControlTemplate nesnesi oluşturuyoruz, içerisinde bir Grid ve Grid içerisine bir Image ve ProgressBar kontrollerini ekliyoruz. Image kontrolünü filmin posterini göstermekte, ProgressBar kontrolünü de resmin online olarak çekilip yüklenmesi esnasında bir işlem yapıldığını belirtmek için kullanacağız.

 

ImdbPoster sınıfı içerisinde tanımlanan Style’ın belirtilmesi

generic.xaml dosyası içerisinde Style tanımını yaptık ancak ImdbPoster sınıfı içerisinde bu stili kullanması gerektiğini belirtmedik. Bu adımda da bunu belirtelim. ImdbPoster sınıfı constructor’ı içerisinde DefaultStyleKey özelliğini set etmemiz gerekiyor. DefaultStyleKey özelliğine atadığımız tipe göre kullanılacak olan stil ResourceDictionary üzerinden getirilecek.

[cpp]public ImdbPoster()
{
    DefaultStyleKey = typeof(ImdbPoster);
}[/cpp]

 

ImdbPoster Sınıfı İçerisinden Style İçerisinde Tanımlanmış Controllere Erişim

Öncelikle ImdbPoster sınıfı içerisinde birer tane Image ve ProgressBar nesnesi tanımlıyoruz.

[cpp]private Image _image;

private ProgressBar _progress;[/cpp]

Tanımladığımız bu nesnelere, Style içerisinde tanımladığımız nesnelerin referanslarını atayalım. Style içerisinde tanımlanmış bir Control’ün referansına erişmek için önerilen method OnApplyTemplate methodu. Bu method style içerisinde tanımlanmış nesnelerin ve visual tree’nin oluşturulduğu method. OnApplyTemplate methodunu aşağıdaki gibi override ediyoruz ve Control sınıfı tarafından tanımlanmış GetTemplateChild fonksiyonunu çağırarak Image ve ProgressBar nesnelerinin referanslarını sınıfımız içerisinde tanımladığımız nesnelere atıyoruz. GetTemplateChild fonksiyonuna parametre olarak Style içerisinde belirttiğimiz x:Name niteliği değerlerini veriyoruz.

Methodun başında yer alan base.OnApplyTemplate() satırı sayesinde nesneler ve visual tree oluşturuluyor. O satır çalıştırılmadan GetTemplateChild fonksiyonu ile nesnelere erişmeye çalışılırsa null reference dönecektir.

[cpp]public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    _image = GetTemplateChild("_image") as Image;
    _progress = GetTemplateChild("_progress") as ProgressBar;

    if (_progress != null)
    {
        _progress.IsIndeterminate = true;
        _progress.Visibility = Visibility.Collapsed;
    }

    _initialized = true;
}[/cpp]

 

Film Posterinin Yüklenmesi ve Gösterilmesi

Film poster bilgisini çekmek için ImdbApi sınıfını kullanacağız. ImdbApi sınıfı sağladığı RetrieveFilmModel(String filmImdbId) methodu sayesinde filmin poster linkini de içeren bir model nesnesi dönüyor. Biz de bu methodu Control.Loaded event handler’ı içerisinde çağırarak poster linkine ulaşacağız ve daha sonra ImdbPoster sınıfımız içerisinde tanımladığımız _image nesnesinin Source özelliğine atama yapacağız. Aşağıdaki gibi Loaded eventine handler ekliyoruz:

[cpp]public ImdbPoster()
{
    DefaultStyleKey = typeof(ImdbPoster);
    Loaded += ImdbPosterLoaded;
}

void ImdbPosterLoaded(object sender, RoutedEventArgs e)
{
    OnApplyTemplate();
    RefreshPoster();
}

private async void RefreshPoster()
{
    if (!string.IsNullOrEmpty(ImdbId))
    {
        _progress.Visibility = Visibility.Visible;
        var model = await ImdbApi.RetrieveFilmModel(ImdbId);

        if (model != null && model.ImdbPoster != null)
        _image.Source = new BitmapImage(new Uri(model.ImdbPoster.ImdbPoster, UriKind.Absolute));
        _progress.Visibility = Visibility.Collapsed;
    }
}[/cpp]

Bu işlem için Loaded event handler’ını kullandık çünkü arayüz nesnelerinin özelliklerini set etmek için MSDN üzerinde önerilen yer Loaded event handlerı.

 

ImdbPoster Sınıfına ImdbId Özelliğinin Eklenmesi

Yukarıdaki RefreshPoster fonksiyonunda ihtiyacımız olan bir Imdb değeri var. Son olarak sınıfımıza o değeri de almak için bir Property(özellik) ekleyelim ve değer her değiştiğinde RefreshPoster fonksiyonu çağırılsın ki film posteri güncellensin. ImdbId özelliği aşağıdaki gibi:

[cpp]private string _imdbId;

public string ImdbId
{
    get { return _imdbId; }
    set
    {
        if (value != _imdbId)
        {
            _imdbId = value;
            if (_initialized)
            RefreshPoster();
        }
    }
}[/cpp]

Sınıfımız artık kullanıma hazır. Arka planda bir çok işlem yapılmasına rağmen sayfamızın arka plan koduna bir satır bile eklemeden, bir filme ait posteri ekranda gösterebiliriz. Örnek bir kullanım aşağıdaki gibi:

[cpp]<phone:PhoneApplicationPage

x:Class="ImdbPosterTest.MainPage"

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

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

xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"

xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:imdbFilmPoster="clr-namespace:ImdbFilmPoster;assembly=ImdbFilmPoster"

mc:Ignorable="d"

FontFamily="{StaticResource PhoneFontFamilyNormal}"

FontSize="{StaticResource PhoneFontSizeNormal}"

Foreground="{StaticResource PhoneForegroundBrush}"

SupportedOrientations="Portrait" Orientation="Portrait"

shell:SystemTray.IsVisible="True">

<!--LayoutRoot is the root grid where all page content is placed-->

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

<imdbFilmPoster:ImdbPoster Width="400" ImdbId="tt1538403"

HorizontalAlignment="Center" VerticalAlignment="Center" Height="400" />

</Grid>

</phone:PhoneApplicationPage>[/cpp]

 

 

Kaynak Dosyalarına Erişim

Yazıda parça parça verilmiş örnek uygulamanın tümüne Github hesabım üzerinden erişebilirsiniz.

Sorular ve Hatalar

Hatalı olduğumu düşündüğünüz, anlayamadığınız noktaları lütfen yorum ekleyerek belirtiniz. 

Mehmet Altıparmak |

07.10.2013

Yorumlar

Deniz KARAGÖZ
21.02.2014 - 11:40

çok teşekkürler