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

1536

Giới thiệu

Trong phần trước, chúng ta đã học nhiều cách sử dụng WPF khác nhau để tùy biến các control, chủ yếu là styles và control template. Chúng ta đã tổng quát kiến thức cơ bản về những phần tử thông dụng và resources dictionaries.

Nếu bạn đã quên hoặc muốn đọc lại bài viết thì link đây:

phần 1.

Lần này chúng ta sẽ đi vào trọng tâm vấn đề, bằng cách xem xét kĩ cách tùy chỉnh một số các control chuẩn của WPF:

  • Button
  • Textbox
  • Scrollbar (horizontal & vertical)
  • ProgressBar

BUTTON

Button là 1 trong những control dễ tùy chỉnh, nhưng nó cũng là một trong những control được sử dụng thông dụng nhất và tương tác với người dùng nhiều nhất. Vậy nên chúng ta cần làm nó “lung linh” nhất có thể :

 

 

 

 

Một điều quan trọng cần ghi nhớ khi tạo templates riêng là chúng ta phải hoàn toàn thay thế template mặc định của control, vậy nên nếu bạn muốn đưa ra dấu hiệu hiển thị của button như là kích hoạt, hủy kích hoạt, đã bấm với focus hoặc di chuyển chuột trên đó, chúng ta làm như thế này. Chúng ta làm điều đó bằng cách tận dụng lợi thế của Triggers của control. Đầu tiên chúng ta cần định nghĩa lại style mới cho tất cả controls của loại Button. Chúng ta không muốn áp dụng nó một cách chọn lọc cho những button cố định, chúng ta muốn nó hiển thị mặc định của bất kì button trên ứng dụng:

Trong header, chỉ cần thiết lập thuộc tính TargetType thành Button.

Bây giờ đặt thêm vài thiết lập mặc đinh cho button với các đoạn code trong block:

Đối tượng Setter là môt thuộc tính Property để chỉ định tên loại thuộc tính chúng ta khởi tạo và thuộc tính Value dùng để chỉ định giá trị được áp dụng cho thuộc tính đó. Thuộc tính OverridesDefaultStyle bắt buộc control không bao giờ sử dụng theme mặc định của control cho tât cả thuộc tính, thậm chí nếu không được định nghĩa theo style hoặc templates của chúng ta hoặc nếu chúng ta không thực thi bất kì điều gì. Đó là bởi vì chúng ta không cần hoặc nếu không chúng ta cung cấp. SnapsToDevicePixels cho biết style như là bản vẽ của control nên sử dụng điểm ảnh phụ thuộc vào thiết bị.

Theo mặc định, WPF sử dụng DPI hiện nay cho giải pháp tách biệt khi nói tới vẽ, nhưng hiệu ứng này có thể gây ra một bản vẽ mờ hoặc rườm rà, và trong trường hợp này, chúng ta muốn control nhìn thẩm mĩ. Vì vậy, để tránh điều cần xác định thuộc tính gây mờ. Foreground, không có nhiều điều để nói, thuộc tính Foreground ảnh hưởng đến văn bản. Bởi vì button là màu tối và màu chữ mặc định là màu đen, sẽ rất khó chịu khi phải kiểm tra tất cả các button và thay đổi màu văn bản thành màu trắng hoặc màu khác,do đó chỉ cần khai báo như trên thì màu văn bản trong tất cả button đều màu trắng.

Đối tượng Setter cuối cùng chúng ta cần phải thiết lập thuộc tính Template của button và trong Setter này xác định Control Template :

Khác với style, với cách sử dụng TargetType trong ControlTemplate thì thiết lập x: Names khi nó được chứa trong style và được áp dụng tự động ở bất cứ nơi nào style được gọi.

Với ControlTemplate có thể  định nghĩa lại button, đây là điểm mà WPF sẽ khiến bạn ngạc nhiên vì ControlTemplate là một blank slate trên đó bạn có thể vẽ bất cứ điều gì khác mà bạn muốn và kết quả cuối cùng sẽ là giao diện của button theo ý riêng của bạn. Không có quy tắc (hoặc không nhiều), chỉ cần sự sáng tạo.

Button của chúng ta sẽ được tạo thành bởi 6 control cơ bản: một <code> Grid để giữ mọi thứ lại với nhau, 4 Borders để tạo hình dạng và nơi chứ văn bản, ContentPresenter sẽ trình bày nội dung của Button. Xem ví dụ sau:

Chúng ta có một grid chứa các BordersContentPresenter , Cạnh BaseRectangle đầu tiên là lớp màu ngoài cơ bản, như bạn thấy tất cả các màu được định nghĩa bằng liên kết đến resource đã được thiết lập trong resource dictionary của chúng ta. Bằng cách này nếu thay đổi BaseColor, màu sắc sẽ thay đổi ở tất cả các nơi nó được áp dụng. Borders đều có cùng giá trị cho control. CornerRadius có bốn giá trị, được sắp xếp làm theo thứ tự chiều kim đồng hồ:

  • Góc trên bên phải
  • Góc trên bên trái
  • Góc dưới bên trái
  • Góc dưới bên phải

Sử dụng giá trị 10 ở phía trên bên phải và phía dưới bên trái, và giá trị 0 ở các nơi khác, chúng ta nhận được giao diện mong muốn. Thuộc tính Background của hai Borders đầu tiên xác định cái nhìn tổng quát của button, Border đầu tiên dùng màu cơ bản (BaseColor từ resource) và thứ hai sử dụng một gradient gọi là GlassFX làm cho button có hiệu ứng gương thủy tinh. Border thứ 3 sử dụng resource GlowFX và là animation được sử dụng cho focus button theo mặc định. chúng ta thiết lập Opacity là 0, để kích hoạt thuốc tính này sau với EventTriggers . Border cuối cùng đinh nghĩa bên ngoài của button, trong trường hợp này không xác định thuộc tính Background vì vậy nó là trong suốt nhưng BorderBrush định nghĩa thuộc tính thiết lập màu đường viền và BorderThickness thiết lập độ dày cạnh. Lúc này giao diện của button gần như hoàn chỉnh, chúng ta chỉ cần hiển thị nội dung trên mỗi button, dù là văn bản, hình ảnh hay bất kỳ sự kết hợp nào. Đối với điều này chúng ta sử dụng control ContentPresenter. Thuộc tính thú vị hơn là Content, được liên kết trực tiếp với nội dung button:

  • Binding Path: Cho biết thuộc tính mà chúng ta liên kết.
  • RelativeSource: Chỉ ra resource của thuộc tính này, trong trường hợp đã đi đến source trên template của parent

Vậy là đã hoàn thành giao diện của một button. Nếu đặt một button trên form bất kì, chúng ta thấy rằng style được áp dụng tự động. Tuy nhiên, khi chạy, lưu ý rằng button không phản ứng bằng bất kỳ cách nào khi bạn di chuột qua hoặc nhấp chuột, việc này không trực quan cho người dùng cuối. Điều này chúng ta sẽ giải quyết bằng cách sử dụng Triggers của ControlTemplates. Ngay sau khi Grid, thêm một đối tượng ControlTemplate.Triggers:

Như bạn thấy trong đoạn code có hai loại trigger: TriggerEventTrigger

Trigger được kích hoạt khi thuộc tính được chỉ định có giá trị đã cho trước, trong khi EventTriggers được kích hoạt khi sự kiện đã chỉ định được ném ra. Sự khác biệt lớn nhất là với EventTriggers chúng ta có thể khởi động các animation như chỉ thị trực quan về sự thay đổi trạng thái control.

Trong trường hợp button của chúng ta, nó đáp ứng với bốn sự kiện: MouseEnter, MouseLeave, LostFocusGotFocus.

Mỗi định nghĩa một EventTrigger.Action. EventTrigger và trong BeginStoryboard này tìm thấy đối tượng bắt đầu animation được hiển thị trên thuộc tính StroryBoard (trong chương đầu chúng ta đã nói về animation  và giải thích cách tạo các animation đơn giản).

Chúng ta cũng theo dõi sự thay đổi của hai thuộc tính: IsPressedIsEnabled.

Mỗi trigger sẽ được khởi chạy khi thuộc tính có giá trị được chỉ ra ở trên (khi IsPressedtrue và khi IsEnabledfalse) và được thực hiện thông qua đối tượng Setter đến các giá trị thuộc tính khác. Mỗi đối tượng định nghĩa Setter Property cho biết tên của thuộc tính để sửa đổi. TargetName cho biết tên đối tượng của cái bị ảnh hưởng bởi sự sửa đổi và value cho biết giá trị mới mà nó sẽ nhận.

Nếu chúng ta chạy projec, chúng ta thấy rằng, nhờ Triggers, button phản ứng khi bạn di chuột qua nó, mất focus trên mouse, nhận hoặc mất focus trên tab, nhấn và tắt nó; Phản hồi trực quan mà chúng ta coi là rất quan trọng đối với người dùng cuối .

TEXTBOX

Đối với control Textbox, chúng ta thực hiện giống như button, sự thay đổi lớn nhất trong thành phần control là  thay vì sử dụng control ContentPresenter, chúng ta sử dụng control ScrollViewer, cho phép người dùng sử dụng các thanh cuộn theo chiều dọc và ngang trong một textbox có nhiều dòng:

 

 

 

 

Xaml code cho ScrollViewer :

Trong trường hợp này ngoại trừ ScrollViewer Opacity, tất cả các thuộc tính khác (Content, HorizontalAlignment, VerticalAlignment, WidthHeight) đã được liên kết với parent của control template cho biết bất kỳ thuộc tính nào được chia sẻ giữa một control template và control parent có thể được liên kết .

Tên được định nghĩa của ScrollViewer không phải là ngẫu nhiên,control này được liên kết với ScrollViewer trong template gốc. Đây là một giới hạn nhỏ và là một trong số ít chúng ta tìm thấy trong WPF. Để giữ mọi thứ hoạt động đúng, chúng ta phải giữ nguyên tên, trong trường hợp này là PART_ContentHost. Microsoft khuyến cáo rằng bạn luôn phải sử dụng đối tượng template cho cái mà chúng ta gọi là PART_Xxx do đó theo một danh pháp chung.

Ở đây các Triggers được sử dụng đơn giản hơn các button, chúng ta sử dụng hai EventTrigger LostFocusGotFocus , Trigger  thuộc tính IsEnabled:

Scrollbars

Control Scrollbars thì rắc rối hơn tí so với hai control đầu tiên bởi vì nó liên quan đến các bộ phận chuyển động và có thể thay đổi hướng từ dọc sang ngang

 

 

 

 

Đối với control  này, chúng ta phải xác định một số styles và hai ControlTemplates riêng biệt, một cho thanh dọc và một cho thanh ngang.

Đầu tiên xác định style của các control tạo nên một scrollbar,  LineButtons, PageButtonsScrollThumb:

LineButton

LineButtons là các nút trên đầu của thanh cuộn, style của control là RepeatButtonControlTemplate rất đơn giản:

Nó chỉ bao gồm hai control – một border xác định giao diện của control, và chứa một đối tượng Path. Dữ liệu sở hữu kết hợp với thuộc tính Content của control sẽ thêm template này. Điều làm nên đối tượng Path được cho bởi thuộc tính Data . Nó có một “ngôn ngữ mini” để xác định hình dạng phức tạp một cách nhanh chóng và dễ dàng. Bạn có thể tìm thấy một tài liệu tham khảo bao quát trên MSDN. Tên của nó được xác định bởi các hành động nó thực hiện, bởi vì mỗi khi bạn bấm một LineButton nó di chuyển lên hoặc xuống một dòng trên control mình sử dụng.

PageButtons

Các PageButtons hoạt động như background của bar mà chúng ta di chuyển trên scrollbar. Như tên cho thấy, nó có trách nhiệm di chuyển về phía trước hoặc phía sau một trang đầy đủ. Kiểu điều khiển cũng là RepeatButton.

Control template của nó rất đơn giản và bao gồm một đối tượng Border:.

Như bạn thấy, cả template của LineButton cũng như PageButton có cùng tên, điều này là do các template được nhúng trong một style, phạm vi của nó là style và không va chạm.

ScrollThumb

ScrollThumb là phần di chuyển của thanh cuộn, nó được thay đổi kích cỡ khi cần thiết nhiều lần cuộn hoặc ít hơn và có thể được nhấp và kéo để di chuyển nội dung. Kiểu control được sử dụng là Thumb, vì nó bao gồm các tính năng kéo và thả . Template control cũng phức tạp hơn các Button trước đó:

Chúng ta có một Grid sẽ chứa các control, một hình chữ nhật làm nền tảng gốc cho Thumb, một Border để cho nó một hiệu ứng thủy tinh và trong đối tượng Path này để chúng ta bao gồm một hình học tùy chỉnh nếu muốn.

Bây giờ đã xác định các template và style dựa trên tính toán của chúng ta, chúng ta có thể tiếp tục xác định các template của cả thanh cuộn dọc và ngang Scrollbar:

Vertical Scrollbar

Đây là một control template phức tạp hơn, bao gồm nhiều control hơn. Để bắt đầu chúng ta có một Grid sẽ xác định 3 hàng. Các đầu tiên và cuối cùng có chứa các nút lên và xuống bằng cách sử dụng RepeatButtons, mà chúng ta áp dụng style đã tạo ra LineButton trước đó . Ở giữa Grid, xác định đối tượng Track, vì phần này của thanh cuộn được liên kết với code có cùng tên với bản gốc: PART_Track, và bên trong nó chúng ta có ba thuộc tính điền vào: DecreaseRepeatButton, ThumbIncreaseRepeatButton.

Trong IncreRepeatButton xác định DecreaseRepeatButton, RepeatButtons, PageButton style mà bạn đã tạo ra trước đó và định nghĩa một control Thumb Thumb ScrollThumb style. Một cái gì đó để xem xét là nội dung của style được sử dụng trong RepeatButtonLineButton, các kết hợp của chữ cái và số là “ngôn ngữ mini” của hình học bằng cách sử dụng đối tượng PATH sử dụng để vẽ hình dạng. Nó rất hữu ích và thú vị, phần của LineButton style bạn có một liên kết đến một mô tả đầy đủ của ngôn ngữ này.

Horizontal Scrollbar

Thanh cuộn ngang giống với thanh cuộn dọc, chỉ thay vì chia Grid thành các hàng mà chúng ta chia thành các cột, sử dụng cột đầu tiên và cuối cùng của LineButtons và cột trung tâm cho Track:

Cuối cùng chúng ta chỉ cần xác định một style thông dụng cho control Scrollbar để quyết định template nào áp dụng phụ thuộc vào hướng được chỉ định bởi điều khiển:

Đây là một phong cách đơn giản, đơn giản chỉ có hai trigger điều khiển thuộc tính Orientation, phụ thuộc vào giá trị của nó và áp dụng template hoặc hình thức khác.

ProgressBar

Thanh tiến trình là một control rất hữu ích để cung cấp thông tin người dùng về việc thực hiện một nhiệm vụ cần có thời gian. Trong trường hợp này, ngoài giao diện hấp dẫn hơn, chúng ta cũng làm cho nó nhiều chức năng hơn, cho phép người dùng xem phần trăm công việc được thực hiện bằng số. Chúng ta cũng hỗ trợ cho một tác phẩm có thời gian thực hiện không xác định:

 

 

 

Trong hình trên, chúng ta có thể thấy ba trạng thái của một ProgressBar: Xác định, Chưa được xác định và vô hiệu hóa. Như bạn thấy ở một số trạng thái cung cấp một thông tin % được hiển thị dưới dạng số. Control template này rất giống với một số control khác mà chúng ta đã thấy:

Template dài nhưng không phức tạp, thú vị vì nó có hai điểm:

Thứ nhất, control labe trang điểm cho template, có nội dung được liên kết đến thuộc tính Value của thanh tiến trình, do đó có thể thay đổi giá trị thuộc tính sẽ được phản ánh trong control label.

Thứ hai là kích hoạt để kiểm soát khi thuộc tính IsIndeterminate là đúng, thay đổi giá trị thuộc tính control background sử dụng MultiBinding PART_Indicator Converter, đây là một lớp chấp nhận nhiều giá trị đầu vào và trả về một giá trị đầu ra. Trong trường hợp của chúng ta, chúng ta muốn giữ lại các tính năng của thanh Windows Vista và Windows 7 , yêu cầu đưa vào project bằng cách tham chiếu đến assembly: PressentationFramework.Aero và được tham chiếu trong tệp XAML của chủ đề như sau:

Kết luận

Theme Black Crystal bao gồm các điều khiển khác như DataGrid, TabControl và GroupBox. Tôi để nó như là một bài tập cho người đọc. Với những gì đã trình bày, tôi mong bạn đọc sẽ cảm thấy không có gì khó khăn để hiểu.

Techtalk via codeproject

CHIA SẺ