ג'ף פישר, צוות Google Data APIs
ינואר 2008
מבוא: היקף המדגם
מורידים את קובץ ההפעלה לדוגמה.
אחד היתרונות של Documents List Data API הוא שהוא מאפשר למפתחים ליצור כלי העברה למשתמשים שעדיין מתרגלים לשימוש ב-Google Docs. לצורך השימוש ב-API הזה, השתמשתי בספריית הלקוח של .NET כדי ליצור אפליקציית העלאה ל- .NET 2.0, שנקראת "DocList Uploader". אפשר לקבל את המקור של כלי ההעלאה מ-Subversion.
הדוגמה הזו נועדה להקל על המשתמשים להעביר את המסמכים שלהם מהמחשב ל-Google Docs. הכלי מאפשר למשתמשים להתחבר לחשבון Google שלהם, ואז לגרור ולשחרר קבצים נתמכים שיועלו באופן אוטומטי. הדוגמה מספקת גם אפשרות להוסיף אפשרות לתפריט של קליק ימני במעטפת של סייר Windows כדי להעלות קבצים. הדוגמה הזו מסופקת במסגרת רישיון Apache 2.0, כך שאתם יכולים להשתמש בה כנקודת התחלה לתוכניות משלכם.
במאמר הזה נסביר איך חלק מההתנהגות של הדוגמה הושג באמצעות מסגרת .NET. הוא מורכב בעיקר מקטעי קוד עם הערות מהקטעים הרלוונטיים. במאמר הזה לא מוסבר איך ליצור את הטפסים ורכיבי ממשק המשתמש האחרים של האפליקציה עצמה, כי יש הרבה מאמרים של Visual Studio שכוללים הסברים מפורטים בנושא הזה. אם אתם רוצים לדעת איך רכיבי ממשק המשתמש הוגדרו, אתם יכולים לטעון את קובץ הפרויקט בעצמכם. לשם כך, צריך להוריד את ספריית הלקוח ולעיין בספריית המשנה clients\cs\samples\DocListUploader.
יצירת אפליקציה במגש המערכת
בדרך כלל, כלי העברה יכולים לפעול בצורה לא פולשנית במערכת ההפעלה, ולהרחיב את היכולות של מערכת ההפעלה בלי להפריע יותר מדי למשתמש. אחת הדרכים לארגן כלי כזה ב-Windows היא להפעיל אותו ממגש המערכת במקום להעמיס על שורת המשימות. כך גדל הסיכוי שהמשתמשים ישאירו את התוכנה פועלת ברציפות, במקום לפתוח אותה רק כשהם צריכים לבצע משימה ספציפית. הרעיון הזה שימושי במיוחד בדוגמה הזו, כי אין צורך לאחסן את פרטי האימות בדיסק.
אפליקציה במגש המערכת היא אפליקציה שפועלת בעיקר עם סמל התראה במגש המערכת (האזור ליד השעון בסרגל המשימות). כשמעצבים אפליקציה כזו, חשוב לזכור שהטופס הראשי של הפרויקט לא צריך להיות הטופס שהמשתמש מקיים איתו אינטראקציה. במקום זאת, יוצרים טופס נפרד שיוצג כשהאפליקציה תופעל. הסיבה לכך תובהר בהמשך.
בדוגמה שלי יצרתי שני טפסים: HiddenForm, הטופס הראשי של האפליקציה עם רוב הלוגיקה, ו-OptionsForm, טופס שמאפשר למשתמש להתאים אישית כמה אפשרויות ולהיכנס לחשבון Google שלו. הוספתי גם NotifyIcon בשם DocListNotifyIcon ל-HiddenForm והתאמתי אותו באמצעות סמל משלי. כדי לוודא שהמשתמש לא יראה את HiddenForm, הגדרתי את האטימות שלו ל-0%, את WindowState ל-Minimized ואת המאפיין ShowInTaskbar ל-False.
בדרך כלל, כשתוכנה פועלת ממגש המערכת, סגירת האפליקציה לא אמורה להפסיק את התוכנה, אלא להסתיר את כל הטפסים הפעילים ולהשאיר רק את הסמל NotifyIcon גלוי. כדי לעשות את זה, צריך לבטל את ברירת המחדל של האירוע FormClosing של הטופס שלנו באופן הבא:
private void OptionsForm_FormClosing(object sender, FormClosingEventArgs e)
{
if(e.CloseReason == CloseReason.UserClosing) {
this.Hide();
e.Cancel = true;
}
}בנוסף, כדאי להסתיר את הטופס כשהמשתמש ממזער אותו, כי אין סיבה שהוא יתפוס מקום בסרגל המשימות אם כבר יש לנו סמל התראה. אפשר לעשות זאת באמצעות קטע הקוד הבא:
private void OptionsForm_Resize(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
}
}עכשיו, מכיוון שאנחנו לא מאפשרים למשתמש לסגור את OptionsForm, אנחנו יכולים להשאיר רק מופע אחד שקשור ל-HiddenForm. אם רוצים להציג שוב את OptionsForm, אפשר פשוט לקרוא לשיטה Show שלו.
מכיוון שהטופס הראשי של האפליקציה הזו, HiddenForm, לא גלוי למשתמש, אנחנו צריכים לספק למשתמש דרך לצאת מהאפליקציה. בחרתי להוסיף ContextMenu ל-NotifyIcon עם ToolStripMenuItem כדי לסגור את האפליקציה. כדי לכתוב את ה-handler של הקליקים, פשוט קוראים לשיטה Close של HiddenForm.
הסברים קצרים
הרבה אפליקציות במגש המערכת מתקשרות עם המשתמש באמצעות הצגת טיפ בצורת בועה מעוגלת שיוצאת מהסמל של ההתראה. הבועה יכולה להופיע כך:
DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);
הארגומנט הראשון הוא משך הזמן באלפיות שנייה שבו הבועה תוצג. שימו לב: יש ערכי מינימום ומקסימום של זמן שהמערכת תאפשר להזין בשדה הזה, והם 10 ו-30 שניות בהתאמה. הארגומנטים השני והשלישי מציינים כותרת ותוכן לבועה. הארגומנט האחרון מאפשר לכם לבחור סמל שימחיש את המטרה של הבועה.
העלאת מסמכים
העלאת מסמך היא פשוטה. רוב העבודה מתבצעת על ידי שיטת UploadDocument של אובייקט DocumentsService. הסבר ברור יותר על התהליך הזה מופיע במדריך למפתחים בנושא Documents List API.
service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password);קודם צריך לאתחל את האובייקט DocumentsService ולספק את פרטי הכניסה של המשתמש. כדי למנוע בעיות מסוימות בהעלאה של כמה קבצים, השבתנו את כותרת ה-HTTP 'keep-alive', כי ידוע שהיא גורמת לבעיות מסוימות ב- .NET Framework.
lastUploadEntry = service.UploadDocument(fileName, null);
בקטע הקוד הזה, הקובץ מועלה בנתיב שמופיע במחרוזת fileName. הארגומנט השני הוא null, מה שאומר ששם הקובץ ב-Google Docs יהיה זהה לשם הקובץ המקורי.
טיפול בגרירה ובשחרור
כדי להקל על ההעלאה, כדאי לאפשר למשתמשים לגרור ולשחרר קבצים מהתיקיות שלהם אל האפליקציה כדי להעלות אותם. השלב הראשון הוא לאפשר את פעולת הגרירה מקובץ. הקוד שבהמשך ישנה את הסמן כדי לציין שהגרירה מותרת:
private void OptionsForm_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
{
e.Effect = DragDropEffects.Copy;
}
}אחרי שגוררים את הקובץ או קבוצת הקבצים, צריך לטפל באירוע הזה. לשם כך, עוברים על כל קובץ שנגרר ומעלים אותו:
private void OptionsForm_DragDrop(object sender, DragEventArgs e)
{
string[ fileList = (string[) e.Data.GetData(DataFormats.FileDrop);
foreach (string file in fileList)
{
mainForm.UploadFile(file);
}
}מסמכים שקשורים לרישום
קבלת רשימה של מסמכים מהשרת היא דרך טובה להזכיר למשתמש מה הוא כבר העלה. בקטע הקוד שלמטה נעשה שימוש באובייקט DocumentsService שאותחל קודם כדי לאחזר את כל המסמכים מהשרת.
public DocumentsFeed GetDocs()
{
DocumentsListQuery query = new DocumentsListQuery();
DocumentsFeed feed = service.Query(query);
return feed;
}דרך נוחה להציג את הנתונים האלה היא באמצעות ListView. הוספתי ListView בשם DocList ל-OptionsForm. כדי שהממשק יהיה נעים יותר, יצרתי גם רשימה מותאמת אישית של סמלים כדי להמחיש את סוגי המסמכים השונים. הקוד הבא מאכלס את 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);
}
}המשתנה imageKey בוחר איזו תמונה מתוך ImageList המשויך תשמש לכל שורה. המאפיין Tag משמש כאן לאחסון הרשומה המקורית, שיכולה להיות שימושית לביצוע פעולות במסמך בהמשך. לבסוף, נעשה שימוש בשיטה AutoResize כדי לעצב אוטומטית את רוחב העמודה ב-ListView.
פתיחת מסמכים בדפדפן
מכיוון שהמסמכים האלה מאוחסנים ב-Google Docs, כדאי לאפשר למשתמש לראות את המסמך בדפדפן. יש פונקציונליות מובנית ב-Windows שמאפשרת לעשות את זה:
using System.Diagnostics;
private void OpenSelectedDocument()
{
if (DocList.SelectedItems.Count > 0)
{
DocumentEntry entry = (DocumentEntry) DocList.SelectedItems[0].Tag;
Process.Start(entry.AlternateUri.ToString());
}
}
כאן אנחנו מאחזרים את הרשומה המקורית מתוך המאפיין Tag, ואז משתמשים ב-AlternateUri של המסמך שנבחר כדי לקרוא ל-Process.Start. כל השאר מטופל על ידי הקסם של .NET Framework.
הוספה של תפריט הקשר של Shell
הדרך הפשוטה ביותר להוסיף פריט לתפריט ההקשר של המעטפת היא באמצעות שינוי הרישום. מה שצריך לעשות הוא ליצור רשומה ב-HKEY_CLASSES_ROOT שמפנה לאפליקציה שלנו. שימו לב: כשמשתמש לוחץ על פריט בתפריט, נפתח מופע חדש של האפליקציה שלנו. נסביר איך לטפל בזה בקטעים הבאים.
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\"");
}הקוד הזה יוצר את מפתח הרישום שבו נמצאת האפליקציה שפועלת כרגע. הסימון '%1' משמש כדי לציין שהקובץ שנבחר במעטפת צריך לעבור בתוך הפרמטר הזה. KEY_NAME היא קבוע מחרוזת מוגדר שקובע את הטקסט של הערך בתפריט ההקשר.
public void UnRegister()
{
RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME);
if (key != null)
{
Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\"+KEY_NAME);
}
}השיטה הזו פשוט מסירה את המפתח המותאם אישית שהוספנו, אם הוא קיים.
מניעת מופעים מרובים
מכיוון שהאפליקציה שלנו נמצאת במגש המערכת, אנחנו לא רוצים שיופעלו כמה מופעים של התוכנה בו-זמנית. אנחנו יכולים להשתמש בMutex כדי לוודא שרק מופע אחד ימשיך לפעול.
using System.Threading;
bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
return;
}אפשר להציב את הקוד שלמעלה בשיטה Main של האפליקציה כדי לצאת מוקדם אם התוכנית כבר פועלת. מאחר ש-Mutex נמצא במרחב השמות Local, הוא מאפשר לסשן אחר במכונה להריץ את האפליקציה שלנו בנפרד. עם זאת, צריך לנקוט משנה זהירות, כי אנחנו משנים את הרישום הגלובלי.
תקשורת בין תהליכים (IPC)
כשמשתמש לוחץ על הפריט בתפריט ההקשר של המעטפת של קובץ שהוספנו קודם, מופעל מופע חדש של האפליקציה שלנו ומועבר אליו הנתיב המלא של מיקום הקובץ בדיסק. עכשיו צריך להעביר את המידע הזה למופע שכבר פועל של האפליקציה. אפשר לעשות את זה באמצעות מנגנוני ה-IPC של .NET Framework שהוצגו בגרסה 2.0.
using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Ipc;
ההודעה שאנחנו מעבירים היא בצורה של אובייקט מותאם אישית. כאן יצרתי אובייקט שמכיל הפניה חזרה אל HiddenForm שמכיל את הלוגיקה של האפליקציה הזו. מכיוון שהאובייקט הזה יתארח במופע המקורי, הוא מאפשר למופע מאוחר יותר לתקשר עם הטופס הראשי של המופע המקורי.
class RemoteMessage : MarshalByRefObject
{
private HiddenForm mainForm;
public RemoteMessage(HiddenForm mainForm)
{
this.mainForm = mainForm;
}
public void SendMessage(string file)
{
mainForm.HandleUpload(file);
}
}
כשמופעלת הפעם הראשונה של האפליקציה, הקוד הבא מאפשר לה להאזין להפעלות הבאות:
public void ListenForSuccessor()
{
IpcServerChannel serverChannel = new IpcServerChannel("DocListUploader");
ChannelServices.RegisterChannel(serverChannel, false);
RemoteMessage remoteMessage = new RemoteMessage(this);
RemotingServices.Marshal(remoteMessage,"FirstInstance");
}שימו לב בדוגמה שלמעלה שהיא רושמת ערוץ IPC עם שם, ומספקת עותק של אובייקט RemoteMessage שהגדרנו, ומאתחלת אותו עם הפניה לעצמו.
במקרים הבאים של התוכנית, המחרוזת שסופקה ל-Main דרך הפרמטר args צריכה לעבור למופע המקורי. אפשר לקרוא לקוד הבא כדי להתחבר לערוץ ה-IPC שממתין להודעות ולאחזר את האובייקט RemoteMessage מהמופע המקורי. לאחר מכן נעשה שימוש בשיטה SendMessage כדי להעביר את שם הקובץ למופע המקורי.
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);
}
}מערכת ההודעות מרחוק היא עוצמתית מאוד כי דרכה אנחנו יכולים להפוך אובייקטים ששייכים למופע אחד של התוכנית שלנו לגלויים בתוך ערוצי IPC מקומיים למופעים אחרים.
סיכום
במאמר הזה אנחנו מסבירים ברמה גבוהה כמה מהשיטות והטריקים השונים שבהם השתמשנו בדוגמה של כלי ההעלאה DocList כדי לספק כלי ידידותי להעברה של מסמכי Google. עדיין יש הרבה פונקציונליות שאפשר להוסיף לאפליקציות שלכם, ואתם יכולים להרחיב את הדוגמה כך שתתאים למטרות שלכם.
ריכזנו כאן כמה מקורות מידע שימושיים למפתחים שרוצים לעבוד עם Documents List Data API, וגם למפתחים שרוצים להשתמש ב- .NET עם Google Data APIs אחרים: