Silverlight 布局(附照片墻示例及源碼)

作者: hievis  來源: 博客園  發布時間: 2010-10-17 22:36  閱讀: 2753 次  推薦: 1   原文鏈接   [收藏]  

  前言

  很喜歡一種人,他們可以把一種技術分析得很透徹,由淺入深,深入淺出,不管你是初學者還是資深人士,看上去都會感覺非常舒服。

  但是有時候,試圖去描述一個技術點是很困難的事情。

  開始,你覺得這個技術很有價值,你通過自己花了一定時間去學習,想要把自己的理解與心得分享,于是就打算寫一篇隨筆;可是當你開筆之后,突然發現描述起來很困難,雖然在你心中那個概念已經很簡單了,但是卻要用很多文字來描述,而且不同的技術可能有不同的“上下文”,每個人可能也有不同的“上下文”,在寫了幾個字之后你有點想放棄了;可是最后又不甘心,聰明的你靈機一動,做了一個Demo,大概對著圖片描述一下,然后發布,讓讀者自己去猜吧!

  我想說的是,這并不算心得和學習經驗,這充其量算是一篇索引或者“提醒”,讀者看了想,還可以這樣子,于是又不得不自己去找資料學習。因為你心中的經驗沒有描述出來啊。

  想想看,我們現在學習技術的途徑是什么。可能主要是SDK之類的,其次呢,我相信你印象應該很深刻:其中有一些是別人寫的你覺得非常好的文章。很多Blog尤其是國外的Blog已經成為了我們提升技術和積累經驗的一種無形的方式。

  文化的傳承有口述和媒介承載,口述精準簡單,但是也許文字更能深刻的描述事物而且可以反復理解,所以好的文字是文化傳載的重要方式。誠然,寫技術文章也許需要一點點技巧,有些時候描述一個技術點需要篇幅,需要時間,需要耐心,更需要的是對讀者負責,你能為讀者帶來什么?

  所以,寫作需要用心,從構思到文字,這篇文章的價值是什么?你對技術的理解是什么,不要因為時間問題(一篇有內容的文章肯定需要花很多時間)就對你認為核心的理解一筆帶過。我相信,這樣才是再學習的過程,這樣才對得起讀者的時間和關注,這樣才叫分享!

  Silverlight中的布局

  對于Silverlight學習來說,首先要面對的應該是布局:你得把元素放到你想擺放的位置,然后是考慮元素的層次以及可見性,之后可能你想讓它動起來,就學習動畫,最后理解更深入之后,可能會開發如Behavior之類的行為,或者設置復雜的控件狀態,模板。

  很多教程是從使用Grid開始,然后是Canvas,StackPanel之類的控件,他教你怎樣設置元素的位置。然后就沒有下文,很少會去講解布局的原理,不過如果是看Silverlight SDK,是能看到的。其實最好的教程就是Silverlight SDK,包括像兩個不同的SL插件(即使它們在不同的瀏覽器窗口)之間的通信這些一般人沒有注意到的特性,里面都是講得清清楚楚。所以,建議不要花錢去買Silverlight相關的書。

  其實,不是能使用Grid之類的就知道了布局,Silverlight布局包含更多的東西,理解布局系統有利于更深層的理解Silverlight,從而開發更得心應手。遺憾的事初學者理解布局也許有點復雜,可能是因為其中的遞歸,但是我們生活中其實有很多遞歸系統的。我試著學習郭欣用鐵路系統描述網絡傳輸一樣,也來構造這樣一個場景。

  故事場景-巴比難題

  我們假設巴比基金現在愿意把所有的錢捐獻給某個國家的收入在該國平均收入以下的老百姓。但是他們不能自己去每個地方發放這筆資金,所以要求該國中央政府處理好這件事情,將這筆資金發放到各個省份的相關百姓手中。中央政府高度重視這件事情,但是這么大的工程應該怎樣完成了,財政官員很為難?這時候負責行政的長官提出了自己的建議,中央政府覺得很好,于是采納了該建議。

  行政長官的建議分為兩道程序:調查和發放。

  調查:首先中央向各級地方行政官發出調查通知,要求各地方政府統計一下年收入在平均收入以下的人數,并要匯報每個人的差額。省級行政長官在收到通知后統計了一下省會的情況,然后把通知轉發給縣級行政官;縣級行政官接到通知還是首先統計縣城的情況,然后轉發通知到各個村子。村子就是最小的行政單位了,于是村委會秘密調查了每個人的收入,并計算差額。然后上報給縣級,縣級在結合縣里面的情況,整理一份整個縣的報告給省級,省級重新整理,上報給中央政府。

  發放:哈哈,現在中央政府就拿著全國的需求單。哇,全國有1000億的需求,而巴比只基金只有500億。也不好意思叫巴比找中國的富豪勸捐。于是只有把每個省的比例縮小一半了,并且最后做了一些調整,有的省份潛力比較好,自己能解決一些問題,就少發放一些,有些地方經濟差,就多發了一些,有的省級居然亂報說需要10000億,可能是計算錯誤,最后只發了100,有的地方雖然貧窮,但是很有志氣,不想要美國人的施舍,一分錢都沒有報。

  這樣,做一些根據實際情況調整之后,中央政府就按照調整的數額將錢發放到各個省級政府,省級政府拿到錢感到不對勁,感覺跟自己上報金額不一致,但有不好意思反應情況,畢竟上面是老大,想給多少就多少,反應也沒有用;但是有些政府也發現比自己期望的要多,有良知的官員還是決定平分到每個縣級,但是也有一些官員主張直接貪污算了。

  總之也按中央政府的做法重新調整各個縣的發放金額,然后發放到各個縣級,最后到村子,村委會在發到每個人手中。

  這個巴比問題就這樣解決了。值得注意的是:

  1,各級政府在發放調查通知的時候也可以給縣級政府一個指標,我能給你的最大金額,或者最小金額,或者固定金額,或者無限制,你盡管反應實際情況,這樣下級政府可能就會在上報前做一個實際調整。但下級也要做好心理準備,這個數字不一定是真的,只是個參考數字。

  2,每個人或政府在發放之后拿到的錢不一樣,可能比需要的要多,這個時候政府可以選擇貪污或者公平發放,而來百姓會去買二鍋頭慶祝一下;有時候比需要的少,這個時候就只有拮據點花了。

  讀完這個故事,我們想一想,把一定數量的錢一層一層的發到每個人手里,是不是類似于Silverlight把插件大小的空間一塊一塊地分給每個空間啊?

   布局原理

  沒錯,Silverlight也是按類似的原理來實現布局:

  首先,所有元素的最頂層必須是一個容器(通常如Grid,Canvas,StackPanel等),然后在容器中擺放元素,容器中也可能包含容器。這里的容器就像行政長官一樣,他們負責分配元素的空間。同樣,首先頂層的容器一個一個的問自己的子元素:你想要多大的空間?如果子元素也是容器,它又繼續向下遞歸,最后又頂層開始向上匯報。這就是所謂的測量。

  測量完之后就是排列,這個時候每個容器知道自己每個子元素想要的空間大小,就按自己的實際情況進行分配。一致遞歸到最底層。

  注意上述紅色字體部分,通過前面的故事,我們知道,資金的發放完全由行政長官控制,不管下面想要多少,都是他說了算,他甚至可以一分錢都不給,或者給你超多你的預期的數目。

  這里的容器也一樣,容器擁有完全的分配權,不過這里容器不僅僅是分配空間,還決定元素的位置,因為空間總是跟位置相關的。也就是說,容器說想給你多大空間你就只有有那么大的空間可使用,容器想讓你擺在什么位置,你就得乖乖呆著什么位置。

  只不過,這里的容器是遵守規則的,它遵守開發者指定的規則:

  Grid的規則是:我把我這個空間分成一格一格的格子,看起來有些像Table,在我里面的元素我完全按照附加屬性Grid.Row,Grid.Column,Grid.RowSpan,Grid.ColumnSpan來決定其大小和位置。

  Canvas的規則是:我讀取附加屬性Canvas.Left,Canvas.Right,Canvas.Top,Canvas.Bottom,并以此來決定元素的位置,我通常不限制元素開用空間

  StackPanel的規則是:根據附加屬性,我要么讓元素橫著排列,要么豎著排列。

  聰明的你是不是立刻想到,我可不可以定義自己的規則呢?哈哈,當然可以!比如,你可以讓Panel里面的元素隨機分布,并可讓它們隨機旋轉一定角度,這不就是現在某些很酷的相冊嗎;你可以讓元素排成一個圓形,這不就是Blend里面的例子嗎;你可以讓元素根據某個Path元素排列,這不就是PathListBox嗎?如下圖:   

  所以,你現在是不是覺得布局不是那么簡單并且很好玩呢?下面我們就來看怎么實現這么酷的東西!

  基礎框架-FrameworkElement

  為Silverlight布局中涉及的對象提供公共API的框架。FrameworkElement還定義在Silverlight中與數據綁定,對象樹和對象生存期功能區域相關的API。繼承層次結構:

  System.Object
    System.Windows.DependencyObject
      System.Windows.UIElement
      System.Windows.FrameworkElement
        System.Windows.Controls.Border
        System.Windows.Controls.Control
        System.Windows.Controls.Panel
        System.Windows.Shapes.Shape

  類定義:

 
namespace System.Windows
{

public abstract class FrameworkElement : UIElement

{


protected virtual Size ArrangeOverride(Size finalSize);
protected virtual Size MeasureOverride(Size availableSize);

......

}

}

  我們看到FrameworkElement定義了一個布局的框架:其中MeasureOverride就是測量過程,ArrangeOverride就是排列過程。我們一般的控件都繼承自它,所以就可能建立起了這樣一個遞歸系統。

  Silverlight要求,要定位可視元素,必須將它們放置于Panel或者其他容器對象中。因為Panel定義了確定如何在屏幕上繪制Panel元素的Children集合成員的布局行為。自定義Panel很簡單,從Panel派生并重寫其MeasureOverride和ArrangeOverride方法即可。在兩個方法中,都可以獲取Children屬性(下面列舉SDK例子)。

  測量:

 
protected override Size MeasureOverride(Size availableSize)
{

int i =0;
foreach (FrameworkElement child in Children)
{

if (i < 9) child.Measure(new Size(100, 100));
else child.Measure(new Size(0, 0));
i
++;
}

return new Size(300,300);
}

  在MeasureOverride中,必須調用每個子元素的Measure方法,傳遞該容器可以分配的空間,確定分配多少空間給子級,然后像上級返回整個容器需要的空間大小。該方法會觸發子元素執行內部的MeasureOverride方法,如此遞歸。

 

  其實我們想到,測量過程可以是不必要的,排列可以完全按照自己的邏輯進行,它可以不管子元素到底需要多大,但是這樣肯定是不行的,除非容器額外了解很多子元素的情況,否則可能排列出的效果很差。那么測量其實是為排列提供一個重要的參考信息:

  DesiredSize,DesiredSize可以理解為:行政長官給在發放通知的時候給下級一個指標,但是下級會有一個實際需求,而DesiredSize相當于指標和需求之間的最小值。具體的說,DesiredSize在Measure發放之后計算的,布局系統基于傳遞給Measure的availableSize和元素的固有大小去定子元素的DesiredSize,一般講DesiredSize設置為兩種的最小值。這個值可以在排列的時候作為一個參考依據。

  排列:

  在排列處理過程中,必須確定每個子級的布局槽的位置并設置面板的最終大小。當你計算好位置之后,調用每個元素的Arrange方法就可以了,其參數是一個Rect對象,這個對象可以表示一個元素的空間大小和位置。如下所示:

  image.Arrange(new Rect(10, 10, 150, 150));

  該句將元素image放在x為10,y為10的位置,所占寬度和高度都為150的大小空間。定義一個Panel就這里簡單,這里是定義Panel規則的地方,你可以設計不同的規則,根據不同的附加屬性或依賴屬性進行布局。Grid,Canvas,StackPanel等都是經過這樣定義的。所以,你可以在這里設計很復雜的計算,使元素按不同的方式布局排列。

  神秘絕招-讓Panel的子元素都具有某種行為

  如果只是能擺放元素的位置和限制大小,這樣未免沒有多大的用途,所有元素都只是一個擺設。但是,想到我們前面花了大量篇幅分析得Behavior,試想,我們能不能給容器設置某種行為,讓它把這種行為附加到每一個元素,從而讓容器還能控制元素的行為呢?哈哈,你太聰明了,沒錯,這是完全可以的!由于講過Behavior的原理,以及布局的原理,這里我就不分析代碼了,附上源文件供參考,代碼寫得很粗糙,僅作演示用,請勿用在項目中,這里還是講一下大概思路:

  首先定義了一個派生自Panel的類PhotoWallPanel,在ArrangeOverride中將每個元素隨機選擇了一個位置,并隨機旋轉了一個角度;然后定義一個Behavior,它作用于PhotoWallPanel,遍歷其中的每一個元素,給每個元素添加一個鼠標經過和離開的效果,然后附加到PhotoWallPanel上,值得注意的是:

 
<i:Interaction.Behaviors>
<local:ShowPictureBehavior/>
</i:Interaction.Behaviors>
</local:PhotoWallPanel>

  在XAML中,附加Behavior的代碼應該位于Panel的結尾之前,或者在其他字元素之后,這樣才能保證將行為添加到元素上,因為XAML是順序解析的,在前面的元素首先實例化其對象,如果放在前面,這個時候Panel的子元素還沒有初始化。

  總結

  寫了這么多,有的人覺得煩了,確實也是這么點東西寫那么多廢話,但是自定義Panel以及Behavior這些都是重要的基本概念,如果你想深入Silverlight開發,或者很在意UI,這些應該是必須掌握的。希望本篇能對學習Silverlight的朋友有點啟示。

  值得重視的是,我們看到Silverlight中附加屬性的重要性,基本上容器控件都有定義附加屬性,而Behavior也是利用附加屬性,很值得好好研究,并在項目中充分利用。

  Blend截圖:

  實際效果

  補充:

  經@super110的提示,這里可能忽略了一點東西,而我又仔細分析了一下,可能一般人很容易在這里理解起來比較納悶。注意,以下描述基于實驗,而非基于原理:

  我們再仔細分析這兩個方法:

 
protected override Size MeasureOverride(Size availableSize)
{

foreach (var item in this.Children)
{
item.Measure(availableSize);
}

return “此返回值將作為該容器的DesiredSize”;
}


protected override Size ArrangeOverride(Size finalSize)
{

foreach (var item in this.Children)
{
FrameworkElement my
= item as FrameworkElement;
my.Arrange(
new Rect(0, 0, my.Width, my.Height));
}


return “此返回值將作為此容器的ActualWidth和ActualHeight”;
}

  在Measure每個元素的時候,這個方法執行完就會產生DesiredSize值,其實它也就是MeasureOverride()方法的返回值。因為這其實是一個遞歸系統。

  DesiredSize的取值方法:

  1,對于非容器類的控件如Image,布局系統會取其所設置的Width和Height與availablesize相比較的最小值,打個比如,這個有點像進入公司的時候老板問你期望的待遇,當然他心中有一個數字,如果你的數字大于他的數字,以他的為準;如果你的數字小于他的數字,以你的為準,我暫且稱其為“老板不吃虧原則”。

  2,對于容器控件,由于是我們寫代碼控制MeasureOverride方法的返回值,所以這個返回值就是該容器的DesiredSize值。換句話說,如果這個容器處于另外一個容器當中,當父容器調用子容器的Measure方法之后,這個子容器返回的值就是它的DesiredSize值。

  ActualWidth和ActualHeight的計算:

  1,對于非容器控件,往往是由容器給定的值決定,即容器在調用的Arrange方法的時候,給根據自身規則情況來分配值,這個值就是非容器控件的ActualWidth和ActualHeight。

  2,對于容器控件,其值由ArrangeOverride方法的返回值來確定,原理同DesiredSize一樣。

  DesiredSize,ActualWidth和ActualHeight這幾個值都是不能在程序中設置的,我們看到它要么由布局系統計算,要么由容器來計算,我們在程序中一般只讀取這幾個值。

  源代碼下載:http://files.cnblogs.com/hielvis/PhotoWall.rar

1
0
 
標簽:Silverlight
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()