[WPF] Tùy chỉnh ứng dụng với Style và Control Templates – Phần 2
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 ...
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:
1 2 3 4 5 |
<Style TargetType="{x:Type Button}"> </Style> |
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:
1 2 3 4 5 |
<Setter Property="OverridesDefaultStyle" Value="True" /> <Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="Foreground" Value="White" /> |
Đố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 :
1 2 3 4 5 6 7 8 |
<Setter Property="Template"> <Setter.Value> <ControlTemplate x:Name="tmpltButton"> </ControlTemplate> </Setter.Value> </Setter> |
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:
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 |
<Grid> <Border x:Name="BaseRectangle" Background="{StaticResource BaseColor}" CornerRadius="10,0,10,0"> </Border> <Border x:Name="GlassRectangle" Background="{StaticResource GlassFX}" CornerRadius="10,0,10,0"> </Border> <Border x:Name="GlowRectangle" Background="{StaticResource GlowFX}" CornerRadius="10,0,10,0" Opacity="0"> </Border> <Border x:Name="ButtonBorder" CornerRadius="10,0,10,0" BorderBrush="Black" Opacity="1" BorderThickness="1"> </Border> <ContentPresenter x:Name="ButtonContent" Opacity=".7" Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="center" VerticalAlignment="center"> </ContentPresenter> </Grid> |
Chúng ta có một grid chứa các Borders và ContentPresenter , 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:
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 |
<ControlTemplate.Triggers> <EventTrigger RoutedEvent="Button.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard Storyboard="{StaticResource GlowOut}"> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Button.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard Storyboard="{StaticResource GlowIn}"> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Button.LostFocus"> <EventTrigger.Actions> <BeginStoryboard Storyboard="{StaticResource GlowOut}"> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Button.GotFocus"> <EventTrigger.Actions> <BeginStoryboard Storyboard="{StaticResource GlowIn}"> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <Trigger Property="Button.IsPressed" Value="True"> <Setter Property="Background" TargetName="GlowRectangle" Value="{StaticResource GlowFXPressed}"> </Setter> <Setter Property="Opacity" TargetName="ButtonContent" Value="1"> </Setter> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Opacity" TargetName="ButtonContent" Value=".3"> </Setter> <Setter Property="Opacity" TargetName="ButtonBorder" Value=".5"> </Setter> <Setter Property="Background" TargetName="GlassRectangle" Value="{StaticResource GlassFXDisabled}"> </Setter> </Trigger> </ControlTemplate.Triggers> |
Như bạn thấy trong đoạn code có hai loại trigger: Trigger và EventTrigger
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, LostFocus và GotFocus.
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: IsPressed và IsEnabled.
Mỗi trigger sẽ được khởi chạy khi thuộc tính có giá trị được chỉ ra ở trên (khi IsPressed là true và khi IsEnabled là false) 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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="code-keyword"><</span><span class="code-leadattribute">ScrollViewer</span> <span class="code-attribute">x:Name</span><span class="code-keyword">="</span><span class="code-keyword">PART_ContentHost"</span> <span class="code-attribute">Opacity</span><span class="code-keyword">="</span><span class="code-keyword">.7"</span> <span class="code-attribute">Content</span><span class="code-keyword">="</span><span class="code-keyword">{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}"</span> <span class="code-attribute">HorizontalAlignment</span><span class="code-keyword">="</span><span class="code-keyword">{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource TemplatedParent}}"</span> <span class="code-attribute">VerticalAlignment</span><span class="code-keyword">="</span><span class="code-keyword">{Binding Path=VerticalAlignment, RelativeSource={RelativeSource TemplatedParent}}"</span> <span class="code-attribute">Width</span><span class="code-keyword">="</span><span class="code-keyword">{Binding Path=Width, RelativeSource={RelativeSource TemplatedParent}}"</span>
Có thể bạn quan tâm
0
|