Silverlight'ta DataGrid'in gruplama özelliği ve görsel özelleştirme taktiği.

0 dakikada yazıldı

6675 defa okundu

Düzenle

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.