05/09/2018, 14:43

[WPF] Tùy chỉnh ứng dụng với Style và Control Templates – Phần 1

Giới thiệu Về mặt lịch sử, từ Visual Basic đến Windows Forms ngày nay, nếu chúng ta muốn tạo ra một giao diện tùy biến cho ứng dụng, chúng ta bị giới hạn khi thay đổi các thuộc tính chuẩn của controls như màu nền, chiều rộng đường viền hoặc kích thước phông chữ. Nếu muốn đi xa ...

Giới thiệu


Về mặt lịch sử, từ Visual Basic đến Windows Forms ngày nay, nếu chúng ta muốn tạo ra một giao diện tùy biến cho ứng dụng, chúng ta bị giới hạn khi thay đổi các thuộc tính chuẩn của controls như màu nền, chiều rộng đường viền hoặc kích thước phông chữ. Nếu muốn đi xa hơn, lựa chọn duy nhất là tạo ra một control, kế thừa từ control đã có và override phương thức vẽ để đáp ứng nhu cầu riêng.

Với WPF chúng ta vẫn làm được việc này, nhưng không cần thiết. Vì WPF có một mô hình mở rộng dựa trên các styles và templates, cho phép chúng ta thực hiện các thay đổi phức tạp và định nghĩa lại hình ảnh trực quan của một control mà không cần phải tạo mới.

Mục tiêu

Đây là lúc chúng ta bắt đầu

Và đây là những gì chúng ta đạt được

Như bạn thấy, có một sự khác biệt lớn trong giao diện, nó hấp dẫn trực quan. Vậy nên người dùng cảm thấy thoải mái hơn và ứng dụng của chúng ta sẽ có giao điện độc đáo và duy nhất. Vậy bắt đầu làm thôi, nhưng trước tiên, chúng ta cần xem lại một số khái niệm cơ bản về Control Templates and Styles trong WPF.

Sơ lược lý thuyết

WPF cho chúng ta một số cách để tùy chỉnh các điều khiển:

  • Rich Content
  • Styles
  • Data Templates
  • Control Templates

Trong loạt bài này, tôi sẽ tập trung vào Styles và Control Templates.

Styles

Một đối tượng Style là một tập hợp các giá trị thể hiện thuộc tính cho control cụ thể. Chúng ta có thể tự động gán cho tất cả các control cùng loại như button, các thuộc tính nhất định như màu nền, phông chữ, … Bằng cách này tất cả các control cùng loại đều nhận được những giá trị từ style..

Bạn có thể đặt tên cho style và áp dụng nó theo cách thủ công cho những control bạn muốn sử dụng style này

Control templates

Nhiều controls trong WPF sử dụng templates để xác định cấu trúc và giao diện của chúng. Chức năng của control vẫn được giữ nguyên, nhưng hiển thị của nó đã bị thay đổi, và điều này cho phép chúng ta kiểm soát được hoàn toàn việc tùy biến cách hiển thị. Ngoài ra nó còn cho chúng ta chọn một chức năng control để thực hiện một nhiệm vụ, bất kể sự xuất hiện của nó không phù hợp với những gì chúng ta muốn, tuy việc này cần định nghĩa lại hoàn toàn. Đây là một ví dụ đơn giản về Control tempaltes cho button:

Kết quả của template trên nếu bạn sử dụng cho một button (hãy nhớ rằng đây chỉ là một ví dụ cực kỳ cơ bản):

Như bạn thấy, button không còn giữ được giao diện vốn có, nó được thay thế bởi những gì chúng ta muốn, nhưng vẫn giữ được chức năng cơ bản của một button.

Xác định Style của ứng dụng

Một bước quan trọng trong việc xác định style của ứng dụng là duy trì tính nhất quán hiển thị trên các màn hình khác nhau, để người dùng có cảm giác quen thuộc với ứng dụng nhưng phải đối mặt với nhiều loại màn hình.

Một khía cạnh quan trọng nữa là tính đơn giản của việc thực thi. Trong một ứng dụng với hàng trăm control và hàng chục màn hình, chúng ta không thể tinh chỉnh từng control để điều chỉnh các thông số hiển thị, điều đó phải được thực hiện tự động. Với WPF cung cấp cho ta ResourceDictionary, một tệp XAML nơi chúng ta gom tất cả các code của style, và bao gồm nó trong cả ứng dụng. Do đó, bất kỳ thay đổi nào chúng ta thực hiện với style hoặc template sẽ tự động áp dụng cho tất cả controls.

Điều đầu tiên chúng ta làm là thêm một phần tử mới của ResourceDictionary, trong ví dụ này là BlackCrystal.xaml. Đây là tệp XAML mới và khi được đặt vào trong dự án, nó sẽ xuất hiện như thế này:

Một khi ResourceDictionary được thêm vào, đó là lúc để nói với ứng dụng rằng có thể sử dụng nó. Chúng ta có thể làm điều này bằng cách chỉnh sửa Application.xaml để trỏ đến đường dẫn của một ResourceDictionary (file chứa style vừa tạo) mà chúng ta muốn sử dụng. Nó sẽ giống như thế này:

Chúng ta chỉ cần thêm một khai báo mới của Application.Resources, tạo ra một ResourceDictionary mới, cho thấy các đường dẫn (nguồn) từ đó để nạp ResourceDictionary. Với điều này, bất kỳ style hoặc template mà chúng ta tạo ra trong ResourceDictionary sẽ ngay lập tức khả dụng trong toàn bộ ứng dụng.

Common Resources

Một điều quan trọng cần ghi nhớ khi thiết kế giao diện ứng dụng là cung cấp cho nó một sự hiển thị đồng nhất – cùng màu sắc, cùng một hình học và các animation tương tự. Nếu chúng ta định nghĩa các tham số này một cách trực tiếp trong từng style và template, nó sẽ làm cho việc duy trì khó khăn, phải kiếm hàng chục chỗ để thay đổi một style đơn lẻ. Để khắc phục điều này, điều đầu tiên là xác định trong ResourceDictionary tất cả các màu sắc và animation mà chúng ta muốn sử dụng. Ví dụ tôi tạo, Black Crystal, đã sử dụng tổng cộng 9 gradient khác nhau và hai animation áp dụng cho tất cả các controls. Những gradient này là LinearGradient và RadialGradient, animation là DoubleAnimations.

Ở đây bạn có thể thấy một ví dụ của LinearGradient, được sử dụng cho nền của giao diện, áp dụng một sắc độ của màu xám gradient:

Nó rất đơn giản. Dòng đầu tiên định nghĩa loại đối tượng (LinearGradientBrush). chúng ta gán key để sử dụng resource được tạo ra từ bất cứ đâu và chỉ ra điểm bắt đầu và kết thúc. Các điểm này được chỉ định với một cặp số từ 0 đến 1, (0, 0) chỉ góc trên bên trái và (1, 1) góc dưới bên phải. Số đầu tiên trong mỗi cặp cho biết điểm trên trục X và số thứ hai cho biết điểm trên trục Y. Theo cách này, từ (0, 0) tới (1, 1) chỉ ra rằng gradient của chúng ta đi qua đường chéo từ góc trên cùng bên trái đến dưới cùng bên phải. Giá trị của (0, 0) đến (1, 0) sẽ chỉ ra gradient bắt đầu ở góc trên bên trái và kết thúc ở góc trên bên phải,nó có thể có một cạnh gradient ngang từ trái sang phải. Tương tự, giá trị của (0, 0) đến (0,1) sẽ tạo ra hiệu ứng gradient dọc.

Các đối tượng GradientStop cho biết màu sắc đã được sử dụng trong mỗi một phần gradient. Một gradient có thể có nhiều GradientStops như bạn muốn. Thuộc tính Offset cho biết vị trí của màu sắc trong gradient, trong đó 0 là đầu và 1 kết thúc. Chúng ta có thể sử dụng bất kỳ giá trị nào giữa các con số là 0,2, 0,35 hoặc 0,98. Cuối cùng, Color cho biết màu sắc để sử dụng tại điểm đó của gradient. Nó có thể là màu được xác định trước bởi hệ thống, chẳng hạn như Trắng, Tím hoặc Đen, hoặc giá trị màu ARGB có A = Alpha (transparency), R = Màu đỏ, G = Màu Xanh và B = Xanh lam.

Đối tượng RadialGradientBrush rất giống với LinearGradientBrush và bao gồm nhiều đối tượng tương đồng nhau, như sau:

Sự khác biệt lớn nhất ở đây là chúng ta không còn thuộc tính StartPoint và EndPoint, thay vào đó là GradientOrigin và Center. GradientOrigin xác định điểm mà gradient bắt đầu, và Center biểu thị điểm là trung tâm của vòng tròn bên ngoài, vòng tròn đó xác định gradient. Trong ví dụ này, hai thuộc tính có các giá trị giống nhau nhưng cũng có thể khác nhau mà không gặp lỗi nào.

Để áp dụng các resources này cho thuộc tính controls, chúng ta chỉ cần liên kết với một resources tĩnh và chỉ định tên của resources, như sau:

Cuối cùng chúng ta có animation. Trong ví dụ sau, tôi đã sử dụng để tạo hiệu ứng khi di chuyển chuột qua các điều khiển hoặc khi nhận focus vào các control sử dụng phím Tab:

Như bạn thấy, XAML, nó không quá phức tạp. Đầu tiên chúng ta định nghĩa một đối tượng StoryBoard và tạo một cái tên được sử dụng để gọi nó từ các đối tượng khác. Storyboard này chứa animation. Trong WPF có rất nhiều loại animation khác nhau. Phần đầu tiên của tên cho biết loại giá trị có thể chạy, trong trường hợp này sử dụng giá trị Double, nhưng chúng ta cũng có thể tìm thấy BooleanAnimation, CharAnimation, ColorAnimation và nhiều kiểu khác.

Các thuộc tính được xác định rất đơn giản. Tên là tên áp dụng cho animation. BeginTime cho biết thời gian sau khi gọi đến storyboard mà animation bắt đầu.Duration là tổng thời gian để animation hoạt động. TargetName chỉ ra đối tượng mà StoryBoard thực thi. TargetProperty áp dụng animation cho thuộc tính được chỉ định. Cuối cùng, From và To hiển thị giá trị ban đầu và giá trị cuối cùng được áp dụng cho animation này.

Trong trường hợp này animation sẽ được áp dụng cho một đối tượng được gọi là GlowRectangle, và thuộc tính Opacity sẽ thay đổi dần dần trong khoảng 250 phần nghìn giây để chuyển từ 1 (đục hoàn toàn) sang 0 (trong suốt). Việc áp dụng animation được thực hiện thông qua sự kiện kích hoạt của một control. Trong bài tiếp theo của tôi, tôi sẽ giải thích chi tiết hơn về điều này, nhưng nó sẽ là một cái gì đó như thế này: