السلام عليكم ورحمة الله وبركاته.
تقريبا كل البرامج باستثناء القليل تدعم عمليات الـ Drag & Drop مهما كان طبيعة عمل هذه البرامج ... وتجد البرامج تدعمها بأشكال مختلفه على حسب طبيعة عملها, تجد مثلا برنامج مثل الـ FrontPage أو Word يدعمها بطريقه رائعه فاذا قررت أن تقوم بسحب Text من صفحه انترنت مفتوحه بالـ Browser وعمل Drop لهذا الـ Text على المساحه الخاصه بالكتابه الموجده مثلا في الـ FrontPage تجد البرنامج قد نقل الـ Text بالتنسيق الذي كان عليه في الصفحه ( معه تاجات الـ HTML ) أيضا لو كان الـ Text مكتوب في جدول مثلا تجده قد نقله لك هو الآخر بالاضافه الى أنه يتيح لك أن تترك الـ text الذي نقلته بدون نقل أى تنسيق.أيضا تجد في معظم البرامج التي تتيح لك أنت تفتح منها ملف ,تدعم الـ Drag & Drop بأن تتيح لك أن تقوم بسحب الملف وعمل Drop له في مكان ما في البرنامج حتى يتم فتحه , بدلا من عمل File > Open ثم تحديد مكانه وهكذا.
عمليات الـ Drag & Drop في الـ Windows Forms عمليات سهله جدا وخطواتها بسيطه وغير معقده .. بداية يجب علينا تحديد ما هى الـ Control الموجوده على الـ Form التي نود أن نجعلها تسمح بأن يحدث Drop عليها مثلا من الممكن أن تكون ListBox ونريد أن نسمح للمستخدم أن يسحب ملف عليها فيتم اضافة عنوان الملف للـ List أو مثلا TextBox لنتيح للمستخدم أن يسحب أى Text من أى مكان لينقله أو ينسخه في الـ TextBox ... أو مثلا PictureBox ونريد أن نتيح للمستخدم أن يسحب ملف يحتوي على صوره ليتم عرضها .... عموما بعد تحديد الـ Control يجب أن نقوم بعمل الخاصيه AllowDrop = ture لأنها تكون false بطريقه افتراضيه.
نأتي للأحداث الخاصه بهذا الأمر ... أربع أحداث سنهتم بهم في هذا الموضوع :
DragEnter , DragOver , DragDrop , DragLeave
وتجد delegate واحد خاص بهذه الأحداث هو DragEventHandler ماعدا DragLeave تجده EventHandler عادي.
الأحداث واضحه من أسمائها فـ DragEnter هو أول حدث يحدث وذلك عندما يكون المستخدم ساحب شئ معه ويدخل بهذا الشئ في حدود الـ Control ,وبعدها مباشرة تشعر الـ Control بأن شئ ما فوقها فيحدث الحدث DragOver مباشرة ... وبمجرد أن يقوم المستخدم برفع يده عن زرار الـ Mouse ليسقط الأشياء المسحوبه على الـ Contorl تجد حدث DragDrop تنفذ, أما حدث DragLeave فهو يحدث عندما يقوم المستخدم بسحب شئ ما على الـ Control ثم لا يقوم بالقاء هذا الشئ عليها وانما يذهب مثلا ليقليه في مكان آخر أو يغير رأيه.
بشكل عام الذي يحدث هو انك من خلال الحدث DragOver أو DragEnter تقوم بمعرفة نوع الأشياء التي يسحبها المستخدم لترى اذا كانت هذه الأشياء تتناسب مع برنامجك أم لا ولترى ماذا ستفعل اذا كانت هذه الأشياء عباره عن ملف يسحبه أم Text أم ماذا ? وذلك لتتصرف تصرف معين, وكل هذه الأشياء نعرفها من خلال المعلومات المتوفره في الـ DragEventArgs.
DragEventArgs يحتوي على بعض الخواص الشيقه .. منها الـ X والـ Y الخاصين بالـ Mouse و الـ KeyState الذي يمكننا أن نعرف من خلاله حالة الأزرار الخاصه بكل من الـ Mouse والـ Keyboard حتى نستطيع أن نحدد مثلا أن نقوم بنسخ الشئ المسحوب اذا كان المستخدم ضاغط على زرار CTRL أو نقوم بعمل لينك للشئ اذا كان ضاغط على CTRL+SHIFT وهو يقوم بعملية السحب.
تجد فيه أيضا الخاصيه الهامه جدا المسماه Effect ... وهى تأخذ قيمها من Enumeration اسمها DragDropEffects ... ويجب علينا تحديد هذه الخاصيه باختبارات معينه مثلا اذا كان المستخدم ضغط على CTRL وهو يقوم بالسحب فنقوم بعمل هذه الخاصيه = DragDropEffects.Copy أو اذا كان المستخدم ضاغط على CTRL+SHIFT فنقوم بجعلها DragDropEffects.Link مثلا وغير ذلك نجعلها DragDropEffect.Move أو كما نريد( ولكن هذه قوانين متفق عليها وتعودنا عليها في Windows ). وطبعا نعرف الأزرار المضغوط عليها من خلال الخاصيه KeyState كما ذكرت في الأعلى ولكن , لا ينبغي اسناد القيم لهذه الخاصيه ( Effect ) على أساس معرفة الأزرار المضغوطه فقط ! فماذا اذا كان المستخدم يسحب شيئا من برنامج معين ولم يضغط على شئ ( في هذه الحاله نقوم بعملها Move ) ولكن اذا كان البرنامج الذي يسحب منه لا يدعم Move أى لا يسمح للمستخدم بأن يقوم بعمل Move للشئ ويسمح فقط بعمل Copy ؟! كيف سنعرف ذلك ؟ نعرفه من خلال الخاصيه التاليه.
الخاصيه AllowedEffect وهى خاصيه ( Readonly ليس بها set ) ترجع قيم من نفس الـ Enumeration الموجوده في العلى ( DragDropEffects ) وهى هامه جدا فهى التي نعرف من خلالها الـ Effect الذي يسمح به المصدر ! ماذا يعني ذلك ؟ مثلا برامج مثل المتصفحات لا تسمح لك بنقل الـ Text الى مكان آخر ( أقصد أنها لا تسمح بعمل MOVE للـ Text وتجدها أيضلا لا تتيح عمل Cut له ) بل هى تسمح بعمل نسخ له عند سحبه, وهذا نعرفه من خلال هذه الخاصيه حتى نعرف ماذا سنضع في الخاصيه Effect التي ذكرتها في الأعلى بجانب معرفتنا بالـ KeyState ... أى أن الخاصيتان KeyState و AllowedEffects موجودتان لكى نحدد تماما ما هى القيمه التي سنضعها في الخاصيه Effect.
مازال يوجد بعض الخواص ولكني أحب أن أفصلهم .. فالخواص التي ذكرتها تستخدم دائما في حدث DragOver أو DragEnter وهذا هو الشائع أى عندما يسحب المستخدم شئ ويدخل به على الـ Control ولكنه لم يقم بعد بعمل Drop للأشياء التي معه.
عندما يقوم المستخدم بعمل Drop يتنفذ حدث DragDrop كما ذكرت لك , هذا الحدث أيضا نستخدم معه نفس الـ EventArgs وهو DragEventArgs وسأذكر هنا باقي خواصه التي سنتسفيد منها داخل الـ EventHandler الخاص بهذا الحدث ( DragDop ).
أول هذه الخواص أهميه هى الخاصيه Data طبعا وهى التي تحتوي على الأشياء التي يسحبها المستخدم, مثلا عنوان الملف الذي يسحبه اذا كان يسحب ملفا أو النص نفسه الذي يسحبه اذا كان يسحب نصا وهكذا.
الخاصيه Data ترجع IDataObject ويوجد به بعض الدوال التي تفيدنا في جلب البيانات ومعرفة نوع البيانات ... أول هذه الدوال هى GetDataPresent وهى ترجع bool وهذه الداله نستخدمها أولا لنختبر اذا كان نوع البيانات التي يسحبها المستخدم تتوافق مع برنامجنا أم لا أو بمعنى أدق نستخدمها لنعرف كيف نتصرف بناء على نوع البيانات التي يقوم المستخدم بعمل Drop لها.
اما الداله GetData فهى ترجع object وتأخذ نفس ما تأخذه الداله السابقه ولها نفس أشكالها ولكنها ترجع البيانات المنقله أو المنسوخه نفسها.
تلاحظ في الدالتين السابقتين من ضمن أشكالهم أنهم يأخذون Type أو string تعبر عن الـ Format .. ماذا يعني ذلك ؟
بعض الـ Browsers من أمثال الـ Internet Explorer شعر بأهمية موضوع الـ Format هذا فقرر أن كل ما يخرج منه أن يخرج ومعه تاجات الـ HTML الخاصه به حتى يوفر للمستخدم أو المصمم أن يقوم بسحب مايريده من الصفحه المعروضه فيه الى محرر HTML وتكون معها نفس تاجات الـ HTML التي تحافظ على تنسيقها وبعض المعلومات الأخرى, وهذا يكشف لنا السر ( ليس سرا ولا حاجه ) الذي يجعل برنامج مثل الـ FrontPage عندما تقم بعمل Drop عليه أو Paste من أشياء أنت تنسخها من صفحه موجوده على المتصفح فانه يعرضها بنفس ألوانها وأحجامها.
قمت بعمل برنامج لمعرفة ما هى البينات التي يتم نقلها عند حدوث هذا الشئ ( سنفعل مثله في نهاية الموضوع ) بعد ذلك قمت بفتح Google وقمت بتحديد تلك الجمله الموجوده أسفل الصفحه الرئيسيه ( Make Google Your Homepage! ) وسحبتها على برنامجي .. فانظر كيف وجدت البيانات المرسله
::
Version:1.0
StartHTML:000000193
EndHTML:000001057
StartFragment:000000837
EndFragment:000001010
StartSelection:000000851
EndSelection:000001010
SourceURL:http://www.google.com/intl/en/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>Google</TITLE>
<STYLE><!--
body,td,a,p,.h{font-family:arial,sans-serif;}
.h{font-size: 20px;}
.q{color:#0000cc;}
//-->
</STYLE>
<script>
<!--
function sf(){document.f.q.focus();}
function clk(url,ct,cd,sg){if(document.images){(new Image()).src="/url?sa=T&ct="+escape(ct)+"&cd="+escape(cd)+"&url="+escape(url).replace(/\+/g,"%2B")+"&ei=0Tl4Q5LmKKG2aLTQlNsK"+sg;}return true;}
// -->
</SCRIPT>
</HEAD>
<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000cc bgColor=#ffffff
topMargin=3 onload=sf() marginheight="3">
<CENTER><!--StartFragment--><FONT size=-1><P><A
onclick="style.behavior='url(#default#homepage)';setHomePage('http://www.google.com/');"
href="/mgyhp.html">Make Google Your Homepage!</A></FONT></P><!--EndFragment--></CENTER>
</BODY>
</HTML>كل هذه البيانات نقلت بمجرد سحبي لـ أربع كلمات فقط ! تلاحظ في أول السطور :
اقتباسStartSelection:000000851 و EndSelection:000001010
وهى المعلومات الخاصه بالنص الفعلي الذي حددته ( ومعه تاجات الـ HTML أيضا ) فعندما قمت بعمل SubString للبيانات ككل على أساس EndSelection و StartSelection ::
string selectedText = myTBox.Text.Substring(851,1010-851);
استخلصت البيانات التاليه ,وهى الأهم هنا والتي تستفيد منها المحررات ( بجانب الاستفاده من المعلومات الأخرى طبعا لأن الصفحه يمكن أن تحتوي على Styles )
<P><A
onclick="style.behavior='url(#default#homepage)';setHomePage('http://www.google.com/');"
href="/mgyhp.html">Make Google Your Homepage!</A></FONT></P>اتفقنا على هذا ... ماذا يحدث لو نقلت نص من الـ Internet Explorer الى برنامج آخر ليس يفهم ولا يهمه أن يفهم تاجات الـ HTML ؟؟ مثل TextBox عادي موجود في أى برنامج ؟ في هذه الحاله يجب أن يكون مطور هذا البرنامج الذي به الـ TextBox واعي لهذا الأمر حتى لا يتم نقل كل هذا الكم من البيانات .. فمثلا لو كان الـ TextBox خاص لوضع كلمة سر وقام المستخدم بفتح بريده من المتصفح وقام بعمل Drag لكلمة السر من على المتصفح ووضعها في الـ TextBox .|. فماذا سيتم نقله ؟ فلن يرضى أحد أن تكون كلمة السر البسيطه تنقل بتاجات الـ HTML وبـ Style الصفحه !
ولهذا ينبغي علينا في برنامجنا أن نحدد الـ Format الذي نريده وذلك باستخدام أولا GetDataPresent التي ترجع لنا bool لكى تخبرنا اذ كان الـ Format الذي يتم نقله هل هو فعلا ما نريده أم لا ؟ فاذا كان ما نريده نقوم باستخراج البيانات منه من خلال الداله GetData ونمرر اليها أيضا نفس الـ Format , فاذا كانت البيانات المنقوله بصيغة HTML مثلا ونحن لا يهمنا هذا وما نريده هو النص المحدد فقط بدون أى شئ غيره ... فنمرر الـ Format كـ Text أو UniCodeText مثلا واذا كانت طبيعة برنامجنا لا تسمح بنقل Text أو أى شئ من ذلك ونريد أن نسمح فقط بعمل Drop لملف أو ملفات ... فنمرر FileDrop وهكذا.
طبعا من الصعب أن نتذكر أسماء كل الـ Formats فهى كثيره ! ومن الممكن أن نخطئ في كتابتها ولذلك تم توفير Enumeration ترجع أسماء الـ Formats المختلفه واسمها DataFormats وتجد بها كل ماتحتاج مثل FileDrop و Html و Rtf والخ.
خلصنا .. تعالى نتذكر الخطوات مره اخرى ونقم بعمل مثال.
في الحدث DragOver أو DragEnter نريد أن نسأل هذه الأسئله :
ماذا يريد المستخدم ؟ هل يريد عمل Copy أم Move أم Link أم ماذا ؟ وذلك نعرفه من خلال ضغطات المستخدم على الأزرار من خلال الخاصيه KeyState فاذا كان يضغط CTRL فانه يريد Copy واذا كان يضغط CTRL + SHIFT فانه يريد Link واذا كان لم يضغط شيئا فانه يريد Move ... بعد ذلك نسأل مره أخرى هل البرنامج الذي يسحب منه المستخدم يسمح بذلك ؟ فاذا كان المستخدم يرغب بعمل Move مثلا فهل البرنامج الذي يسحب منه فعلا يسمح بعمل Move ؟ وذلك نعرفه من الخاصيه AllowedEffects.
بعد التأكد من هذه الأشياء ... نسند القيمه المناسبه للخاصيه Effect من خلال استخدام الـ DragDropEffects Enumeration ونلاحظ أننا عندما نسند شئ لهذه الخاصيه فيتحدد على أساسها شكل مؤشر الـ Mouse فاذا أسندنا مثلا Copy فتجد اشارة ( + ) موجوده في أسفل مؤشر الماوس.
بعد ذلك نستعد بتحضير الـ EventHandlers الخاصه بالحدث DragDrop وفي هذا الحدث نقوم باختبار نوع البينات أو نوع الأشياء التي يريد أن يلقيها المستخدم على الـ Control لنتعامل معها بطرق نحددها تتناسب مع طبيعة عمل برنامجنا.. كما سيتضح في المثال التالي.
عملت مثال بسيط به ListBox مستعده فقط أن يتم سحب ووضع عليها ملف أو مجموعه من الملفات التي حددها المستخدم لتضع عنوان الملف أو الملفات , وبجانبها TextBox تسمح للمستخدم بسحب عليها نصوص موجوده في صفحه HTML من الـ Internet Explorer مثلا وتسألك اذا وضعت عليها نص الـ HTML , هل تريد أن تضعه HTML أم Text عادي ؟ ( كما يفعل برنامج الـ FrontPage وغيره ).
نبدء أولا بالـ TextBox .. اجعل الخاصيه MultiLine = true حتى تحتوي على أكثر من سطر وطبعا اجعل الخاصيه AllowDrop = true حتى نسمح بأن يتم عمل Drop عليها واعلن عنها باسم textBox1.
في الـ EventHandler الخاص بالحدث DragOver ::
private void textBox1_DragOver(object sender,DragEventArgs e)
{
if((e.AllowedEffect & DragDropEffects.Move) !=0)
e.Effect = DragDropEffects.Move;
if((e.AllowedEffect & DragDropEffects.Copy) !=0 && e.KeyState==9 )
e.Effect = DragDropEffects.Copy;
}الخاصيه AllowedEffect كما ذكرت لك تخبرنا بالـ Effects الذي يسمح بها البرنامج وترجع DragDropEffects ... واذا نظرت الى DragDropEffects في الـ MSDN لتعرف قيم الـ Effects التي تحتويها تجدها :
All = -2147483645 Copy = 1 Link = 4 Move = 2 None = 0 Scroll = -2147483648
طبعا لو استخدمنا المقارانات العاديه هنا فسنتعب كثيرا لأن البرنامج نادرا ما يسمح بـ Effect واحد فلن تجد برنامج مثلا يسمح بـ Move فقط ( 2 ) فالبرامج تسمح بمجموعه من الـ Effects مثلا Move + Copy تساوي (1+2=3) ولذلك تجدهم قاموا بعمل DragDropEffects لها FlagsAttribute حتى نستطيع أن نستخدم عملية الـ AND ( & ) معها.
بالنسبه للـ KeyState فهى ترجع int يساوي ( 32 بيت ) ويتم عمل ON للـ Bits على حسب الأزرار التي تم ضغطها ::
راجع الـ MSDN في هذا الأمر.
ونظرا لأن المستخدم من المتوقع أن يضغط على مجموعه من الأزرار وبحد أدنى زرار واحد وهو الزرار الأيسر للـ Mouse فينصح بالقيام أيضا بعملية AND ( & ) كما في المثال الموجود في الـ MSDN ولكني لم أستخدمها في مثالي فأنا قارنت برقم 9 للتسهيل وهو ( 1 + 8 ) حيث أن 1 يعبر عن زرار الماوس الأيسر و 8 يعبر عن زرار CTRL لأنه يجب الضغط عليهما سويا لعمل Copy وان لم يكن ضاغطا على CTRL فنعرف حينها أن المستخدم يريد أن يقوم بعمل Move ,, ونجح الأمر.
نأتي للجزء الأهم هنا والذي من خلاله نقوم بنقل البيانات .. ويتم ذلك طبعا داخل الـ EventHandler الخاصه بالحدث DragDrop .. انظر الكود ::
private void textBox1_DragDrop(object sender,DragEventArgs e)
{
IDataObject dObject = e.Data;
DialogResult dResult = DialogResult.Cancel;
if(dObject.GetDataPresent(DataFormats.Html) == true)
{
dResult = MessageBox.Show("Need HTML Tags ?","MoutazShams,D&D",MessageBoxButtons.OKCancel);
if(dResult == DialogResult.OK)
textBox1.Text = (string)dObject.GetData(DataFormats.Html);
}
if(dObject.GetDataPresent(DataFormats.StringFormat) && dResult == DialogResult.Cancel)
textBox1.Text = (string)dObject.GetData(DataFormats.StringFormat);
}قمت بعمل اوبجكت نوعه IDataObject ليحمل البينات الموجوده في DragEventArgs من خلال الخاصيه Data ... قمت بعد ذلك بالمقارنه اذا كانت تلك الـ Data هى HTML أم لا وذلك باستخدام GetDataPresent وتمرير اليها DataFormats.Html , واذا كانت البيانات فعلا HTML فأسأل المستخدم اذا كان يريد أن يضعها كـ HTML أم لا ,فان أراد ذلك سأضع البيانات بالفعل وذلك باستخدام GetData وأمرر اليها الـ Format الذي أريده ( لاحظ أنا قمت بعمل cast لـ string لأن GetData ترجع Object ),وان لم يوافق على ذلك أو كانت البيانات أصلا ليست HTML .. سأقوم بوضع النص عادي بتمرير DataFormats.StringFormat ولكن أتأكد أولا.
نأتي بعد ذلك للـ ListBox ( اجعل اسمها listBox1 ) التي نريدها أن تسمح للمستخدم بان يقوم بسحب ملف أو مجموعه من الملفات ووضعها عليها ليتم وضع عناوينها فيها :
في الـ EventHandler الخاص بـ DragOver ::
if((e.AllowedEffect & DragDropEffects.Link) !=0) e.Effect = DragDropEffects.Move;
طبعا كنا في هذا الحدث نقوم باختبار أولا الـ Effects المسموح بها وننظر على الأزرار التي يضغطها المستخدم وهكذا , ولكن هنا أنا سمحت للمستخدم أن يقوم بعمل Link فقط اذا كان البرنامج المصدر يسمح بذلك ( أعتقد أنه في معظم الأحوال تسمح البرامج بعمل Link للملفات فلم يكن هناك داعي للتأكد ) حتى لو كان المستخدم يضغط على زرار CTRL ويفعل المستحيل لن نقوم بالسماح له بأن يفعل شئ غير Link ! لأنه ليس من المنطقي أن أسمح للمستخدم أن يقوم بعمل MOVE للملف اذا وضعه في برنامجي ( طبعا أنا لو قمت بعمل Effect = Move لن يقوم الـ Windows بعمل MOVE للملف ولا شئ من هذا ولكن عندما لم يجد المستخدم علامة الـ Link التي تكون عباره عن سهم وهو يقوم بعملية السحب فسيخاف أن يقوم بذلك لأنه ممكن أن يتخيل أن برنامجك سينقل الملف لمكان مجهول ربما ! ولذلك أنت ستجعله يضطر بأن يقوم بالضغط على CTRL + SHIFT بطريقه صريحه وهو يسحب الملفات حتى يطمأن قلبه برؤية العلامه التي تدل على أنه يقوم بعمل Link وليس يقوم بعمل Copy أو Move للملفات فتذكر بأن القيمه المسنده للخاصيه Effect هى التي تحدد شكل الماوس. ) ولكن , سيختلف الأمر اذا كنت أقوم بعمل برنامج مثل الـ Windows Explorer ويهمني أن أجعل المستخدم قادر على نقل ونسخ وعمل لينكات للملفات التي يسحبها , فحينها سأقوم بفعل ما فعلته في الحدث الخاص بالـ TextBox بالاضافه طبعا لبعض الأشياء.
وفي الـ EventHandler الخاصه بـ DragDop ::
if(e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach(string file in files)
listBox1.Items.Add(file);
}في الكود قمت أولا بالتأكد أن المستخدم يقوم بسحب ملف أو مجموعه من الملفات باستخدام GetDataPresent وتمرير DataFormats.FileDrop , وبعد التأكد قمت بجلب البيانات باستخدام GetData ومررت اليها FileDrop .. ولاحظ أنني قمت بعمل cast للبينات الى مصفوفه من الـ string وليست string واحده ( لأنه لا يسمح بغير بذلك ) ولأننا نتوقع من المستخدم أن يقوم بسحب مجموعه من الملفات والمجلدات وليس ملف أو مجلد واحد مثلا. بعد ذلك عملت foreach على المصفوفه ووضعت أعضائها في الـ ListBox.
خلاص ..
بجد اعذروني أنا مشغول جدا هذه الأيام وكتبت الدرس ده يعني والله بالقوه لأنني وعدت بذلك ولم أجد لدى أى وقت كافي ( زنقت نفسي :) ) ... فالموضوع معرض طحن أن يكون به أخطاء أو أشياء غامضه ... فأرجوا المتابعه واخباري عند وجود أخطاء أو شئ غامض لم أشرحه جيدا ... أيضا أنا لم أذكر مجموعه من الأشياء التي تتعلق بهذا الموضوع مثل GiveFeedBack و QueryContinueDrag وعن الـ Method الي اسمها DoDragDrop التي نستخدمها في جعل برنامجنا يوفر للمستخدم امكانية عمل Drag من برنامجنا الى برامج أخرى ... فحاول أن تستكشفهم وعندما أجد الوقت سأكتب عنهم هنا ان شاء الله.



