Xây dựng và sử dụng UserControl trong WPF - Part 1
Giới thiệu về UserControl
UserControl là một loại control do người dùng định nghĩa, thiết kế giao diện, chức năng cho nó. Cách sử dụng của UserControl cũng tương tự như các loại control thông thường khác. Sau khi xây dựng hoàn chỉnh, lập trình viên có thể sử dụng lại UserControl đó cho nhiều ứng dụng, nhiều cửa sổ khác nhau rất đơn giản. Các loại UserControl đều kế thừa từ class System.Windows.Controls.UserControl.
Xây dựng UserControl
Trong phần này, tôi sẽ demo cách tạo và sử dụng một loại usercontrol mà tôi đã làm cho đồ án cuối kì của mình: CarRacer. Một số tính năng chính của control này bao gồm: cho phép bạn load hình ảnh của thân xe vào, property quy định khoảng cách tối đa xe sẽ chạy (chiều dài của đường đua) và vận tốc của xe, các animation mô phỏng chuyển động của xe, một event cho phép bạn đăng kí phương thức sẽ xử lí khi xe cán đích (vượt qua chiều dài đường đua).
Dưới đây là hình ảnh của một control CarRacer. Nó bao gồm 3 phần: thân xe, bánh trước, bánh sau. Ba phần này đều được cắt ra từ một hình ảnh duy nhất (size 100 x 55 px), do đó để đảm bảo về kích thước, ta sẽ sử dụng size mặc định của hình ảnh làm kích thước của CarRacer.
Để tạo ra sự khác biệt của các CarRacer khác nhau, chúng ta sẽ sử dụng phần thân xe có màu sắc khác nhau.
Đầu tiên, bạn cần tạo ra một project trong Visual Studio, loại project mà chúng ta tạo là WPF User Control Library như hình bên dưới. Ở đây tôi đặt tên project là CarRacer.
Vào phần Solution Explorer của project, bạn có thể xóa file UserControl1.xaml và UserControl1.xaml.cs mà VS tạo sẵn. Click chuột phải vào project, chọn Add -> New Item. Chọn đến mục WPF trong Installed Template, chọn UserControl(WPF), đặt tên là CarRacer.
###
Xây dựng giao diện
Trước tiên, bạn add tất cả các hình ảnh có trong folder CarImage vào project. Các hình ảnh này bao gồm bánh xe trước, sau và 2 thân xe khác màu nhau. Trong file CarRacer.xaml, sửa lại DesignHeight và DesignWidth được khai báo ở đầu như sau:
1
2
<span style="color:red;">d</span><span style="color:blue;">:</span><span style="color:red;">DesignHeight</span><span style="color:blue;">="55" </span><span style="color:red;">d</span><span style="color:blue;">:</span><span style="color:red;">DesignWidth</span><span style="color:blue;">="100"
</span>
Tiếp theo, chúng ta sử dụng Canvas để làm panel thay cho Grid. Lần lượt add image của bánh trước, bánh sau và thân xe vào canvas, tiến hành đặt tên và định vị lại vị trí của các images cho chính xác. Dưới đây là đoạn mã xaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<span style="color:blue;"><</span><span style="color:#a31515;">UserControl </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Class</span><span style="color:blue;">="CarRacer.CarRacer"
</span><span style="color:red;">xmlns</span><span style="color:blue;">="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
</span><span style="color:red;">xmlns</span><span style="color:blue;">:</span><span style="color:red;">x</span><span style="color:blue;">="http://schemas.microsoft.com/winfx/2006/xaml"
</span><span style="color:red;">xmlns</span><span style="color:blue;">:</span><span style="color:red;">mc</span><span style="color:blue;">="http://schemas.openxmlformats.org/markup-compatibility/2006"
</span><span style="color:red;">xmlns</span><span style="color:blue;">:</span><span style="color:red;">d</span><span style="color:blue;">="http://schemas.microsoft.com/expression/blend/2008"
</span><span style="color:red;">mc</span><span style="color:blue;">:</span><span style="color:red;">Ignorable</span><span style="color:blue;">="d"
</span><span style="color:red;">d</span><span style="color:blue;">:</span><span style="color:red;">DesignHeight</span><span style="color:blue;">="55" </span><span style="color:red;">d</span><span style="color:blue;">:</span><span style="color:red;">DesignWidth</span><span style="color:blue;">="100"
>
<</span><span style="color:#a31515;">Canvas</span><span style="color:blue;">>
<</span><span style="color:#a31515;">Image </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Name</span><span style="color:blue;">="Than_xe" </span><span style="color:red;">Height</span><span style="color:blue;">="54" </span><span style="color:red;">Canvas.Left</span><span style="color:blue;">="0" </span><span style="color:red;">Source</span><span style="color:blue;">="Than_xe.png" </span><span style="color:red;">Canvas.Top</span><span style="color:blue;">="0" </span><span style="color:red;">Width</span><span style="color:blue;">="100"/>
<</span><span style="color:#a31515;">Image </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Name</span><span style="color:blue;">="Banh_Xe_2" </span><span style="color:red;">Height</span><span style="color:blue;">="28" </span><span style="color:red;">Canvas.Left</span><span style="color:blue;">="69" </span><span style="color:red;">Source</span><span style="color:blue;">="Banh_truoc.png" </span><span style="color:red;">Canvas.Top</span><span style="color:blue;">="27" </span><span style="color:red;">Width</span><span style="color:blue;">="27" />
<</span><span style="color:#a31515;">Image </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Name</span><span style="color:blue;">="Banh_Xe_1" </span><span style="color:red;">Height</span><span style="color:blue;">="27" </span><span style="color:red;">Canvas.Left</span><span style="color:blue;">="8" </span><span style="color:red;">Source</span><span style="color:blue;">="Banh_sau.png" </span><span style="color:red;">Canvas.Top</span><span style="color:blue;">="27" </span><span style="color:red;">Width</span><span style="color:blue;">="28" />
</</span><span style="color:#a31515;">Canvas</span><span style="color:blue;">>
</</span><span style="color:#a31515;">UserControl</span><span style="color:blue;">>
</span>
Define DependencyProperty và RoutedEvent
Như đã nói ở trên, chúng ta sẽ phân biệt các CarRacer khác nhau dựa vào màu sắc của thân xe, ở đây thân xe lại là một image, do đó chúng ta sẽ tạo ra một dependency property để lấy đường dẫn của image chứa phần thân xe và gán vào property Source của đối tượng có tên x:Name=”Than_xe” mà chúng ta đã khai báo trong file xaml. Một dependency property nữa sẽ cho phép chúng ta khai báo khoảng cách đường đua và một để xác định vận tốc của xe. Phần khai báo các property này như sau:
1
2
3
4
5
<span style="color:blue;">public static </span><span style="color:#2b91af;">DependencyProperty </span>SourceProperty;
<span style="color:blue;">public static </span><span style="color:#2b91af;">DependencyProperty </span>LengthProperty;
<span style="color:blue;">public static </span><span style="color:#2b91af;">DependencyProperty </span>SpeedProperty;
Bây giờ ta tiến hành đăng kí các DependencyProperty này trong một static constructor:
1
2
3
4
5
6
<span style="color:blue;">static </span>CarRacer()
{
SourceProperty = <span style="color:#2b91af;">DependencyProperty</span>.Register(<span style="color:#a31515;">"Source"</span>, <span style="color:blue;">typeof</span>(<span style="color:blue;">string</span>), <span style="color:blue;">typeof</span>(<span style="color:#2b91af;">CarRacer</span>));
LengthProperty = <span style="color:#2b91af;">DependencyProperty</span>.Register(<span style="color:#a31515;">"Length"</span>, <span style="color:blue;">typeof</span>(<span style="color:blue;">double</span>), <span style="color:blue;">typeof</span>(<span style="color:#2b91af;">CarRacer</span>));
SpeedProperty = <span style="color:#2b91af;">DependencyProperty</span>.Register(<span style="color:#a31515;">"Speed"</span>, <span style="color:blue;">typeof</span>(<span style="color:blue;">double</span>), <span style="color:blue;">typeof</span>(<span style="color:#2b91af;">CarRacer</span>));
}
Các tham số trong hàm Register bao gồm: tên của property, kiểu dữ liệu của property, và class sở hữu property đó. Ngoài ra còn có các tham số về PropertyMetadata mà bạn có thể tìm hiểu thêm.
Công việc cuối cùng là tạo cho các DependencyProperty này có dạng như một property thông thường:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<span style="color:blue;">public string </span>Source
{
<span style="color:blue;">get </span>{ <span style="color:blue;">return </span>(<span style="color:blue;">string</span>)GetValue(SourceProperty); }
<span style="color:blue;">set </span>{ SetValue(SourceProperty, <span style="color:blue;">value</span>); }
}
<span style="color:blue;">public double </span>Length
{
<span style="color:blue;">get </span>{ <span style="color:blue;">return </span>(<span style="color:blue;">double</span>)GetValue(LengthProperty); }
<span style="color:blue;">set </span>{ SetValue(LengthProperty, <span style="color:blue;">value</span>); }
}
<span style="color:blue;">public double </span>Speed
{
<span style="color:blue;">get </span>{ <span style="color:blue;">return </span>(<span style="color:blue;">double</span>)GetValue(SpeedProperty); }
<span style="color:blue;">set </span>{ SetValue(SpeedProperty, <span style="color:blue;">value</span>); }
}
Bây giờ, chúng ta sẽ tạo ra một routed event có tên là Finished để xử lí sự kiện khi xe chạm đích. Các công đoạn khai báo và đưa Routed Event về dạng như các event thông thường như sau:
1
2
3
4
5
6
7
<span style="color:blue;">public static readonly </span><span style="color:#2b91af;">RoutedEvent </span>FinishedEvent;
<span style="color:blue;">public event </span><span style="color:#2b91af;">RoutedEventHandler </span>Finished
{
<span style="color:blue;">add </span>{ AddHandler(FinishedEvent, <span style="color:blue;">value</span>); }
<span style="color:blue;">remove </span>{ RemoveHandler(FinishedEvent, <span style="color:blue;">value</span>); }
}
Tương tự như DependencyProperty, chúng ta cũng phải đăng kí RoutedEvent trong static constructor. Các tham số truyền vào hàm RegisterRoutedEvent bao gồm: tên event, kiểu routing, class handler, class sở hữu event đó:
1
FinishedEvent = <span style="color:#2b91af;">EventManager</span>.RegisterRoutedEvent(<span style="color:#a31515;">"Finished"</span>, <span style="color:#2b91af;">RoutingStrategy</span>.Direct, <span style="color:blue;">typeof</span>(<span style="color:#2b91af;">RoutedEventHandler</span>), <span style="color:blue;">typeof</span>(<span style="color:#2b91af;">CarRacer</span>));
Tới đây chúng ta đã có thể sử dụng được các property cũng như event trong file xaml như các loại control khác. Bây giờ, chúng ta sẽ sử dụng DataBinding trên Image có x:Name=”Than_xe” để lấy đường dẫn của phần thân xe:
1
2
3
4
5
<span style="color:blue;"><</span><span style="color:#a31515;">Image </span><span style="color:red;">Height</span><span style="color:blue;">="54" </span><span style="color:red;">Canvas.Left</span><span style="color:blue;">="0"
</span><span style="color:red;">Source</span><span style="color:blue;">="{</span><span style="color:#a31515;">Binding </span><span style="color:red;">Path</span><span style="color:blue;">=Source, </span><span style="color:red;">RelativeSource</span><span style="color:blue;">={</span><span style="color:#a31515;">RelativeSource </span><span style="color:red;">FindAncestor</span><span style="color:blue;">, </span><span style="color:red;">AncestorType</span><span style="color:blue;">=UserControl}}"
</span><span style="color:red;">Canvas.Top</span><span style="color:blue;">="0" </span><span style="color:red;">Width</span><span style="color:blue;">="100"
/>
</span>
Sử dụng UserControl
Để kiểm thử chúng ta sẽ add thêm một project WPF Application. Trong project mới tạo, click chuột phải, chọn Set as StartUp Project. Tiếp theo chúng ta add reference cho project này bằng cách bấm chuột phải vào tên project, chọn Add Reference… Trong thẻ Browse, tìm tới thư mục bin của folder chứa project CarRacer mà chúng ta tạo đầu tiên ở trên, chọn file CarRacer.dll và nhấn OK.
Trong file xaml, ta tiến hành khai báo namespace của CarRacer như sau bằng cách thêm phần khai báo của file MainWindow.xaml nội dung như sau:
1
2
<span style="color:red;">xmlns</span><span style="color:blue;">:</span><span style="color:red;">car</span><span style="color:blue;">="clr-namespace:CarRacer;assembly=CarRacer"
</span>
Add 2 image thân xe vào project mới tạo. Bây giờ chúng ta sẽ tạo ra 2 đối tượng CarRacer, một màu xanh và một màu đỏ tương ứng với 2 hình thân xe mà chúng ta đã add từ trước. Ở đây vì trong cùng 1 solution có 2 project nên chúng ta cần xác định rõ file image nằm trong assembly nào. Để hiểu rõ hơn về pack URIs trong WPF, các bạn có thể xem tại đây.
1
2
3
4
5
<span style="color:blue;"><</span><span style="color:#a31515;">Grid</span><span style="color:blue;">>
<</span><span style="color:#a31515;">car</span><span style="color:blue;">:</span><span style="color:#a31515;">CarRacer </span><span style="color:red;">Source</span><span style="color:blue;">="pack://application:,,,/CarRacerDemo;component/Than_xe.png" </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Name</span><span style="color:blue;">="xe_xanh"/>
<</span><span style="color:#a31515;">car</span><span style="color:blue;">:</span><span style="color:#a31515;">CarRacer </span><span style="color:red;">Source</span><span style="color:blue;">="pack://application:,,,/CarRacerDemo;component/Xe_Do.png" </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Name</span><span style="color:blue;">="xe_do" </span><span style="color:red;">Margin</span><span style="color:blue;">="0 150 0 0"/>
</</span><span style="color:#a31515;">Grid</span><span style="color:blue;">>
</span>
Kết quả tương ứng mà chúng ta thu được:
Tới đây là xong phần 1. Trong phần 2 tôi sẽ trình bày vấn đề về animation cũng như cài đặt một số chức năng khác cho control.
Comments powered by Disqus.