DCSIMG
Question from Tapuz .Net forum: Copy to Output directory, Build Action And Custom Tool - Justin myJustin = new Justin( Expriences.Current );

Question from Tapuz .Net forum: Copy to Output directory, Build Action And Custom Tool

שאלה:

יש לי אפליקציה שצריכה להריץ קובץ שהוא חלק מהפרוייקט, אבל אני לא יודע איזה נתיב לתת לה. אני לא מוצא את הקובץ בשום נתיב שהוא לא אבסולוטי.

כמו כן, יש לי תמונות כחלק מהאפליקציה, וגם אותם אני לא מצליח להציג בלי נתיב אבסולוטי.

 

תשובה:

שאלה מצויינת שתיתן לנו מבט יותר מעמיק לגבי שימוש בקבצים כחלק מהפרוייקטים שלנו.

בואו נפתח פרוייקט Console חדש.

image

נראה מה קיבלנו ב-Solution Explorer:

image

קיבלנו קובץ בשם 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 של הקובץ.

image

ועכשיו נראה את הדבר הבא:

image

יש לנו File Name ו-Full Path שאומרים לנו מה שכבר ידענו - יש קובץ בשם Program.cs בנתיב מסויים.

image

אבל יש כאן עוד שלוש שדות מאוד מעניינים שבמהלך המאמר הזה נעבור עליהם.

image

נזכיר שאנחנו מביטים על קובץ #C ולכן הדבר ההגיוני לעשות כאשר נתחיל תהליך Build יהיה לקמפל אותו.
ולכן יש לנו BuildAction = Compile לקובץ ה-cs שלנו.

בואו נראה איזה אפשרויות נוספות יש:

image

 

נעבור לפי דוגמה מה כל אחד מהסוגים האלו.
* Compile - ראינו שהקובץ program.cs שלנו הוא Compile. כלומר, מדובר בקבצים שבסופו של דבר מגיעים לאחד מהקומפיילרים הדוט-נטיים.
* Content - אם למשל נפתח ASP.Net WebSite מרבית הקבצים הם הרי לא עוברים קומפילציה עד הרגע ולכן מרבית הקבצים יהיו Content.

image image image

כלומר, מדובר בצורה הפשוטה ביותר של קבצים בפרוייקט. 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 שהתוכנית עולה.

image

image

ועכשיו יש לנו בפרוייקט קובץ TXT חדש.

image

image

נביט על המאפיינים שלו.

image

ועכשיו נכתוב קצת קוד שיגרום לקובץ להיפתח.
ספציפית, נשתמש בזה שקובץ מסוג 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();

        }

    }

}

נריץ את האפליקציה הקטנה שלנו.

image

 

בשלב זה, שמתם לב אולי לזריזות ידיים שביצעתי למעלה - הנתיב של הקובץ TXT הוא נתיב מערכת קבצים מלא.

לא ניתן לפרוס ככה אפליקציות, הרי שלא כולם יתקינו את זה בדיוק באותו מקום של המכונות פיתוח.

אז נכתוב נתיב חלקי:

        static void Main(string[] args)

        {

            Process.Start("myText.txt");

 

            Console.ReadLine();

        }

וקיבלנו את השגיאה הבאה:

image 

דוט נט אומרת לנו שהיא לא הצליחה למצוא את הקובץ המבוקש.
כברירת מחדל הנתיב ש-Process.Start מחפש הוא בתיקייה של ה-exe ובכמה תיקיות System ו-Windows.

אז נרצה להעתיק את הקובץ TXT שלנו לתיקייה בה הקומפילציה שמה את הקובץ EXE שלנו.

כלומר, זוהי תיקיית הפרוייקט שלנו:

image

ובתוך Bin\Debug נמצא את הקובץ EXE שלנו.

image

(הערה צדדית. למי שתוהה, הקובץ vshost הכרחי כדי ש-Visual Studio יוכל לעשות Attach ולתת לנו יכולות Debugging. הקובץ PDB הוא הקובץ שמאפשר ל-Visual Studio להכיר את מבנה הקוד הפנימי שלנו).

נרצה שלתוך התיקייה הזו באופן אוטומטי יועתק קובץ ה-TXT שלנו.

אז ניכנס למאפיינים שלו ונראה מה אפשר לקבוע תחת Copy To Output Directory.

image

מהפירוט על Copy to Output Directory נראה שכאן נקבע את צורת ההעתקה לספריית היעד.
ויש שלושה אפשרויות: לא להעתיק (שזאת הברירת מחדל לכל קבצי ה-Content), תמיד להעתיק ולהעתיק רק אם יש שינוי.

נבחר את Copy Always.

image

נקמפל את האפליקציה (בלי להריץ עדיין).

image

ונראה שב-Bin\debug שלנו נמצא הקובץ TXT.

image

 

עכשיו אם נריץ את האפליקציה רק עם שם הקובץ.

image

כלומר, כדי להריץ באמצעות Process.Start עם שם קובץ בלבד, מספיק לקבוע Copy To Output Directory.

 

עכשיו נדבר על Embedded Resources אם כבר אנחנו על נושא שלושת ה-Build Action.

נפתח אפליקציית Winforms חדשה.

image

וקיבלנו את האפליקציה הבסיסית הבאה:

image

image

נוסיף לפרוייקט שלנו תמונה.

image

נוסיף תמונה של בקבוקי הוויסקי שלי.

image

נביט על הקובץ ב-Solution Explorer.

image

כלומר, קובץ רגיל מסוג Content.

נוסיף לטופס הראשי של האפליקציה PictureBox ונדאג שתצביע לנתיב המלא של התמונה.

image

כלומר, יש לנו PictureBox שמצביע על תמונה עם נתיב מלא על הדיסק.

נריץ את האפליקציה.

image

עכשיו נרצה לעשות אותו טריק מקודם בכדי להוריד את הנתיב המלא.
נקבע Copy To Output Directory = Copy If Newer.

image

ונשנה את ה-PictureBox.ImageLocation לשם הקובץ בלבד.

נריץ את האפליקציה.

image

אבל רגע, בואו נביט בספריית Bin\Debug שלנו.

image

זה לא טוב לנו שאנחנו מעתיקים כל תמונה כקובץ נפרד לתוך ה-Bin\debug.
הרי, אין באמת סיבה שהקובץ יהיה "בחוץ" ככה.
זה עוד קבצים שנצטרך לפרוס ללקוח, עוד קבצים שנצטרך הרשאה לקרוא אותם, עוד קבצים שהלקוח יכול לשנות מחוץ לאפליקציה, ועוד קבצים שנצטרך לדאוג שחס וחלילה לא ישנו את השם שלהם.

סה"כ לא טוב.

אז כאן נכנס הקונספט של Embedded Resource לפעולה.
בואו קודם נראה הוא עושה ואז נבין מה בדיוק הולך.

נשנה את ה-Build Action של התמונה ל-Embedded Resource, וגם נגיד שלא צריך להעתיק אותה ל-Bin שלנו.

image

עכשיו נקמפל את האפליקציה.

דבר ראשון, אין יותר קובץ תמונה ב-Bin.

image 

נפתח את ה-EXE שלנו ב-Reflector.

image

ואם נתחיל לדפדף בתוך Resources של האסמבלי הזו, נמצא את התמונה שלנו.

image

נבקש לראות את התמונה בתוך Reflector.

image

וכן יש מולנו בקבוק וויסקי ענק.

image

 

כלומר, הקובץ תמונה לא ב-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 שלנו.

נריץ את האפליקציה, ונראה שהתמונה מוצגת.

image

 

כלומר, האפליקציה ניגשה ל-Resource בתוך האסמבלי לפי שם קובץ, הוציא החוצה כרצף בינארי והפכה לתמונה.

 

ניקח את זה צעד קדימה ונראה איך אפשר לעשות את זה אוטומטית יותר.
נמחק את התמונה מהאפליקציה שלנו.

image

עכשיו נפתח את תגית ה-Properties של הפרוייקט ונראה שמסתתר שם קובץ בשם Resources.resx.

image

לחיצה כפולה עליו ונגיע למסך הבא.

image

נבחר Add Resource --> Existing Resource.

image

והפעם נוסיף תמונה של האקדח שלי.

image

נוכל לראות שהתמונה התווספה כ-Image Resource לפרוייקט.

image

וב-Solution Explorer נוכל לראות שהקובץ חלק מהפרוייקט כ-Embedded Resource.

image

 

נחזור לטופס שלנו ונרצה תכנותית לקבוע תמונה ל-PictureBox.

image

        private void Form1_Load(object sender, EventArgs e)

        {

            pictureBox1.Image = Resources._100_2115;

        }

האפליקציה רצה ומציגה את התמונה שלנו:

image

כלומר לקבצים שנוסיף דרך ממשק ה-Resources נקבל גישה שהיא Strongly-typed. נחזור עוד מעט לאיך זה קורה שנדבר על Custom Tool.

 

עכשיו נלך על משהו עו יותר פשוט ובלי קוד בכלל. נמחק את הקוד ב-Form_Load שלנו.
נחזור ל-Designer ונלחץ על השלוש נקודות ב-Image.

image

 

וכאן נוכל לבחור את ה-Image לפי מה שהוספנו בקובץ Resources שלנו.

image

וככה זה נראה ב-Designer:

image

 

אם נפתח את Reflector נוכל לראות שהתמונה שלנו שהוספנו דרך Resources מופיעה ב-Resources של האסמבלי.

image 

 

נעבור לדבר על Custom Tool בקצרה.
שנפרט Custom Tool לקובץ מדובר על להריץ תוכנה קטנה כל פעם שהקובץ משתנה.

למשל, בואו נדבר על איך ה-Resources שלנו קיבל ל-Intellisense את התמונה שהוספנו.
נוכל לראות שתחת Resources.resx יש קובץ בשם Resources.designer.cs.

image

נפתח את הקובץ ונביט עליו.

image

סיננתי חלק מהקובץ שיהיה נוח לצפייה, אבל אפשר לראות שני דברים מעניינים:
1. אזהרה ענקית למעלה לא לגעת בקובץ כי הקובץ עבר ג'נרונט (מלשון To Generate) אוטומטי.
2. שיש אובייקט בשם 2115_100 שמממש כתוב בקוד.

רגע, אנחנו לא הוספנו את האובייקט הזה לקוד!
הרי, הוא התווסף אוטומטית ע"י ג'נרונט הקוד.

 

אז בואו נביט לרגע על המאפיינים של Resources.resx.

image

אפשר לראות שהקובץ עצמו הוא Embedded Resource, אבל יותר מזה - יש לו Custom Tool.
ה-Custom Tool הזה רץ הרי כל פעם שהקובץ Resources.resx משתנה.

אז אנחנו רואים כאן רצף עניינים.
1. אנחנו עורכים ב- Resources Designer את המשאבים.

image

2. אלו מתווספים לקובץ ה-Resources.resx שהוא סה"כ XML.

image

3. היות וקובץ ה-Resources.resx השתנה ה-Custom Tool שלו רץ.

image

4. נקבל בסוף קובץ בשם Resources.designer.cs שיוצר ע"י הכלי האוטומטי ResXFileCodeGenerator.

image

 

יש עוד כמה Custom Tools ב-Visual Studio. למשל: MSDiscoGenerator שאחראי על יצור WebService Proxy מחדש כל פעם שה-Webservice משתנה או ה-URL אליו משתנה, ו-MSLinqToSqlGenerator שאחראי לג'נרנט מחדש מחלקות שקובץ dmbl של Linq To Sql משתנה.
אם אתם לא מכירים אותם, זה לא משנה, הקונספט הוא זה שחשוב.

image

 

נסכם על מה עברנו במאמר זה.

1. Build Action.

image

Content כברירת מחדל לא מעניינת, Compile שעובר קומפילציה, ו-Embedded Resource שנכנס כקובץ לתוך האסמבלי.

 

2. Copy To Out Directory שדואג להעתיק קובץ לספריית ה-bin.

image

 

3. Custom Tool שמג'נרנט קוד אחרי שהקובץ השתנה.

image

 

קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=111552444

Published Sunday, January 20, 2008 1:36 PM by Justin-Josef Angel [MVP]

Comments

No Comments