[Silverlight]學習筆記-使用 Deep Zoom 功能並顯示Sub-Images的Metadata(1)

  • 4459
  • 0
  • C#
  • 2011-01-10

[Silverlight]學習筆記-使用 Deep Zoom 功能並顯示Sub-Images的Metadata(1)

此例主要是使用Deep Zoom功能進行影片圖片的縮放移動效果,並於點選某張圖片後,該圖片自動置中並且於頁面右方顯示該影片的相關資訊(見下圖)。

012_ie

 

 

要製作Deep Zoom效果,我們必須要運用Deep Zoom Composer這個軟體來進行。

 

 

Deep Zoom Composer的使用方式請參考下列這二篇文章:

小猴子的零元學Expression Blend 4 - Chapter 23 Deep Zoom Composer與Deep Zoom功能

艾小克的試玩 Deep Zoom Composer WIth Silverlight Beta2

 

 

簡述如下:

1.使用Deep Zoom Composer開啟一個新專案,在Import這個步驟底下,使用【Add Image】將原始的圖片加入。

001_DeepZoom

 

 

2.在Compose這個步驟底下,將剛Import進來的圖片分別拉至工作區,並依自已的需求適當的進行排列。

這裡要注意的一點,在工作區裡的每張圖片的 Tag 屬性都要設定一個唯一值,每張圖片都要不一樣,因後面的程式會以此Tag值為Key值去取出該圖片對應的資訊。

002_DeepZoom

 

 

3.最後一個步驟就是Export,此例Export options要選擇【Export as a collection(multiple images)】,Templates則選擇【Deep Zoom Classis + Source】,Name就設成【MoviesList】,設定完成之後,執行【Export】即可完成。

003_DeepZoom

 

 

4.完成後,該專案目錄底下的Exported Data目錄中,會有一個movieslist(第三步驟所設定的Name)目錄產生,該目錄底下Deep Zoom Composer會自動產生一個方案(因第三步驟Export options有設定【Deep Zoom Classis + Source】)。

004_DeepZoom

 

基本上該方案已可直接執行,讓我們不用寫一行code就可体驗Deep Zoom的功能。

 

 

但我們還要繼續追加顯示影片說明的功能,所以還要繼續進行下去。

 

 

在繼續之前,先開啟movieslist\DeepZoomProjectSite\ClientBin\GeneratedImages\Metadata.xml 檔案看一下內容,可看到其中已包含剛才為每張圖片加上的Tag屬性。

005_DeepZoom

 

 

接著使用 Blend 4 開啟剛建立的 DeepZoomProject.sln 方案。

 

 

預設的Page.xaml頁面配置如下,其LayoutRoot底下只放一個 MultiScaleImage控制項 (msi)及一個Canvas容器 (buttonCanvas),buttonCanvas底下則又放四個Button控制項,分另可執行ZoomIn、ZoomOut、GoHome、FullScreen的功能。

006_Blend

 

 

因要追加在頁面的右方顯示影片資訊的功能,所以必須將原頁面的配置進行調整。

 

 

新增一個Canvas容器 (ImageCanvas),將原有的msibuttonCanvas置入此ImageCanvas容器內,並調整其右方可以有多餘的空間再放置一個Canvas容器(InfoCanvas)以顯示影片內容,InfoCanvas容器底下則再放入三個TextBlock控制項分別顯示影片的英文片名(tbECapital)、中文片名(tbCCpital)及影片描述(tbDesciption)。

 

調整完的頁面如下:

007_Blend

XAML的內容如下:


	<Grid x:Name="LayoutRoot" Background="White" Width="800" Height="480" MouseEnter="EnterMovie" MouseLeave="LeaveMovie">
		<Canvas x:Name="ImageCanvas" Margin="0,0,160,112" Width="640" Height="480" Background="Black">
			<MultiScaleImage x:Name="msi" Source="/GeneratedImages/dzc_output.xml" Width="640" Height="480" d:LayoutOverrides="Width, Margin"/>
			<Canvas Height="37" Margin="300,440,164,8" x:Name="buttonCanvas" VerticalAlignment="Bottom" Opacity="0" Background="{x:Null}">
				<Button Height="30" x:Name="zoomIn" Width="42" Canvas.Left="197" Canvas.Top="4" Template="{StaticResource zoomInTemplate}" Content="Button" Click="ZoomInClick"/>
				<Button Height="30" x:Name="zoomOut" Width="42" Template="{StaticResource zoomOutTemplate}" Content="Button" Canvas.Left="227" Canvas.Top="4" Click="ZoomOutClick"/>
				<Button Height="30" x:Name="goHome" Width="42" Template="{StaticResource homeTemplate}" Content="Button" Canvas.Left="257" Canvas.Top="4" Click="GoHomeClick"/>
				<Button Height="30" x:Name="fullScreen" Width="42" Template="{StaticResource fullScreenTemplate}" Content="Button" Canvas.Left="287" Canvas.Top="4" Click="GoFullScreenClick"/>
			</Canvas>
		</Canvas>
    	<Canvas x:Name="InfoCanvas" HorizontalAlignment="Right" Width="160">
    		<TextBlock x:Name="tbECapital" Canvas.Left="8" TextWrapping="Wrap" Text="TextBlock" Canvas.Top="25" Width="144" Height="16"/>
    		<TextBlock x:Name="tbCCapital" Canvas.Left="8" TextWrapping="Wrap" Text="TextBlock" Canvas.Top="58" Width="144" Height="16"/>
    		<TextBlock x:Name="tbDesciption" Canvas.Left="8" TextWrapping="Wrap" Text="TextBlock" Canvas.Top="88" Width="144" Height="384"/>
    	</Canvas>
    </Grid>

 

 

頁面調整完畢,接下來就要開始加上顯示影片資訊的程式碼。

 

 

1.使用Visual Studio 2010 開啟DeepZoomProject.sln 方案。

這時可發現先前Deep Zoom Composer已自動產一個MouseWheelHelper.cs類別,並且Page.xaml.cs裡也包含了使用滑鼠操作Deep Zoom的相關EventHandler

008_vs2010

 

 

2.影片資料的部份,一般資料會存放在DataBase以方便維護,此例我另外準備一個XML檔(MoviesContent.xml)取代DB資料。

XML資料檔的內容如下,其中<Tag>就是之為每張圖片所定的Key值,<E_Caption>為英文片名,<C_Caption>為中文片名,<Description>為影片的說明。

009_vs2010

 

 

 

 

3.切換到 Page.xaml.cs ,先加這二個namespace (必須先將 System.Xml.Linq.dll 加入參考)


	using System.Xml.Linq;
using System.Linq;

 

 

4.接著另外宣告一組class用來存放影片的資訊。

 


	public class SubImageInfo
    {
        public int Index { get; set; }
        public string E_Caption { get; set; }
        public string C_Caption { get; set; }
        public string Description { get; set; }
    }

 

 

然後再宣告一個 List<SubImageInfo>泛型集合,用來存放每張圖片的資訊集合。


	List<SubImageInfo> ImageInfo = new List<SubImageInfo>();

 

 

5.再來修改msi_ImageopenSucceeded()MultiScaleImage載入成功之後,則使用WebClient 去下載Metadata.xml資源,以取得圖片的Tag資料。

另外再為WebClient追加一個DownloadStringCompleted事件,在非同步資源下載作業完成時執行該事件。

 


	void msi_ImageOpenSucceeded(object sender, RoutedEventArgs e)
        {
            //If collection, this gets you a list of all of the MultiScaleSubImages
            //
            //foreach (MultiScaleSubImage subImage in msi.SubImages)
            //{
            //    Do something
            //}
            WebClient xmlClient = new WebClient();
            xmlClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(xmlClient_DownloadStringCompleted);
            xmlClient.DownloadStringAsync(new Uri("GeneratedImages/Metadata.xml", UriKind.Relative));   //下載 Metadata.xml 檔

            msi.ViewportWidth = 1;
        }

 

 

 

xmlClient_DownloadStringCompleted()的內容如下,主要是以Metadata.xml中每張圖片的Tag值當key,去讀取存放影片資料的 MoviesContent.xml 資料檔。

讀到的每個影片資料全置入 List<SubImageInfo>中,之後若點選每張圖片,則會至此ImageInfo中取得影片資料。


	// Metadata.xml下載完成
        private void xmlClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                //取得 Metadata.xml 的內容
                XDocument metadataXML = XDocument.Parse(e.Result);
                XElement metaDataElement = metadataXML.Element("Metadata");
                var imageItems = from g in metaDataElement.Descendants()
                                 where g.Name == "Image"
                                 select g;

                //載入 MoviesContent.xml 電影的說明內容
                XDocument docMenu = XDocument.Load("MoviesContent.xml");
                ImageInfo = new List<SubImageInfo>();
                foreach (XElement g in imageItems)
                {
                    SubImageInfo subImageInfo = new SubImageInfo();
                    int zOrder = int.Parse(g.Element("ZOrder").Value) - 1;
                    string strTag = g.Element("Tag").Value;    //取得每張圖片的 Tag 值

                    subImageInfo.Index = zOrder;

                    if (strTag == "")
                    {
                        strTag = "None";
                        subImageInfo.E_Caption = string.Empty;
                        subImageInfo.C_Caption = string.Empty;
                        subImageInfo.Description = string.Empty;
                    }
                    else
                    {
                        //以每張圖片的 Tag 為 key 去取得 MoviesContent.xml 裡的電影的說明內容
                        var descriptions = from movies in docMenu.Descendants("Movie")
                                           where movies.Element("Tag").Value == strTag
                                           select new
                                           {
                                               E_Caption = movies.Element("E_Caption").Value,
                                               C_Caption = movies.Element("C_Caption").Value,
                                               Description = movies.Element("Description").Value
                                           };

                        foreach (var movie in descriptions)
                        {
                            subImageInfo.E_Caption = movie.E_Caption;
                            subImageInfo.C_Caption = movie.C_Caption;
                            subImageInfo.Description = movie.Description;
                        }
                    }
                    ImageInfo.Add(subImageInfo);
                }
            }
        }

 

 

6.接著在加上SubImageHit()這個Method,傳入滑鼠在MultiScaleImage點擊處的Point,這裡另外使用 GetSubImageRect()取得每張圖片的方框的範圍,然後使用 Rect.Contains(Point)方法判斷哪張圖片包含該Point,並回傳圖片的索引值

	// 取得滑鼠點擊的圖片索引
        private int SubImageHit(Point p)
        {
            for (int i = 0; i < msi.SubImages.Count; i++)
            {
                Rect subImageRect = GetSubImageRect(i);
                if (subImageRect.Contains(p))
                    return i;
            }
            return -1;
        }

        //取得每張圖片的方框
        private Rect GetSubImageRect(int indexSubImage)
        {
            if (indexSubImage < 0 || indexSubImage >= msi.SubImages.Count)
                return Rect.Empty;
            MultiScaleSubImage subImage = msi.SubImages[indexSubImage];
            double scaleBy = 1 / subImage.ViewportWidth;
            return new Rect(-subImage.ViewportOrigin.X * scaleBy, -subImage.ViewportOrigin.Y * scaleBy, 1 * scaleBy, (1 / subImage.AspectRatio) * scaleBy);
        }

 

知道點擊哪張圖片後,就可自List<SubImageInfo>中取出影片的資料。

 

 

7.修改Page()這個Method底下的 this.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e){}如下,先使用SubImageHit()取得點擊的圖片index值,再用該index值去取得List<SubImageInfo>中的影片資訊。

 


	this.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e)
            {
                if (!duringDrag)
                {
                    bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
                    double newzoom = zoom;

                    if (shiftDown)
                        newzoom /= 2;
                    else
                        newzoom *= 2;
     
                    int index = SubImageHit(this.msi.ElementToLogicalPoint(e.GetPosition(this.msi)));  //取得滑鼠點擊的圖片索引
                    if (index >= 0)
                    {
                        //由ImageInfo取得該張圖片的影片資訊
                        SubImageInfo subImageInfo = ImageInfo.FirstOrDefault(s => s.Index == index);
                        tbECapital.Text = subImageInfo.E_Caption;       //英文片名
                        tbCCapital.Text = subImageInfo.C_Caption;       //中文片名
                        tbDesciption.Text = subImageInfo.Description;   //影片說明

                        this.DisplaySubImageCentered(index);            //將點擊的圖片置中
                        Zoom(newzoom, msi.ElementToLogicalPoint(this.lastMousePos));
                    }
                    else
                    {
                        tbECapital.Text = string.Empty;
                        tbCCapital.Text = string.Empty;
                        tbDesciption.Text = string.Empty;
                    }
                }
                duringDrag = false;
                mouseDown = false;

                msi.ReleaseMouseCapture();
            };

 

 

其中將圖片置中的DisplaySubImageCentered() Method內容如下


	//將指定的圖片置中
        private void DisplaySubImageCentered(int indexSubImage)
        {
            if (indexSubImage < 0 || indexSubImage >= msi.SubImages.Count)
                return;
            Rect subImageRect = GetSubImageRect(indexSubImage);
            double msiAspectRatio = msi.ActualWidth / msi.ActualHeight;
            Point newOrigin = new Point(subImageRect.X - (msi.ViewportWidth / 2) + (subImageRect.Width / 2), subImageRect.Y - ((msi.ViewportWidth / msiAspectRatio) / 2) + (subImageRect.Height / 2));
            msi.ViewportOrigin = newOrigin;
        }

 

8.到此大功告成,直接執行看看。

010_ie

 

 

點選任一張圖片驗證一下結果。點選的圖片會放大且置中,並且頁面右方會顯示該影片的資訊。

011_ie

 

 

以上是使用Deep Zoom功能並顯示Sub-ImagesMetadata的基本介紹,範例程式碼可於此下載MoviesList.zip

 

 

但若要做到像如下圖Hard Rock Cafe Memorabilia網站一樣的效果,則必須另外再加工,下回再來將此例修改成類似Hard Rock Cafe Memorabliia網站那樣自動顯示Metadata的效果。

013_ie

 

 

今天剛好是民國100年的元旦,最後祝大家新年快樂!!

 

 

參考資料:

How to add text in Deep Zoom Composer?

[Silverlight]要如何把Deep Zoom產生的檔案內嵌到已完成的Silverlight網頁中?

零元學Expression Blend 4 - Chapter 23 Deep Zoom Composer與Deep Zoom功能

試玩 Deep Zoom Composer WIth Silverlight Beta2

將 Silverlight 深縮放帶到下一層級

 

電影圖片及資料來源:

KingNet歡樂網路王國

開眼電影網