WPF - Tìm Hiểu Về Control Styles Và Templates Trong WPF


Windows Presentation Foundation(WPF) cho phép các nhà phát triển có thể  thay đổi hoàn toàn cái nhìn tổng quan và cách ứng xử của các Control. Điều này được thực hiện bằng cách sử dụng Control-Style và Control-Templates. Điều đó có nghĩa là bạn có một Button nhưng nó không chỉ đơn điệu là một hình chữ nhật mà có thể là một hình ảnh náo đó mà bạn muốn. Ở nội dung này tôi sẽ hướng dẫn các bạn tạo một Control-Templates cho TextBox.




 Trước hết chúng ta cần phân biệt khái niệm StyleTamplates




  • Style: là cách thể hiện của một đối tượng về màu sắc, độ trong suốt.. và các thuộc tính ấy được phủ lên đối tượng - chúng có thể được ghi đè lên các thuộc tính mặc định nếu cần thiết, các Style có thể được áp dụng cho tất cả các Element cùng loại.

  • Template: là cách thể hiện nội dung mang tính "cơ bắp" hơn, nó như một bộ khung được đặc lên trên bộ khung mặc định và không thay thế chúng, các Template chỉ được áp dụng cho các Control.


Một ví dụ cho sự khác biệt giữa StyleTamplates: Style như một bộ đồ, nếu bộ đồ ấy dành cho con người dĩ nhiên phải có tay áo, cổ áo, ống quần... tất cả những thứ đó một người bình thường đều phải có, bộ đồ ấy được khoác lên người và chúng ta sẽ không thể thấy được phần tự nhiên trước đó, trong khi đó Template có thể là một bộ cánh, một chiếc giáp chiến đồ sộ hoặc cũng có thể là một cánh tay thứ ba... Và bộ cánh sẽ mang nhiều tính hành xử hơn một bộ đồ vì thế Styles áp dụng cho Element và Template chỉ áp dụng cho Control.


Các StyleTamplates có thể được định nghĩa trong một thẻ con của đối tượng cần áp dụng(chỉ có tác dụng trong đối tượng đó)




  • <TextBox.Style>[my style]</TextBox.Style>

  • <TextBox.Template>[my template]</TextBox.Template>


hoặc được định nghĩa trong cặp thẻ Resources của thẻ root(phạm vi sử dụng trong thẻ root)




  • <Window.Resources>[my style][my template]</Window.Resources>

  • <Control.Resources>[my style][my template]</Control.Resources>


hay mở rộng phạm vi sử dụng lớn tối đa bằng cách định nghĩa chúng trong một file Resource riêng và đăng ký resource trong file App.xaml


Hôm nay tôi sẽ hướng dẫn các bạn cách tạo Style và Template cho TextBox, các bạn có thể xem các Template và Styles tôi đã làm sẵn dưới đậy.


tuanphamdg Phạm Tuân


Trước tiên chúng ta sẽ Add một file Resource bằng cách Add->New item và chọn Resource(WPF)




[code language="xml"]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d">

<!-- Các resource được được định nghĩa dưới đây-->

<!-- Các resource được được định nghĩa bên trên-->
</ResourceDictionary>
[/code]

Quay lại file App.xaml, chúng ta hoàn thiện việc đăng ký resource như sau




[code language="xml"]
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication1.App"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Resources scoped at the Application level should be defined here. -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Các tập tin resource được đăng ký bên dưới-->
<ResourceDictionary Source="ReDicTemplate.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
[/code]

I> Cách tạo một Style


Tôi thêm một thẻ <Style/> với nội dung hoàn thiện bên dưới, trong đó các property "Key, TargetType, BasedOn" nhất thiết phải có.


Mục đích của Style này là các TextBox áp dụng nó đều sẽ có nền màu xanh và chữ trắng, Padding = 5 và BorderThickness = 1, lượi ích là tôi sẽ tiết kiệm khá nhiều thời gian cho việc quy định các thuộc tính trên cho 1000 TextBox giống nhau.




[code language="xml"]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d">

<!-- Các resource được được định nghĩa dưới đây-->
<!-- Một Style phải có x:Key-->
<Style x:Key="TextBoxStyle1" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Green"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- Có thể định nghĩa một Template như thế này-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Các resource được được định nghĩa bên trên-->
</ResourceDictionary>
[/code]

Key --> là mã nhận dạn cho một đối tượng trong ngôn ngữ XAML, được dùng khi muốn áp dụng style
BasedOn --> Chỉ ra một style parent bạn muốn kế thừa, tôi không kế thừa từ style nào cả
TargetType --> Chỉ ra một kiểu đối tượng có thể áp dụng style này, ở đây tôi dùng cho TextBox

Quay về Control chúng ta muốn áp dụng Style và set propery Style của control đó với giá trị là Key của Style trên.




[code language="xml"]
<TextBox Margin="10,10,10,0" Tag="My Name"
Text="Pham Tuan" VerticalAlignment="Top"
Style="{DynamicResource TextBoxStyle1}"/>
[/code]

Kết quả
tuanphamdg
Như vậy là xong phần Style, để có một style thật sự đẹp mắt chúng ta cần phải sử dụng nhiều kỹ thuật khác nhau Binding, Trigger, Storybroad ... tôi sẽ hướng dẫn ở một bài khác.



II> Cách tạo một Template


Template có hai dạng: Control Template và Data Template




  • Control Template: Là cách thức hiển thị của một Control, tức vẽ bề ngoài của chính nó

  • Data Template: Là cách hiển thị của phần dữ liệu mà nó chứa đựng, tức vẽ bề ngoài của các Item khi nó hiển thị trong lòng mình(Thường dùng với các control như ListBox, GridView, ComboBox...)


A> ControlTemplate


Tôi thêm một thẻ <ControlTemplate/> với nội dung hoàn thiện bên dưới, trong đó các property "Key, TargetType" nhất thiết phải có.
Mục đính tôi tạo một Template vì tôi muốn có một TextBox với phần Title hiển thị phía trước và phần nhập nội dung lền kề bên phải, phần cuối cùng bên phải TextBox có một Icon làm cảnh, màu nền phần Title trùng màu viền của TextBox(Nên tôi dùng TemplateBinding BorderBrush), nội dung Title được Binding đến thuộc tính Tag của TextBox(Nên tôi dùng TemplateBinding Tag)




[code language="xml"]
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d">

<!-- Các resource được được định nghĩa dưới đây-->
<!-- Phần Style ở trên sẽ ở đây-->
<!-- Một ControlTemplate phải có x:Key-->
<ControlTemplate x:Key="TextBoxBaseTemplate" TargetType="{x:Type TextBoxBase}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" MinWidth="100" Content="{TemplateBinding Tag}" Padding="4,1,4,2" VerticalContentAlignment="Center" Background="{TemplateBinding BorderBrush}" Foreground="White"/>
<ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Grid.ColumnSpan="1" Grid.Column="1" VerticalContentAlignment="Center" Padding="0,0,0,2"/>
<Label FontFamily="Webdings" Content="." VerticalContentAlignment="Center" Padding="5,0" Foreground="White" Margin="0" Grid.Column="3" Background="{TemplateBinding BorderBrush}"/>
</Grid>
</Border>

</ControlTemplate>
<!-- Các resource được được định nghĩa bên trên-->
</ResourceDictionary>
[/code]

Border  --> kể từ thẻ này đến các thẻ con là phần chúng ta thiết kế
ScrollViewer --> là thẻ mặc định của TextBox chịu trách nhiệm hiển thị và nhận key nhập từ bản phím, tôi giữ nguyên như mặc định.
Chú ý --> Chúng ta sẽ bắc gặp một định dạng như sau Content="{TemplateBinding Tag}" ý nghĩa như sau:  TemplateBinding là một kiểu DataBinding nhưng Binding đến các thuộc tính của một Control đã được chỉ ra tại thuộc tính TargetType.

Cũng như Style, chúng ta áp dụng template cho một control bằng cách gọi key của nó




[code language="xml"]
<TextBox Margin="10,10,10,0" TextWrapping="Wrap" Tag="My Name"
Text="Pham Tuan" VerticalAlignment="Top" BorderBrush="#FF007ACC"
Template="{DynamicResource TextBoxBaseTemplate}" Padding="0,2"/>
[/code]

Kết quả
tuanphamdgB> DataTemplate


Tôi xin nhắc lại rằng, DataTemplate dùng để thể hiện cho một Item trong ItemSource của các control có khả năng chứa các list item như ComboBox, ListBox, GridView...


Ở ví dụ này tôi có một class Per chứa thông tin một người gồm Name, YearOld, Address, Title, nếu tôi dùng một ListBox đơn thuần - tôi sẽ hiển thị được duy nhất một nội dung tương ứng một property trong các property của đối tượng Per cho mỗi item của ListBox.




[code language="csharp"]
internal class Per
{
public string Title { set; get; }
public string Name { set; get; }
public int YearOld { set; get; }

public string Address { set; get; }
}

public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();

var list = new List<Per>()
{
new Per() {Name = "Phạm Tuân",Title = "Quán quân" ,Address = "Phước Long B, Q9", YearOld = 21},
new Per() {Name = "Phạm Văn Chu", Title = "Á quân",Address = "Phước Long A, Q9", YearOld = 22}
};

listBox.ItemsSource = list; //ListBox trên
listBox2.ItemsSource = list; //ListBox dưới
}
}
[/code]

[caption id="attachment_294" align="aligncenter" width="660"]DataTemplate No-DataTemplate[/caption]

Nhưng tôi muốn hiển thị trong ListBox nhiều thông tin hơn thế thì làm thế nào? Tôi tiến hành thiết kế DataTemplate, tôi add dataTemplate cho ListBox , với mỗi Item của ListBox sẽ chứa 3 TextBox hiển thị 3 nội dung khác nhau được Binding đến 3 thuộc tính của một Item như sau trong ListBox.ItemSource.




[code language="xml"]
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Tiến hành thiết kế Start-->
<!--Thiết kế cho một Item của ListBox Start-->
<GroupBox Header="{Binding Title}"> <!--Binding đến thuộc tính Title-->
<StackPanel x:Name="panel" HorizontalAlignment="Stretch" >
<!--TextBox1 Binding đến thuộc tính Name-->
<TextBox Margin="2,2,2,0" Text="{Binding Name}"/>
<!--TextBox1 Binding đến thuộc tính YearOld-->
<TextBox Margin="2,2,2,0" Text="{Binding YearOld}" />
<!--TextBox1 Binding đến thuộc tính Address-->
<TextBox Margin="2,2,2,2" Text="{Binding Address}"/>
</StackPanel>
</GroupBox>
<!--Thiết kế cho một Item của ListBox End-->
<!-- Tiến hành thiết kế End-->
</DataTemplate>
</ListBox.ItemTemplate>
[/code]

Kết quả


dâttemplate


Để cho đẹp hơn(theo cách cảu tôi), 3 TextBox kia tôi sẽ apply cho nó 3 ControlTemplate tôi đã thiết kế trước đó.




[code language="xml"]
<ListBox.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding Title}">
<StackPanel x:Name="panel" HorizontalAlignment="Stretch" >
<TextBox Margin="2,2,2,0" TextWrapping="Wrap" Tag="Name" BorderThickness="0"
Text="{Binding Name}" VerticalAlignment="Top" VerticalContentAlignment="Center"
BorderBrush="Green" Template="{DynamicResource TextBoxBase2}"
Background="{x:Null}" Padding="0,2"/>
<TextBox Margin="2,2,2,0" TextWrapping="Wrap" Tag="Year-Old"
Text="{Binding YearOld}" VerticalAlignment="Top" VerticalContentAlignment="Center"
BorderBrush="#FF007ACC" Template="{DynamicResource TextBoxBaseTemplate}"
Background="{x:Null}" Padding="0,2"/>
<TextBox Margin="2,2,2,2" TextWrapping="Wrap" Tag="Address"
Text="{Binding Address}" VerticalAlignment="Top" VerticalContentAlignment="Center"
BorderBrush="#FF007ACC" Template="{DynamicResource TextBoxBaseTemplate}"
Background="{x:Null}" Padding="0,2"/>
</StackPanel>
</GroupBox>
</DataTemplate>
</ListBox.ItemTemplate>
[/code]

Kết quả là


datatemplate


Các bạn có thể tham khảo sourcecode, hãy đoán đọc các kỹ thuật tiếp theo để có một Template thật sự linh hoạt và thân thiện.
Chúc các bạn thành công!
Phạm Tuân


Chúc các bạn thành công!
PHẠM TUÂN