جيف فيشر، فريق Google Data APIs
كانون الثاني (يناير) 2008
مقدمة: نطاق العيّنة
نزِّل الملف التنفيذي النموذجي.
من الميزات الرائعة في Documents List Data API أنّها تتيح للمطوّرين إنشاء أدوات نقل البيانات للمستخدمين الذين لم يعتادوا بعد على مستندات Google. لأغراض استخدام واجهة برمجة التطبيقات هذه، استعملتُ مكتبة برامج.NET لإنشاء تطبيق تحميل .NET 2.0، وأطلقتُ عليه الاسم المناسب "أداة تحميل DocList". يمكنك الحصول على مصدر القائم بالتحميل من Subversion.
يهدف هذا النموذج إلى تسهيل نقل المستخدمين لمستنداتهم من أجهزة الكمبيوتر إلى "مستندات Google". تتيح هذه الأداة للمستخدمين تسجيل الدخول إلى حساباتهم على Google ثم سحب الملفات المتوافقة وإفلاتها ليتم تحميلها تلقائيًا. يوفّر النموذج أيضًا خيار إضافة خيار قائمة النقر بزر الماوس الأيمن إلى واجهة Windows Explorer لتحميل الملفات. يتم توفير هذا النموذج بموجب ترخيص Apache 2.0، لذا يمكنك استخدامه كنقطة بداية لبرامجك الخاصة.
تهدف هذه المقالة إلى توضيح كيفية إنجاز بعض سلوك العيّنة باستخدام إطار عمل .NET. ويتألف في الغالب من مقتطفات رموز مشروحة من الأقسام ذات الصلة. لا تغطي هذه المقالة كيفية إنشاء النماذج ومكوّنات واجهة المستخدم الأخرى للتطبيق نفسه، لأنّ هناك العديد من مقالات Visual Studio التي تتناول هذا الموضوع بالتفصيل. إذا كنت مهتمًا بمعرفة كيفية ضبط إعدادات عناصر واجهة المستخدم، يمكنك تحميل ملف المشروع بنفسك عن طريق تنزيل مكتبة العميل والبحث داخل الدليل الفرعي "clients\cs\samples\DocListUploader".
إنشاء تطبيق في لوحة النظام
عادةً ما يمكن تشغيل أدوات نقل البيانات بشكل غير مزعج في نظام التشغيل، ما يؤدي إلى توسيع نطاق ما يمكن لنظام التشغيل فعله بدون تشتيت انتباه المستخدم بشكل كبير. إحدى طرق تنظيم هذه الأداة في Windows هي تشغيلها من "صينية النظام" بدلاً من إحداث فوضى في شريط المهام. ويزيد ذلك من احتمالية ترك المستخدمين للبرنامج يعمل باستمرار بدلاً من فتحه فقط عندما يحتاجون إلى تنفيذ مهمة معيّنة. هذه فكرة مفيدة بشكل خاص لهذا النموذج لأنّه لا يحتاج إلى تخزين بيانات اعتماد المصادقة على القرص.
تطبيق لوحة النظام هو تطبيق يعمل بشكل أساسي باستخدام NotifyIcon فقط في لوحة النظام (المنطقة القريبة من الساعة على شريط المهام). عند تصميم تطبيق من هذا النوع، ضَع في اعتبارك أنّك لا تريد أن يكون الشكل الرئيسي للمشروع هو الشكل الذي يتفاعل معه المستخدم. بدلاً من ذلك، يمكنك إنشاء نموذج منفصل لعرضه عند تشغيل التطبيق. سيتضح السبب في ذلك بعد لحظات.
في المثال الذي أقدّمه، أنشأتُ نموذجين: HiddenForm، وهو النموذج الرئيسي للتطبيق الذي يتضمّن معظم منطق التطبيق، وOptionsForm، وهو نموذج يتيح للمستخدم تخصيص بعض الخيارات وتسجيل الدخول إلى حسابه على Google. أضفتُ أيضًا NotifyIcon باسم DocListNotifyIcon إلى HiddenForm وعدّلتُه باستخدام الرمز الخاص بي. للتأكّد من أنّ المستخدم لا يرى 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 لإغلاق التطبيق. لكتابة معالج النقرات، ما عليك سوى استدعاء الطريقة Close من HiddenForm.
تلميحات البالونات
تتواصل العديد من تطبيقات علبة النظام مع المستخدم من خلال عرض تلميح بالون، والذي يبدو كفقاعة مستديرة تنبثق من NotifyIcon. يمكن عرض الفقاعة على النحو التالي:
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. تشير القيمة الخالية للوسيطة الثانية إلى أنّ اسم ملف "مستندات Google" سيكون هو نفسه اسم الملف الأصلي.
التعامل مع السحب والإفلات
لتسهيل عملية التحميل، من المنطقي السماح للمستخدم بسحب الملفات وإفلاتها من مجلداته إلى التطبيق لتحميلها. الخطوة الأولى هي السماح بعملية السحب والإفلات من ملف، وسيؤدي الرمز التالي إلى تغيير المؤشر للإشارة إلى أنّه يُسمح بالسحب والإفلات:
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. لتحسين المظهر، أنشأتُ أيضًا ImageList مخصّصًا للرموز لتوضيح أنواع المستندات المختلفة. يملأ الرمز التالي 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"، من الجيد أن يتمكّن المستخدم من الاطّلاع على المستند في متصفحه. تتوفّر وظيفة مضمّنة لإجراء ذلك في نظام التشغيل 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" للإشارة إلى أنّه يجب تمرير الملف المحدّد في shell داخل هذه المَعلمة. 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"، يتيح ذلك تشغيل تطبيقنا بشكل منفصل في جلسة مختلفة على الجهاز. ومع ذلك، يجب توخّي بعض الحذر الإضافي لأنّنا نعدّل السجلّ العام.
التواصل البيني للعمليات
عندما ينقر المستخدم على عنصر قائمة سياقية في Shell لملف أضفناه سابقًا، يتم تشغيل مثيل جديد من تطبيقنا ويتم منحه المسار الكامل لمكان وجود الملف على القرص. ويجب الآن إرسال هذه المعلومات إلى نسخة التطبيق التي تعمل حاليًا. يمكن إجراء ذلك باستخدام آليات 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 Uploader لتوفير أداة سهلة لنقل البيانات إلى "مستندات Google". لا يزال هناك الكثير من الوظائف التي يمكن إضافتها في تطبيقاتك، ويمكنك توسيع نطاق العيّنة لتناسب أغراضك.
في ما يلي بعض المراجع المفيدة للمطوّرين المهتمين بالعمل مع Documents List Data API، بالإضافة إلى أولئك الذين يريدون استخدام .NET مع واجهات Google Data API الأخرى: