İlkay İlknur

just a developer...

Windows 8 Metro Style Uygulamalarda Semantic Zoom - C# & XAML

Merhaba Arkadaslar,

Windows 8 ile beraber baktigimizda hayatimiza kullanici deneyimi bakimindan pek çok yenilik gelmekte. Bu yeniliklerin en dikkat çekici olanlarindan biri de hiç süphesiz ki Semantic Zoom kavrami.

Semantic Zoom, zoom-in ve zoom-out islemleri ile kullaniciya elindeki data üzerinde gerek bütün olarak veri üzerinde dolasabilmeye imkan verirken gerekse veriyi gruplayarak veya özetleyerek bu gruplanmis veya özetlenmis veri kümesi üzerinde kullanicinin dolasmasina imkan saglamakta. Böylece kullanici aslinda önünde bulunan veri kümesi üzerinde zoom-out islemi yaptigi zaman görmüs oldugu verinin daha küçük gruplanmis veya özetlenmis halini görerek bu sekilde belki de daha hizli bir biçimde istedigi dataya ulasabilmekte. Su anda Windows 8 üzerinde Semantic Zoom'un kullanildigi en önemli yerlerden biri de Start Screen. Start Screen üzerinde Zoom-in ve Zoom-out yaptiginizda StartScreen içerisinde bulunan itemlar ekranda daha ufak bir biçimde gösterilerek kullanicinin Start Screen içerisinde bulunan tüm gruplari görebilmesi saglanmakta.

Not : Semantic Zoom aslinda touch screen bir cihazda klasik zoom gesturelari ile saglanirken touch screen olmayan cihazlarda ise Ctrl tusu basili tutularak mouse scroll ile ayni deneyim saglanabilmekte.

 

Hiç süphesiz ki Windows 8 içerisinde kullanilan bu deneyiminin bizim gelistirdigimiz uygulamalarda da olmasi hem kullanicilara büyük bir kolaylik ve deneyim saglayacak hem de bizim uygulamamiza ayri bir deger katacaktir. Simdi gelelim uygulamalarimiza semantic zoom'u nasil entegre edecegimiz konusuna. (Biz semantic zoom'u önceki yazilarimizda da gerçeklestirimini yaptigimiz yemek uygulamasi üzerinde yapiyor olacagiz.)

Uygulamamiza baslamadan önce karar vermemiz gereken noktalardan biri de semantic zoom'u nasil bir konseptte implemente edecegimiz. Yani kullanici elimizdeki data üzerinde zoom-out yaptiginda ne yapacagimiza karar vermemiz gerekiyor. Böyle bir durumda biz kullaniciya sadece yemek kategorilerini gösteriyor olacagiz. Böylece kullanici istediginde de ilgili kategoriye tiklayarak o kategori içerisindeki yemekleri görebiliyor olacak. Kategorileri gösterdigimiz durumda da eger kullanici zoom-in yaparsa elimizdeki tüm yemek listesini bir gridview içerisinde gösterecegiz.

Bu karari da verdikten sonra simdi sira geldi uygulamamizi gelistirme kismina. Hemen bos bir Metro Style Application yaratalim ve uygulamamizi gelistirmeye baslayalim.

Ilk olarak sahnemize sol taraftaki toolboxtan bir SemanticZoom kontrolü sürükleyerek uygulamaya baslayalim.

<SemanticZoom>
    <SemanticZoom.ZoomedInView>
        <GridView/>
    </SemanticZoom.ZoomedInView>
    <SemanticZoom.ZoomedOutView>
        <ListView/>
    </SemanticZoom.ZoomedOutView>
</SemanticZoom>

SemanticZoom kontrolünü sahneye ekledikten sonra olusan XAML kodumuz yukaridaki sekilde. Bu olusan XAML kodunun detaylarina indigimizde ise aslinda karsimiza oldukça basit bir sekilde 2 property çikmakta. Bunlardan biri ZoomedInView digeri de ZoomedOutView. Aslinda anlamlari adlarindan da anlasilacagi gibi oldukça basit. Kullanici Zoom-in yaptiginda olusacak görünümü ZoomedInView'a koyacagiz, yine kullanici zoom-out yaptiginda da olusacak görünümü de ZoomOutView'a koyacagiz :) Hatta yol göstermek amaciyla Visual Studio bizim ZoomedInView'imiz içerisine bir GridView yerlestirmis. ZoomedOutView'a da ayni sekilde bir ListView yerlestirmis. :) O zaman bizde bu yolu takip ederek devam edelim.

ZoomedInView aslinda bizim uygulamamiz açildiginda da gösterilecek olan default görünüm olacak. Bu nedenle burada bir GridView kontrolü kullanarak ilerlememiz bizim için en dogru çözüm. Bu nedenle burada daha önceki yazilarimizdan birinde inceledigimiz gruplanmis GridView kontrolünü yerlestirmemiz bizim için en dogrusu olacaktir. Bunun için gerekli olan templatelarimizi ve servis kodumuzu hemen projemize ekliyoruz. (Ekledigimiz tüm bilesenleri ve kodu açiklamalariyla beraber buradaki yazida bulabilirsiniz.)

<Page
    x:Class="SemanticZoom.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SemanticZoom"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Resources>
        <CollectionViewSource  x:Name="foodDataSource" ItemsPath="Foods" IsSourceGrouped="True"/>
        <DataTemplate x:Key="FoodTemplate">
            <Grid>
                <Image HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="250" Stretch="UniformToFill" Source="{Binding ImageUrl}"/>
                <Border BorderThickness="1" HorizontalAlignment="Left" Height="22.393" Margin="0,223.607,0,0" VerticalAlignment="Top" Width="250" Background="Black" Opacity="0.4">
                    <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Margin="3,2,0,0" Text="{Binding FoodName}"/>
                </Border>
            </Grid>
        </DataTemplate>
        <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </Page.Resources>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <SemanticZoom x:Name="semanticZoom" Margin="50,140,15,15">
            <SemanticZoom.ZoomedInView>
                <GridView x:Name="foodGrid" ItemsSource="{Binding Source={StaticResource foodDataSource}}" ItemTemplate="{StaticResource FoodTemplate}" ItemsPanel="{StaticResource ItemsPanelTemplate}">
                    <GridView.GroupStyle>
                        <GroupStyle>
                            <GroupStyle.HeaderTemplate>
                                <DataTemplate>
                                    <TextBlock FontSize="18.667" Text="{Binding CategoryName}"/>
                                </DataTemplate>
                            </GroupStyle.HeaderTemplate>
                            <GroupStyle.Panel>
                                <ItemsPanelTemplate>
                                    <VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="3"/>
                                </ItemsPanelTemplate>
                            </GroupStyle.Panel>
                        </GroupStyle>
                    </GridView.GroupStyle>
                </GridView>
            </SemanticZoom.ZoomedInView>
            <SemanticZoom.ZoomedOutView>
                <ListView/>
            </SemanticZoom.ZoomedOutView>
        </SemanticZoom>
    </Grid>
</Page>
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    FoodService.ServiceClient client = new FoodService.ServiceClient();

    foodDataSource.Source = await client.GetFoodsAsync();
}

Evet uygulamamizin ZoomedInView'i hazir. :) Zaten daha önceden hazirlamistik. :) Simdi sira geldi ZoomedOutView'a ki bunu sifirdan hazirliyor olacagiz. ZoomedOutView'da bir ListView kullanacagiz demistik ve bu ListView içerisinde  de kategori isimlerini gösterecektik. Bunun için ilk olarak kullanacagimiz veri kaynagini ilgili ListView kontrolünün ItemsSource'una baglamamiz gerekmekte ve bu islem için GridView kontrolünde de kullandigimiz sayfa içerisinde bulunan CollectionViewSource tipinden yararlaniyor olacagiz. Böylece ortak bir veri kaynagini kullandigimiz için de ilgili kategori ismine tiklandiginda GridView üzerindeki kategori üzerine yönlenmesi islemi de otomatik olarak gerçeklesiyor olacak.

CollectionViewSource tipinin içerisine baktigimizda ise karsimiza View isimli bir property çikmakta. Bu property aslinda CollectionViewSource tipinin içerisinde bulunan verileri disariya veya bagli oldugu kontrollere sundugu seklin birebir kopyasi. Bu nedenle View propertysi üzerinden aslinda "verilerimi bana kategorili bir biçimde ver" seklinde bir kullanim da gerçeklestirebilmekteyiz ve bu sekilde bir kullanim için de View.CollectionGroups isimli property'i kullanmaktayiz.

CollectionViewSource içerisinde bulunan View.CollectionGroups isimli propertynin tipine baktigimizda IObservableCollection<object> oldugunu görüyoruz. Peki biz yemek kategorilerinin isimlerini nasil bulacagiz da ListView içerisinde gösterecegiz. :) Isterseniz ilk olarak kategorilerimizi elimizdeki ListView'in ItemsSource'una baglayalim ve uygulamamizi çalistiralim, bakalim debug sirasinda bu property bize nasil bir deger döndürecek. :)

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    FoodService.ServiceClient client = new FoodService.ServiceClient();
    foodDataSource.Source = await client.GetFoodsAsync();
    (semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = foodDataSource.View.CollectionGroups;
}

DependencyObject :) yine istedigimiz degere ulasamadik. Oldukça can sikabilecek bu durumun çözümünü su sekilde yapabiliriz. Aslinda foodDataSource.View.CollectionGroups dedigimizde bize dönen IObservableCollection<object> içerisinde bulunan her bir eleman object olmanin yani sira ayni zamanda ICollectionViewGroup :) Buradan da hareket edersek ListView içerisinde Binding islemi yaparken ICollectionViewGroup interface'i üzerinden ilerleyebiliriz. Öyleyse ICollectionViewGroup interface'inin içerisinde bulunan üyelere bir göz atalim.

public interface ICollectionViewGroup
{
    object Group { get; }
    IObservableVector<object> GroupItems { get; }
}

Simdi istedigimize ulastik gibi :) . Interface'in yapisina baktigimizda karsimiza oldukça basit bir yapi çikiyor. Group propertysi  içerisinde bagladigimiz gruplar bulurken GroupItems propertysi içinde de grup içerisindeki elemanlar bulunmakta. Uygulamamiz üzerinden ilerlersekte Group içerisinde FoodCategory bulunurken GroupItems içerisinde de Food tipini içerisinde barindiran bir IObservableVector bulunmakta. O zaman yemek kategorilerinin isimlerini göstermek için Group.CategoryName propertysini kullanabiliriz.

O zaman gelin önce ListView içerisindeki itemlari nasil görecegimizi belirlemek için bir ItemTemplate yaratalim.

<DataTemplate x:Key="CategoryTemplate">
           <Grid d:DesignWidth="205.403" d:DesignHeight="203.463">
               <Button Content="{Binding Group.CategoryName}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="200" Width="200"/>
         </Grid>
</DataTemplate>

Bir de son olarak ListView içerisinde bulunacak olan Itemlarimizi yatay olarak göstermek için ListView içerisindeki ItemsPanel'de bulunan StackPanel'in Orientation'ini Vertical'dan Horizontal'a aliyoruz.

<ItemsPanelTemplate x:Key="CategoryPanelTemplate">
           <VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>

Son olarak sayfamizin olusan XAML kodu ise su sekilde.

<Page
    x:Class="SemanticZoom.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SemanticZoom"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Resources>
        <CollectionViewSource  x:Name="foodDataSource" ItemsPath="Foods" IsSourceGrouped="True"/>
        <DataTemplate x:Key="FoodTemplate">
            <Grid>
                <Image HorizontalAlignment="Left" Height="246" VerticalAlignment="Top" Width="250" Stretch="UniformToFill" Source="{Binding ImageUrl}"/>
                <Border BorderThickness="1" HorizontalAlignment="Left" Height="22.393" Margin="0,223.607,0,0" VerticalAlignment="Top" Width="250" Background="Black" Opacity="0.4">
                    <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Margin="3,2,0,0" Text="{Binding FoodName}"/>
                </Border>
            </Grid>
        </DataTemplate>
        <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
        <ItemsPanelTemplate x:Key="CategoryPanelTemplate">
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
        <DataTemplate x:Key="CategoryTemplate">
            <Grid d:DesignWidth="205.403" d:DesignHeight="203.463">
                <Button Content="{Binding Group.CategoryName}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="200" Width="200"/>
            </Grid>
        </DataTemplate>
    </Page.Resources>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <SemanticZoom x:Name="semanticZoom" Margin="50,140,15,15">
            <SemanticZoom.ZoomedInView>
                <GridView x:Name="foodGrid" ItemsSource="{Binding Source={StaticResource foodDataSource}}" ItemTemplate="{StaticResource FoodTemplate}" ItemsPanel="{StaticResource ItemsPanelTemplate}">
                    <GridView.GroupStyle>
                        <GroupStyle>
                            <GroupStyle.HeaderTemplate>
                                <DataTemplate>
                                    <TextBlock FontSize="18.667" Text="{Binding CategoryName}"/>
                                </DataTemplate>
                            </GroupStyle.HeaderTemplate>
                            <GroupStyle.Panel>
                                <ItemsPanelTemplate>
                                    <VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="3"/>
                                </ItemsPanelTemplate>
                            </GroupStyle.Panel>
                        </GroupStyle>
                    </GridView.GroupStyle>
                </GridView>
            </SemanticZoom.ZoomedInView>
            <SemanticZoom.ZoomedOutView>
                <ListView x:Name="lstCategories" ItemsPanel="{StaticResource CategoryPanelTemplate}" ItemTemplate="{StaticResource CategoryTemplate}" Height="240">
                </ListView>
            </SemanticZoom.ZoomedOutView>
        </SemanticZoom>
    </Grid>
</Page>

Simdi uygulamamizi çalistiralim ve yaptigimiz Semantic Zoom implementasyonunu inceleyelim.

ZoomedInView

ZoomOutView

Gördügünüz gibi 1-2 trick nokta haricinde aslinda uygulamalariniza Semantic Zoom destegi eklemek oldukça basit. Semantic Zoom'un uygulamalariniza katacagi deger ise harcayacagimiz efora göre çok çok daha  yüksek :)

Hepinize kolay gelsin


Yorum Gönder