Xử Lý Sự Kiện Trong WPF

1.Sự Kiện là gì ?

Mỗi khi bạn nhắp chuột vào một nút bấm hay gõ dòng văn bản nào đó vào một form, bạn đang sử dụng sự
kiện (events). Trong lập trình, có thể định nghĩa sự kiện là một hành động được phát động bởi người dùng,
bởi một thiết bị như đồng hồ đếm (timer) hay bàn phím, hoặc thậm chí là bởi hệ điều hành, tại những thời
điểm phần  lớn  là không theo chu trình nhất định. Ví dụ, với một  thiết bị định vị  con trỏ như chuột, hành
động nhắp phím chuột sẽ gây nên sự kiện “nhắp chuột”. Mỗi khi một sự kiện xảy ra, thông thường dữ liệu
liên quan đến sự kiện đó được thu thập và chuyển nó tới một đơn vị xử lý sự kiện (event handler) để xử lý
tiếp. Cũng có khi, sự kiện bị bỏ qua hay chuyển tới nhiều hàm xử lý sự kiện một lúc nếu những hàm xử lý
này cùng đồng thời lắng nghe sự kiện đó. Dữ liệu tương ứng với một sự kiện ít nhất xác định loại sự kiện,
nhưng đôi khi cũng bao gồm các thông tin khác như sự kiện xảy ra tại thời điểm nào, đối tượng nào phát
động nó…
Thông thường, ta hầu như không  suy nghĩ về việc  sự kiện xảy  ra như  thế nào, ví dụ  làm sao để máy  tính
nhận biết  chuột  trái được nhắp, hay một phím  trên bàn phím được bấm… Lý do là vì  các  chi tiết ở mức
thấp này đã được framework đồ hoạ trong máy tính xử lý. Ngay cả đối với người phát triển, công việc của
ta với sự kiện phần lớn là xử lý phần bề nổi của nhiều vấn đề ở phía sau mỗi sự kiện. Ngay cả trong trường
hợp đó, có rất nhiều phần “bề nổi” cần được xem xét. Trong phần này, trước hết ta tìm hiểu cơ chế xử lý sự
kiện trong WPF.

2.Đơn Vị Xử Lý Sự Kiện:

Mỗi đơn vị xử lý sự kiện (event handler) đơn giản  là một phương thức (hàm) nhận đầu vào từ một  thiết bị
như chuột hay bàn phím và thực hiện một việc nào đó để phản ứng lại với một sự kiện xảy ra trên thiết bị
đó. Ví dụ sau đây minh hoạ đoạn mã lệnh C# là một đơn vị xử lý sự kiện có tên ButtonOkClicked có tác
dụng xử lý sự kiện nút chuột được bấm:

private void ButtonOkClicked(object sender, RoutedEventArgs e)
{
    this.Close(); //đóng cửa sổ hiện thời
}

Trong các phần tiếp theo, để đễ hiểu, ta dùng từ “hàm xử lý sự kiện” với nghĩa tương đương “đơn vị xử lý
sự kiện”
Thực chất, có hai bước cần thực hiện để xử lý một sự kiện:

1.  Liên kết đơn vị xử lý sự kiện với điều khiển (nút bấm, trường văn bản, thực đơn…), nơi sự kiện
tương ứng được phát động.
2.  Viết mã lệnh trong đơn vị xử lý sự kiện để lập trình các công việc phản ứng lại với sự kiện.
Có hai cách để liên kết một sự kiện với một đơn vị xử lý sự kiện. Bạn có thể dùng (1) một môi trường phát
triển  tích hợp  (IDE) như Expression Blend hoặc WPF Designer của Visual Studio  (cách trực quan); hoặc
(2) viết mã lệnh trực tiếp.

  • Liên Kết Sự Kiện Trực Quan Bằng Công Cụ :

Để  liên kết  theo  cách này, ta cần  có  các  công cụ  thiết kế  giao diện GUI dành  cho WPF chẳng hạn  như
Expression Blend hoặc WPF Designer của Visual Studio. Với  các  công cụ này, với mỗi  phần  từ UI trên
giao diện  ta có cửa sổ liệt kế các sự kiện. Với mỗi sự kiện, ta có thể phân định đơn vị xử lý sự kiện bằng
cách khai báo tên hàm xử lý (không gồm đối số) bên cạnh sự kiện ta muốn bắt và xử lý. Hình 6.1 minh hoạ
việc  khai báo  hàm  xử  lý  sự  kiện  ButtonOkClicked  ứng  với  sự  kiện Click của  nút  bấm  btnOK sử  dụng Expression Blend. như hình minh hoạ sau:

image

Sau khi khai báo, ta nhấn Enter, môi trường sẽ tự động tạo sinh và chuyển ta đến khuôn rỗng của hàm xử lý
sự kiện có tên giống với tên ta đã đặt cho đơn vị xử lý sự kiện khi khai báo, và với danh sách tham số ngầm
định  tương ứng với loại sự kiện. Nhiệm vụ của người lập trình lúc này là viết mã lệnh thực hiện các hành
động phản ứng với sự kiện bên trong hàm xử lý này. Trong ví dụ về nút bấm trên, khuôn dạng tự sinh của
hàm xử lý sẽ là:

private void ButtonOkClicked(object sender, RoutedEventArgs e)
{
//viết mã xử lý vào đây
}

Đồng thời bên  Code XAML tương ứng cũng tự sinh :

<Button HorizontalAlignment="Left" Margin="130,92,0,86" x:Name="btnOK" Width="80"
Content="OK" <span style="color: #ff0000;"Click="ButtonOkClicked"/>

Như  đã  thấy,  để  gắn  kết  sự  kiện  Click với  hàm  xử  lý  ButtonOkClicked, ta  có  thể  khai báo
Click=”ButtonOkClicked” trong khai báo tạo lập nút bấm trong mã XAML.

  • Cách liên kết bằng mã lệnh trực tiếp:

Đây là cách tạo sự kiện thông dụng đã được sử dụng từ Windowform cho đến WPF và Silverlight ……

các bạn có thể tạo sự kiện cho các Control trong WPF trực tiếp từ code Behide (trong file MainWindow.xaml.cs)

Các bạn chỉ cần gọi sự kiện của Control trong Contructor của MainWindow thì sự kiện sẽ tự Sinh. như hình  sau :

image

sau khi đã gọi tên “Control.SuKien” các bạn gõ tiếp ”+=” và ấn tiếp Phím Tab 2 lần thì sự kiện sẽ tự sinh phía dưới :

namespace WpfApplicationtest
{
public partial class MainWindow : Window
{
   public MainWindow()
  {
    InitializeComponent();
    btn_Click.Click += new RoutedEventHandler(btn_Click_Click);
  }
  void btn_Click_Click(object sender, RoutedEventArgs e)
  {
   // Sự Kiện đã tự Sinh
  // Xử lý cho Button_Click trong này
  }
 }
}

Để ý  rằng  hàm  xử  lý  sự kiện  trong ví dụ  chứa 2 tham số mà giá  trị  của  chúng  sẽ được  lấy  từ  sự kiện – sender tham chiếu đến đối tượng phát động sự kiện (ở đây là nút bấm btnOK) và event (e) chỉ ra dạng tác
động cụ  thể để sự kiện bị kích hoạt, chẳng hạn như bấm phím hay nhắp chuột… Trong nhiều  trường hợp
bạn không cần phải quan tâm đến các  tham số  của hàm xử  lý sự kiện. Ví dụ, trong đoạn mã ví dụ ở  trên
phần  nội  dung xử  lý  sự  kiện  không hề  dùng  tới  tham số  sender  lẫn  tham số  e. Tuy nhiên, sẽ  có  những trường hợp trong đó, bạn muốn sử dụng cùng một hàm xử lý ứng với nhiều sự kiện có cùng bản chất hoặc cho một loại sự kiện của nhiều đối tượng cùng loại. Khi đó, ta phải quan tâm đến điều khiển nào đã gửi sự kiện, lúc đó tham số sender và event có thể sẽ hữu dụng.

3.Sự Kiện Định Tuyến (Routed Event)

WPF mở rộng mô hình lập trình hướng sự kiện chuẩn của .NET, bằng việc đưa ra một loại sự kiện mới gọi
là sự kiện có định tuyến (routed event). Loại sự kiện này nâng cao tính linh hoạt trong các  tình huống lập
trình hướng  sự kiện. Việc  thiết  lập và xử  lý một  sự kiện có định tuyến có  thể  thực hiện với cùng cú pháp
với một sự kiện “thường” (CLR event).

  • Cây trực quan :

Trước khi bàn  luận thêm về sự kiện có định tuyến, một khái niệm quan trọng cần biết đó là cây trực quan
(visual tree). Một giao diện  người dùng WPF được  xây dựng  theo phương thức  phân lớp, trong đó một
phần tử trực quan không có hoặc có các phần tử con. Cấu trúc phân cấp của các lớp phần tử trực quan như
thế  trên một giao diện người dùng được gọi  là cây trực quan của giao diện đó. Ví dụ, xét giao diện được
định nghĩa bằng đoạn mã XAML sau:

<Border Height="200" Width="300" BorderBrush="Gray" BorderThickness="1">
<StackPanel Background="LightGray" Orientation="Horizontal"
Button.Click="CommonClickHandler">
<Button Name="YesButton" Width="Auto" >Yes</Button>
<Button Name="NoButton" Width="Auto" >No</Button>
<Button Name="CancelButton" Width="Auto" >Cancel</Button>
</StackPanel>
</Border>

Kết quả khi chạy chương trình:

image

Cây trực quan tương ứng sẽ là:

image

  • Sự kiện có định tuyến là gì?

Về mặt chức năng, sự kiện có định tuyến  là một  loại  sự kiện  có  thể kích hoạt nhiều đơn vị xử  lý  sự kiện
thuộc về nhiều điều khiển khác nhau trên cây trực quan, chứ không chỉ trên đối tượng đã phát động sự kiện.
Một ứng dụng WPF điển hình  thường  chứa nhiều phần  tử UI. Bất kể được  tạo  ra bằng mã  lệnh hay được
khai báo bằng XAML, các thành phần này tồn tại trong mối quan hệ kiểu cây trực quan với nhau – tạo nên
các tuyến quan hệ đi từ thành phần này tới thành phần kia. Theo các tuyến quan hệ đó, có ba phương thức
định tuyến sự kiện: lan truyền lên (bubble), lan truyền xuống (tunnel) và trực tiếp (direct).

Lan truyền lên (bubble) là phương thức thường thấy nhất. Nó có nghĩa là một sự kiện sẽ được truyền đi trên
cây trực quan từ thành phần nguồn (nơi sự kiện được phát động) cho tới khi nó được xử lý hoặc nó chạm
tới nút gốc. Điều này cho phép ta xử lý một sự kiện trên một đối tượng nằm ở cấp trên so với thành phần
nguồn. Ví dụ, bạn có thể gắn một hàm xử lý sự kiện Button.Click vào đối tượng Grid có chứa nút bấm thay
vì gắn hàm xử lý đó vào bản thân nút bấm. Sự kiện lan truyền lên có tên gọi thể hiện hành  động của sự
kiện, ví dụ: MouseDown.

Sự kiện lan truyền xuống  (tunnel) đi theo hướng ngược lại, bắt đầu từ nút gốc và truyền xuống cây trực
quan cho tới khi nó được xử lý hoặc chạm tới thành phần gốc của sự kiện đó. Điều này cho phép các thành
phần cấp trên có thể chặn sự kiện và xử lý nó trước khi sự kiện đó chạm tới thành phần nguồn (nơi dự định
xảy ra sự kiện). Các sự kiện lan truyền xuống có tên được gắn thêm tiền tố Preview, ví dụ,  sự kiện
PreviewMouseDown.

Sự kiện trực tiếp  (direct) hoạt động giống  như sự kiện thông thường trong .NET Framework. Chỉ có một
đơn vị xử lý duy nhất sẽ được gắn với sự kiện trực tiếp.

Thông thường, nếu một sự kiện lan truyền xuống được định nghĩa cho một sự kiện nào đó, đồng thời cũng
sẽ có một sự kiện lan truyền lên tương ứng. Trong trường hợp đó, sự kiện lan truyền xuống sẽ được phát
động trước, bắt đầu từ gốc và chạy xuống tìm kiếm hàm xử lý trên cây trực quan. Một khi nó đã được xử lý
hoặc chạm tới thành phần nguồn, sự kiện lan truyền lên sẽ được phát động, lan truyền từ thành phần nguồn
đi ngược lên để tìm tới hàm xử lý nó trên cây trực quan. Sự kiện lan truyền lên hay xuống sẽ không ngừng
lan truyền vì một hàm xử lý nó được gọi. Do vậy, nếu ta muốn dừng quá trình truyền xuống hoặc lên, ta
phải đánh dấu “đã xử lý” cho tham số sự kiện truyền vào, cụ thể:

private void OnChildElementMouseDown(object sender, MouseButtonEventArgs e)
{
     e.Handled = true;
}

Một khi ta đã đánh dấu “đã xử lý” cho sự kiện (e.Handled = true), nó sẽ không được lan truyền tiếp nữa.

image

Hình : Sự kiện có định tuyến trên cây trực quan

Trở lại ví dụ trên, nguồn của sự kiện Click là một trong những thành phần nút bấm, và bất kể nút nào được
bấm, nó sẽ trở thành thành phần đầu tiên được phép xử lý sự kiện. Tuy nhiên, nếu không có đơn vị xử lý
nào tương ứng với sự kiện Click gắn với nút đó, thì sự kiện sẽ được lan truyền lên trên phần tử cha của nút
bấm, trong trường  hợp  này  là  StackPanel, rồi  sau  đó, lan truyền  tới Border… Nói cách  khác, tuyến  lan
truyền sự kiện Click sẽ là:

Button—>StackPanel—>Border—>…

  • Các tình huống cơ bản sử dụng sự kiện có định tuyến:

Phần sau đây tổng kết những  tình huống cần vận dụng khái niệm sự kiện có định tuyến, và tại sao một sự
kiện CLR điển hình là không đủ trong những tình huống đó.

a. Bao đóng và kết hợp điều khiển
Nhiều điều khiển  trong WPF có  cấu  trúc nội dung phức hợp. Ví dụ, ta có  thể đặt một hình ảnh bên trong
một nút bấm, làm mở rộng cây trực quan của nút bấm. Tuy nhiên, hình ảnh thêm vào không được phép phá
vỡ cơ chế hit-testing, cơ chế khiến nút bấm phản ứng với việc nhắp chuột vào trong nó, ngay cả khi người
dùng nhắp chuột vào những pixel là một phần của hình ảnh thêm vào.

b. Các điều khiển sử dụng cùng một đơn vị xử lý sự kiện
Trong Windows Forms,  có  trường hợp  ta cần gán nhiều  lần  cùng một đơn vị xử  lý để xử  lý  các  sự kiện
thuộc vào nhiều  thành phần khác nhau. Sự kiện có định tuyến  cho phép  ta gán đơn vị  xử  lý  chỉ một  lần
trong trường hợp đó. Như trong ví dụ đã nêu trong đoạn mã XAML, sau đây là hàm xử lý tương ứng:

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
 FrameworkElement feSource = e.Source as FrameworkElement;
 switch (feSource.Name)
  {
   case "YesButton":
   // do something here ...
   break;
   case "NoButton":
   // do something ...
   break;
   case "CancelButton":
   // do something ...
   break;
  }
  e.Handled=true;
}

c. Xử lý lớp:
Sự kiện có định tuyến cho phép một đơn vị xử lý tĩnh (static) được định nghĩa trong lớp. Đơn vị xử lý lớp
này có cơ hội xử lý một sự kiện trước khi một đơn vị xử lý gắn với đối tượng cụ thể nào đó của lớp có thể.

d. Tham chiếu đến một sự kiện mà không bị hiện tượng phản xạ:
Các kỹ thuật markup và mã lệnh đòi hỏi phải có cách để định danh một sự kiện. Một sự kiện có định tuyến
tạo  ra trường RoutedEvent như một định danh, cung cấp một kỹ  thuật định danh sự kiện mạnh mà không
đòi hỏi hiện tượng phản xạ tĩnh hoặc run-time.

  • Lợi ích của sự kiện có định tuyến

Cơ chế  thông báo sự kiện kiểu định tuyến có nhiều lợi ích. Một lợi ích rất quan trọng của sự kiện có định
tuyến là một thành phần UI trực quan không cần móc nối cùng một sự kiện trên tất cả các thành phần con trong nó, chẳng hạn sự kiện MouseMove. Thay vào đó, nó có thể móc nối sự kiện này vào bản thân nó, và
khi con chuột di chuyển qua một trong các thành phần con của nó, sự kiện này sẽ được lan truyền tới nó.
Một ưu điểm quan trọng khác của sự kiện có định tuyến là các thành phần ở tất cả các mức trong cây trực
quan có  thể  tự động  thực  thi mã  lệnh để phản ứng  lại các  sự kiện  của các  thành phần con của chúng, mà không cần các thành phần con phải thông báo khi sự kiện xảy ra.

4.Một ví dụ đầy đủ về sự kiện có định tuyến

Form chỉ bao gồm một StackPanel chứa 2 Button và 1 TextBlock có  tên xác định. StackPanel được phân
định bắt sự kiện Click trên hai nút bấm nằm trong nó. Nhiệm vụ của đơn vị xử lý sự kiện Click là cho biết
đối tượng nào đã xử lý sự kiện Click, sự kiện Click phát ra từ loại đối tượng nào, tên gọi là gì nào, và loại
lan truyền định tuyến đã được thực hiện. Các thông tin trên được đưa vào nội dung của TextBlock và hiển
thị lên màn hình sau mỗi sự kiện Click.
Đoạn mã XAML khai báo giao diện như sau:

<Window x:Class="WPF_Training.RoutedEvents"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xamlTitle="WPF Training - RoutedEvents"
Height="300" Width="550">
<!--Khai báo stack panel làm layout chính.
Trong đó, có bắt sự kiện Click của Button và xử lý qua hàm HandleClick-->
<StackPanel
Name="My_StackPanel"
Button.Click="HandleClick" >
<!--Khai báo tạo lập Button 1-->
<Button Name="Button1" Click="Button1_Click">Nút bấm 1</Button>
<!--Khai báo tạo lập Button 2-->
<Button Name="Button2">Nút bấm 2</Button>
<!--Khai báo tạo lập TextBlock lưu trữ và hiển thị kết quả-->
<TextBlock Name="Results"/>
</StackPanel>
</Window>

Đoạn mã lệnh C# cho hàm HandleClick để xử lý sự kiện Click:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace WPF_Training
{
/// &lt;summary&gt;
/// Interaction logic for RoutedEvents.xaml
/// &lt;/summary&gt;
public partial class RoutedEvents : Window
{
 public RoutedEvents()
  {
   InitializeComponent();
  }
   //Dùng một StringBuilder để lưu trữ thông tin kết quả
   StringBuilder eventstr = new StringBuilder();
   //Đơn vị xử lý sự kiện Click của Button
  void HandleClick(object sender, RoutedEventArgs args)
  {
   //Lấy thông tin về đối tượng xử lý sự kiện Click, trong trường hợp này là My_StackPanel
   FrameworkElement fe = (FrameworkElement)sender;
   eventstr.Append("Sự kiện được xử lý bởi đối tượng có tên: ");
   eventstr.Append(fe.Name);
   eventstr.Append("\n");
   //Lấy thông tin về nguồn phát ra sự kiện CLick:
   FrameworkElement fe2 = (FrameworkElement)args.Source;
   eventstr.Append("Sự kiện xuất phát từ nguồn đối tượng kiểu: ");
   //+ Loại thành phần UI;
   eventstr.Append(args.Source.GetType().ToString());
   //+ Định danh;
   eventstr.Append(" với tên gọi: ");
   eventstr.Append(fe2.Name);
   eventstr.Append("\n");
   //Lấy thông tin về phương thức định tuyến
   eventstr.Append("Sự kiện sử dụng phương thức định tuyến: ");
   eventstr.Append(args.RoutedEvent.RoutingStrategy);
   eventstr.Append("\n");
   //Đưa thông tin ra màn hình
   Results.Text = eventstr.ToString();
 }
 private void Button1_Click(object sender, RoutedEventArgs args)
  {
    HandleClick(sender, args);
  }
 }
}

và sau đây là Kết quả :

image

4 thoughts on “Xử Lý Sự Kiện Trong WPF

Leave a comment