Sayfalar

25 Ağustos 2012 Cumartesi

WPF DataGrid üzerinde programatik olarak nasıl DataGridTemplateColumn oluşturup yönetiriz?

WPF ile ilgilenen çoğu geliştiricinin de bildiği gibi XAML yapısı arka planda yazılacak birçok kodu ortadan kaldırabilmektedir. Ancak Windows Forms geleneğinden gelen kullanıcıların XAML yapısına geçiş sürecinde syntax'a tamamen hakim olamamasından dolayı bazı işlemleri programatik olarak kodla yapmayı tercih etmektedirler.

Ancak XAML syntax'ı dışında formların yapısındaki köklü değişiklikler de bu noktada geliştiricinin karşısına zorluk olarak çıkmaktadır. "Windows Form'da
item.Controls.Add(c)
şeklindeki işlem nasıl yapılır?" sorusunun cevabı bile araştırma konusu olup büyük zaman harcatabiliyor.

Hal böyle olunca her ne kadar WPF kullanmasamda naçizane WPF bilgimle ufak bir örnek ile bu yapıda bir DataGrid'i programatik olarak nasıl kontrol edebileceğimize değinmek ve ihtiyaç sahiplerine de yardım ulaştırmak istedim.

Hedef:

Yapılmak istenen şey bir DataGrid içine bir veri kaynağı bağlamak, bu kaynaktaki her nesneye karşılık oluşacak satırlara ek kolonlar ilave edip bunu da kaynak nesnesi ile ilişkilendirmek.

Uygulama:

Önce WPF Application projesi yaratıp MainWindow içine ToolBox içindeki DataGrid kontrolünü sürükleyip bırakıyoruz. Adına da dokunmuyor ve dataGrid1 olarak bırakıyoruz. Bundan sonra MainWindow'a çift tıklayarak Window_Loaded void'ini oluşturuyor ve XAML tarafındaki işimizi bitiriyoruz.

Bundan sonra herşeyi programatik olarak kodla yapacağız. İş bitince arayüzü çalıştırıp oynayabiliriz :)

Önce işe veri kaynağımızı oluşturarak başlayalım. Kaynağımız klasik kişi listesi olsun, bu kişi nesnelerinin de basitçe isim ve yaş bilgisinden başka bir özelliği olmasın. Bir de string üreten ToString() metodunu ezelim:

public struct Person
{
    public string Name { set; get; }
    public int Age { set; get; }

    public override string ToString()
    {
        return string.Format("{0} şu anda {1} yaşındadır.", this.Name, this.Age);
    }
}

Bir de bu kişilerden bir liste oluşturan statik bir metot yazalım:

private static Person[] GetDataSource()
{
    return new Person[] { new Person() { Name = "Onur", Age = 31 },
                          new Person() { Name = "Ahmet", Age = 29 },
                          new Person() { Name = "Ali", Age = 11 },
    };
}

Bu kişileri DataGrid'imize bağlamak için DataGrid.ItemsSource özelliğini set etmemiz gerekiyor.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    dataGrid1.ItemsSource = GetDataSource();
}

Buraya kadar bir sıkıntımız yok ama uygulama çalıştığında görürsünüz ki ekran bomboş. Neden? Çünkü WPF'te Windows Forms'un aksine kolonlar otomatik oluşmuyor ve biz henüz kolon eklemedik.

dataGrid1.Columns.Add(new DataGridTextColumn() { Header = "Adı", Binding = new Binding("Name") });
dataGrid1.Columns.Add(new DataGridTextColumn() { Header = "Yaşı", Binding = new Binding("Age") });

Yukarıdaki kodları da ekledikten sonra şimdi kişilerin adını ve yaşını görüyor olmamız gerek. Artık Template kolon eklemeye geçebiliriz.

Bir kolon daha ekleyelim ama bunun içinde bir buton olsun, üzerinde kişinin yaşı yazsın, tıklandığında da kişinin string gösterimini MessageBox ile göstersin. Bununla da yetinmesin düzenleme işlemlerinde de üzerindeki yazı (yaş) değişsin.

Bu kez Columns koleksiyonuna DataGridTextColumn yerine DataGridTemplateColumn ekleyeceğiz. Ama bunu eklerken Binding özelliğinin olmadığını görürüz. Bu kez FrameworkElementFactory tipindeki VisualTree özelliğini kullanacağız. Bu değer çok karışık olacağından GetFactory<T> şeklinde generic bir metot hazırladım. Ne tipte kontrol oluşturacaksak T parametresine veriyoruz. Burada T tipi FrameworkElement tipinden inherit etmelidir.

private static FrameworkElementFactory GetFactory<T>(DependencyPropertyData[] properties, RoutedEventData[] events) where T : FrameworkElement
{
    var F = new FrameworkElementFactory(typeof(T));

    foreach (var x in properties)
        F.SetValue(x.DependencyProperty, x.Value);

    foreach (var x in events)
        F.AddHandler(x.RoutedEvent, x.Handler, x.HandledEventsToo);

    return F;
}

public struct DependencyPropertyData
{
    public DependencyProperty DependencyProperty;
    public object Value;

    public DependencyPropertyData(DependencyProperty dependencyProperty, object value)
    {
        this.DependencyProperty = dependencyProperty;
        this.Value = value;
    }
}

public struct RoutedEventData
{
    public RoutedEvent RoutedEvent;
    public Delegate Handler;
    public bool HandledEventsToo;

    public RoutedEventData(RoutedEvent routedEvent, Delegate handler) : this(routedEvent, handler, false) { }

    public RoutedEventData(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo)
    {
        this.RoutedEvent = routedEvent;
        this.Handler = handler;
        this.HandledEventsToo = handledEventsToo;
    }
}

Yukarıda gördüğümüz metot istediğimiz T tipinde bir FrameworkElementFactory örnekleyip, buna verdiğimiz DependencyPropertyData ve RoutedEventData tipindeki listeleri set edip, verdiğimiz event'ları da handle eder. Bu iki tipi topluca özellik ve olay listesini inline olara verebilmek için tanımladım. Kullanımını görünce ışık çakacaktır!

dataGrid1.Columns.Add(new DataGridTemplateColumn()
{
    Header = "Göster",
    IsReadOnly = true, //buton alanı editlenemesin
    CellTemplate = new DataTemplate()
    {
        VisualTree = GetFactory<Button>(new DependencyPropertyData[] { 
                                            new DependencyPropertyData(Button.ContentStringFormatProperty, "N1"), //Button tipindeki kontrollerde StringFormat özelliği yoksayılacağından bunu kullanıyoruz!
                                            new DependencyPropertyData(Button.ContentProperty, new Binding("Age") { NotifyOnSourceUpdated = true }), //NotifyOnSourceUpdated ile data değişince buton üzerindeki yazı da güncellensin!
                                            new DependencyPropertyData(Button.MarginProperty, new Thickness(20, 2, 2, 2)),
                                            new DependencyPropertyData(Button.WidthProperty, 60d ) //Genişliği double tipinde 60 yapıyoruz. double vermezsek exception oluşur
                                        },
                                        new RoutedEventData[] { 
                                            new RoutedEventData(Button.ClickEvent, new RoutedEventHandler(OnButtonClick)), 
                                            new RoutedEventData(Button.MouseEnterEvent, new MouseEventHandler(OnButtonMouseEnter)), 
                                            new RoutedEventData(Button.MouseLeaveEvent, new MouseEventHandler(OnButtonMouseLeave)) 
                                        })
    }
});

Burada OnButtonClick, OnButtonMouseEnter, OnButtonMouseLeave delegeleri de anlamışsınızdır ki o butona tıklayınca, üzerine fare ile gelince ve fare üzerinden ayrılınca tetiklenecek metotlardır:

private static void OnButtonClick(object sender, RoutedEventArgs e)
{
    //Bu satırın orjinal kaynağı olan butona bağlanmış olan veri nesnesi (Person) üzerindeki ToString() metodunun sonucunu mesaj kutusunda göster.
    MessageBox.Show(((Person)((Button)e.OriginalSource).DataContext).ToString());
}

private static void OnButtonMouseEnter(object sender, MouseEventArgs e)
{
    //Fare imleci bu butonun üzerine geldiğinde butonun font stilini Italic yap
    ((Button)sender).SetValue(Button.FontStyleProperty, FontStyles.Italic);
}

private static void OnButtonMouseLeave(object sender, MouseEventArgs e)
{
    //Fare imleci bu butonun üzerinden ayrıldığında butonun font stilini Normal yap
    ((Button)sender).SetValue(Button.FontStyleProperty, FontStyles.Normal);
}

Çalıştırdığımızda aşağıdaki görüntüyü alırız. (İsim ve yaşı edit edip butona tekrar tıklayarak değişikliklerin uygulandığını kontrol edebilirsiniz.)


Örnek projeyi buradan indirebilirsiniz.

Hiç yorum yok:

Yorum Gönder