Bugün sizlerle yeni bir açık kaynak kodlu Silverlight 2.0
uygulamamı paylaşacağım. Hatırlarsanız bundan aylar önce
Silverlight 1.0 ile bir Twitter Widget hazırlamış ve sizlerle kaynak
kodlarını paylaşmıştım. Şimdi de 2.0 ile yeni bir Widget hazırladım. Bu sefer
Widget'ın TwitXR desteği
gibi ilginç özellikleri var
Bir Widget hikayesi...
Silverlight 2.0 ile geliştirme yapmanın 1.0'a kıyasla çok sayıda avantajı var
fakat unutmayalım ki artık .NET tarafındayız ve JavaScript ile olduğu kadar low
level kod yazmıyoruz. Ne demek istediğimi makale boyunca daha net
anlayacağınızdan eminim. Peki size neler anlatacağım?
Uygulamanın içindeki kodların açıklamalarını zaten kodlar içerisindeki yorum
satırlarında bulabilirsiniz. Yorum satırlarını İngilizce yazdım çünkü uygulamayı
yurt dışı ile de paylaşacağım. Önemli olan ve benim özellikle değinmek istediğim
noktalar bir Widget hazırlarken karşılaştığım duruma özel sorunlarla ilgili.
Gelin daha fazla konuşmak yerine sorunlara el atalım.
Twitter API saçmalığı!
Twitter'ın çok güzel bir XML API yapısı var. Sorunlardan ilki bu API için
ister Flash ister Silverlight hiç fark etmez ClientAccessPolicy
dosyası konmamış, yani Cross-Domain-Request yasak! Durum böyle olunca sunucu
tarafında bir proxy oluşturarak veriyi kendi sunucunuzaalıp kendi Widget'ınıza
aktarmanız gerekiyor, ama bunu da yapamıyoruz çünkü şöyle bir saçmalık mevcut;
istemci başına bir saatte 70 request sınırı var. Eeee? Tüm requestleri benim Web
Server'ım yapacak sonuçta tüm ziyaretçiler için, 70 kesinlikle kabul edilebilir
bir sınır değil. Büyük ihtimal ile Windows uygulamaları falan düşünülmüş.
Sonuç olarak Silverlight 1.0 Widget'da kullandığımız Remote Script
Injection ile Cross-Domain-Reqest taktiğini Silverlight 2.0 da da
kullanmamız gerek. Dinamik olarak sayfaya bir script tagı
ekleyip twitter sitesinden JavaScript alıp sitemizde çalıştırıyorduk. Parametre
olarak da bizim istediğimiz veriler geliyordu. İşte sorular;
Silverlight 2.0 ile dinamik Script tagları
sayfaya nasıl eklenir?
'Insert our dynamic Script Tag to get Cross Domain Data
Dim MyDoc As HtmlDocument = HtmlPage.Document
Dim ScriptTag = MyDoc.CreateElement("script")
ScriptTag.SetAttribute("type", "text/javascript")
ScriptTag.SetAttribute("src", "http://twitter.com/statuses/user_timeline/" & InitParams("twitterid") & ".json?callback=TwitterIncData&count=" & InitParams("count"))
MyDoc.Body.AppendChild(ScriptTag)
Yukarıdaki kod ile rahatlıkla dinamik olarak bir Script tagı
yaratıp özelliklerini de ayarlayıp içerisinde bulunduğumuz sayfanın
Body'sine ekleyebiliyoruz. Böylece uzaktaki dosya çağrılacaktır.
Twitter adresinin içindeki parametrelerin konumuzla şimdilik alakası yok. Ama bu
adres üzerinden gönderdiğimiz callback parametresi Twitter'dan
data gelince sayfamızda çalıştırılacak olan JavaScript'in ta kendisi. Peki bu
durumda data gelince çağrılan JavaScript'ten bizim Silverlight 2.0'ın nasıl
haberi olacak?
JavaScript'ten Silverlight 2.0 fonksiyonları nasıl çağrılır?
Buyurun makalemi okuyun :
http://daron.yondem.com/tr/PermaLink.aspx?guid=a1426eb0-7120-4a66-9d5c-de5027fd59ed
Şimdi veri geldi elimize ama gelen veri JSON! Bunu nasıl olacak da
anlaşılabilir bir hale çevireceğiz ve .NET nesneleri şeklinde kullanabileceğiz?
JSON verisi Silverlight 2.0'a nasıl alınır?
Buyurun bir makale daha:
http://daron.yondem.com/tr/PermaLink.aspx?guid=457fbb28-892e-4a37-b7d3-cb297d97020b
Ama ben yukarıdaki makalede anlatılanı yapmadım, JSON'dan çekmek istediğim
veri belli olduğu için ve twitter'ın JSON formatı basit olduğu için doğrudan
aşağıdaki kod ile gelen her JavaScript nesnesine ScriptObject muamelesi yaptım.
For x As Integer = 0 To CInt(InitParams("count")) - 1
AllData.Add(New TwitPost(JSON.GetProperty(x).GetProperty("text").ToString, JSON.GetProperty(x).GetProperty("created_at").ToString, JSON.GetProperty(x).GetProperty("id").ToString))
Next
Yukarıda JSON değişkeninin içerisinde doğrudan JavaScript tarafından gelen
JSON objesi bulunuyor. GetProperty metodu ile herhangi bir
diziden istediğimiz öğeyi ve özelliklerini tek tek alabiliyorum.
Bir sonraki sorunumuz hangi Twitter hesabından veri çekeceğimiz Widget'ı
kullananların nasıl karar verebileceği. Bunun için Silverlight 2.0'ın sayfaya
yerleştirildiği Object taglarının parametrelerini kullanacağız.
Parametreli Silverlight 2.0 dosyaları kullanımı nasıl olur?
Buyurun size bir makale :
http://daron.yondem.com/tr/PermaLink.aspx?guid=4834596e-b5ec-450f-8e3c-cfba929d958e
Twitter'dan data alma işleminin Widget'ın bulunduğu sayfa tamamen
yüklendikten sonra başlasın istiyoruz. Bun durum data yüklendikten sonra DOM'a
ulaşıyor olmamızdan kaynaklanıyor, eğer sayfa tam yüklenmemişse daha DOM
listeleri sabitlenmemiş olabilir.
HTML / DOM eventlarını Silverlight 2.0 ile nasıl yakalarım?
Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Dim MyDoc As HtmlDocument = HtmlPage.Document
HtmlPage.Window.AttachEvent("onload", AddressOf DocLoaded)
End Sub
Private Sub DocLoaded(ByVal sender As Object, ByVal e As EventArgs)
End Sub
Yukarıdaki kod ile yaratmış olduğunuz bir event-handlerı nasıl sayfanın
eventların veya bulduğunuz herhangi bir HTML nesnesinin eventlarına
ekleyebileceğinizi görebilirsiniz. Kullanım şekli epey basit.
Bir sonraki adımda bir TextBlock içerisine farklı formatlarda yazı eklemek
istiyoruz. Bunda zor ne var? diyebilirsiniz :) Ama bir TextBlock'un Text
özelliğine baktığınızda String tipinde olduğunu görüyorsunuz. Peki buna nasıl
bir formatlama bilgisi aktarabiliriz ki? Aktaramayız :) Aslında TextBlock'un bir
de Inlines diye bir dizisi var. Bunun içerisinde satır içi
Item'lar saklanıyor. Eğer Inlines ile ilgili bir ayarlama
yapılmamışsa ve doğrudan Text özelliği set edilmiş ise
arkaplanda otomatik olarak bir Inline Item yaratılarak bu
diziye ekleniyor. Tüm bunlar programatik olarak da yapılabilir.
Bir TextBlock'un içindeki belirli bir metnin
rengi programatik olarak nasıl değiştirilir?
Dim Span As New Documents.Run
Span.Text = AllText.Substring(0, AllText.IndexOf(FoundURLs.Item(0).Value))
Span.Foreground = CType(App.Current.Resources("PostTextForeGround"), SolidColorBrush)
Current.Inlines.Item(0) = Span
Span = New Documents.Run
Span.Text = FoundURLs.Item(0).Value
Span.Foreground = CType(App.Current.Resources("PostTextLinkForeGround"), SolidColorBrush)
Span.TextDecorations = TextDecorations.Underline
Current.Inlines.Add(Span)
Span = New Documents.Run
Span.Text = AllText.Substring(AllText.IndexOf(FoundURLs.Item(0).Value) + FoundURLs.Item(0).Value.Length, AllText.Length - (AllText.IndexOf(FoundURLs.Item(0).Value) + FoundURLs.Item(0).Value.Length))
Span.Foreground = CType(App.Current.Resources("PostTextForeGround"), SolidColorBrush)
Current.Inlines.Add(Span)
Yukarıdaki kod içerisinde Current değişkeni aslında bir
TextBlock. Bu TextBlock içerisinde URL'i alıp URL'den öncesini
ayrı bir Run olarak, URL'i ayrı bir Run
olarak, kalanı da ayrı bir Run olarak TextBlock'un Inlines
dizisine ekliyoruz. Böylece her bir Run için görsel olarak
farklı ayarlar yapabiliyoruz.
Yazımın en başında TwitXR desteği derken ne demek istediğimi biraz anlatıyım.
Twitter ile TwitXR beraber çalışan sitelerdir aslında. TwitXR üzerine
yolladığınız bir resim ve yazı otomatik olarak Twitter'a da aktarılır. Bizim
Widget Twitter'dan Post'ları alırken kontrol edecek eğer o Post TwitXR'dan
gelmişse uygun fotoğrafı da bularak gösterecek. Tüm bu hikayede benim istediğim
nokta TwitXR'dan alınan resim istemciye yüklenirken yüklemenin durumundan
haberdar olmaktı. Yani resmi istedim ama tam olarak yüklendi mi yoksa
indiriliyor mu?
Remote Image yüklerken Progress göstermek!
Başından beridir Remote-Request'e izin verilmiyor ve Policy dosyaları yok
diyoruz! Bu durumda benim normal bir WebClient sınıfı ile resmi indirmem ve
sonra gelen Stream'i Image nesnesine bağlayıp sahneye koyma şansım yok. Mecburen
Image'ı doğrudan Image nesnesine bağlamak zorundayım, ancak bu şekilde remote
resim alabiliyorum. Ama bunu yaparken de zaten Imaging.BitmapImage
sınıfını kullanmak zorundayım ve bu sınıfın kendine özel bir DownloadProgress'i
var :)
WithEvents
PhotoDownload As New Imaging.BitmapImage
'The Photo for the PhotoFrame has been downloaded.
Private Sub PhotoDownload_DownloadProgress(ByVal sender As Object, ByVal e As System.Windows.Media.Imaging.DownloadProgressEventArgs) Handles PhotoDownload.DownloadProgress
If e.Progress = 100 Then
GetBig(MousePos.Y)
End If
End Sub
'Play the anim when the mouse is on
Private Sub Clickable_MouseEnter(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseEventArgs)
PhotoDownload.UriSource = New Uri(MyPhoto, UriKind.Absolute)
Photo.Source = PhotoDownload
End Sub
Yukarıdaki kod içerisinde PhotoDownload adındaki
BitmapImage'e fotoğrafın URL'ini veriyorum ve sonra da Photo adında
Image UIElement nesneme Source olarak
veriyorum. Böylece Silverlight resmi BitmapImage
ile indirerek Image nesnesine bağlayıp göstermeye çalışıyor. Bu esnada
BitmapImage'ın DownloadProgress'i çalışıyor. İlginç
bir şekilde bu Progress event'ı içerisindeki Progress özelliği 1 ile 0 arasında
olması gerekirken ya 0 ya da 100 döndürüyor. Normalde 0 = indirilemedi ve 1 =
indirildi anlamına gelmeliydi. Sanırım bu bir BUG :) Her neyse bir işimizi
şimdilik halletik. Unutmayın buradaki Progress WebClient'taki biri Double değil,
Integer. Yani aslında bir Progress değil de Status
değeri veriyor.
Şeffaf uygulama ve Overlay Sorunu
Aslında uygulama fonunu şeffaf yapmak çok kolay. Buyurun makalesi :
http://daron.yondem.com/tr/PermaLink.aspx?guid=b334e195-feb7-4411-a77d-b6f07d482068
Esas sorun ben şeffaf olan yerlerin sadece şeffaf gözükmesini değil aynı anda
o şeffaflığın arkasındaki HTML'in de kullanılabilir olmasını istiyorum. İşte bu
olmuyor! Hedefim Widget içerisinde herhangi bir Post'un fare ile üzerine gelince
yana doğru sayfanın üzerine taşacak şekilde uygulamanın genişlemesi ve orada da
postun fotosunun gözükmesi. Tabi tüm uygulama gelişmeyecek sadece fotoğrafın
gözükeceği bir kısım açılacak. Şeffaf fon kullanıldığında görsel olarak bir
sorun yok gibi gözükse de bir bakıyorsunuz ki şeffaf olmasına rağmen uygulama
alanınız fotoğrafın gösterileceği yerleri de kapsadığı yani sayfaya taştığı için
o kısımlardaki HTML kontrolleri çalışmıyor. Bu konu maalesef Flash'ta daha iyi
çözülmüş durumda, Flash'ta şeffaf olan yerler gerçekten şeffaf :(
Peki nasıl çözeriz, dinamik olarak Silverlight uygulamasının sayfadaki
kapladığı alanı yine kodlarımız ile düzenlememiz gerek. Silverlight'ın OBJECT
taglarına bir ID değeri vererek bunun üzerinden OBJECT'in genişliğini
değiştirebiliriz.
HtmlPage.Document.GetElementById("TwitterSLWidget").SetAttribute("width", "360px")
Bu kod ile kullandığımız OBJECT tagları aşağıdaki gibi.
<object style="position:absolute; z-index: 2;" id="TwitterSLWidget" data="data:application/x-silverlight-2,"
type="application/x-silverlight-2">
<param name="source" value="ClientBin/TwitterWidget.xap" />
<param name="initParams" value="twitterid=daronyondem,count=10,twitxrid=daronyondem" />
<param name="onerror" value="onSilverlightError" />
<param name="background" value="Transparent" />
<param name="pluginbackground" value="Transparent" />
<param name="windowless" value="true" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="2.0.31005.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>
Widget'ı nasıl kullanırız?
Birazdan kaynak kodları ile beraber Widget'ı sizinle paylaşacağım. Ama onun
öncesinde hemen Widget'ı nasıl kullanırız ona bakalım.
<div id="TwitterSLWidgetHost">
<object style="position:absolute; z-index: 2;" id="TwitterSLWidget" data="data:application/x-silverlight-2,"
type="application/x-silverlight-2">
<param name="source" value="ClientBin/TwitterWidget.xap" />
<param name="initParams" value="twitterid=daronyondem,count=10,twitxrid=daronyondem" />
<param name="onerror" value="onSilverlightError" />
<param name="background" value="Transparent" />
<param name="pluginbackground" value="Transparent" />
<param name="windowless" value="true" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="2.0.31005.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object></div>
Yukarıdaki HTML kodunu sayfanıza yerleştirmeniz gerekiyor. Özellikle koyu
olan yerlere dikkat. daronyondem yerine kendi Twitter
ve TwitXT hesaplarınızın adlarını yazmanız gerek. count
kısmına da koç Post gözüksün istiyorsanız onu yazabilirsiniz. OBJECT ve DIV
taglarının ID'leri çok önemli. Bu ID'ler kodlarda kullanılıyor, eğer
değiştirirseniz kodları da tekrar düzenleyip XAP dosyasını Compile
etmeniz gerekecektir.
function TwitterIncData(object)
{
document.getElementById("TwitterSLWidget").Content.Page.IncData(object);
}
Son olarak yukarıdaki kodu da sayfanızda uygun bir JavaScript dosyasına veya
tagları arasında koymanız ve XAP dosyasını sunucuya kopyalamanız yeterli
olacaktır.
Kaynak Kodlar
Tüm projenin kaynak kodlarını aşağıdaki adresten indirebilirsiniz. Proje epey
karışık oldu, özellikle Remote Script Injection kullandığımız için Cross-Browser
uyumluluğu konusunda sıkıntılar var. Diğer yandan Overlay konusunda da farklı
tarayıcılarda sorunlar var. O nedenle şimdilik ben ancak IE 7 desteği
sunabiliyorum, uğraşacak pek zaman olmadı. Eğer siz uygulamayı diğer
tarayıcıları da destekleyecek şekilde modifiye ederseniz beni haberdar etmeniz
yeterli. Böylece mini bir açık kaynak projesi olmuş olur.
Makaledeki tüm kodlar VB :) çünkü uygulamadan kesip yapıştırdım. Uygulamayı
da malum VB ile yazdım :) C#'cılara selamlar :)
Silverlight 2.0 Twitter / TwitXR Widget
Kaynak Kodlar ve Proje - 15102008_1.rar (558,89 KB)
Hepinize kolay gelsin.