Silverlight 3.0 ile beraber tarayıcı içerisinde Multitouch desteği de geldi.
Şu an için sadece Windows 7 ve Internet Explorer üzerinde sunulabilen bu
deneyimi yaratmak için WPF tarafından biraz daha farklı tekniklerle ilerlemek
gerekiyor. Bu yazımıda Silverlight 3.0 tarafında Multitouch API'larına göz
attıktan sonra bir Manipulation örneği yapacağız.
Silverlight ve Multitouch
Silverlight içerisinde herhangi bir şekilde gerçekleşen Touch durumunu
algılamak için kullanabileceğimiz tek bir event bulunuyor. Söz konusu event'ın
adı Touch.FrameReported şeklinde. Bu eventa bağlanan bir event
listener'ın argümanı ile beraber gelen bilgiler bizim için fazlası ile yeterli
olacaktır.
[VB]
Public Sub New()
InitializeComponent()
AddHandler Touch.FrameReported, AddressOf Touched
End Sub
Sub Touched(ByVal sender As Object, ByVal e As System.Windows.Input.TouchFrameEventArgs)
End Sub
[C#]
public MainPage()
{
InitializeComponent();
Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
}
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
}
Event'ımızın TouchFrameEventArgs'ında birçok değerli veri bulunuyor.
Örneğin her bir touch için birer Timestamp alabiliyoruz.
Timestamp'i özünde eski OLEAutomationDate'lere benzetebilirsiniz. Size farklı
touch işlemleri arasında süreyi rahatlıkla hesaplayabilmeniz için integer
Timestamp'ler döndürüyor. Bunun haricince argüman tarafında önemli üç adet metod
bulunuyor. Bunlardan ilki GetPrimaryTouchPoint metodu. Touch
işlemi esnasından bir yada daha çok noktadan ekrana dokunulabileceği için ilk
dokunma noktası Primary denerek GetPrimaryTouchPoint aracılığı
ile bize iletiliyor. Ayrıca GetTouchPoints metodu da tüm
dokunulan noktaların bir listesini getirir. TouchPoint tipinde gelen bu
noktalara ait ek bilgileri de TouchPoint sınıfı üzerinden alabilirsiniz. Örneğin
dokunulan noktanın pozisyonu, alanı, hatta TouchDevice üzerinden de unique
ID'sini elde etmek mümkün. Son olarak argüman üzerindeki SuspendMousePromotionUntilTouchUp
metodu de Touch işlemleri bitene yani kullanıcı tüm parmaklarını ekrandan çekene
kadar fare kullanımını engelleyecektir.
Tüm bu hikaye içerisinde en önemli şeylerden biri Touch işleminin hangi
aşamada olduğu. Toplam üç farklı aşama mevcut. Bunlardan ilgi kullanıcının
ekrana değdiği ilk an (Down), bir sonraki kullanıcının parmağını ekranda
gezdirdiği süre (Move), son olarak (Up) kullanıcının parmağını ekranda çektiği
an şeklinde üç farklı aksyon bulunuyor. Bu her aksyon TouchPoint nesnelerinin
Action değişkeninde bir enumaration olarak bizi bekliyor.
Herhangi bir şekilde bize raporlanan TouchPoint'in pozisyonunu alabildiğimiz
gibi o anda ilk dokunma mı, bırakma mı yoksa sürekli dokunma mı oluştuğunu takip
edebiliyoruz. Unutmadan hatırlatmak fayda var, kullanıcı parmağını ekranda
oynatmadan tutarsa da bu bir Move aksyonu olarak algılanıyor.
Şimdi gelin manipülasyon örneğimize geçelim ve tüm bunların birlikte nasıl
kullanıldığına göz atalım. Manipülasyon örneğinde bildiğiniz üzere amacımız
ekrana basit bir resim koyarak onun kullanıcı tarafından iki parmak kullanılarak
boyutlandırılabilmesini, taşınabilmesini ve çevrilebilmesini sağlamaktır. WPF
tarafından farklı olarak daha Silverlight için etrafta hazırlanmış bir
ManipulationProcessor bulunmadığı için tüm işlemleri bizim yapmamız gerekecek.
İlk aşamada gelin uygulamamızın ekranını hazırlayarak konuya girelim.
[XAML]
<Grid x:Name="LayoutRoot">
<Image Source="Koala.jpg" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="ImageScale" />
<TranslateTransform x:Name="ImageTranslate" />
<RotateTransform x:Name="ImageRotate" />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
Uygulama ekranımızda basit bir Image nesnesi bulunuyor. Bu Image nesnesinin
durumda göre pozisyonunu, dönüş açısını ve boyutunu değiştireceğimiz için uygun
Transform nesnelerini de içerisinde yerleştirerek gerekli isimlendirmeleri de
yaptık. Böylece rahatlıkla kod tarafından bu işlemleri halledebiliriz. Şimdi
sıra geldi Touch ile ilgili gerekli işlemleri arka tarafta yapmaya.
Hemen makalemizin başında da gördüğümüz üzere FrameReported eventına bir
event-listener bağlıyoruz.
[VB]
Public Sub New()
InitializeComponent()
AddHandler Touch.FrameReported, AddressOf Touched
End Sub
[C#]
public MainPage()
{
InitializeComponent();
Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
}
Amacımız FrameReported içerisinde ilk olarak en az iki tane TouchPoint
olurkenki durumları yakalamak. Kullanıcının resmi tutup manipulasyon yapabilmesi
için en az iki parmağının ekrana dokunuyor olması gerek. Ayrıca Action olarak da
parmaklarını ilk dokundurduğu anda değil parmaklarını sürüklerken işlem yapmamız
şart. Tüm bu süreçleri kontrol ederken sürekli olarak parmakların bir önceki
durumu ile şu anki durumu arasındaki farklara göre gerekli hesaplmaları yaparak
resmimize yansıtacağız. Aslında tüm bu sürece gelişmiş bir drag&drop gözü ile
bakabilirsiniz.
[VB]
Dim _FirstTouch As Point = New Point(0, 0)
Dim _SecondTouch As Point = New Point(0, 0)
Uygulamamızda ilk olarak yukarıdaki şekli ile iki tane Point değişkenini
global olarak tanımlıyoruz. Bu değişkenler sürekli olarak kullanıcının
parmaklarının bir önceki koordinatlarını saklayacak. Böylece biz de o anki
koordinatlar ile bir önceki arasında farkları yakalayabileceğiz.
[VB]
Sub Touched(ByVal sender As Object, ByVal e As System.Windows.Input.TouchFrameEventArgs)
If Not e.GetPrimaryTouchPoint(Me) Is Nothing Then
Dim IlkDokunus As TouchPoint
Dim IkinciDokunus As TouchPoint
IlkDokunus = e.GetPrimaryTouchPoint(Me)
If IlkDokunus.Action = TouchAction.Down Then
_FirstTouch = New Point(0, 0)
_SecondTouch = New Point(0, 0)
ElseIf IlkDokunus.Action = TouchAction.Move Then
If e.GetTouchPoints(Me).Count > 1 Then
IkinciDokunus = e.GetTouchPoints(Me)(1)
''BURADA MANIPULATION YAPILACAK
End If
ElseIf IlkDokunus.Action = TouchAction.Up Then
_FirstTouch = New Point(0, 0)
_SecondTouch = New Point(0, 0)
End If
End If
End Sub
[C#]
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
if ((e.GetPrimaryTouchPoint(this) != null))
{
TouchPoint IlkDokunus = default(TouchPoint);
TouchPoint IkinciDokunus = default(TouchPoint);
IlkDokunus = e.GetPrimaryTouchPoint(this);
if (IlkDokunus.Action == TouchAction.Down)
{
_FirstTouch = new Point(0, 0);
_SecondTouch = new Point(0, 0);
}
else if (IlkDokunus.Action == TouchAction.Move)
{
if (e.GetTouchPoints(this).Count > 1)
{
IkinciDokunus = e.GetTouchPoints(this)[1];
//BURADA MANIPULATION YAPILACAK
}
}
else if (IlkDokunus.Action == TouchAction.Up)
{
_FirstTouch = new Point(0, 0);
_SecondTouch = new Point(0, 0);
}
}
}
Yukarıdaki ilk kodumuz karışık gibi gözükse de aslında basit birkaç koşul
kontrolünden farklı değil. İlk olarak GetPrimaryTouchPoint ile
hali hazırda ilk TouchPoint var mı yok mu kontrolünü gerçekleştiriyoruz. Eğer
varsa ikinci amacımız söz konusu Touch işleminin Action'una göre işlem yapmak.
Eğer ilk TouchPoint'imize ait Action Up veya Down ise yani kullanıcı parmağını
ilk defa dokunduruyor veya çekiyorsa hemen global değişkenlerimizi sıfırlıyoruz.
Böylece bir sonraki Touch ile sürükleme işleminde gerekli kontrolleri yaparak
işlemleri sıfırdan başlatabiliriz. Fakat unutmayın ki daha bu sadece ilk
TouchPoint yani kullanıcının ilk parmağı! Belki de hiç ikinci bir parmak
değimedi ekrana. Böyle bir durumda manipülasyon yapamayacağımız için ikinci
parmak var mı diye kontrol etmemiz şart.
Eğer ilk TouchPoint'in aksyonu Move ise bu sefer hemen
GetTouchPoints ile toplam parmak sayısını alıyoruz. Eğer bu
sayı birden yüksekse belli ki ekranda iki parmak var. Her iki parmağa ait
TouchPoint'lerini ayrı birer değişkene aldıktan sonra sıra gelecek bu
parmakların koordinatlarına göre hesaplamaları yapmaya.
[VB]
Dim FirstTouch As Point = IlkDokunus.Position
Dim SecondTouch As Point = IkinciDokunus.Position
If _FirstTouch.X <> 0 Then
''ESAS OLAY BURADA :)
End If
_FirstTouch = FirstTouch
_SecondTouch = SecondTouch
[C#]
Point FirstTouch = IlkDokunus.Position;
Point SecondTouch = IkinciDokunus.Position;
if (_FirstTouch.X != 0)
{
//ESAS OLAY BURADA :)
}
_FirstTouch = FirstTouch;
_SecondTouch = SecondTouch;
Ele aldığımız TuchPoint'lerden Position alarak birer değişkene aktarıyoruz.
Sonrasında tabi ki bu pozisyonları bir önceki pozisyonlar ile
karşılaştıracağımız için aslında "bir önceki pozisyon" diye birşey var mı onu
kontrol etmemiz gerekiyor. Eğer varsa gerekli işlemleri yapacağız yoksa eldeki
pozisyonu kenara atacağız ki bir sonraki işlemde bu pozisyona göre
değişiklikleri hesaplayabilelim.
[VB]
Dim ScaleDelta = (((((FirstTouch.X - SecondTouch.X) ^ 2) + ((FirstTouch.Y - SecondTouch.Y) ^ 2)) ^ (1 / 2)) / _
((((_FirstTouch.X - _SecondTouch.X) ^ 2) + ((_FirstTouch.Y - _SecondTouch.Y) ^ 2)) ^ (1 / 2))) - 1
[C#]
double ScaleDelta = (Math.Sqrt(Math.Pow(FirstTouch.X - SecondTouch.X, 2) + Math.Pow(FirstTouch.Y - SecondTouch.Y, 2)) /
Math.Sqrt(Math.Pow(_FirstTouch.X - _SecondTouch.X, 2) + Math.Pow(_FirstTouch.Y - _SecondTouch.Y, 2))) - 1;
İlk olarak boyutlandırma işlemi ile başlayalım. Resmimizin boyutunun ne kadar
değişeceğini 1 üzerinden orantılayarak vermemiz gerekiyor ki ScaleTransform'un
ScaleX ve ScaleY'sine aktarabilelim. Gelen ScaleDelta'yı sonrasında bu ScaleX ve
ScaleY'ye ekleyeceğiz o nedenle bulduğumuz sonucu 1'den çıkartıyoruz ki normal
boyuta göre farkı bulalım.
Resmin boyut değişikliği ile ilgili hesaplamayı yaparken izlediğimiz yol her
iki parmağın bir önceki pozisyonlarına göre aralarındaki mesafeyi bulup sonra da
şu an pozisyonlara göre mesafeleri arasında oranı bir üzerinden hesaplamak.
Basit bir hipotenüs hesaplaması gözü ile bakarsak elimizdeki iki noktadan bir
üçgen oluşturup hipotenüsü bulmamız mesafe için yeterli olacaktır. Üçgenin yatay
kenarı ve dikey kenarının uzunlukları için noktaların X ve Y koordinatları
arasındaki farkları kullanabiliyoruz.
[VB]
Dim PositionPoint = New Point(((FirstTouch.X + SecondTouch.X) / 2) - ((_FirstTouch.X + _SecondTouch.X) / 2), _
((FirstTouch.Y + SecondTouch.Y) / 2) - ((_FirstTouch.Y + _SecondTouch.Y) / 2))
[C#]
var PositionPoint = new Point(((FirstTouch.X + SecondTouch.X) / 2) - ((_FirstTouch.X + _SecondTouch.X) / 2),
((FirstTouch.Y + SecondTouch.Y) / 2) - ((_FirstTouch.Y + _SecondTouch.Y) / 2));
Resmin pozisyonu ile ilgili değişikliği hesaplamak biraz daha kolay. İki
parmak arasındaki doğrunun orta noktasını bularak bir önceki orta nokta ile
şimdiki orta nokta arasındaki farkı almak pozisyon değişikliğini yakalamak için
yeterli olacaktır.
[VB]
Dim AngleDelta = (Math.Atan2(FirstTouch.Y - SecondTouch.Y, FirstTouch.X - SecondTouch.X) * 180 / Math.PI) - _
(Math.Atan2(_FirstTouch.Y - _SecondTouch.Y, _FirstTouch.X - _SecondTouch.X) * 180 / Math.PI)
[C#]
var AngleDelta = (Math.Atan2(FirstTouch.Y - SecondTouch.Y, FirstTouch.X - SecondTouch.X) * 180 / Math.PI) -
(Math.Atan2(_FirstTouch.Y - _SecondTouch.Y, _FirstTouch.X - _SecondTouch.X) * 180 / Math.PI);
İki keranın bildiğiniz bir üçgenin iç açılarından birini nasıl bulursunuz? :)
Bazılarınızı yıllar önceki lise yıllarına döndürdüğümün farkındayım. Dikey
mesafe (Y) ve yatay mesafeyi (X) verip bir noktanın x eksenine göre (0,0)'dan
açısını radyan olarak veren Math.Atan2 metodunu kullanarak
parmaklarımızla oluşturduğumuz çizginin orta noktasının (0,0)'a göre x
ekseninden açısını alabiliyoruz. Tabi radyanı da bildiğimiz açıya çevirmek için
180'le çarpıp PI'ye bölüyoruz. Eski pozsiyonlara göre hesapladığımız açı ile
şimdiki açı arasındaki fark da tam olarak bulmak istediğimiz şeydi.
[VB]
ImageScale.ScaleX += ScaleDelta
ImageScale.ScaleY += ScaleDelta
If ImageScale.ScaleX < 0 Then ImageScale.ScaleX = 0
If ImageScale.ScaleY < 0 Then ImageScale.ScaleY = 0
ImageTranslate.X += PositionPoint.X
ImageTranslate.Y += PositionPoint.Y
ImageRotate.Angle += AngleDelta
[C#]
ImageScale.ScaleX += ScaleDelta;
ImageScale.ScaleY += ScaleDelta;
if (ImageScale.ScaleX < 0) ImageScale.ScaleX = 0;
if (ImageScale.ScaleY < 0) ImageScale.ScaleY = 0;
ImageTranslate.X += PositionPoint.X;
ImageTranslate.Y += PositionPoint.Y;
ImageRotate.Angle += AngleDelta;
Sıra geldi tüm bu hesaplamalarla bulduğumuz değerleri Image nesnesminde
Transform'lara aktarmaya. Sadece Scale için dikkat etmemiz gereken şey eksi
değer vermemek. Aksi halde resim ters dönecektir.
Kodumuz bu kadar. Manipülasyon işlemimizi de tamamladık ve artık projemiz
çalışmaya hazır.
Hepinize kolay gelsin.
Daron Yöndem
Örneklere ait kaynak kodlar