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
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.
Hepinize kolay gelsin.