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 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..
1
2
3
4
5
6
|
<Style TargetType =" { x : Type Button } ">
<Setter Property=" Background" Value ="Red"></Setter>
<Setter Property ="Foreground" Value ="White"></Setter>
</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
1
2
3
4
5
6
7
|
<Style x:Key="OwnStyle" TargetType=" { x : Type Button } ">
<Setter Property=" Background" Value ="Red"></Setter>
<Setter Property ="Foreground" Value ="White"></Setter>
</Style>
< Button Style = "{StaticResource OwnStyle}" > < / Button >
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
< ControlTemplate TargetType = "{x:Type Button}" >
< Border Name = "fondoboton" BorderBrush = "DarkGray" BorderThickness = "5"
CornerRadius = "10,0,10,0"
Background = "LightGray" >
< ContentPresenter Name = "contenido"
Content = "{Binding Path=Content,
RelativeSource={RelativeSource TemplatedParent}}" >
< / ContentPresenter >
< / Border >
< / ControlTemplate >
|
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:
1
2
3
4
5
6
|
< ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns : x = "http://schemas.microsoft.com/winfx/2006/xaml" >
< / ResourceDictionary >
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< Application x : Class = "Application"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns : x = "http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri = "MainWindow.xaml" >
< Application . Resources >
< ResourceDictionary Source = "BlackCrystal.xaml" > < / ResourceDictionary >
< / Application . Resources >
< / Application >
|
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:
1
2
3
4
5
6
|
< LinearGradientBrush x : Key = "DarkBackground" StartPoint = "0,0" EndPoint = "1,1" >
< GradientStop Offset = "0" Color = "#FF333344" > < / GradientStop >
< GradientStop Offset = "1" Color = "#FF666677" > < / GradientStop >
< / LinearGradientBrush >
|
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:
1
2
3
4
5
6
7
|
< span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > RadialGradientBrush < / span > < span class = "code-attribute" > x : Key < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">GlowFX" < / span > < span class = "code-attribute" > GradientOrigin < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">.5,1" < / span > < span class = "code-attribute" > Center < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">.5,1" < / span > < span class = "code-keyword" > & gt ; < / span >
< span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > GradientStop < / span > < span class = "code-attribute" > Offset < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">0" < / span > < span class = "code-attribute" > Color < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">#990000FF" < / span > < span class = "code-keyword" > & gt ; < / span > < span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > / GradientStop < / span > < span class = "code-keyword" > & gt ; < / span >
< span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > GradientStop < / span > < span class = "code-attribute" > Offset < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">.5" < / span > < span class = "code-attribute" > Color < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">#660000DD" < / span > < span class = "code-keyword" > & gt ; < / span > < span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > / GradientStop < / span > < span class = "code-keyword" > & gt ; < / span >
< span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > GradientStop < / span > < span class = "code-attribute" > Offset < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">1" < / span > < span class = "code-attribute" > Color < / span > < span class = "code-keyword" >= "</span><span class=" code - keyword ">#33000000" < / span > < span class = "code-keyword" > & gt ; < / span > < span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > / GradientStop < / span > < span class = "code-keyword" > & gt ; < / span >
< span class = "code-keyword" > & lt ; < / span > < span class = "code-leadattribute" > / RadialGradientBrush < / span > < span class = "code-keyword" > & gt ; < / span >
|
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:
1
2
3
4
5
6
|
< Button Content = "Hola"
Background = "{StaticResource GlowFX}" >
< / Button >
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
|
< Storyboard x : Key = "GlowOut" >
< DoubleAnimation x : Name = "AnimGlowOut" BeginTime = "00:00:00"
Storyboard . TargetName = "GlowRectangle"
Duration = "00:00:00.250" From = "1" To = "0"
Storyboard . TargetProperty = "Opacity" >
< / DoubleAnimation > < / Storyboard >
< / Storyboard >
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
|
< Button Name = "GlowRectangle" Content = "Hi Animation" >
< Button . Triggers >
< EventTrigger RoutedEvent = "MouseLeave" >
< EventTrigger . Actions >
< BeginStoryboard Name = "{StaticResource GlowOut}" >
< / BeginStoryboard >
< / EventTrigger . Actions >
< / EventTrigger >
< / Button . Triggers >
< / Button >
|
Kết thúc phần 1
Tôi sẽ để lại một danh sách tất cả các gradient và animation được sử dụng trong bài này, vì vậy bạn có thể bắt đầu “nghịch” với nó và hiểu được cách sử dụng. Trong phần hai, tôi sẽ bắt tay vào tạo Styles và Control Templates. Tôi hy vọng bạn thấy hữu ích và thích nó. Hẹn sớm gặp lại!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
< ! -- DARK BACKGROUND -- >
< LinearGradientBrush x : Key = "DarkBackground" StartPoint = "0,0" EndPoint = "1,1" >
< GradientStop Offset = "0" Color = "#FF333344" > < / GradientStop >
< GradientStop Offset = "1" Color = "#FF666677" > < / GradientStop >
< / LinearGradientBrush >
< ! -- GLASS EFFECT -- >
< LinearGradientBrush x : Key = "GlassFX" StartPoint = ".5,0" EndPoint = ".5,.5" >
< GradientStop Offset = "1" Color = "#33DDDDDD" > < / GradientStop >
< GradientStop Offset = "1" Color = "#33000000" > < / GradientStop >
< / LinearGradientBrush >
< LinearGradientBrush x : Key = "GlassFXDisabled" StartPoint = ".5,0" EndPoint = ".5,.5" >
< GradientStop Offset = "1" Color = "#33BBBBBB" > < / GradientStop >
< GradientStop Offset = "1" Color = "#11000000" > < / GradientStop >
< / LinearGradientBrush >
< ! -- GLOW EFFECT -- >
< RadialGradientBrush x : Key = "GlowFX" GradientOrigin = ".5,1" Center = ".5,1" >
< GradientStop Offset = "0" Color = "#990000FF" > < / GradientStop >
< GradientStop Offset = ".5" Color = "#660000DD" > < / GradientStop >
< GradientStop Offset = "1" Color = "#33000000" > < / GradientStop >
< / RadialGradientBrush >
< RadialGradientBrush x : Key = "GlowFXPressed" GradientOrigin = ".5,1" Center = ".5,1" >
< GradientStop Offset = "0" Color = "#660000CC" > < / GradientStop >
< GradientStop Offset = "1.2" Color = "#33FFFFFF" > < / GradientStop >
< / RadialGradientBrush >
< LinearGradientBrush x : Key = "GlowFXRowPressed" >
< GradientStop Offset = "0" Color = "#660000FF" > < / GradientStop >
< GradientStop Offset = ".7" Color = "#660000AA" > < / GradientStop >
< GradientStop Offset = "1" Color = "#66000000" > < / GradientStop >
< / LinearGradientBrush >
< LinearGradientBrush x : Key = "GlowFXProgress" >
< GradientStop Offset = "0" Color = "#660099FF" > < / GradientStop >
< GradientStop Offset = ".99" Color = "#660022AA" > < / GradientStop >
< GradientStop Offset = "1" Color = "#00000000" > < / GradientStop >
< / LinearGradientBrush >
< LinearGradientBrush x : Key = "GlowFXProgressAnimated" MappingMode = "RelativeToBoundingBox" >
< GradientStop Offset = "0" Color = "#00000000" > < / GradientStop >
< GradientStop Offset = ".50" Color = "#660099FF" > < / GradientStop >
< GradientStop Offset = "1" Color = "#00000000" > < / GradientStop >
< / LinearGradientBrush >
< LinearGradientBrush x : Key = "GlowFXTabSelected" StartPoint = ".5,1" EndPoint = ".5,0" >
< GradientStop Offset = "0" Color = "#33DDDDDD" > < / GradientStop >
< GradientStop Offset = "1" Color = "#332222FF" > < / GradientStop >
< / LinearGradientBrush >
< ! -- GLOW ANIMATION -- >
< Storyboard x : Key = "GlowOut" >
< DoubleAnimation x : Name = "AnimGlowOut" BeginTime = "00:00:00"
Storyboard . TargetName = "GlowRectangle"
Duration = "00:00:00.250"
From = "1" To = "0"
Storyboard . TargetProperty = "Opacity" >
< / DoubleAnimation >
< / Storyboard >
< Storyboard x : Key = "GlowIn" >
< DoubleAnimation x : Name = "AnimGlow"
BeginTime = "00:00:00"
Storyboard . TargetName = "GlowRectangle"
Duration = "00:00:00.250"
From = "0" To = "1"
Storyboard . TargetProperty = "Opacity" >
< / DoubleAnimation >
< / Storyboard >
|
- SSale Hệ thống quản lý phân phối, bán hàng, chăm sóc khách hàng tối ưu
- Mua vé xe, đặt vé xe trên smartphone, smart TV
- Giao vận, Logistic
- Quản lý giao vận thông minh
- Bán vé máy bay thông qua smartphone và tablet, smart TV
- Hệ thống đánh giá đại lý, chi nhánh, cửa hàng SKPI ( hệ thống đánh giá thông minh trên smartphone và tablet)
- SChat là lựa chọn tuyệt vời nhất để trò chuyện và chăm sóc khách hàng của bạn
- Ứng dụng quản lý vận tải trên smartphone
- Hệ thống giới thiệu sản phẩm, dịch vụ thông minh SCatalog và SBrochure ( hệ thống catalog và brochre thông minh trên smartphone và tablet)
- Hệ thống chấm công từ xa qua vệ tinh SAttendance và hệ thống định vị STracking
- Ứng dụng bán hàng trên smartphone, smart TV, mạng xã hội...
- Ứng dụng quản lý garage trên smartphone và tablet
- Điều hành hãng xe công nghệ, ứng dụng đặt xe trên smartphone tương tự Uber, Grab,...
- Hệ thống điều hành, tìm gọi và quản lý xe sử dụng công nghệ mới
DVMS chuyên:
- Tư vấn, xây dựng, chuyển giao công nghệ Blockchain, mạng xã hội,...
- Tư vấn ứng dụng cho smartphone và máy tính bảng, tư vấn ứng dụng vận tải thông minh, thực tế ảo, game mobile,...
- Tư vấn các hệ thống theo mô hình kinh tế chia sẻ như Uber, Grab, ứng dụng giúp việc,...
- Xây dựng các giải pháp quản lý vận tải, quản lý xe công vụ, quản lý xe doanh nghiệp, phần mềm và ứng dụng logistics, kho vận, vé xe điện tử,...
- Tư vấn và xây dựng mạng xã hội, tư vấn giải pháp CNTT cho doanh nghiệp, startup,...
Vì sao chọn DVMS?
- DVMS nắm vững nhiều công nghệ phần mềm, mạng và viễn thông. Như Payment gateway, SMS gateway, GIS, VOIP, iOS, Android, Blackberry, Windows Phone, cloud computing,…
- DVMS có kinh nghiệm triển khai các hệ thống trên các nền tảng điện toán đám mây nổi tiếng như Google, Amazon, Microsoft,…
- DVMS có kinh nghiệm thực tế tư vấn, xây dựng, triển khai, chuyển giao, gia công các giải pháp phần mềm cho khách hàng Việt Nam, USA, Singapore, Germany, France, các tập đoàn của nước ngoài tại Việt Nam,…
Quý khách xem Hồ sơ năng lực của DVMS tại đây >>
Quý khách gửi yêu cầu tư vấn và báo giá tại đây >>