Properties and Events in XAML

Ở phần trước, chúng ta chỉ xem xét một ví dụ khá là nhàm chán – một cửa sổ trống rỗng. Bây giờ, chúng ta sẽ làm việc với một ví dụ thực tế hơn, một cửa sổ windows bao gồm nhiều controls (xem hình bên dưới). alt

 

Trong ví dụ này, Eight ball window chứa 4 controls, bao gồm một Grid, 2 Textbox và 1 button. Các chi tiết về sắp xếp và thiết đặt những control này phức tạp hơn ví dụ trước. Dưới đây là đoạn mã XAML rút gọn của cửa sổ này, trong đó một vài chi tiết sẽ được thay thế bằng dấu 3 chấm (…):

<Window x:Class="EightBall.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Eight Ball Answer" Height="328" Width="412">
  <Grid Name="grid1">
    <Grid.Background>
    ...
    </Grid.Background>
    <Grid.RowDefinitions>
    ...
  </Grid.RowDefinitions>
    <TextBox Name="txtQuestion" ... >
    ...
    </TextBox>
    <Button Name="cmdAnswer" ... >
    ...
    </Button>
    <TextBox Name="txtAnswer" ...>
    ...
    </TextBox>
  </Grid>
</Window>

Note: XAML không giới hạn các class phải thuộc WPF. Bạn có thể sử dụng XAML để tạo ra một instance của bất kì class nào thỏa một vài điều kiện cơ bản. Bạn sẽ học cách sử dụng class do bạn định nghĩa với XAML ở phần sau của chương này.


Simple Properties and Type Converters

Như bạn đã biết, các thuộc tính (attribute) của một thành phần (element) chính là các properties của đối tượng đó. Ví dụ, text box trong eigth ball thiết lập các thông số về alignment, margin và font như sau:

<TextBox Name="txtQuestion" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" FontFamily="Vernada"
FontSize="24" Foreground="Green" ... >

Để cho đoạn mã này hoạt động, class System.Windows.Controls.TextBox phải cung cấp các property sau: VerticalAlignment, HorizontalAlignment, FontFamily, FontSize và Foreground. Bạn sẽ được học về ý nghĩa cũng như công dụng của các thuộc tính này trong các chương sau.

Như các bạn thấy, các thuộc tính XML luôn có dạng của một chuỗi kí tự. Tuy nhiên, các property của các đối tượng có thể thuộc bất cứ kiểu nào trong .NET. Và để cho chương trình hoạt động, trình phân tích XAML cần phải thực hiện việc chuyển đổi từ giá trị kiểu chuỗi sang một kiểu nonstring. Và việc chuyển đổi này được thực hiện bởi type converters, thành phần cơ bản của .NET được giới thiệu ngay từ phiên bản .NET 1.0.

Bộ chuyển đổi kiểu (type converter) chỉ có một vai trò duy nhất – nó cung cấp các phương thức tiện lợi giúp thực hiện việc chuyển từ một kiểu dữ liệu nhất định của .NET sang một kiểu dữ liệu khác cũng của .NET, như ví dụ ở trên, nó sẽ chuyển từ string sang một kiểu dữ liệu nào khác. XAML parser sẽ tìm ra bộ chuyển đổi kiểu theo 2 bước:

  1. Phân tích các khai báo của property, tìm xem có thuộc tính TypeConverter nào ko (nếu có, TypeConverter attribute sẽ cho biết class nào có thể thực hiện việc chuyển đổi kiểu). Ví dụ, khi sử dụng property Foreground, .NET sẽ kiểm tra khai báo của Foreground property.
  2. Nếu trong khai báo của property không có TypeConverter, XAML parser sẽ kiểm tra trong class có kiểu dữ liệu tương ứng. Ví dụ, Foreground property sử dụng một đối tượng là Brush. Trong class Brush (và các dẫn xuất của nó) sử dụng BrushConverter bởi vì Brush class có xây dựng sẵn attribute TypeConverter(typeof(BrushConverter)).

Nếu không tìm thấy bất kì một bộ chuyển đổi kiểu nào tương ứng, XAML parser sẽ đưa ra một thông báo lỗi.


Note: XAML cũng như các ngôn ngữ dựa trên chuẩn XML đều là case-sensitive. Nghĩa là bạn không thể sử dụng <button> thay cho thẻ <Button>. Tuy nhiên, type converters thường không để ý tới vấn đề này, nghĩa là việc sử dụng Foreground=”White” và Foreground=”white” đều có kết quả như nhau.


Complex Properties

Type converter cho phép bạn khai báo giá trị của các property một cách đơn giản, tuy nhiên không phải bao giờ việc này cũng có thể thực hiện được. Trong một số trường hợp, các property lại là một đối tượng riêng biệt và tất nhiên các đối tượng này cũng có các property của riêng nó. Mặc dù, chúng ta vẫn có thể định nghĩa lại giá trị của property từ kiểu string sang kiểu tương ứng bằng cách sử dụng type converter, nhưng cú pháp để thực hiện việc này thường khá phức tạp và dễ sinh ra lỗi. May mắn là XAML cung cấp cho bạn một giải pháp dễ dàng hơn, đó là property-element syntax. Với property-element syntax, bạn có thể add các element con theo mẫu Parent.PropertyName. Ví dụ, Grid có một thuộc tính là Background cho phép bạn sử dụng brush để tô màu cho nền của control. Nếu bạn muốn sử dụng các kiểu tô màu phức tạp hơn thay vì chỉ dùng một màu duy nhất, bạn cần phải add một thẻ con với tên là Grid.Background:

<Grid Name="grid1">
   <Grid.Background>
      ...
   </Grid.Background>
   ...
</Grid>

Chi tiết quan trọng cần phải lưu ý ở đây chính là dấu . trong tên của element. Dấu chấm này dùng để phân biệt giữa property của element với các kiểu nội dung khác được lồng vào bên trong element cha.

Sau khi bạn đã khai báo complex property mà bạn muốn điều chính, bạn sẽ quan tâm đến việc làm sao để sử dụng nó? Như chúng ta biết, bên trong một element, bạn có thể add thêm các tag khác dùng để thể hiện cho một class nhất định nào đó.

Trong ví dụ eight ball, background được fill với một gradient. Để định nghĩa gradient bạn muốn sử dụng, bạn cần phải tạo ra một đối tượng kiểu LinearGradientBrush.

Với các quy tắc của XAML, bạn có thể tạo ra đối tượng LinearGradientBrush bằng cách định nghĩa một thẻ thành phần có tên là LinearGradientBrush:

<Grid Name="grid1">
   <Grid.Background>
      <LinearGradientBrush>
      </LinearGradientBrush>
   </Grid.Background>
   ...
</Grid>

LinearGradientBrush là một phần nằm trong các namespace của WPF, vì vậy bạn vẫn có thể sử dụng default XML namespace cho các tag. Tuy nhiên, sẽ là chưa đủ nếu chỉ tạo ra đối tượng LinearGradientBrush, bạn cần phải khai báo thêm các màu sắc trong gradient đó. Để thực hiện việc này, bạn sẽ tô màu cho property LinearGradientBrush.GradientStops bằng tập hợp các đối tượng có kiểu GradientStop. Một lần nữa, GradientStops property quá phức tạp, và bạn lại phải sử dụng property-element syntax:

<Grid Name="grid1">
   <Grid.Background>
      <LinearGradientBrush>
         <LinearGradientBrush.GradientStops>
            ...
         </LinearGradientBrush.GradientStops>
      </LinearGradientBrush>
   </Grid.Background>
   ...
</Grid>

Sau cùng, bạn có thể tô màu GradientStops collection với một series các đối tượng GradientStop. Mỗi GradientStop sẽ có một property Offset và một property Color.

<Grid>
       <Grid.Background>
            <LinearGradientBrush>
                <LinearGradientBrush.GradientStops>
                    <GradientStop Offset="0.00" Color="Red"/>
                    <GradientStop Offset="0.50" Color="Indigo"/>
                    <GradientStop Offset="1.00" Color="Violet"/>
                </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
        </Grid.Background>
</Grid>

Bất kì các thẻ XAML nào đều có thể thay thế bằng mã C# và chúng đều thực hiện các công việc như nhau. Đoạn code dưới đây cũng thực hiện việc fill background như ví dụ trên:

GradientStop gradientStop1 = new GradientStop();
gradientStop1.Offset = 0;
gradientStop1.Color = Colors.Red;
brush.GradientStops.Add(gradientStop1);   GradientStop gradientStop2 = new GradientStop();
gradientStop2.Offset = 0.5;
gradientStop2.Color = Colors.Indigo;
brush.GradientStops.Add(gradientStop2);   GradientStop gradientStop3 = new GradientStop();
gradientStop3.Offset = 1;
gradientStop3.Color = Colors.Violet;
brush.GradientStops.Add(gradientStop3);
grid1.Background = brush;  

Markup Extensions

Đối với hầu hết các property, ta có thể set giá trị cho các nó theo cách thông thường. Tuy nhiên, trong một số trường hợp, ta không thể sử dụng cách trên. Ví dụ như, bạn muốn set một đối tượng đã có sẵn nào đó làm value của property, hoặc bạn muốn set value cho một property mà giá trị này có thể thay đổi nhờ vào việc binding vào một property của một control khác. Trong cả hai trường hợp trên, bạn phải sử dụng markup extension – cú pháp đặc biệt dùng để gán giá trị của property theo kiểu nonstandard. Markup extension có thể được sử dụng theo hai cách là nested tags hoặc theo kiểu XML attribute (thường được sử dụng nhiều hơn). Khi sử dụng markup extension theo kiểu attribute, ta phải đặt nó trong cặp dấu ngoặc nhọn { }. Ví dụ dưới đây sẽ minh họa cách sử dụng Static Extension, nó cho phép bạn tham chiếu đến một static property thuộc một class khác:

<Button ... Foreground="{x:Static SystemColors.ActiveCaptionBursh}" >

Cú pháp khi sử dụng Markup Extension là {MarkupExtensionClass Argument}. Trong ví dụ trên, markup extension chính là class StaticExtension. (Thông thường, chúng ta có thể bỏ đi chữ Extension khi tham chiếu đến một extension class.) Prefix x được sử dụng để chỉ ra rằng StaticExtension class được tìm thấy trong một XAML namespace nào đó. Sẽ có trường hợp bạn gặp các markup extension nằm trong WPF namespaces và khi đó sẽ không cần sử dụng x prefix.

Tất cả các markup extension đều là các class dẫn xuất từ System.Windows.Markup.MarkupExtension. Class MarkupExtension được xây dựng rất đơn giản – nó chỉ cung cấp mỗi một phương thức là ProvideValue() cho phép bạn lấy giá trị mà bạn cần. Như trong ví dụ, khi trình phân tích XAML gặp câu lệnh trên, nó sẽ tạo ra một instance của StaticExtension class (sử dụng chuỗi “SystemColors.ActiveCaptionBrush” làm tham số trong constructor) và sau đó gọi phương thức ProvideValue() để lấy đối tượng được trả về bởi static property SystemColors.ActiveCaption.Brush. Thuộc tính Foreground của button cmdAnswer lúc này chính là đối tượng trả về đó. Kết quả cuối cùng của đoạn mã XAML hoàn toàn giống với đoạn mã dưới đây:

cmdAnswer.Foreground = SystemColors.ActiveCaptionBursh;

Ngoài ra, bạn còn có thể sử dụng markup extension theo kiểu nested property (các thuộc tính lồng nhau). Ví dụ dưới đây cũng tương tự như ví dụ trên nhưng sử dụng cách thứ 2:

<Button ... >
   <Button.Foreground>
       <x:Static Member="SystemColors.ActiveCaptionBrush"></x:Static>
   </Button.Foreground>
</Button>

[To be continued]

Xem thêm: