Jeff Fisher, Nhóm API Dữ liệu của Google
Tháng 1 năm 2008
Giới thiệu: Phạm vi của mẫu
Một trong những điểm hay của Documents List Data API là API này cho phép nhà phát triển tạo các công cụ di chuyển cho những người dùng vẫn đang làm quen với Google Tài liệu. Để sử dụng API này, tôi đã dùng Thư viện ứng dụng.NET để tạo một ứng dụng tải lên .NET 2.0, có tiêu đề thích hợp là "DocList Uploader". Bạn có thể lấy nguồn của người tải lên từ subversion.
Mục đích của mẫu này là giúp người dùng dễ dàng di chuyển tài liệu từ máy tính sang Google Tài liệu. Công cụ này cho phép người dùng đăng nhập vào Tài khoản Google của họ, sau đó kéo và thả các tệp được hỗ trợ để tự động tải lên. Mẫu này cũng cung cấp lựa chọn thêm một lựa chọn trong trình đơn nhấp chuột phải vào trình khám phá Windows để tải tệp lên. Mẫu này được cung cấp theo Giấy phép Apache 2.0, vì vậy bạn có thể sử dụng mẫu này làm điểm khởi đầu cho các chương trình của riêng mình.
Bài viết này nhằm mục đích cho thấy cách một số hành vi của mẫu được thực hiện bằng cách sử dụng .NET Framework. Nội dung này chủ yếu bao gồm các đoạn mã được chú thích từ các phần có liên quan. Bài viết này không đề cập đến cách tạo biểu mẫu và các thành phần khác trên giao diện người dùng của chính ứng dụng, vì có nhiều bài viết về Visual Studio đề cập chi tiết đến vấn đề này. Nếu muốn biết cách định cấu hình các thành phần giao diện người dùng, bạn có thể tự tải tệp dự án bằng cách tải thư viện ứng dụng xuống và xem bên trong thư mục con "clients\cs\samples\DocListUploader".
Tạo ứng dụng khay hệ thống
Các công cụ di chuyển thường có thể chạy một cách kín đáo trong hệ điều hành, mở rộng khả năng của hệ điều hành mà không gây quá nhiều phiền toái cho người dùng. Một cách để cấu trúc công cụ như vậy trong Windows là chạy công cụ đó ra khỏi khay hệ thống thay vì làm lộn xộn thanh tác vụ. Điều này giúp người dùng có nhiều khả năng để chương trình chạy liên tục thay vì chỉ mở chương trình khi cần thực hiện một tác vụ cụ thể. Đây là một ý tưởng đặc biệt hữu ích cho mẫu này vì mẫu không cần lưu trữ thông tin xác thực vào ổ đĩa.
Ứng dụng khay hệ thống là ứng dụng chủ yếu chạy chỉ với một NotifyIcon trong khay hệ thống (vùng gần đồng hồ trên thanh tác vụ). Khi thiết kế một ứng dụng như vậy, hãy nhớ rằng bạn không muốn biểu mẫu chính của dự án là biểu mẫu mà người dùng tương tác. Thay vào đó, hãy tạo một biểu mẫu riêng để hiển thị khi ứng dụng chạy. Lý do của việc này sẽ được giải thích rõ ràng sau đây.
Trong ví dụ của mình, tôi đã tạo hai biểu mẫu: HiddenForm, biểu mẫu chính của ứng dụng với hầu hết logic và OptionsForm, một biểu mẫu cho phép người dùng tuỳ chỉnh một số lựa chọn và đăng nhập vào Tài khoản Google của họ. Tôi cũng đã thêm một NotifyIcon có tên là DocListNotifyIcon vào HiddenForm và tuỳ chỉnh nó bằng biểu tượng của riêng mình. Để đảm bảo người dùng không nhìn thấy HiddenForm, tôi đặt độ mờ của HiddenForm thành 0%, WindowState thành Minimized và thuộc tính ShowInTaskbar thành False.
Thông thường, khi một chương trình đang chạy ngoài khay hệ thống, việc đóng ứng dụng không được phép dừng chương trình, mà thay vào đó, ứng dụng sẽ ẩn mọi biểu mẫu đang hoạt động và chỉ để lại NotifyIcon. Để làm được điều này, chúng ta phải ghi đè sự kiện "FormClosing" của biểu mẫu như sau:
private void OptionsForm_FormClosing(object sender, FormClosingEventArgs e)
{
if(e.CloseReason == CloseReason.UserClosing) {
this.Hide();
e.Cancel = true;
}
}Chúng ta cũng nên ẩn biểu mẫu khi người dùng thu nhỏ biểu mẫu, vì không có lý do gì để chiếm không gian trên thanh tác vụ vì chúng ta đã có biểu tượng thông báo. Bạn có thể thực hiện việc này bằng đoạn mã sau:
private void OptionsForm_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
}
}Vì chúng ta không cho phép người dùng đóng OptionsForm, nên chúng ta chỉ cần giữ một phiên bản được liên kết với HiddenForm. Khi muốn hiển thị lại OptionsForm, chúng ta chỉ cần gọi phương thức Show của OptionsForm.
Vì biểu mẫu chính của ứng dụng này (HiddenForm) không hiển thị cho người dùng, nên chúng ta phải cung cấp cho họ một cách để thực sự thoát khỏi ứng dụng. Tôi đã chọn thêm một ContextMenu vào NotifyIcon bằng một ToolStripMenuItem để đóng ứng dụng. Để viết trình xử lý lượt nhấp, bạn chỉ cần gọi phương thức Close của HiddenForm.
Mẹo về bong bóng
Nhiều ứng dụng trong khay hệ thống giao tiếp với người dùng bằng cách hiển thị một mẹo dạng bong bóng. Mẹo này trông giống như một bong bóng tròn xuất phát từ NotifyIcon. Bong bóng có thể xuất hiện như sau:
DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);
Đối số đầu tiên là khoảng thời gian (tính bằng mili giây) để hiển thị bong bóng. Xin lưu ý rằng hệ điều hành sẽ cho phép thời gian tối thiểu và tối đa cho trường này, lần lượt là 10 và 30 giây. Đối số thứ hai và thứ ba chỉ định tiêu đề và một số nội dung cho bong bóng. Đối số cuối cùng cho phép bạn chọn một biểu tượng để minh hoạ mục đích của bong bóng.
Tải tài liệu lên
Việc tải giấy tờ lên rất đơn giản. Hầu hết công việc được thực hiện bằng phương thức UploadDocument của đối tượng DocumentsService. Quy trình này được giải thích rõ ràng hơn trong Hướng dẫn dành cho nhà phát triển về Documents List API.
service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password);Trước tiên, bạn phải khởi chạy đối tượng DocumentsService và cung cấp thông tin đăng nhập của người dùng. Để ngăn chặn một số vấn đề khi tải nhiều tệp lên, tiêu đề HTTP "keep-alive" đã bị vô hiệu hoá vì tiêu đề này được biết là gây ra một số vấn đề với .NET Framework.
lastUploadEntry = service.UploadDocument(fileName, null);
Đoạn mã này tải tệp lên theo đường dẫn có trong chuỗi fileName. Đối số thứ hai là giá trị rỗng cho biết tên tệp trên Google Tài liệu sẽ giống với tên tệp gốc.
Xử lý thao tác kéo và thả
Để giúp người dùng tải lên dễ dàng hơn, bạn nên cho phép họ kéo và thả tệp từ thư mục vào ứng dụng để tải lên. Bước đầu tiên là cho phép thao tác thả từ một tệp, mã bên dưới sẽ thay đổi con trỏ để cho biết thao tác thả được phép:
private void OptionsForm_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
{
e.Effect = DragDropEffects.Copy;
}
}Sau khi người dùng thả tệp hoặc nhóm tệp, chúng ta phải xử lý sự kiện đó bằng cách duyệt qua từng tệp đã được thả và tải tệp đó lên:
private void OptionsForm_DragDrop(object sender, DragEventArgs e)
{
string[ fileList = (string[) e.Data.GetData(DataFormats.FileDrop);
foreach (string file in fileList)
{
mainForm.UploadFile(file);
}
}Liệt kê tài liệu
Việc lấy danh sách tài liệu từ máy chủ là một cách hay để nhắc người dùng về những gì họ đã tải lên. Đoạn mã dưới đây sử dụng đối tượng DocumentsService mà chúng ta đã khởi tạo trước đó để truy xuất tất cả tài liệu từ máy chủ.
public DocumentsFeed GetDocs()
{
DocumentsListQuery query = new DocumentsListQuery();
DocumentsFeed feed = service.Query(query);
return feed;
}Một cách thuận tiện để trực quan hoá dữ liệu này là sử dụng ListView. Tôi đã thêm một ListView có tên là DocList vào OptionsForm. Để làm cho giao diện đẹp hơn, tôi cũng đã tạo một ImageList tuỳ chỉnh gồm các biểu tượng minh hoạ nhiều loại tài liệu. Đoạn mã sau đây điền sẵn thông tin được truy xuất từ nguồn cấp dữ liệu ở trên vào ListView:
public void UpdateDocList()
{
DocList.Items.Clear();
DocumentsFeed feed = mainForm.GetDocs();
foreach (DocumentEntry entry in feed.Entries)
{
string imageKey = "";
if (entry.IsDocument)
{
imageKey = "Document.gif";
}
else if (entry.IsSpreadsheet)
{
imageKey = "Spreadsheet.gif";
}
else
{
imageKey = "Presentation.gif";
}
ListViewItem item = new ListViewItem(entry.Title.Text, imageKey);
item.SubItems.Add(entry.Updated.ToString());
item.Tag = entry;
DocList.Items.Add(item);
}
foreach (ColumnHeader column in DocList.Columns)
{
column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
}
}Biến imageKey sẽ chọn hình ảnh nào trong ImageList được liên kết sẽ dùng cho mỗi hàng. Thuộc tính Tag được dùng ở đây để lưu trữ mục nhập ban đầu. Điều này có thể hữu ích khi thực hiện các thao tác trên tài liệu sau này. Cuối cùng, phương thức AutoResize được dùng để tự động định dạng chiều rộng cột trong ListView.
Mở tài liệu trong trình duyệt
Vì những tài liệu này được lưu trữ trong Google Tài liệu, nên bạn có thể cho phép người dùng xem tài liệu trong trình duyệt của họ. Windows có chức năng tích hợp sẵn để thực hiện việc này:
using System.Diagnostics;
private void OpenSelectedDocument()
{
if (DocList.SelectedItems.Count > 0)
{
DocumentEntry entry = (DocumentEntry) DocList.SelectedItems[0].Tag;
Process.Start(entry.AlternateUri.ToString());
}
}
Ở đây, chúng ta truy xuất mục nhập ban đầu từ thuộc tính Tag, sau đó dùng AlternateUri của tài liệu đã chọn để gọi Process.Start. Phần còn lại được xử lý bằng tính năng của .NET Framework.
Thêm trình đơn theo bối cảnh của Shell
Cách đơn giản nhất để thêm một mục vào trình đơn theo bối cảnh của shell là sửa đổi sổ đăng ký. Chúng ta cần tạo một mục trong HKEY_CLASSES_ROOT trỏ đến ứng dụng của mình. Xin lưu ý rằng thao tác này sẽ mở một phiên bản mới của ứng dụng khi người dùng nhấp vào mục trong trình đơn. Đây là điều chúng ta sẽ phải xử lý trong các phần sau.
using Microsoft.Win32;
public void Register()
{
RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME+"\\command");
if (key == null)
{
key = Registry.ClassesRoot.CreateSubKey("*\\shell\\" + KEY_NAME + "\\command");
}
key.SetValue("", Application.ExecutablePath + " \"%1\"");
}Mã này tạo khoá đăng ký cho vị trí của ứng dụng hiện đang chạy. Ký hiệu "%1" được dùng để cho biết rằng tệp đã chọn trong trình bao sẽ được truyền vào bên trong tham số này. KEY_NAME là một hằng số chuỗi được xác định, xác định văn bản của mục trong trình đơn theo bối cảnh.
public void UnRegister()
{
RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME);
if (key != null)
{
Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\"+KEY_NAME);
}
}Phương thức này chỉ cần xoá khoá tuỳ chỉnh mà chúng ta đã thêm (nếu có).
Ngăn chặn nhiều phiên bản
Vì ứng dụng của chúng ta nằm trong khay hệ thống, nên chúng ta không muốn nhiều phiên bản của chương trình chạy cùng một lúc. Chúng ta có thể dùng Mutex để đảm bảo chỉ có một phiên bản tiếp tục chạy.
using System.Threading;
bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
return;
}Bạn có thể đặt mã ở trên trong phương thức Main của ứng dụng để thoát sớm nếu chương trình của chúng ta đã chạy. Vì Mutex nằm trong không gian tên "Local" (Cục bộ), nên điều này cho phép một phiên khác trên máy chạy ứng dụng của chúng ta một cách riêng biệt. Tuy nhiên, bạn cần thận trọng hơn vì chúng tôi đang sửa đổi sổ đăng ký toàn cầu.
Giao tiếp liên quy trình
Khi người dùng nhấp vào mục trong trình đơn theo ngữ cảnh của shell cho một tệp mà chúng ta đã thêm trước đó, một phiên bản mới của ứng dụng sẽ được chạy và có đường dẫn đầy đủ đến vị trí của tệp trên đĩa. Giờ đây, thông tin này phải được truyền đến phiên bản đang chạy của ứng dụng. Bạn có thể thực hiện việc này bằng cách sử dụng cơ chế IPC của .NET Framework được giới thiệu trong phiên bản 2.0.
using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc;
Thông báo mà chúng ta đang truyền có dạng một đối tượng tuỳ chỉnh. Ở đây, tôi đã tạo một đối tượng chứa thông tin tham chiếu trở lại HiddenForm chứa logic của ứng dụng này. Vì đối tượng này sẽ được lưu trữ trên phiên bản ban đầu, nên đối tượng này cung cấp cho phiên bản sau một cách để giao tiếp với biểu mẫu chính của phiên bản ban đầu.
class RemoteMessage : MarshalByRefObject
{
private HiddenForm mainForm;
public RemoteMessage(HiddenForm mainForm)
{
this.mainForm = mainForm;
}
public void SendMessage(string file)
{
mainForm.HandleUpload(file);
}
}
Khi phiên bản đầu tiên của ứng dụng được khởi chạy, đoạn mã sau đây sẽ cho phép ứng dụng đó theo dõi các phiên bản tiếp theo:
public void ListenForSuccessor()
{
IpcServerChannel serverChannel = new IpcServerChannel("DocListUploader");
ChannelServices.RegisterChannel(serverChannel, false);
RemoteMessage remoteMessage = new RemoteMessage(this);
RemotingServices.Marshal(remoteMessage,"FirstInstance");
}Lưu ý rằng trong ví dụ trên, mã này đăng ký một kênh IPC có tên và cung cấp bản sao của đối tượng RemoteMessage mà chúng ta đã xác định, đồng thời khởi động đối tượng đó bằng một tham chiếu đến chính nó.
Đối với các phiên bản tiếp theo của chương trình, chuỗi được cung cấp cho Main thông qua tham số args cần được truyền đến phiên bản ban đầu. Bạn có thể gọi mã sau để kết nối với kênh IPC đang nghe và truy xuất đối tượng RemoteMessage từ phiên bản ban đầu. Sau đó, phương thức SendMessage được dùng để truyền tên tệp cùng với phiên bản ban đầu.
public static void NotifyPredecessor(string file)
{
IpcClientChannel clientChannel = new IpcClientChannel();
ChannelServices.RegisterChannel(clientChannel, false);
RemoteMessage message = (RemoteMessage) Activator.GetObject(typeof(RemoteMessage),
"ipc://DocListUploader/FirstInstance");
if (!message.Equals(null))
{
message.SendMessage(file);
}
}Hệ thống nhắn tin từ xa rất mạnh mẽ vì thông qua hệ thống này, chúng ta có thể làm cho các đối tượng thuộc một phiên bản của chương trình hiển thị trên các kênh IPC cục bộ cho các phiên bản khác.
Kết luận
Bài viết này giải thích ở mức độ tổng quan về một số phương pháp và thủ thuật được dùng trong mẫu Trình tải lên DocList để cung cấp một tiện ích di chuyển thân thiện cho Google Tài liệu. Bạn vẫn có thể thêm nhiều chức năng vào các ứng dụng của riêng mình và bạn có thể thoải mái mở rộng mẫu này cho phù hợp với mục đích của riêng mình.
Sau đây là một số tài nguyên hữu ích dành cho những nhà phát triển quan tâm đến việc sử dụng Documents List Data API, cũng như những người muốn sử dụng .NET với các Google Data API khác: