Archive for Tháng Mười Hai, 2010
Tìm hiểu về HTML5 – Phần IV
HTML5 Canvas API
Canvas API là một trong những bổ sung đáng giá của HTML5, giúp tăng cường mạnh mẽ khả năng đồ họa so với các phiên bản trước. Bài viết này tập trung giới thiệu một số nét chính về Canvas API và cách sử dụng nó trong ứng dụng.
Thẻ <canvas>
HTML5 cung cấp thẻ <canvas>, thẻ này định nghĩa một khung hình chữ nhật trên trang web mà trong đó ta có thể thực hiện các thao tác vẽ hình bằng JavaScript. <canvas> có các thuộc tính width và height để xác định kích thước vùng vẽ hình. Mặc định, vùng này có kích thước 300 x 150 (px). Ví dụ sau đây minh họa cách khai báo một canvas trong trang HTML5:
<canvas id=”myCanvas” style=”border: 1px solid;” width=”300″ height=”300″>
Trình duyệt của bạn không hỗ trợ Canvas!
</canvas>
Đoạn mã trên khai báo một vùng chữ nhật kích thước 300 x 300, thuộc tính id là cần thiết để tương tác với vùng này bằng script, và như mọi thẻ HTML khác, ta cũng có thể định dạng canvas bằng CSS: thuộc tính border tạo một đường viền mỏng bao quanh. Cuối cùng, thẻ <canvas> cho phép xác định đoạn text sẽ được hiển thị khi browser không hỗ trợ. Một điều may mắn là phần lớn các trình duyệt phổ biến ngày nay (trừ IE8) đều tương thích rất tốt, và IE9 cũng đang hứa hẹn khả năng này. Hi vọng rằng đây sẽ không phải là vấn đề quá lo lắng cho các lập trình viên!
Vẽ hình trên Canvas
Sau khi đã thiết lập xong một khu vực dành riêng để sáng tạo bằng thẻ <canvas>, chúng ta có thể bắt đầu đi vào công việc chính. Về nguyên tắc, vẽ hình trên canvas gồm ba bước:
- Lấy về context của canvas cần điều khiển
- Thực hiện các thao tác vẽ
- Áp dụng các thao tác này cho context
Lưu ý rằng ta cần phải chính thức áp dụng các thao tác vẽ trên context để chúng có hiệu lực (điều này tương tự như khi bạn thay đổi các thiết lập trong một hộp thoại, các thiết lập mới sẽ chưa được áp dụng cho đến khi bạn nhấn OK hoặc Apply). Đoạn code sau cho thấy cách vẽ một đường thẳng với Canvas API:
<script>
function drawLine()
{
// Lấy về context của canvas cần vẽ
var canvas = document.getElementById(‘myCanvas’);
var context = canvas.getContext(’2d’);
// Vẽ đường thẳng
context.beginPath();
context.moveTo(100, 200);
context.lineTo(200, 100);
// apply kết quả cho canvas
context.stroke();
}
window.addEventListener(“load”, drawLine, true);
</script>
Có lẽ không cần giải thích gì thêm về đoạn code trên, ngoại trừ một điểm đáng chú ý: phương thức getContext của đối tượng canvas nhận vào một tham số xác định context muốn lấy về. Hiện tại, “2d” là giá trị hợp lệ duy nhất. Tuy nhiên rất có thể 3D context sẽ được bổ sung vào một ngày không xa (một số phiên bản thử nghiệm của Opera và plugin cho FireFox đã hỗ trợ context này). Bên cạnh đó, HTML5 Canvas API cũng cung cấp nhiều phương thức để tùy biến nét vẽ và tô màu:
// thay đổi dộ dày nét vẽ
context.lineWidth = 3;
// đổi màu nét vẽ sang màu đỏ
context.strokeStyle = ‘#FF0000′;
// đổi màu tô
context.fillStyle = ‘#0000FF’;
// vẽ hình chữ nhật được tô kín
context.fillRect(10, 10, 40, 60);
Ngoài ra, ta cũng có thể vẽ một ảnh trực tiếp trong canvas:
// load ảnh
var img = new Image();
img.src = “image.jpg”;
// vẽ ảnh khi đã load xong
img.onload = function () {
context.drawImage(img, 10, 10, 60, 60);
}
Một rắc rối nảy sinh khi làm việc với các file ảnh là cần phải đảm bảo chúng được load xong trước khi sử dụng. Trong thực tế, các trình duyệt thường load ảnh theo kiểu bất đồng bộ và điều này có thể gây ra vấn đề. Để giải quyết, đoạn code trên sử dụng sự kiện onload của đối tượng Image, sự kiện này chỉ phát sinh một khi quá trình load đã hoàn tất. Nhờ đó mà đoạn code trên sẽ đạt được hiệu quả mong muốn.
Canvas API trong HTML5 còn rất nhiều những tính năng hấp dẫn: scale , transform, tạo hiệu ứng đổ bóng, animation… Công bằng mà nói thì đây cũng là một đề tài lớn và phức tạp. Trong khuôn khổ giới hạn, bài viết chỉ trình bày những gì cơ bản nhất, hi vọng rằng có thể mang lại một chút hứng thú cho bạn đọc, để từ đó các bạn sẽ tự mình khám phá sâu hơn!
* Mục lục:
Đa luồng trong WPF
Đa luồng không phải là một khái niệm mới mẻ hay đặc trưng của WPF mà nó là một tính năng đã được hỗ trợ trong mọi phiên bản của .NET Framework. Tuy vậy, việc áp dụng kĩ thuật đa luồng vào WPF vẫn có những điểm riêng đáng lưu ý.
WPF sử dụng mô hình STA (Single Threaded Apartment) tương tự Windows Form trước đây với các quy tắc cơ bản:
- Các phần tử WPF được sở hữu bởi một luồng duy nhất là luồng tạo ra phần tử đó. Các luồng khác không thể tương tác trực tiếp với phần tử này.
- DispatcherObject là lớp cha của các phần tử WPF, cung cấp các phương thức đảm bảo các đối tượng WPF được sử dụng bởi đúng luồng sở hữu.
Vì sao lại có những hạn chế như vậy? Ban đầu, các nhà thiết kế WPF đã dự định xây dựng một mô hình luồng mới, trong đó các đối tượng giao diện có thể được truy xuất từ bất cứ luồng nào. Tuy nhiên, hướng tiếp cận này gây nên những phức tạp không cần thiết cho các ứng dụng đơn luồng, hơn nữa còn gây khó khăn khi phải giao tiếp với các thư viện cũ. Kết quả là kế hoạch trên đã bị hủy bỏ, và WPF lại quay trở về với mô hình STA như ngày nay.
Điều này có gì quan trọng? Một sai lầm phổ biến của những người mới làm quen với WPF và lập trình đa luồng là cố điều khiển WPF control từ một luồng không sở hữu nó.
private void Button1_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(ChangeText);
thread.Start();
}
private void ChangeText()
{
Thread.Sleep(TimeSpan.FromSeconds(10));
textbox.Text = "Warning! Exception will happen here!";
}
Giả định rằng có một cửa sổ WPF với một button và một textbox. Hàm Button1_Click xử lý sự kiện click chuột trên button, tạo ra một luồng mới và trong luồng đó thay đổi thuộc tính Text của button. Đoạn code trên sẽ gây ra InvalidOperationException. Cách làm đúng trong trường hợp này là sử dụng phương thức BeginInvoke() của DispatcherObject. BeginInvoke có nhiệm vụ điều phối để thực thi đoạn code trong cùng luồng của dispatcher, phương thức này nhận hai tham số: tham số thứ nhất xác định mức độ ưu tiên của tác vụ cần thực hiện, và tham số thứ hai là một delegate trỏ tới hàm thực hiện tác vụ đó. Đoạn code gây lỗi có thể được viết lại như sau:
private void ChangeText()
{
Thread.Sleep(TimeSpan.FromSeconds(10));
// Lấy về dispatcher của cửa sổ hiện tại và thay đổi text cho textbox
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart) delegate()
{
textbox.Text = "Hooray! No error!";
});
}
Lưu ý là DispatcherObject còn có một phương thức Invoke() với cùng tác dụng như trên, khác biệt duy nhất ở chỗ Invoke được thực thi đồng bộ, điều này có nghĩa là luồng gọi Invoke sẽ bị treo đến khi dispatcher thực thi xong tác vụ đó, trong khi lời gọi BeginInvoke() sẽ trả điều khiển ngay lập tức.
Ngoài ra, DispatcherObject cũng cung cấp hai hàm CheckAccess() và VerifyAccess(). CheckAccess trả về true khi đoạn code điều khiển các phần tử WPF được chạy trên đúng luồng, và false nếu ngược lại. Trong khi VerifyAccess() là hàm không trả trị, hàm này sẽ phát sinh InvalidOperationException trong trường hợp luồng thực thi là không hợp lệ. Đây cũng chính là hàm được WPF sử dụng để đảm bảo chúng ta không thao tác với các phần tử WPF từ các luồng không được phép.
Đơn giản hóa quản lý luồng với BackgroundWorker
Trong các ví dụ trước, chúng ta đã tạo và sử dụng luồng tường minh. Cách làm này có ưu điểm là sự linh động tối đa (không hạn chế số luồng được tạo, quản lý độ ưu tiên, trạng thái luồng…). Tuy nhiên trong nhiều trường hợp, chúng ta chỉ cần một giải pháp đơn giản nhất, và đó là nơi mà BackgroundWorker phát huy tác dụng. Về cơ bản, BackgroundWorker hoạt động trên một luồng riêng trong khi ẩn đi các chi tiết phức tạp của lập trình đa luồng. BackgroundWorker cung cấp các sự kiện:
- DoWork: được kích hoạt khi BackgroundWorker bắt đầu thực thi, ta đặt đoạn code cần xử lý bất đồng bộ vào trong hàm xử lý sự kiện này.
- RunWorkerCompleted: khi hoàn tất, BackgroundWorker phát sinh sự kiện này trên luồng của dispatcher, đây là nơi nhận kết quả xử lý và cập nhật các phần tử giao diện.
Sau đây là ví dụ trên viết lại dùng BackgroundWorker. BeginInvoke() bây giờ không còn cần thiết nữa, vì BackgroundWorker đã đảm nhận tất cả:
private void Button1_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(DoSomething);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(SomethingDone);
worker.RunWorkerAsync();
}
private void DoSomething(object sender, DoWorkEventArgs e)
{
Thread.Sleep(10000);
}
private void SomethingDone(object sender, RunWorkerCompletedEventArgs e)
{
textbox.Text = "Hooray! No error!";
}
Hàm Button1_Click tạo đối tượng BackgroundWorker, gắn các hàm xử lý sự kiện, và sau đó thực thi BackgroundWorker theo kiểu bất đồng bộ bằng phương thức RunWorkerAsync (lưu ý là bạn có thể cần thêm khai báo using cho namespace System.ComponentModel). Bên cạnh hai sự kiện phổ biến DoWork và RunWorkerCompleted, BackgroundWorker còn cung cấp hai sự kiện khác là Disposed và ProgressChanged, chúng ta có thể bắt các sự kiện này để xử lý thêm khi có nhu cầu. Một điều thú vị cuối cùng là trong WPF, BackgroundWorker có thể được khai báo trực tiếp trong XAML thay vì phải viết code:
<Window.Resources>
<cm:BackgroundWorker x:Key="backgroundWorker" DoWork="DoSomething" RunWorkerCompleted="SomethingDone">
</cm:BackgroundWorker>
</Window.Resources>
Trong khuôn khổ giới hạn, bài viết chỉ trình bày một phần nhỏ về kĩ thuật lập trình đa luồng với WPF. Lập trình là một công việc luôn luôn mới mẻ và thú vị, chắc chắn vẫn còn rất nhiều vấn đề đang chờ bạn khám phá. Chúc các bạn thành công!