Yazıda Xamarin Forms içerisinde tekrar kullanılabilir arayüz nesnelerini (UserControl) nasıl oluşturabileceğimize bir örnek anlatacağım. Bunu yapabiliyor olmak için öncelikle bir miktar bilgi sahibi olmamız gereken iki konu var ilki MVVM ( bu yazıları okuyabilirsiniz) ve diğeri BindablePropertyler.
Projemizde şöyle bir amacımız olsun :
ViewModel içerisindeki bir dizide iki parametre alan toplama, çıkarma gibi işlemler tanımlanmış olsun. Kullanıcı arayüzünde bu dizideki her işlem tanımı için her parametreye denk düşen iki adet metin kutusu ve bir hesapla düğmesi oluşturulsun. Hesapla düğmesi sonucu bir ileti kutusu ile kullanıcıya göstersin.
Bitmiş proje şuna benzeyecek:
Buradaki amacım her işlem için tekrar tekrar metin kutularını, etiketler, düğmeler oluşturmamak ve bu işlemlerin tanımlı olması. Kim bilir belki bu işlemleri uygulama sırasında download edeceğim ve ekranda öyle göstereceğim. Kaç adet göstereceğimi bilmediğim için böylesi daha faydalı olacak.
NOT : Bu örneği tabi ki DataTemplate yaklaşımı ile de çözebilirsiniz. Fakat kullanıcı kontrolleri oluşturduğunuzda daha sonra bunları paketleyip diğer projelerinizde kullanmanız ya da bunları diğer programcılara dağıtmak isterseniz daha rahat olacaktır. .
Boş bir proje açıp önce MVVM yapımızı aşağıdaki gibi kuralım :
Dosyalar :
.
│ App.xaml
│ App.xaml.cs
│ App1.csproj
│ packages.config
│ tree.txt
│
├───Properties
│ AssemblyInfo.cs
│
├───UserControls
│
├───Models
│
├───ViewModels
│ MainPageViewModel.cs
│
└───Views
MainPage.xaml
MainPage.xaml.cs
Views/MainPage.xaml
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:App1.ViewModels;assembly=App1"
BindingContext="{DynamicResource ViewModel}"
x:Class="App1.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<viewModels:MainPageViewModel x:Key="ViewModel" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout></StackLayout>
</ContentPage>
ViewModels/MainPageViewModel.cs
:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace App1.ViewModels
{
public sealed class MainPageViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Öncelikle Models klasörümüze matematik işlemini tanıtmamız için kullanacağımız Islem.cs
dosyasını ekleyelim içeriği şöyle olacak :
using System;
namespace App1.Models
{
public sealed class Islem
{
public Islem(string islemOperatoru, Func<double, double, double> fonksiyon)
{
IslemOperatoru = islemOperatoru;
Fonksiyon = fonksiyon;
}
public Func<double, double, double> Fonksiyon { get; }
public string IslemOperatoru { get; }
}
}
ViewModel mize işlemlerimizi ekleyebiliriz (konu dışı kısımları kırptım) :
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public Islem[] Islemler { get; } = {
new Islem(islemOperatoru: "+" , fonksiyon: (x,y)=> x+y),
new Islem(islemOperatoru: "*" , fonksiyon: (x,y)=> x*y),
new Islem(islemOperatoru: "/" , fonksiyon: (x,y)=> x/y),
new Islem(islemOperatoru: "-" , fonksiyon: (x,y)=> x-y),
new Islem(islemOperatoru: "+%" , fonksiyon: (x,y)=> x * (1 + 100 / y)),
};
... INotifyPropertyChanged
}
Kullanıcı kontrolümüzü oluşturmak için UserControls klasörümüze sağ tıklıyoruz (klasörü açmayı unuttuysanız oluşturmak için şimdi tam sırası) Add
🡺 New Item
🡺 Cross-Platform / Forms Xaml View
seçip adını IslemBox
koyuyoruz. Bizim için IslemBox.xaml
ve IslemBox.xaml.cs
dosyaları oluşturulacak. Öncelikle tasarım dosyamız ile başlayalım:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App1.UserControls.IslemBox">
<ContentView.Content>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Entry Grid.Column="0" Text="{Binding ParametreSol}" />
<Label Grid.Column="1" Text="{Binding Islem.IslemOperatoru}" />
<Entry Grid.Column="2" Text="{Binding ParametreSag}" />
<Button Grid.Column="3" Text="Hesapla" Command="{Binding IslemCommand}" />
</Grid>
</ContentView.Content>
</ContentView>
Burada ne yaptık? Öncelikle bir Grid oluşturup ona alışıldık LayoutRoot adını verdik. Daha sonra arayüzümüzün parçası olan nesneleri oluşturduk ve herbirine birazdan CodeBehind tarafından bağlayacağımız BindingContext için bağlamalar yaptık.
Gelelim CodeBehind dosyamıza yani IslemBox.xaml.cs
e :
using System;
using App1.Models;
using Xamarin.Forms;
namespace App1.UserControls
{
public partial class IslemBox : ContentView
{
public static readonly BindableProperty IslemProperty = BindableProperty.Create("Islem", typeof(Islem),
typeof(IslemBox));
private string _parametreSag;
private string _parametreSol;
public IslemBox()
{
IslemCommand = new Command(() =>
{
var sonuc = Islem.Fonksiyon(double.Parse(ParametreSol),
double.Parse(ParametreSag));
OnSouncHesaplandi(new SonucEventArgs(sonuc));
}, () => SayiMi(ParametreSol) && SayiMi(ParametreSag));
InitializeComponent();
LayoutRoot.BindingContext = this;
}
public Islem Islem
{
get { return (Islem) GetValue(IslemProperty); }
set { SetValue(IslemProperty, value); }
}
public Command IslemCommand { get; }
public string ParametreSag
{
get { return _parametreSag; }
set
{
if (!SayiMi(value))
{
return;
}
_parametreSag = value;
OnPropertyChanged();
IslemCommand.ChangeCanExecute();
}
}
public string ParametreSol
{
get { return _parametreSol; }
set
{
if (!SayiMi(value))
{
return;
}
_parametreSol = value;
OnPropertyChanged();
IslemCommand.ChangeCanExecute();
}
}
public event EventHandler<SonucEventArgs> SonucHesaplandi;
protected virtual void OnSouncHesaplandi(SonucEventArgs e)
{
SonucHesaplandi?.Invoke(this, e);
}
private static bool SayiMi(string yazi)
{
if (string.IsNullOrWhiteSpace(yazi))
{
return false;
}
double sayi;
return double.TryParse(yazi, out sayi);
}
public sealed class SonucEventArgs : EventArgs
{
public SonucEventArgs(double sonuc)
{
Sonuc = sonuc;
}
public double Sonuc { get; }
}
}
}
Üsteki kodumuz biraz uzunca… Önemli kısımlarını açıklayayım.
public static readonly BindableProperty IslemProperty = BindableProperty.Create("Islem", typeof(Islem),
typeof(IslemBox));
//...
public Islem Islem
{
get { return (Islem) GetValue(IslemProperty); }
set { SetValue(IslemProperty, value); }
}
Dışarıdan kullanıcı kontrolüme bağlanabilir olan bir BindableProperty oluşturuyoruz.
public IslemBox()
{
IslemCommand = new Command(() =>
{
var sonuc = Islem.Fonksiyon(double.Parse(ParametreSol),
double.Parse(ParametreSag));
OnSouncHesaplandi(new SonucEventArgs(sonuc));
}, () => SayiMi(ParametreSol) && SayiMi(ParametreSag));
InitializeComponent();
LayoutRoot.BindingContext = this;
}
Kullanıcı kontrolümüzün yapıcı (constructor) metodunda 2 ana iş yapıyoruz. İlki Düğmeye basıldığında yapılacak işi tanımlıyoruz. new Command
ın ilk parametresi düğmeye tıklanıldığında kullanıcı kontrolümüze bind edilmiş Islem özelliğinde tanımlı fonksiyona ekrandaki iki değeri verip. Daha sonra tanımlayacağımız işlemin yapıldığını dış dünyaya ileten olayımız (event) tetikleniyor. İkinci parametre ise hangi şartlarda bu düğmeye tıklanılabilir olduğunu belirtiyor biz sol ve sağ parametreler birer sayı ise bu işi yapacağız. Yaptığımız ikinci iş ise LayoutRoota bir BindingContext atamak.
Peki BindingContext’i kontrolümüze değilde neden içindeki başka bir kontrole atadık? Çünkü bir UserControl arayüzde yer edinirse arayüz tarafında kendisini kapsayan kontrolün BindingContext’ini otomatik olarak alır. Bu durumda bizim atayacağımız BindingContext ezilmiş olacaktı.
Sayısallık kontrolünü yaptığımız methodumuz:
private static bool SayiMi(string yazi)
{
if (string.IsNullOrWhiteSpace(yazi))
{
return false;
}
double sayi;
return double.TryParse(yazi, out sayi);
}
burada basitçe önce metin dolu mu diye kontrol ediyoruz ardından bunu double türüne parse etmeye çalışıyoruz.
Bir de sonuçlar hesaplanınca fırlattığımız olayımız var :
public event EventHandler<SonucEventArgs> SonucHesaplandi;
protected virtual void OnSouncHesaplandi(SonucEventArgs e)
{
SonucHesaplandi?.Invoke(this, e);
}
//...
public sealed class SonucEventArgs : EventArgs
{
public SonucEventArgs(double sonuc)
{
Sonuc = sonuc;
}
public double Sonuc { get; }
}
Burada tipik C# event tanımı ve bu event’in dinleyicisine ileteceğimiz bilgiyi taşıyacak olan EventArgsımızın tanımı bulunuyor.
Şimdi bu kontrolümüzü arayüzümüze (MainWindow.xaml
) koyalım :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:App1.ViewModels;assembly=App1"
xmlns:userControls="clr-namespace:App1.UserControls;assembly=App1"
BindingContext="{DynamicResource ViewModel}"
x:Class="App1.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<viewModels:MainPageViewModel x:Key="ViewModel" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<ListView ItemsSource="{Binding Islemler}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<userControls:IslemBox Islem="{Binding}" SonucHesaplandi="IslemBox_SonucHesaplandi" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
Tipik bir ListView kontrolü koyduk. Kendisine daha önce oluşturduğumuz ViewModeldeki Islemler dizisini kaynak olarak verdik.
<userControls:IslemBox Islem="{Binding}" SonucHesaplandi="IslemBox_SonucHesaplandi" />
Satırı ile her bir işlem için bir IslemBox oluşturmasını ve bu kutunun Islem özelliğini döngüdeki islem nesnesine bağladık. Ve nesnenin sonucu hesaplandığında çalışacak eventi dinleyecek bir metot bağlantısı yaptık.
İlgili method MainWindow.xaml.cs de şöyle tanımlandı :
private void IslemBox_SonucHesaplandi(object sender, IslemBox.SonucEventArgs e)
{
DisplayAlert("Sonuç", e.Sonuc.ToString(), "Kapat");
}
Başka örneklerle görüşmek üzere.
Yorum Gönder