Silverlight 3 ile Multitouch programlama.

0 dakikada yazıldı

9752 defa okundu

Düzenle

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.

Örneklere ait kaynak kodlar. - 14082009_1.rar (73,14
KB)