Makale Özeti

Silverlight içerisindeki DataGrid'in gruplama özelliğine baktığımız bu sayıda ayrıca bazı görsel tasarım değişikliklerinin de nasıl implemente edilebileceğini göreceğiz.

Makale

DataGrid kontrolü belki de iş uygulamalarında en sık kullanılan kontrollerden biridir. Silverlight içerisinde de uzun bir süredir DataGrid kontrolü bulunuyor. Özellikle performans artıları ve esnekliği ile aslında Silverlight ile beraber gelen DataGrid emin olun üçüncü parti bir DataGrid almanızı gerektirmeyecek kadar kuvvetli. Daha da güzel bu DataGrid'in kaynak kodları da CodePlex üzerindeki Silverlight Toolkit içerisinde bulunuyor. Hatırlarsanız çok önceleri DataPager kontrolünden bahsederken PagedCollectionView adında bir sınıftan bahsetmiştim. Söz konusu sınıf aslında bir DataGrid kontrolünün gruplama özelliğini de ortaya çıkarak ilginç bir yapıya sahip. Bu yazımızda ilk olarak DataGrid'in gruplama özelliğinin kullanımına değineceğiz. Sonrasında da bu gruplama özelliğini daha da özelleştirmeye çalışacağız.

DataGrid ile gruplama....

DataGrid kontrolü aslında kendi içerisinde gruplama sistemini barındırıyor. Tek yapmanız gereken gruplama desteğine sahip ve gerekli ayarları yapılmış bir PagedCollectionView kullanmak. Şimdi elimizde hali hazırda bir DataGrid olduğunu düşünelim ayrıca bir de entitylerimizden oluşan listemiz var.

[XAML / DataGrid]

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

        <sdk:DataGrid HorizontalAlignment="Stretch" Name="DataGrid1" VerticalAlignment="Stretch" />

    </Grid>

[VB]

    Public Class OrnekEntity

        Public Property Adam As String

        Public Property Sehir As String

        Public Property Ilce As String

    End Class

 

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

        Dim EldekiListe As New List(Of OrnekEntity)

        For index = 1 To 10

            EldekiListe.Add(New OrnekEntity With {.Adam = "Örnek Adam" & index,

                                                   .Ilce = "İlçe" & (index Mod 2).ToString(),

                                                   .Sehir = "Şehir" & (index Mod 5).ToString()})

 

        Next

 

    End Sub

Bu listeyi hemen aşağıdaki gibi bir PagedCollectionView'e çevirebiliriz.

[VB]

        Dim Paged = New PagedCollectionView(EldekiListe)

        Paged.GroupDescriptions.Add(New PropertyGroupDescription("Sehir"))

        Paged.GroupDescriptions.Add(New PropertyGroupDescription("Ilce"))

        DataGrid1.ItemsSource = Paged

Kodumuz olabildiğince kısa ve basit. Paged adındaki PagedCollectionView bir liste üzerinden yaratılıyor. Liste içerisindeki entitynin property'lerinin ikisinin adı Sehir ve Ilce şeklinde. İşte tam da bu noktada eldeki listenin söz konusu property'lerin değerlerine göre gruplanmasına gerektiğine dair bilgiyi PagedCollectionView'a iletiyoruz. Bunun için bir PropertyGroupDescription yaratarak parametre olarak olası property'lerin adlarını String olarakveriyoruz ve PropertyGroupDescription'ları da PagedCollectionView'ın GroupDescriptions listesine ekliyoruz. Son olarak eldeki PGD'yi de gride aktarıyoruz gösterilmek üzere. Gördüğünüz üzere aslında herşey yeterince basit.

DataGrid'den Grouping Desteği
DataGrid'den Grouping Desteği

Gördüğünüz üzere iç içe gruplamalar dahil kolaylıkla güzel bir sistem oluşturulabiliyor. Bu manzarada hoşunuza gitmeyebilecek ilk şey gruplama için kullanılan bilgilerin DataGrid içerisinde de kolonlarda gösteriliyor olması. Bu sorunu çözmek çok kolay. Eğer DataGrid'in AutoGenerateColumns özelliği False yapar ve kolonları siz belirlerseniz istediğiniz property'lerin kolon olarak gösterilmemesini sağlayabilirsiniz. Söz konusu Property'lerdeki değerler sadece gruplama amaçlı kullanılabilir.

Aslında bu manzarada en sinir bozucu şeylerden biri her grubun başında "1 item", "2 item" gibi İngilizce birşeylerin yazılı olması ve maalesef bunu değiştiremiyor olmanız. Tabi yazımızın başında da bahsettiğimiz gibi kontrolün kaynak kodları veriliyor ve rahatlıkla o seviyede gerekli değişiklikler yapılabilir fakat SDK dışına çıkmak istemeynler ve sürekli her yeni sürümü çıktığında DataGrid assemblysini özelleştirmek zorunda kalmak istemeyenler için daha pratik bir yol olmalı değil mi? Çok pratik olmasa da sizlerle bir taktik paylaşacağım. Bu taktik ile gruplama için DataGrid içerisindeki kullanılan yapıyı tamamen değiştirebileceksiniz.

Ne de olsa herşey Silverlight değil mi?

DataGrid'in gruplama esnasında kullandığı görsel yapıyı ilk gördüğümde. "Ne de olsa herşey Silverlight değil mi burada?" demiştim. Bir şekilde oradaki yapıya ulaşabilmem ve değiştirebilmem gerekirdi. Fakat maalesef ki DataGrid kontrolü geliştirilirken bu pek de düşünülmemiş ve son developer (son kullanıcıdan yola çıkıp ürettiğim bir terim) pek düşünülmemiş. O nedenle biraz takla atmak gerekecek.

İlk olarak yapılması gereken şey kontrolün kaynak kodlarını inceleyerek hali hazırda DataGrid'in gruplama için kullanılan görsel kısmını bulmak. Böylece söz konusu görsel kısmı değiştirerek belki de parametrik olarak elimizdeki normal DataGrid'e verebiliriz? DataGrid'in iç yapısını ve kaynak kodunu incelediğimizde gruplama görseli için DataGridRowGroupHeader adında primitive bir kontrol kullanıldığını görüyoruz. Söz konusu kontrol System.Windows.Controls.Data assemblysi altında System.Windows.Controls namespace'inde bulunuyor. Bu kontrol DataGrid'in içerisinde gruplama amaçlı kısımlarda kullanıldığına göre bu kontrolün görselliğini yani şablonunu (template) değiştirmemiz yeterli olacaktır. Hatta daha önce de bahsettiğimiz gibi hali hazırda var olan şablonu alıp kaynak kodlarından rahatlıkla ilerleyebiliriz.

[XAML]

   xmlns:dataprimitives="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Yukarıdaki şekilde kontrolü XAML tarafında tanımlanabilir hale getirdikten sonra kaynak dosyalarından kontrolün varsayılan şablonuna ait XAML kodunu da aşağıdaki şekilde alıyoruz.

[XAML]

<ControlTemplate x:Name="Ornek" TargetType="dataprimitives:DataGridRowGroupHeader">

            <sdk:DataGridFrozenGrid x:Name="Root" Background="{TemplateBinding Background}">

                <sdk:DataGridFrozenGrid.Resources>

                    <ControlTemplate x:Key="ToggleButtonTemplate" TargetType="ToggleButton">

                        <Grid Background="Transparent">

                            <VisualStateManager.VisualStateGroups>

                                <VisualStateGroup x:Name="CommonStates">

                                    <VisualState x:Name="Normal"/>

                                    <VisualState x:Name="MouseOver">

                                        <Storyboard>

                                            <ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Stroke).Color" Storyboard.TargetName="CollapsedArrow"/>

                                            <ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Fill).Color" Storyboard.TargetName="ExpandedArrow"/>

                                        </Storyboard>

                                    </VisualState>

                                    <VisualState x:Name="Pressed">

                                        <Storyboard>

                                            <ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Stroke).Color" Storyboard.TargetName="CollapsedArrow"/>

                                            <ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Fill).Color" Storyboard.TargetName="ExpandedArrow"/>

                                        </Storyboard>

                                    </VisualState>

                                    <VisualState x:Name="Disabled">

                                        <Storyboard>

                                            <DoubleAnimation Duration="0" To=".5" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="CollapsedArrow"/>

                                            <DoubleAnimation Duration="0" To=".5" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ExpandedArrow"/>

                                        </Storyboard>

                                    </VisualState>

                                </VisualStateGroup>

                                <VisualStateGroup x:Name="CheckStates">

                                    <VisualState x:Name="Checked"/>

                                    <VisualState x:Name="Unchecked">

                                        <Storyboard>

                                            <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="CollapsedArrow">

                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>

                                            </ObjectAnimationUsingKeyFrames>

                                            <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ExpandedArrow">

                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>

                                            </ObjectAnimationUsingKeyFrames>

                                        </Storyboard>

                                    </VisualState>

                                </VisualStateGroup>

                            </VisualStateManager.VisualStateGroups>

                            <Path x:Name="CollapsedArrow" Data="F1 M 0,0 L 0,1 L .6,.5 L 0,0 Z" HorizontalAlignment="Center" Stretch="Uniform" Stroke="#FF414345" Visibility="Collapsed" VerticalAlignment="Center" Width="5"/>

                            <Path x:Name="ExpandedArrow" Data="F1 M 0,1 L 1,1 L 1,0 L 0,1 Z" Fill="#FF414345" HorizontalAlignment="Center" Stretch="Uniform" VerticalAlignment="Center" Width="6"/>

                        </Grid>

                    </ControlTemplate>

                </sdk:DataGridFrozenGrid.Resources>

                <sdk:DataGridFrozenGrid.ColumnDefinitions>

                    <ColumnDefinition Width="Auto"/>

                    <ColumnDefinition Width="Auto"/>

                    <ColumnDefinition Width="Auto"/>

                    <ColumnDefinition Width="Auto"/>

                    <ColumnDefinition/>

                </sdk:DataGridFrozenGrid.ColumnDefinitions>

                <sdk:DataGridFrozenGrid.RowDefinitions>

                    <RowDefinition Height="Auto"/>

                    <RowDefinition/>

                    <RowDefinition Height="Auto"/>

                </sdk:DataGridFrozenGrid.RowDefinitions>

                <VisualStateManager.VisualStateGroups>

                    <VisualStateGroup x:Name="CurrentStates">

                        <VisualState x:Name="Regular"/>

                        <VisualState x:Name="Current">

                            <Storyboard>

                                <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisual"/>

                            </Storyboard>

                        </VisualState>

                    </VisualStateGroup>

                </VisualStateManager.VisualStateGroups>

                <Rectangle Grid.ColumnSpan="5" Grid.Column="1" Fill="#FFFFFFFF" Height="1"/>

                <Rectangle x:Name="IndentSpacer" Grid.Column="1" Grid.Row="1"/>

                <ToggleButton x:Name="ExpanderButton" Grid.Column="2" Height="15" IsTabStop="False" Margin="2,0,0,0" Grid.Row="1" Template="{StaticResource ToggleButtonTemplate}" Width="15"/>

                <StackPanel Grid.Column="3" Margin="0,1,0,1" Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Center">

                    <TextBlock x:Name="PropertyNameElement" Margin="4,0,0,0" Visibility="{TemplateBinding PropertyNameVisibility}"/>

                    <TextBlock Margin="4,0,0,0" Text="{Binding Name}"/>

                    <TextBlock Loaded="TextBlock_Loaded" Margin="4,0,0,0" DataContext="{Binding}" Text="DENEME"/>

                    <TextBlock x:Name="ItemCountElement" Margin="4,0,0,0" Visibility="{TemplateBinding ItemCountVisibility}"/>

                </StackPanel>

                <Rectangle Grid.ColumnSpan="5" Grid.Column="1" Fill="#FFD3D3D3" Height="1" Grid.Row="2"/>

                <Rectangle x:Name="FocusVisual" Grid.ColumnSpan="4" Grid.Column="1" HorizontalAlignment="Stretch" IsHitTestVisible="false" Opacity="0" Grid.RowSpan="3" Stroke="#FF6DBDD1" StrokeThickness="1" VerticalAlignment="Stretch"/>

                <sdk:DataGridRowHeader x:Name="RowHeader" sdk:DataGridFrozenGrid.IsFrozen="True" Grid.RowSpan="3"/>

            </sdk:DataGridFrozenGrid>

        </ControlTemplate>

Yukarıdaki kod içerisinde özellikle dikkat edilmesi gereken kısmı renki bırakmaya çalıştım. Gördüğünüz renkli kod kısmı tam da bizim DataGrid içerisindeki gruplama kısmını tanımlıyor. Gruplamanın açılıp kapanmasını sağlayan bir ToggleButton ve gruba ait bilgilerin yazıldığı TextBlock'lar. Ne kadar doğal değil mi? :) Biz de yapsak böyle yapardık herhalde.  Ben kod içerisinde bir de ekstra TextBlock yerleştirdim. Söz konusu TextBlock'a DataContext olarak gelen bütün veriyi Bind ettim. Malum diğer kontrollere de baktığımızda Binding'ler görebiliyoruz. Hatta gruplama yapılan Property'nin adının yazıldığı TextBlock'un Text'i Name adında birşeye bind edilmiş. Acaba bu nesne nedir diyerek deneme amaçlı TextBlock'u koyalım.

Şimdi sıra geldi bu şablonu eldeki sıfır bi DataGrid'in içerisindeki tüm otomatik yaratılan DataGridRowGroupHeader nesnelerine Template olarak aktarmaya. Peki bunu nasıl yapacağız?

[VB]

    Private Sub DataGrid1_LoadingRowGroup(ByVal sender As Object, ByVal e As System.Windows.Controls.DataGridRowGroupHeaderEventArgs) Handles DataGrid1.LoadingRowGroup

        e.RowGroupHeader.Template = Me.Resources("Ornek")

    End Sub

Her DataGrid'in zaten LoadingRowGroup adında bir event'i var. Eğer yukarıda tanımladığımız ControlTemplate'i DataGrid ile aynı sayfada UserControl.Resources kolleksiyonu içerisine koyarsanız ismi ile resource'u bulup aynı yukarıdaki şekilde yaratılan her RowGroupHeader'a Template olarak atayabiliriz. Her atama sonrasında da bizim TextBlock yaratılacağı için kendi Binding'i ile beraber Loaded eventini çalıştıracaktır. Böylece biz de datayı alıp birşeyler yapabiliriz.

[VB]

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

        Dim TXT As TextBlock = sender

        Dim x As CollectionViewGroup = CType(sender, TextBlock).DataContext

        Dim Subx = x.Items(0)

 

        If TypeOf Subx Is OrnekEntity Then

            TXT.Text = Subx.Adam

        Else

            TXT.Text = CType(Subx.Items(0), OrnekEntity).Adam

        End If

    End Sub

Textblock'un Loaded eventını yukarıda bulabilirsiniz. Aslında ControlTemplate içerisine Binding ile gelen nesne bir CollectionViewGroup ve bu nesne kendi içerisinde hem alt itemlarının sayısını hem de alt itemların bir kolleksiyonunu saklıyor. Tabi bazen alt item dediğimiz şey bir başka CollectionViewGroup olabiliyor. O neden gerekli kontrolleri yazarak en alt item'a kadar gidip istediğimiz bir entity'ye ulaştığımızdan emin olmamız gerek. Sonrasında artık grubun altındaki herhangi bir Entity'le ulaştığınız (veya hepsine) artık istediğinizi yapabilirsiniz. Örneğimizde biz sadece grubun altındaki ilk Entity'nin bir propertysini doğrudan TextBlock'a yazdırıyoruz. Siz kendi örneklerinizde hem tasarım tarafında XAML'ı istediğiniz gibi değiştirebilir hem de farklı işlevsellikler ekleyebilirsiniz.

[XAML]

                <StackPanel Grid.Column="3" Margin="0,1,0,1" Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Center">

                    <TextBlock x:Name="PropertyNameElement" Margin="4,0,0,0" Visibility="{TemplateBinding PropertyNameVisibility}"/>

                    <TextBlock Margin="4,0,0,0" Text="{Binding Name}"/>

                    <StackPanel Orientation="Horizontal">

                        <TextBlock Margin="4,0,0,0" Text="("/>

                        <TextBlock Margin="0,0,0,0" Text="{Binding ItemCount}"/>

                        <TextBlock Margin="4,0,0,0" Text="öğe)"/>

                    </StackPanel>                   

                </StackPanel>

Yukarıdaki örnekte sadece ControlTemplate içerisinde değişiklik yaparak "(1 item)" gibi İngilizce yazıları Türkçe'ye çevirebiliyoruz. Yatay bir StackPanel koyduktan sonra üç adet TextBlock ile "(1 öğe)" gibi bir metni oluşturabiliriz. Binding üzerinden gelen CollectionViewGroup sınıfı ile beraber zaten ItemCount adında bir Property geliyor ve her grubun altındaki sayı hızlıca bir TextBlock'a bind edilebiliyor. Siz isterseniz ToggleButton'un tasarımını bile değiştirebilirsiniz ;) İpler sizin elinizde....

DataGrid'de gruplama Türkçeleştirildi.
DataGrid'de gruplama Türkçeleştirildi.

Hepinize kolay gelsin.