Caner Tosuner

Leave your code better than you found it

INotifyPropertyChanged Nedir Nasıl Kullanılır

WPF, Windows10 (Mobile,Desktop etc.) uygulama geliştirme ile uğraşan arkadaşlar bilirler ki arayüz tarafında XAML (Extensible Application Markup Language) kullanılır ve codebehind'dan yani C# tarafından uygulama ekranında bulunan herhangi bir UI Control'ünün değeri değiştirme işlemlerini sık sık yaparız. İşte bu gibi işlemleri örneğin TextBlock'un Text'ini değiştirme işlemini C# tarafında tblName.Text="Caner"; yazmak yerine INotifyPropertyChanged interface'ini kullanarak bu gibi işlemleri kolaylıkla ve daha yönetilebilir bir şekilde yapabiliriz. (Tabi sadece TextBlock için geçerli değil, Button'un click event'i gibi durumlarda da INotifyPropertyChanged'İ kullanabiliriz)

INotifyPropertyChanged nedir dersek kısaca şöyle tanımlayabiliriz ;

"C# tarafında yani CodeBehind da tanımlı olan bir class'ın property'sinin değeri değiştiğinde bu değişimden UI'ı yani XAML tarafını bilgilendirmesi" demektir.

 

 

 

Şöyle bir örneğimiz olsun; bir adet Windows Phone uygulaması ve ekranda 2 tane TextBox, 1 tane Button ve 1 tane de Label olsun. Kullanıcı bu 2 TextBox'a birer sayı girecek ve Button'a tıkladığında hemen altında bulunan Label'da bu iki sayının toplamını yazacak.

 

 

HesaplaViewModel.cs class

MVVM pattern ile daha önce uğraşan arkadaşlar bilirler hiyerarşi Model, View, ViewModel diye ayrılır. Bizim uygulamamızda şuan Model yok ancak MainPage.xaml View'i ve hemen aşağıda bulunan ViewModel class'ımız var. Bu class View'imizin DataContext'i olacak ve UI tarafı ile bütün haberleşme bu class üzerinden gerçekleşecektir. ViewModel içerisinde tanımlı olan parametreleri UI'a DataContext üzerinden Binding işlemleri yapıp propertChanged anından UI thread'den durumu haberdar edip Bind olduğu UI Control' deki değerini update edecektir veya bir event ise o event'in davranışına göre çalışacaktır.  

  public class HesaplaViewModel : INotifyPropertyChanged
    {
        private ICommand _HesaplaCommand;
        private int _ilkSayi;
        private int _ikinciSayi;
        private int _sonuc;

        public HesaplaViewModel()
        {
            HesaplaCommand = new RelayCommand(Sum);
        }

        public int İlkSayi
        {
            get { return _ilkSayi; }
            set
            {
                _ilkSayi = value;
                OnPropertyChanged("İlkSayi");
            }
        }

        public int İkinciSayi
        {
            get { return _ikinciSayi; }
            set
            {
                _ikinciSayi = value;
                OnPropertyChanged("İkinciSayi");
            }
        }

        public int Sonuc
        {
            get { return _sonuc; }
            set
            {
                _sonuc = value;
                OnPropertyChanged("Sonuc");
            }
        }

        public ICommand HesaplaCommand
        {
            get { return _HesaplaCommand; }
            private set
            {
                _HesaplaCommand = value;
                OnPropertyChanged("HesaplaCommand");
            }
        }

        private void Sum(object obj)
        {
            Sonuc = İlkSayi + İkinciSayi;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

 

RelayCommand.cs class

RelayCommand button'a tıklandığında çalışacak olan event gibi düşünebiliriz, Butonun click statelerini aşağıda ki metodlar sayesinde handle edip yönetimini sağlıyoruz

    public class RelayCommand : ICommand
    {
        private Action<object> _action;
        public RelayCommand(Action<object> action)
        {
            _action = action;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }
        public event EventHandler CanExecuteChanged;
        public void Execute(object parameter)
        {
            _action(parameter);
        }
    }

 

App.xaml 

Burda HesaplaViewModel'ini Resource olarak tanımlama işlemini yapıyoruz.

<Application
    x:Class="App1.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1">
    <Application.Resources>
        <local:HesaplaViewModel x:Key="HesaplaViewModel" />
    </Application.Resources>
</Application>

 

MainPage.xaml View'ı

App.xaml de tanımlaış olduğumuz Resource'u DataContext = "{StaticResource HesaplaViewModel}" olarak View'imize verip Binding işlemlerini yapacağız.

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    DataContext="{StaticResource HesaplaViewModel}">

    <Grid>
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <TextBox Grid.Row="0" Text="{Binding İlkSayi,Mode=TwoWay}" PlaceholderText="İlk Sayı" Width="200" HorizontalAlignment="Left"/>
            <TextBox Grid.Row="1" Text="{Binding İkinciSayi,Mode=TwoWay}" PlaceholderText="İkinci Sayı" Width="200" HorizontalAlignment="Left"/>
            <Button Grid.Row="2" Content="Hesapla" Width="200" Command="{Binding HesaplaCommand}" />
            <StackPanel Orientation="Horizontal" Grid.Row="3">
                <TextBlock Text="Sonuç : " FontSize="20"/>
                <TextBlock Text="{Binding Sonuc,Mode=TwoWay}" FontSize="20"/>
            </StackPanel>
        </Grid>
    </Grid>
</Page>

 

OnPropertyChanged() metoduna parametre olarak string bir değer almakta. Bu değer propertychanged anında hangi değerin değiştiğini anlamak için bir nevi ID ye benzer bir string değer veriyoruz ve ilgili property'nin değerinin update olma anında hangi property ise bu string parametrelere bakrak anlayabiliriz. Ama bu parametreyi vermek zorunda da değiliz eğer OnPropertyChanged() metodunu bu şekilde kullanırsak da otomatik olarak proeprty'nin ismini alacaktır.

 

Sonuç olarak ise hesapla butonuna tıklandığında çıktı şu şekilde olacaktır.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Görüldüğü üzre yazımızın başında bahsettiğimiz gibi code behind da tblName.Text="Caner";  gibi bir işlem yapmayıp bunu yerine INotifyPropertyChanged interface'inden faydalanıp işlemlerimizi öyle yaptık. Bu bize ne katar dersek, büyük çaplı projelerde genellikle MVVM pattern'i kullanılır ve INotifyPropertyChanged de bu pattern'in ayrılmaz bir parçasıdır. Bu şekilde yazdığımız kod hem daha "kaliteli"(tırnak içinde) hemde daha yönetilebilir oldu. Yarın bir gün dendi ki aynı işlevi yapan bir WPF app geliştirelim. Bu gibi bir durum için yazmış olduğumuz HesaplaViewModel'ini aynen olduğu gibi tek bir satır bile değiştirmeden referans olarak verilen ilgili proje içinde kullanabilir ve böylece çok büyük bir yazılım maliyetinden de kurtulmuş oluruz.

 

XAML Binding Mode OneWay, TwoWay

Binding, Binding..

Eski den WPF ile uğraşanların oldukça haşır neşir olduğu ve Windows8-WindowsPhone sonrası tamamen hayatımıza girmiş olan XAML ve onun getirdikleri. XAML ile UI geliştirenler Binding'i yakından tanırlar.

Nedir bu Binding diye soracak olursak;

"Databinding(Veri Bağlama) kısaca bir veri kaynağını (Array, Dictionary,Object), bir UI kontrolüne bağlamaya yarayan bir tekniktir."

 

 

Üstteki görselde de görüldüğü üzre Source olarak kullandığımız bir objemiz var ve bu objede ki property'ler de set edilen değerleri XAML tarafta bulunan UI kontrollerine Binding yaparak aktarıyoruz.

 

 Peki ya UI Control için Binding nasıl yapılır dersek aşağıda bulunan görsel nasıl kullanıldığını oldukça iyi özetler gibi.

Yukarıda görüldüğü üzre Source "Article" adında bir class ve içerisinde string tipinde bir "Title" property'si olsun. Bu property'yi TextBox'ın Text'ine Binding olarak set ediyoruz. Böylece şu olmuş oluyor Article objesindeki Title propery'si nin değeri değiştiğinde UI thread'i tetikleyerek Bind olduğu control'ün property'sini de güncelleyecektir.

 

Binding Modları

Binding Modları, binding işlemi sırasında source'ta bulunan verinin değiştiğinde UI'da Bind edildiği yrerin değişip değişmeyeceği veya UI da Bind edilen property'nin içeriği değiştiğinde source da bulunan verinin değişip değişmeyeceğine karar veren özellik diyebiliriz. Bu özellik Binding sınıfının “Mode” property'si ile belirlenir. Bunlar; OneWay, TwoWay, OneTime, OneWayToSource ve Default’ dur.

 

OneWay

Source yani Bind edilen property update edildiği anda UI taraftaki yani target control'de Bind edilen yer de değişir.

 public class Person : INotifyPropertyChanged 
    {
        public string Name { get; set; }
    }

Örneğin bir INotifyPropertyChanged interface'ini implemente etmiş bir Person class'ımız olsun ve bu class'ta bulunan Name alanı UI tarafta bir Textbox'ın Text alanına Bind edilmiş olsun. (INotifyPropertyChanged implementasyonun şu yazıda bulabilirsiniz.)

<TextBox Text="{Binding Name}, Mode=OneWay}"/>

Person objesinde ki Name alanı Codebehind'dan değiştirildiğinde bu değişimden TextBox'ı da haberdar edip Text property'sini de güncelleyecektir.

 

TwoWay

Source yani Bind edilen property update edildiği anda UI da Bind edilen yer de değişir. Aynı şekilde UI'da bulunan control'ün property'si kullanıcı tarafından değiştirildiğinde yani TextBox'a birisi birşeyler yazdığında codeBehind'da Bind edildiği alanıda günceller.

<TextBox Text="{Binding Name}, Mode=TwoWay}"/>

Yukarıda verdiğimiz örnekten devam edelim. Kullanıcı ilgili TextBox'a kendisi birşeyler girip Text'i değiştiğinde bu değişiklikten source'u yani Person objesinde ki Name alanını güncelleyecektir.

 

OneTime

UI tarafında ki control source'dan yani Person objesindeki Name property'sinden sadece 1 defa değerini alır ve ondan sonra ki Name property'sinin güncellenmesi UI tarafı etkilenmez. Ördeğin uygulama açıldıktan sonra Name = "Caner" dedik ve TextBox'ın Text'i de "Caner" oldu ancak bundan sonraki bütün Name="bla bla.." değişikliklerinden TextBox'ın Text'i etkilenmeyecektir.

 

OneWayToSource

OneWay BindingMode'unun tam tersi gibi çalışır. TextBox'ın Text'ine kullanıcı tarafından birşeyler girildiğinde Person objesindeki Name alanını da update eder.Örnek olarak; kullanıcı uygulamada TextBox'ın Text'ine bir şeyler girdiği anda Person class'ında ki Name alanıda otomatik update olur ancak CodeBehind'dan Name alanı değiştirildiğinde bu değişiklik TextBox'ın Text'ine yansımaz.

 

Windows Phone, Windows 8 Converter Kullanımı

Windwos Phone yada Windows 8 app. dev.’da ItemSource ile uğraşıyorsanız emin olun muhakkak uygulamanın bir yerinde Converter’a ihtiyacınız olmuştur yada olacaktır. Peki nedir Converter, Ne işe yarar, Nasıl kullanılır ?

Converter kısaca ; ItemSource olarak verilen Data’ya Binding işleminden önce müdahale etmektir şeklinde tanımlayabiliriz.

Örneğin bir sayfa var ve o sayfada WebService’den gelen bir liste var elimizde ve içerisinde Product objesi ve objeninde ProductName ProductID ve Quantity bilgileri var diyelim. Servisten gelen bu response’u alıp bir WinPhone uygulamasında Binding ile ekrana taşımak istiyoruz. Binding le uğraşan arkadaşlar biliyorlardır buraya kadar hiçbir sorun yok direk olarak Listbox yada başka bir kontrole ItemSource verip gerekli Binding’ leri verdikten sonra ekranda kolayca göreceğizdir.

Peki ya müşteriden şöyle bir istek gelirse ; “Ben Qantity bilgisi sıfır gelen ürünlerin adet satırında sıfır değilde Not Available yazsın istiyorum” derse ne yapacağız ? Gereksiz yere Database'de ilgili tabloda alan açıp o bilgiyide orada saklayacak halimiz yok. İşte Converter bu ve benzeri durumlarda devreye giriyor ve işimizi kolaylaştırıyor. Şimdi yukarıda bahsettiğimiz case'i aşağıda olduğu gibi bir adet ConverterSample adında WinPhone projesi oluşturarak inceleyelim.

 Öncelikle aşağıda olduğu gibi Producta adında bir class tanımlıyoruz.

public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
}

 Sornasında geriye List<Product> dönen GetAllProducts() adında metod yazıyoruz ve örnek ürünleri store ediyoruz.

public List<Product> GetAllProducts()
{
List<Product> _allProducts = new List<Product>();
_allProducts.Add(newProduct()
{
ProductID = 111,
ProductName = "Product 111",
Quantity = 124
});
_allProducts.Add(newProduct()
{
ProductID = 112,
ProductName = "Product 112",
Quantity = 75
});
_allProducts.Add(newProduct()
{
ProductID = 113,
ProductName = "Product 113",
Quantity = 47
});
_allProducts.Add(newProduct()
{
ProductID = 114,
ProductName = "Product 114",
Quantity = 32
});
_allProducts.Add(newProduct()
{
ProductID = 115,
ProductName = "Product 115",
Quantity = 0
});
_allProducts.Add(newProduct()
{
ProductID = 116,
ProductName = "Product 116",
Quantity = 4
});
return _allProducts;
}

Code behind da en son olarak Constructor içerisinde üstte yazdığımız metoddan dönen List’i alıp Xaml tarafta oluşturacağımız ListBox’a ItemSource olarak vereceğiz.

// Constructor
public MainPage()
{
InitializeComponent();
lb_products.ItemsSource = GetAllProducts();
}

Xaml tarafında ise Grid içerisinde Row, ve  ListBox’ı oluşturup gerekli Bind işlemlerini yapıp projemizi çalıştırıyoruz ve aşağıdaki gibi bir görüntü alıyoruz.

    

<!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="Converter Expample" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="Product List" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="50"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="250"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TextBlock
                Grid.Row="0"
                Grid.Column="0"
                Text="ID"
                FontWeight="Bold"/>
            <TextBlock
                Grid.Row="0"
                Grid.Column="1"
                Text="ProductName"
                FontWeight="Bold"/>
            <TextBlock
                Grid.Row="0"
                Grid.Column="2"
                Text="Quantity"
                FontWeight="Bold"/>
            <ListBox
                x:Name="lb_products"
                Margin="0,60,0,0"
                Width="480">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="50"/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="50"/>
                                <ColumnDefinition Width="250"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock
                                Grid.Row="0"
                                Grid.Column="0"
                                FontWeight="Bold"
                                Text="{Binding ProductID}"/>
                            <TextBlock
                                Grid.Row="0"
                                Grid.Column="1"
                                FontWeight="Bold"
                                Text="{Binding ProductName}"/>
                            <TextBlock
                                Grid.Row="0"
                                Grid.Column="2"
                                FontWeight="Bold"
                                Text="{Binding Quantity}"/>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

 

Yujkarıda olduğu gibi listbox'ımıza Itemsource olarak ürünlerimizi verdik ve Quantity bölümündeki bilgileri uygulamamızda gösterdik. ID'si 115 olan ürünün Quantity değeride "0" olarak geldi. Buraya kadar herşey güzel ancak müşteri bizden Quantity bölümünde sıfır olan ürünler için Not Available yazılmasını söyledi ama biz ekrana Service’den gelen datayı yazdırdık.

Converter için ilk olarak Projemize bir adet QuantityConverter adında class ekliyoruz ve class’ımıza IvalueConverter interface’ini uyarlıyoruz ve IvalueConverter Convert ve ConvertBack adında sahip olduğu 2 adet metodu class’ımıza ekleniyor.Biz şimdilik Convert metodunu kullanacağız. ConvertBack’in ne işe yaradığını ilerki yazılarda bahsedeceğiz. Convert metodunun içerisini aşağıda olduğu gibi değiştiriyoruz.

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value == 0 ? "Not Available" : value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}

Şimdi sırada yazmış olduğumuz Converter’ı Binding işlemine entegre etmek. Bunun için xaml tarafına yazmış olduğumuz Converter’ı tanıtmalıyız. İlk olarak xmlns:Converter="clr-namespace:ConverterSample" Converter adını verdiğimiz namespace’i eklemek ve en dıştaki Grid’in üstüne aşağıda olduğu gibi Converter’ı Resource olarak yazmak.

   

 <phone:PhoneApplicationPage.Resources>
        <ResourceDictionary>
            <Converter:QuantityConverter x:Key="QuantityConverter"/>
        </ResourceDictionary>
    </phone:PhoneApplicationPage.Resources>

 

Son işlem olarak Quantity bilgisini Bind ettiğimiz yere QuantityConverter’ı yazmak,

<TextBlock
Grid.Row="0"
Grid.Column="2"
FontWeight="Bold"
           Text="{Binding Quantity,Converter={StaticResource QuantityConverter}}"/>