Minggu lalu saya membuat avatar Silverlight untuk http://geeks.netindonesia.net/blogs). Kali ini saya akan sampaikan bagaimana saya membuat avatar tersebut. Tidaklah sulit untuk membuat avatar dengan Silverlight dari halaman web yang sudah ada. Cukup dengan melakukan query terhadap DOM avatar yang sekarang, ambil informasi Url foto dan penghitung halaman tampil (view counter). Setelah informasi didapat tinggal sembunyikan avatar yang lama dan tampilkan versi Silverlightnya. Berikut adalah langkah-langkah untuk membuat avatar Silverlight.
Buat Project Silverlight dengan Expression Blend.
Buat dua path yang digunakan untuk menempatkan foto. Satu untuk foto dan satunya untuk bayangan foro. Buat juga dua textbox dalam StackPanel, miringkan stack panel dan copy satu untuk bayangannya. Buat OpacityMask untuk bayangan sehingga bayangan tampak agak kabur.
Barikut adalah XAML dari design tersebut.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avatar.Page"
Width="133" Height="130">
<Grid x:Name="LayoutRoot" Clip="M0,0 L133,0 L133,130 L0,130 z">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF8FC2E4"/>
<GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid>
<Canvas Margin="48 1 1 10" >
<Path Data="M0,0 L82,0 L82,102 L0,91 z" Stretch="Fill" Stroke="#CCFFFFFF" >
<Path.Fill>
<ImageBrush x:Name="Photo" />
</Path.Fill>
</Path>
<Path Canvas.Top="193.5" Data="M1.5,12 L81,0 L81.75,73.5 L0.25,82.5 z" Stretch="Fill" Stroke="#CCFFFFFF" RenderTransformOrigin="0,0" Height="102.188" Width="82.5" Canvas.Left="0.125" >
<Path.OpacityMask>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.4,0">
<GradientStop Color="#AAFFFFFF" Offset="1"/>
<GradientStop Color="#00FFFFFF" Offset="0"/>
</LinearGradientBrush>
</Path.OpacityMask>
<Path.Fill>
<ImageBrush x:Name="PhotoShadow" />
</Path.Fill>
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Canvas>
<Canvas Margin="2,0,0,0">
<Path Height="18" Width="134" Canvas.Left="-3" Canvas.Top="86.25" Data="M128,103.25 L-3,86.25" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#B2FFFFFF"/>
<StackPanel Canvas.Top="48" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleY="6.972"/>
<RotateTransform/>
<TranslateTransform Y="4.006"/>
</TransformGroup>
</StackPanel.RenderTransform>
<TextBlock x:Name="ViewCount" Text="9999" TextWrapping="Wrap" FontFamily="Arial" FontSize="20" Foreground="#FF023D9E" HorizontalAlignment="Right"/>
<TextBlock Text="Views" TextWrapping="Wrap" FontFamily="Arial" FontSize="16" Foreground="#FF023D9E" HorizontalAlignment="Right"/>
</StackPanel>
<StackPanel Canvas.Top="84" RenderTransformOrigin="0.5,0.5">
<StackPanel.OpacityMask>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#00FFFFFF"/>
<GradientStop Color="#AAFFFFFF" Offset="1"/>
</LinearGradientBrush>
</StackPanel.OpacityMask>
<StackPanel.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1"/>
<SkewTransform AngleY="7.585"/>
<RotateTransform/>
<TranslateTransform Y="3.288"/>
</TransformGroup>
</StackPanel.RenderTransform>
<TextBlock x:Name="ViewCountShadow" Text="9999" TextWrapping="Wrap" FontFamily="Arial" FontSize="20" Foreground="#FF023D9E" HorizontalAlignment="Right"/>
<TextBlock Text="Views" TextWrapping="Wrap" FontFamily="Arial" FontSize="16" Foreground="#FF023D9E" HorizontalAlignment="Right"/>
</StackPanel>
</Canvas>
</Grid>
</Grid>
</UserControl>
Tulis code-behind untuk membinding foto dan view counter.
Informasi url foto didapat dari InitParameters yang berasal dari javascript. File code-behind App.xaml.cs digunakan untuk menangkap informasi InitParams dan meneruskannya ke Page.xaml.cs yang kemudian dibind ke XAML.
App.xaml.cs
1 using System.Windows;
2 using System;
3 using System.Windows.Browser;
4
5 namespace Avatar
6 {
7 public partial class App : Application
8 {
9 public App()
10 {
11 this.Startup += this.OnStartup;
12 this.Exit += this.OnExit;
13 this.UnhandledException += this.Application_UnhandledException;
14
15 InitializeComponent();
16 }
17
18 private void OnStartup(object sender, StartupEventArgs e)
19 {
20 var page = new Page();
21 // Read application parameter
22 var imageSourceUrl = string.Empty;
23 int viewCountInteger = 0;
24 if (e.InitParams.Count > 0)
25 {
26 var viewCount = string.Empty;
27 if (e.InitParams.ContainsKey("ImageSourceUrl"))
28 imageSourceUrl = HttpUtility.HtmlDecode(e.InitParams["ImageSourceUrl"]);
29 if (e.InitParams.ContainsKey("ViewCount")) viewCount = e.InitParams["ViewCount"];
30 // Load the main control here
31 if (!string.IsNullOrEmpty(viewCount))
32 int.TryParse(viewCount, out viewCountInteger);
33 }
34
35 page.SetImageSource(imageSourceUrl);
36 page.SetViewCount(viewCountInteger);
37 this.RootVisual = page;
38 }
39
40 private void OnExit(object sender, EventArgs e)
41 {
42 }
43
44 private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
45 {
46 // If the app is running outside of the debugger then report the exception using
47 // the browser's exception mechanism. On IE this will display it a yellow alert
48 // icon in the status bar and Firefox will display a script error.
49 if (!System.Diagnostics.Debugger.IsAttached)
50 {
51 // NOTE: This will allow the application to continue running after an exception has been thrown
52 // but not handled.
53 // For production applications this error handling should be replaced with something that will
54 // report the error to the website and stop the application.
55 e.Handled = true;
56 Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
57 }
58 }
59
60 private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)
61 {
62 try
63 {
64 string errorMsg = e.ExceptionObject.Message + @"\n" + e.ExceptionObject.StackTrace;
65 errorMsg = errorMsg.Replace("\"", "\\\"").Replace("\r\n", @"\n");
66
67 System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight 2 Application " + errorMsg + "\");");
68 }
69 catch (Exception)
70 {
71 }
72 }
73 }
74 }
Page.xaml.cs
1 using System;
2 using System.Windows;
3 using System.Windows.Browser;
4 using System.Windows.Controls;
5 using System.Windows.Media.Imaging;
6
7 namespace Avatar
8 {
9 public partial class Page : UserControl
10 {
11 public Page()
12 {
13 // Required to initialize variables
14 InitializeComponent();
15
16 }
17 public void SetImageSource(string imageSourceUrl)
18 {
19 if (!string.IsNullOrEmpty(imageSourceUrl))
20 {
21 var url = new Uri(imageSourceUrl, UriKind.RelativeOrAbsolute);
22 if (!url.IsAbsoluteUri)
23 {
24 url = new Uri(HtmlPage.Document.DocumentUri, url);
25 }
26 var image = new BitmapImage(url);
27 Photo.ImageFailed += Photo_ImageFailed;
28 PhotoShadow.ImageFailed += Photo_ImageFailed;
29 Photo.ImageSource = image;
30 PhotoShadow.ImageSource = image;
31 }
32 }
33 public void SetViewCount(int viewCount)
34 {
35 ViewCount.Text = viewCount.ToString();
36 ViewCountShadow.Text = viewCount.ToString();
37 }
38 void Photo_ImageFailed(object sender, ExceptionRoutedEventArgs e)
39 {
40 }
41 }
42 }
Note: Anda harus gunakan absolute Url untuk mendownload foto yang mengandung query string dalam Url, lihat baris 21-25 pada kode di atas. Silverlight akan mengabaikan query string jika menggunakan relative url untuk binding gambar.
Ambil Informasi DOM dengan jQuery
jQyery sangat handal untuk melakukan query dan manipulasi DOM. Anda dapat memanipulasi DOM hanya dalam satu baris kode. Sebelum melakukan query, Anda perlu tahu elemen mana yang akan dimanipulasi. Gunakan IE Development Helper atau software sejenil untuk mencari DOM yang digunakan untuk avatar. Cari ID atau class CSS yang digunakan oleh avatar yang kemudian akan diganti dengan avatar Silverlight.
Berdasarkan informasi ini Anda dapat menganalisis bahwa setiap blog entry berada dalam element DIV dengan CSS class content_main_con_be. Element DIV ini mengandung view count dan avatar pada element anak pertama dan kedua. Anda dapat melakukan query informasi tersebut dengan jQuery seperti terlihat pada baris 11 dan 12 script berikut.
1 /*
2 Filename: avatar.js
3 Project : INDC Geeks
4 Ver. : 1.0
5 Date : March, 4th 2009
6 Author : Ahmad Masykur - www.masykur.web.id
7 */
8
9 $(document).ready(function() {
10 if (Silverlight.isInstalled('2.0')) {
11 var viewCountElements = $("div.content_main_con_be div:nth-child(1) > span:even");
12 var pictureBoxes = $("div.content_main_con_be div:nth-child(2)")
13 pictureBoxes.each(function(i, element) {
14 $(element).width(132).height(130);
15 var imageSourceUrl = $('img', element).attr('src');
16 createSilverlight(element, imageSourceUrl, $(viewCountElements
).text());
17 });
18 $("div.content_main_con_be div:nth-child(1)").hide();
19 }
20 });
21
22 function createSilverlight(host, imageSourceUrl, viewCount) {
23 Silverlight.createObject(
24 '/ClientBin/Avatar.xap?v=0.2',
25 host, host +"_Plugin",
26 {
27 width: "100%", height: "100%",
28 background: "#E8CFB0",
29 version: "2.0.31005.0"
30 },
31 { onLoad: null },
32 'ImageSourceUrl=' + imageSourceUrl + ', ViewCount=' + viewCount);
33 }
34
Penjelasan dari script di atas adalah sebagai berikut.
- Pada baris 9 akan meregister event jika DOM sudah siap maka anonymous function di dalamnya akan dieksekusi.
- Baris 10 akan mengecek apakah Silverlight 2 terinstall. Jika terinstall maka avatar akan diganti dengan versi Silverlight.
- Baris 11 dan 12 digunakan untuk mendapatkan element yang digunakan untuk view counter dan picture box.
- Baris 13: Membuat looping loop untuk DOM yang didapat.
- Baris 14: Set element width dan height yang akan digunakan oleh host Silverlight
- Baris 15: Ambil informasi url foto dari element IMG
- Baris 16: Create Silvelight object dengan javascript.
- Baris 22-33 is function untuk membuat Silverlight object.
- Baris 32: Set nilai InitParams yang akan diteruskan ke Silverlight application.
Script ini membutuhkan Silverlight.js dan jQuery 1.3.2. Langkah terakhir adalah upload file xap and file-file yang dibutuhkan dan juga masukkan referensi script tersebut ke dalam file blog.master pada theme.
<script src="/JavaScripts/Silverlight.js" type="text/javascript"></script>
<script src="/JavaScripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="/JavaScripts/avatar.js" type="text/javascript"></script>
Akhirnya, geeks portal memiliki theme baru dengan avatar Silverlight. Avatar ini hanya akan tampil ketika user memiliki plug-in silverlight, jika tidak user akan melihat avatar versi HTML.
Saya ucapkan terima kasih kepada Wely dan Naren yang telah mendukung terwujudnya avatar ini di geeks portal.
Versi English tulisan ini dapat dibaca di How To Create Silverlight Avatar for INDC blogs site