Makale Özeti

Silverlight 3 ile beraber tarayıcı içerisinde Multitouch programlama yapabiliyoruz. Bu yazımızda hızlı bir giriş ile beraber kendi Manipulasyon imteplementasyonumuzu da yapacağız.

Makale

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.