Tìm kiếm Blog này

Thứ Bảy, 15 tháng 1, 2011

Học WPF 4 trong 1 tuần – Ngày thứ 5: Templates and Styles

Đây là bài viết cuối trong loạt bài học WPF trong 1 tuần. Hy vọng trong một tuần chúng ta có thể nắm bắt được những kiến thức cơ bản của WPF.
Bài viết này một số vấn đề về mẫu và kiểu cách (templates and styles) như: giới thiệu về style trong WPF, Control templates, Data templates.
1. Giới thiệu về kiểu cách trong WPF (Introduction to Styles in WPF)
1. Giới thiệu (introduction)
Hãy tưởng tượng bạn muốn tạo ra một ứng dụng có thiết kế độc đáo. Tất cả cimageác nút phải có một nền màu cam và font chữ nghiêng. Nếu làm theo cách thông thường dưới đây thì bạn chỉ có thể thiết lập thuộc tính background và fontstyle trên một nút duy nhất.
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
     <Button Background="Orange" FontStyle="Italic"
Padding="8,4" Margin="4">Styles</Button>
<Button Background="Orange" FontStyle="Italic"
Padding="8,4" Margin="4">are</Button>
<Button Background="Orange" FontStyle="Italic"
Padding="8,4" Margin="4">cool</Button>
</StackPanel>
- Trong trường hợp muốn tạo một style chung cho các button như trong trường hợp trên thì code dài dòng. Giải pháp cho vấn đề này là styles.
- Khái niệm về styles cho phép bạn loại bỏ tất cả các giá trị thuộc tính từ các giao diện thành phần của người dùng cá nhân để kết hợp chúng thành 1 style. Một style bao gồm một danh sách các setters. Nếu bạn áp dụng style này cho một phần tử nó sẽ thiết lập tất cả các giá trị thuộc tính với các giá trị quy định. Ý tưởng này khá giống với CSS (Cascading Style Sheet) mà chúng ta thường bắt gặp khi nói đến phát triển web.
- Để tạo các style cho các control, bạn cần phải thêm vào các resource. Bất kỳ control nào trong WPF đều có một resource, đó là một kế thừa cho tất cả các control. Đó là lý do tại sao chúng ta phải chỉ định một thuộc tính   x: Key= “myStyle”  để định nghĩa một resource định danh duy nhất.
- Để áp dụng các Style cho các control chúng ta cần thiết lập các thuộc tính style cho style của chúng ta. Để lấy nó từ resource chúng ta sử dụng  đánh dấu mở rộng  {StaticResource [resourceKey]} .
<Window>
<Window.Resources>
<Style x:Key="myStyle" TargetType="Button">
<Setter Property="Background" Value="Orange" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Padding" Value="8,4" />
<Setter Property="Margin" Value="4" />
</Style>
</Window.Resources>

    <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Button Style="{StaticResource myStyle}">Styles</Button>
<Button Style="{StaticResource myStyle}">are</Button>
<Button Style="{StaticResource myStyle}">cool</Button>
</StackPanel>
</Window>
- Những gì mà chúng ta đạt được là:
+ Mã lệnh cơ sở được duy trì (a maintainable code base).
+ Loại bỏ các dư thừa (removed the redundancy).
+ Thay đổi giao diện và các thiết lập của control chỉ từ 1 điểm duy nhất.
+ Khả năng thay đổi các style trong thời gian chạy.
2. Kế thừa Sytle (Style inheritance)
- Một style trong WPF có thể là lớp cơ sở cho các style khác. Điều này cho phép bạn chỉ định một style cơ sở với các thiết lập thuộc tính thông dụng  và xuất phát từ đó để thiết kế các control riêng.
<Style x:Key="baseStyle">
<Setter Property="FontSize" Value="12" />
<Setter Property="Background" Value="Orange" />
</Style>

<Style x:Key="boldStyle" BasedOn="{StaticResource baseStyle}">
<Setter Property="FontWeight" Value="Bold" />
</Style>
2. Control Templates
2.1 Giới thiệu (introduction)
- Các control trong WPF được tách thành các logic, nó định nghĩa các states, các sự kiện (events), các thuộc tính và mẫu(template), để định nghĩa giao diện trực quan của control. Các kết nối giữa logic và template được thực hiện nhờ DataBinding.
- Mỗi một control có một template mặc định, để các control có một giao diện cơ bản. Các template mặc định thường được neo cùng với các control và available cho tất cả các cửa sổ thông thường. Đó là bởi quy ước về style, nó được xác định bởi giá trị thuộc tính của DefaultStyleKey mà mỗi control có.
- Mẫu định nghĩa bởi các thuộc tính phụ thuộc được gọi là Template. Bằng cách thiết lập thuộc tính cho một control template, bạn hoàn toàn có thể thay thế giao diện  (visual tree) của một control.
image
- Các control template thông thường bao gồm một style có chứa các thuộc tính cài đặc khác. Đoạn code sau đây sẽ cho chúng ta thấy cách tạo một control template đơn giản là một button hình ellipse.
<Style x:Key="DialogButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding BorderBrush}"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button Style="{StaticResource DialogButtonStyle}" />
image
2.2 ContentPresenter
- Khi bạn tạo một control template của mình rồi, bạn muốn xác định một nơi để hiển thị các nội dung. Bạn có thể sử dụng ContentPresenter. Theo mặc định nó sẽ bổ dung thêm các thuộc tính Nội dung cho visual tree của template. Để hiển thị nội dung của các thuộc tính khác bạn có thể thiết lập ContentSource với tên của thuộc tính mà bạn thích.
image
2.3 Triggers:
{RelativeResource TemplatedParent} không làm việc trong DataTrigger của ControlTemplate.
- Nếu bạn muốn gắn kết một thuộc tính của một thuộc tính trên control của bạn như Data.Isloaded bạn không thể sử dụng một Trigger bình thường, vì nó không hỗ trợ ký hiệu này, do đó bạn phải sử dụng một DataTrigger.
- Nhưng khi bạn sử dụng một DataTrigger với {RelativeSource TemplateParent} nó sẽ không làm việc. Lý do là, TemplateParent chỉ có thể được sử dụng trong ControlTemplate. Nó không phải làm việc trong phần Trigger. Bạn phải sử dụng {RelativeSource Self}.
Cái gì sẽ xảy ra nếu một gắn kết (Binding) hay một Setter không được áp dụng khi sử dụng control template.
- Bạn cần biết một điều là khi cài đặt giá trị của một phần tử trong một control template: Giá trị có ưu tiên thấp hơn giá trị cục bộ. Vì vậy, nếu bạn nếu bạn đặt giá trị cục bộ trong việc khởi tạo của phần tử, bạn không thể ghi đề lên nó trong trong controltemplate. Nhưng nếu bạn sử dụng các yếu tố trực tiếp trong view của bạn nó sẽ làm việc. Do đó bạn phải nhận thức được hành vi này.
3. Data Templates
3.1 Giới thiệu (introduction)
– Khái niệm về Data Template tương tự như Control Template. Nó cung cấp cho chúng ta một giải pháp rất linh hoạt và giải pháp mạnh mẽ để thay thế giao diện trực quan của một mục dữ liệu trong control giống như Listbox, ComboBox hay ListView. Đây là một điểm mạnh của WPF.
– Nếu bạn không chỉ định một data template, WPF có các mẫu đó là các TextBlock. Nếu bạn gắn kế các đối tượng phức tạp với control, nó chỉ gọi ToString() trên đó. Trong một DataTemplate, các DataContext thiết lập các đối tượng dữ liệu. Vì vậy, bạn có thể dễ dàng gắn kết (bind) lại các data context để hiển thị các thành viên khác nhau của đối tượng dữ liệu.
3.2 DataTemplate trong hành động: xây dựng một PropertyGrid đơn giản
- Để hiển thị các dữ liệu phức tạp đối với một ListBox trong WindowsForm là hết sức khó khăn, nhưng nó lại là công việc cực kỳ dễ dàng (super easy) đối với WPF. Ví dụ sau đây minh họa một ListBox với một danh sách của DependencyPropertyInfo được gắn vào nó. Với DataTemplate bạn chỉ cần xem kết quả của các cuộc gọi ToString() trên đối tượng. Với các data template chúng ta thấy tên của các thuộc tính và một textbox và thậm chí cho phép chúng ta chỉnh sửa giá trị.
image
<!-- Without DataTemplate -->
<ListBox ItemsSource="{Binding}" />
<!-- With DataTemplate -->
<ListBox ItemsSource="{Binding}" BorderBrush="Transparent"
Grid.IsSharedSizeScope="True"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="Bold"  />
<TextBox Grid.Column="1" Text="{Binding Value }" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
3.3  Làm thế nào để sử dụng một DataTemplateSelector để chuyển đổi các Template tùy thuộc vào dữ liệu.
- Các thuộc tính Grid của chúng ta tuy đẹp nhưng nó có thể được sử dụng nhiều hơn nữa nếu chúng ta có thể chuyển đổi biên tập tùy thuộc vào loại thuộc tính.
- Các đơn giản để làm điều này là sử dụng một DataTemplateSelector. DataTemplateSelector có một phương thức đơn để ghi đè là: SelectTemplate(object item, DependencyObject container). Trong phương thức này, chúng ta quyết định cung cấp các item DataTemplate để lựa chọn.
- Các ví dụ sau đây minh họa một DataTemplateSelector để giải quyết cây data template.
public class PropertyDataTemplateSelector : DataTemplateSelector{
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DependencyPropertyInfo dpi = item as DependencyPropertyInfo;
if (dpi.PropertyType == typeof(bool))
{
return BooleanDataTemplate;
}
if (dpi.PropertyType.IsEnum)
{
return EnumDataTemplate;
}

        return DefaultnDataTemplate;
}
}
<Window x:Class="DataTemplates.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:DataTemplates"
xmlns:sys="clr-namespace:System;assembly=mscorlib">

    <Window.Resources>

        <!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplate">
...
</DataTemplate>

        <!-- DataTemplate for Booleans -->
<DataTemplate x:Key="BooleanDataTemplate">
...
</DataTemplate>

        <!-- DataTemplate for Enums -->
<DataTemplate x:Key="EnumDataTemplate">
...
</DataTemplate>

        <!-- DataTemplate Selector -->
<l:PropertyDataTemplateSelector x:Key="templateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplate}"
BooleanDataTemplate="{StaticResource BooleanDataTemplate}"
EnumDataTemplate="{StaticResource EnumDataTemplate}"/>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding}" Grid.IsSharedSizeScope="True"
HorizontalContentAlignment="Stretch"
ItemTemplateSelector="{StaticResource templateSelector}"/>
</Grid>
</Window>
3.4 Làm thế nào để tương tác với IsSelected trong DataTemplate.
- Nếu bạn muốn thay đổi giao diện của một ListBoxItem một khi nó được chọn, bạn phải gắn kết thuộc tính IsSelected của ListBoxItem này.
<DataTemplate x:Key="DefaultDataTemplate">
<Border x:Name="border" Height="50">
...
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource= 
                                                                      {RelativeSource Mode=FindAncestor, AncestorType=
                                                                            {x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<Setter TargetName="border" Property="Height" Value="100"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Tất cả bài viết được tham khảo website wpftutorial.net
Đây là bài viết cuối trong loạt bài học WPF 4 trong 1 tuần. Các bài viết sau sẽ đi chi tiết hơn từng vấn đề của WPF.

Không có nhận xét nào:

Đăng nhận xét