שאלה:
יש לי אפליקציה שצריכה להריץ קובץ שהוא חלק מהפרוייקט, אבל אני לא יודע איזה נתיב לתת לה. אני לא מוצא את הקובץ בשום נתיב שהוא לא אבסולוטי.
כמו כן, יש לי תמונות כחלק מהאפליקציה, וגם אותם אני לא מצליח להציג בלי נתיב אבסולוטי.
תשובה:
שאלה מצויינת שתיתן לנו מבט יותר מעמיק לגבי שימוש בקבצים כחלק מהפרוייקטים שלנו.
בואו נפתח פרוייקט Console חדש.
נראה מה קיבלנו ב-Solution Explorer:
קיבלנו קובץ בשם Program.cs שכמו שאנחנו יודעים, מכיל את נקודת הכניסה (Main) של התוכנית.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleResources
{
class Program
{
static void Main(string[] args)
{
}
}
}
נוכל גם לראות משהו מעניין שהשארתי בכוונה - יש לנו גישה לחלונית ה-Properties של הקובץ.
ועכשיו נראה את הדבר הבא:
יש לנו File Name ו-Full Path שאומרים לנו מה שכבר ידענו - יש קובץ בשם Program.cs בנתיב מסויים.
אבל יש כאן עוד שלוש שדות מאוד מעניינים שבמהלך המאמר הזה נעבור עליהם.
נזכיר שאנחנו מביטים על קובץ #C ולכן הדבר ההגיוני לעשות כאשר נתחיל תהליך Build יהיה לקמפל אותו.
ולכן יש לנו BuildAction = Compile לקובץ ה-cs שלנו.
בואו נראה איזה אפשרויות נוספות יש:
נעבור לפי דוגמה מה כל אחד מהסוגים האלו.
* Compile - ראינו שהקובץ program.cs שלנו הוא Compile. כלומר, מדובר בקבצים שבסופו של דבר מגיעים לאחד מהקומפיילרים הדוט-נטיים.
* Content - אם למשל נפתח ASP.Net WebSite מרבית הקבצים הם הרי לא עוברים קומפילציה עד הרגע ולכן מרבית הקבצים יהיו Content.
כלומר, מדובר בצורה הפשוטה ביותר של קבצים בפרוייקט. Content הוא הברירת מחדל ואומר שהקובץ הוא רק חלק מהפרוייקט, בלי שום דבר מיוחד.
כמובן ש-Default.aspx.cs הוא כן מסוג Compile היות והוא מגיע לאחד הקומפיילרים הדוט-נטיים במהלך Build.
* Page, ApplicationDefinition, Resource - כל השלושה קשורים לטכנולוגיית WPF שהיא חלק מדוט נט 3.0. בקצרה, ApplicaitonDefinition הוא הנקודת כניסה מסוג XAML הראשונה באפליקציה, Page הוא חלון כלשהו ו-Resource הוא תחליף WPFי ל-Embedded Resource. כמו כן, יש גם SilverlightPage שהוא המקביל ל-Page ב-Silverlight.
* CodeAnalysisDictionary - חלק ממנגנון ה-Spell Checking החדש ב-Visual Studio 2008. בקובץ ה-XML הזה נגדיר מילים וביטויים שיעברו Spell Checking.
* Embedded Resource - קובץ שהופך להיות חלק מהאסבלי במהלך קמפול. נדבר עליו בהרחבה בהמשך.
אז כפי שהבנו ישנם שלושה סוגי Build Action מעניינים: Content, Compile ו-Embedded Resource.
כאשר כל השאר הם Extensions למטרות ספציפיות כלשהן.
עכשיו נדבר על Copy To Output Directory ונענה לאחת השאלות המקוריות.
נרצה להוסיף לפרוייקט קובץ TXT ולפתוח אותו באמצעות Notepad שהתוכנית עולה.
ועכשיו יש לנו בפרוייקט קובץ TXT חדש.
נביט על המאפיינים שלו.
ועכשיו נכתוב קצת קוד שיגרום לקובץ להיפתח.
ספציפית, נשתמש בזה שקובץ מסוג TXT מחובר ברמת מערכת ההפעלה ל-Notepad.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace ConsoleResources
{
class Program
{
static void Main(string[] args)
{
Process.Start(
@"C:\Documents and Settings\Justin-Josef Angel\My Documents\Visual Studio 2008\Projects\ConsoleResources\myText.txt");
Console.ReadLine();
}
}
}
נריץ את האפליקציה הקטנה שלנו.
בשלב זה, שמתם לב אולי לזריזות ידיים שביצעתי למעלה - הנתיב של הקובץ TXT הוא נתיב מערכת קבצים מלא.
לא ניתן לפרוס ככה אפליקציות, הרי שלא כולם יתקינו את זה בדיוק באותו מקום של המכונות פיתוח.
אז נכתוב נתיב חלקי:
static void Main(string[] args)
{
Process.Start("myText.txt");
Console.ReadLine();
}
וקיבלנו את השגיאה הבאה:
דוט נט אומרת לנו שהיא לא הצליחה למצוא את הקובץ המבוקש.
כברירת מחדל הנתיב ש-Process.Start מחפש הוא בתיקייה של ה-exe ובכמה תיקיות System ו-Windows.
אז נרצה להעתיק את הקובץ TXT שלנו לתיקייה בה הקומפילציה שמה את הקובץ EXE שלנו.
כלומר, זוהי תיקיית הפרוייקט שלנו:
ובתוך Bin\Debug נמצא את הקובץ EXE שלנו.
(הערה צדדית. למי שתוהה, הקובץ vshost הכרחי כדי ש-Visual Studio יוכל לעשות Attach ולתת לנו יכולות Debugging. הקובץ PDB הוא הקובץ שמאפשר ל-Visual Studio להכיר את מבנה הקוד הפנימי שלנו).
נרצה שלתוך התיקייה הזו באופן אוטומטי יועתק קובץ ה-TXT שלנו.
אז ניכנס למאפיינים שלו ונראה מה אפשר לקבוע תחת Copy To Output Directory.
מהפירוט על Copy to Output Directory נראה שכאן נקבע את צורת ההעתקה לספריית היעד.
ויש שלושה אפשרויות: לא להעתיק (שזאת הברירת מחדל לכל קבצי ה-Content), תמיד להעתיק ולהעתיק רק אם יש שינוי.
נבחר את Copy Always.
נקמפל את האפליקציה (בלי להריץ עדיין).
ונראה שב-Bin\debug שלנו נמצא הקובץ TXT.

עכשיו אם נריץ את האפליקציה רק עם שם הקובץ.
כלומר, כדי להריץ באמצעות Process.Start עם שם קובץ בלבד, מספיק לקבוע Copy To Output Directory.
עכשיו נדבר על Embedded Resources אם כבר אנחנו על נושא שלושת ה-Build Action.
נפתח אפליקציית Winforms חדשה.
וקיבלנו את האפליקציה הבסיסית הבאה:
נוסיף לפרוייקט שלנו תמונה.
נוסיף תמונה של בקבוקי הוויסקי שלי.
נביט על הקובץ ב-Solution Explorer.
כלומר, קובץ רגיל מסוג Content.
נוסיף לטופס הראשי של האפליקציה PictureBox ונדאג שתצביע לנתיב המלא של התמונה.
כלומר, יש לנו PictureBox שמצביע על תמונה עם נתיב מלא על הדיסק.
נריץ את האפליקציה.
עכשיו נרצה לעשות אותו טריק מקודם בכדי להוריד את הנתיב המלא.
נקבע Copy To Output Directory = Copy If Newer.
ונשנה את ה-PictureBox.ImageLocation לשם הקובץ בלבד.
נריץ את האפליקציה.

אבל רגע, בואו נביט בספריית Bin\Debug שלנו.
זה לא טוב לנו שאנחנו מעתיקים כל תמונה כקובץ נפרד לתוך ה-Bin\debug.
הרי, אין באמת סיבה שהקובץ יהיה "בחוץ" ככה.
זה עוד קבצים שנצטרך לפרוס ללקוח, עוד קבצים שנצטרך הרשאה לקרוא אותם, עוד קבצים שהלקוח יכול לשנות מחוץ לאפליקציה, ועוד קבצים שנצטרך לדאוג שחס וחלילה לא ישנו את השם שלהם.
סה"כ לא טוב.
אז כאן נכנס הקונספט של Embedded Resource לפעולה.
בואו קודם נראה הוא עושה ואז נבין מה בדיוק הולך.
נשנה את ה-Build Action של התמונה ל-Embedded Resource, וגם נגיד שלא צריך להעתיק אותה ל-Bin שלנו.
עכשיו נקמפל את האפליקציה.
דבר ראשון, אין יותר קובץ תמונה ב-Bin.
נפתח את ה-EXE שלנו ב-Reflector.
ואם נתחיל לדפדף בתוך Resources של האסמבלי הזו, נמצא את התמונה שלנו.
נבקש לראות את התמונה בתוך Reflector.
וכן יש מולנו בקבוק וויסקי ענק.
כלומר, הקובץ תמונה לא ב-Bin שלנו.
הקובץ Embedded Resource הוא חלק מהאסבלי שלנו!
עכשיו נראה איך אפשר להגיד להאפליקציה בצורה תכנותית להציג את התמונה.
private void Form1_Load(object sender, EventArgs e)
{
using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("WinformsResources.100_2116.JPG"))
{
pictureBox1.Image = Image.FromStream(s);
}
}
סה"כ אמרנו לאמסבלי הנוכחית שלנו שמחזיקה את הקובץ ב-Resources שלה להביא לנו קובץ עם שם מסויים, להמיר אותו לתמונה ולהכניס לתוך ה-PictureBox.Image שלנו.
נריץ את האפליקציה, ונראה שהתמונה מוצגת.
כלומר, האפליקציה ניגשה ל-Resource בתוך האסמבלי לפי שם קובץ, הוציא החוצה כרצף בינארי והפכה לתמונה.
ניקח את זה צעד קדימה ונראה איך אפשר לעשות את זה אוטומטית יותר.
נמחק את התמונה מהאפליקציה שלנו.
עכשיו נפתח את תגית ה-Properties של הפרוייקט ונראה שמסתתר שם קובץ בשם Resources.resx.
לחיצה כפולה עליו ונגיע למסך הבא.
נבחר Add Resource --> Existing Resource.
והפעם נוסיף תמונה של האקדח שלי.
נוכל לראות שהתמונה התווספה כ-Image Resource לפרוייקט.
וב-Solution Explorer נוכל לראות שהקובץ חלק מהפרוייקט כ-Embedded Resource.
נחזור לטופס שלנו ונרצה תכנותית לקבוע תמונה ל-PictureBox.
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Image = Resources._100_2115;
}
האפליקציה רצה ומציגה את התמונה שלנו:
כלומר לקבצים שנוסיף דרך ממשק ה-Resources נקבל גישה שהיא Strongly-typed. נחזור עוד מעט לאיך זה קורה שנדבר על Custom Tool.
עכשיו נלך על משהו עו יותר פשוט ובלי קוד בכלל. נמחק את הקוד ב-Form_Load שלנו.
נחזור ל-Designer ונלחץ על השלוש נקודות ב-Image.
וכאן נוכל לבחור את ה-Image לפי מה שהוספנו בקובץ Resources שלנו.
וככה זה נראה ב-Designer:

אם נפתח את Reflector נוכל לראות שהתמונה שלנו שהוספנו דרך Resources מופיעה ב-Resources של האסמבלי.
נעבור לדבר על Custom Tool בקצרה.
שנפרט Custom Tool לקובץ מדובר על להריץ תוכנה קטנה כל פעם שהקובץ משתנה.
למשל, בואו נדבר על איך ה-Resources שלנו קיבל ל-Intellisense את התמונה שהוספנו.
נוכל לראות שתחת Resources.resx יש קובץ בשם Resources.designer.cs.
נפתח את הקובץ ונביט עליו.
סיננתי חלק מהקובץ שיהיה נוח לצפייה, אבל אפשר לראות שני דברים מעניינים:
1. אזהרה ענקית למעלה לא לגעת בקובץ כי הקובץ עבר ג'נרונט (מלשון To Generate) אוטומטי.
2. שיש אובייקט בשם 2115_100 שמממש כתוב בקוד.
רגע, אנחנו לא הוספנו את האובייקט הזה לקוד!
הרי, הוא התווסף אוטומטית ע"י ג'נרונט הקוד.
אז בואו נביט לרגע על המאפיינים של Resources.resx.
אפשר לראות שהקובץ עצמו הוא Embedded Resource, אבל יותר מזה - יש לו Custom Tool.
ה-Custom Tool הזה רץ הרי כל פעם שהקובץ Resources.resx משתנה.
אז אנחנו רואים כאן רצף עניינים.
1. אנחנו עורכים ב- Resources Designer את המשאבים.
2. אלו מתווספים לקובץ ה-Resources.resx שהוא סה"כ XML.
3. היות וקובץ ה-Resources.resx השתנה ה-Custom Tool שלו רץ.
4. נקבל בסוף קובץ בשם Resources.designer.cs שיוצר ע"י הכלי האוטומטי ResXFileCodeGenerator.
יש עוד כמה Custom Tools ב-Visual Studio. למשל: MSDiscoGenerator שאחראי על יצור WebService Proxy מחדש כל פעם שה-Webservice משתנה או ה-URL אליו משתנה, ו-MSLinqToSqlGenerator שאחראי לג'נרנט מחדש מחלקות שקובץ dmbl של Linq To Sql משתנה.
אם אתם לא מכירים אותם, זה לא משנה, הקונספט הוא זה שחשוב.
נסכם על מה עברנו במאמר זה.
1. Build Action.
Content כברירת מחדל לא מעניינת, Compile שעובר קומפילציה, ו-Embedded Resource שנכנס כקובץ לתוך האסמבלי.
2. Copy To Out Directory שדואג להעתיק קובץ לספריית ה-bin.
3. Custom Tool שמג'נרנט קוד אחרי שהקובץ השתנה.
קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=111552444