Style是一個對象用來設定一個控制項的各種屬性
Style所能做的是設定控制項上已經存在的屬性的值
設想我們的控制項是一輛車, 一個樣式可以說成類似"輪子大小=17","車身顏色=櫻桃紅色"...等等
Template實際上是定義車有哪些部分組成. 舉例來說, 一個便宜的敞篷車的模板可能根本沒有頂棚, 或者一輛車的模板能決定他有兩個門或是四個門, 有4個或8個車輪等等.
我在解釋兩者的時候總是告訴別人, 拿海盜來說模板定義了他的骨架,樣式就是他如何打扮(我喜歡拿海盜做比喻因為一些有一隻眼睛,或一條腿,或一隻胳膊等,是模板使用的很好的範例)。
有意思的是通過樣式可以設定控制項的任何屬性, 包括模板本身, 因為他就是一個屬性, 所以你看到的那些工具(Blend)最常做的就是當你想要覆蓋一個控制項的模板的時候, 他們覆蓋了那個控制項整個的樣式和模板屬性.
不明白沒關係, 下面的文章可以讓你明白的.
generic.xaml的神奇之處是用來定義一個控制項的預設面板. 讓我們假設我們想建立一個GelButton控制項:
複製內容到剪貼簿
代碼:
public class GelButton : Button
{
}
足夠簡單, 現在我們想在Page.xaml裡使用它, 我們在頁面裡增加了namespace和這個控制項的定義.
複製內容到剪貼簿
代碼:
<UserControl x:Class="StylingSample.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:samples="clr-namespace:StylingSample">
<StackPanel Width="50" >
<Button Content="Top" Height="50"/>
<samples:GelButton Content="Cream" Height="50"></samples:GelButton>
<Button Content="Bottom" Height="50"> </Button>
</StackPanel>
</UserControl>
看到下面的結果你會感到驚奇嗎?
我不能告訴你你應該感到驚訝或不驚訝,但我可以告訴你發生了什麼!
控制項預設是無外觀的, 你需要給它定義外觀. 這是通過給控制項指定一個合法的控制項範本來完成的[通過Control類的Template屬性]
要指定這個模板屬性的話, 你可以像下面這麼做:
複製內容到剪貼簿
代碼:
public GelButton ()
{
this.Template = XamlReader.Load ("<ControlTemplate xmlns='http://schemas.microsoft.com/client/2007'
><Grid ..> </Grid></ControlTemplate>");
}
但是一個更好的方式是把控制項範本的定義儲存在一個叫"generic.xaml"的資源檔(Resource Dictionary), 然後神奇的運行時會把這個檔案自動載入上. 你的模板會通過TargetType 屬性關聯到你的控制項上. 這個模板將成為我們所稱的“內建樣式”(“built-in style”)。 這裡有些建立內建樣式的細節.
Generic.xaml是一個包含在你程式集裡的資源字典檔案. 預設的generic.xaml看上去應該這樣:
複製內容到剪貼簿
代碼:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
</ResourceDictionary>
為了定義我們的GelButton控制項的外觀, 我們需要從一些預設的模板開始入手. 我建議用David Anson的 Stylebrowser application工具從Button中拷貝預設樣式到generic.xaml檔案裡.
[不幸的是button的預設範本太大了, 因此本著練習的目的我用了一個更簡單的模板].
複製內容到剪貼簿
代碼:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:samples="clr-namespace:StylingSample;assembly=StylingSample"
>
<Style TargetType="samples:GelButton">
<Setter Property="Background" Value="Black" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="samples:GelButton">
<Grid x:Name="RootElement" >
<Ellipse Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="{TemplateBinding Background}"
/>
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center" HorizontalAlignment="Center"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
讓我們剖析需要建立該模板的工作:
1. 增加一個xmlns:samples到資源字典裡.
2. 下一步我們定義樣式並且設定我們想要使用這個樣式的目標控制項類型(TargetType); 通常你在定義樣式時設定目標類型是為了你的模板能發現屬性和使他們生效, 但是當在generic.xaml裡做這個設定的時候, 神奇的事情是通過這個聲明資訊把樣式和目標控制項類型建立關聯; 現在一旦當控制項被執行個體化後, 如果沒有其他樣式被使用, 這個樣式會被當作為預設樣式.
3. 剩下的是簡單的樣式定義. 模板綁定(TemplateBinding) 可能是最有意思的部分, 這會建立一個在我們設定的屬性和實際控制項屬性的綁定. 舉例來說:
<ContentPresenter Foreground={TemplateBinding Foreground}" > 建立了一個綁定,把 content presenter的Foreground屬性和實際控制項的Foreground屬性聯絡起來.
這可以讓我們在設計工具裡更容易定義使用者介面樣式. 在Blend或是XAML代碼裡你可以聲明一個按鈕 <GelButton Foreground=”Red” > 或是 <GelButton Foreground=White>
更多資訊你可以看看Karen Corby的 MIX presentation on “Rich,Dynamic UIs”.
現在我們可以運行上面相同的代碼, 只是更改了一下資源字典裡的樣式:
既然我已經對Background和Foreground建立了一個模板綁定(Templatebinding ), 我可以更進一步
複製內容到剪貼簿代碼:
<UserControl x:Class="StylingSample.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:samples="clr-namespace:StylingSample">
<StackPanel Width="50" Margin="0,20,0,0">
<samples:GelButton Content="" Height="20.4"
RenderTransformOrigin="0.5,0.5" Width="48.8" Canvas.ZIndex="2">
<samples:GelButton.Background>
<RadialGradientBrush>
<GradientStop Color="#FFF5DEB3"/>
<GradientStop Color="#FFE0B05C" Offset="0.826"/>
</RadialGradientBrush>
</samples:GelButton.Background>
</samples:GelButton>
<samples:GelButton Content="Ham" Height="16" Canvas.ZIndex="1">
<samples:GelButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFD64141"/>
<GradientStop Color="#FFE23939" Offset="1"/>
<GradientStop Color="#FEDAB6B6" Offset="0.43299999833106995"/>
</LinearGradientBrush>
</samples:GelButton.Background>
</samples:GelButton>
<samples:GelButton Content="" Height="16" >
<samples:GelButton.Background>
<RadialGradientBrush>
<GradientStop Color="#FFF5DEB3"/>
<GradientStop Color="#FFECC06E" Offset="0.991"/>
</RadialGradientBrush>
</samples:GelButton.Background>
</samples:GelButton>
</StackPanel>
</UserControl>
我只浪費你10分鐘和我的40分鐘時間來介紹generic.xaml和內建樣式。我曾答應回答幾個問題,他們是:
1 . 內建樣式的好處, 為啥用generic.xaml代替硬式編碼模板?
相對於在代碼設定模板來說, 把你的模板放到資源字典是一種很好的方式因為你能方便的替換這些模板. 想象一下如果你需要建立三個主題樣式, 寫入程式碼方式處理的話會很難.
另外如果你把模板放到資源字典檔案中這些模板可以引用該字典中的其他資源.
2. 為啥我看過的所有例子都沒用使用內建樣式, 總是告訴我們使用在App.xaml儲存的內嵌樣式?
內建樣式被設計為提供給控制項作者使用, 當你編寫一個控制項時, 你提供控制項的外觀和操作. 大多數例子純粹是簡單的自訂一個按鈕樣式, 因此他們採取了不同的方式.(直接在xaml裡聲明相關屬性)
3. 在上面的示範裡,你繼承了按鈕,除了提供內建樣式外什麼都沒做,這是最好的做法嗎?
我喜歡這一做法(但我來自一個企業背景下,我們創造臃腫的架構,往往只是為了繼承來建立一個抽象對象適應以後一些事情的變化), 一個好處是你可以在任何地方使用你的按鈕不用有明確指定一個樣式。當然缺點是繼承需要額外的一點效能和記憶體,但從我所看到過的來說這是非常微不足道的。
同樣,我不能稱之為最佳實務,更多是我個人的喜好。
4. 內建樣式好像可以讓我在一個地方改變所有控制項樣式?我不想每個按鈕就添加<Button Style=”{StaticResource GelButtonStyle}” >.
這是對的,如果你能繼承並且類不是密封的。儘管如此,經過建立一些解決方案,我意識到我有一個集中的錯覺[是我編的名詞]。
理由是使用內建樣式我可以在一個地方改變樣式. 這和使用App.xaml是相同你可以在一個地方為所有對象改變樣式. 你是複製了很多是樣式的名稱,但風格本身在一個地方。
5. 什麼時候不能使用內建樣式?
如果類是密封的或是他們有一個受保護的Template屬性, 那麼你就不能使用內建樣式.
6. 我能否僅建立一個generic.xaml覆蓋System.Windows.Controls的模板而不繼承?
據我所知不能這樣做. 聽上去不是一個好主意, 我曾試著這麼做想看看能否可行, 但是他並不工作.
7. 使用內建樣式會不會破壞或影響控制項的狀態和其他組件?
不會, 如果您的樣式使用相同的名稱,代碼仍然會選擇一切就好像是一個內聯樣式。