DCSIMG
November 2006 - Posts - Justin myJustin = new Justin( Expriences.Current );

November 2006 - Posts

Question from Tapuz .Net forum: Using a GridView HyperLinkField with Mailto Links (Custom GridView Fields)

שאלה:

אני רוצה להוסיף עמודת  קישור mailto ל-GridView אבל שאני מוסיף את הקוד הבא אני מקבל טקסט רגיל :

<asp:HyperLinkField DataTextField="Email" HeaderText="דוא'ל" DataNavigateUrlFields="Email" DataNavigateUrlFormatString="mailto:{0}"  />

מה צריך לעשות כדי שהכתובת דוא"ל ב-Email יופיעו כקישורי mailto? 

 

תשובה:

שאלה מאוד טובה ובאמת התשובה מעניינת.

נסביר את הבעיה קצת יותר לעומק, בואו נניח ויש לי את הקישור הבא  

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="mailto:J@JustinAngel.Net">HyperLink</asp:HyperLink>

במצב הזה הקישור יתרנדר לקישור רגיל לכל דבר שברגע שנלחץ עליו הדפדפן שלנו יפתח את תוכנת הדוא"ל שלנו עם אי-מייל שמכוון ל-J@JustinAngel.Net.

ניקח את זה צעד קדימה ונניח שיש לנו DataTable (למשל) שמכילה כתובות דוא"ל ונרצה ליצור עמודה עם קישורי mailto אליהם. נכתוב את הקוד הבא בדף ה-ASP.Net שלנו:

 

 

// in myForm.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
    GridView1.DataSource = GetLinks();
    GridView1.DataBind();
}
 
private DataTable GetLinks()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("Link", typeof (string));
 
    dt.Rows.Add("J@JustinAngel.Net");
    dt.Rows.Add("J@JustinAngel.Net");
    dt.Rows.Add("J@JustinAngel.Net");
    dt.Rows.Add(J@JustinAngel.Net);
 
    return dt;
}

 

// in myForm.aspx

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False">

    <Columns>

        <asp:BoundField DataField="Link" />

        <asp:HyperLinkField DataNavigateUrlFormatString="mailto:{0}" DataNavigateUrlFields="Link" DataTextField="Link" DataTextFormatString="{0}" />

    </Columns>

</asp:GridView>

 

 

 

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

הגדרנו גם לכאורה עמודת קישורים שאמורה לקחת את הכתובת שכתובה בתוך עמודת Link ולהכניס אותה כל פעם לתבנית "mailto:myLink". נסביר מה כתוב שם. DataTextField הוא השדה של הטקסט שיוצג למשתמש בתור טקסט ו-DataTextFormatString אומר לקחת את השדה הזה ופשוט להציג אותו (הינו יכולים כאן לפרמט כאן את התוכן למספר, לכסף, לתאריך וכך הלאה). בצורה מאוד דומה יש לנו את DataNavigateUrlFields שאומר איזה שדות של מקור המידע נרצה בשביל התהליך של לחבר את מחרוזת הקישור עצמו, אז אמרנו לו "קח את המילים mailto: ותוסיף להם את השדה Link".

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

אז עכשיו המשימה שלנו היא - לחשוב למה? למה אנחנו לא מקבלים Mailto?
בואו נבחן את ה-HTML שמפיקה אחת השורות של ה-GridView:

<tr>

    <td>J@JustinAngel.Net</td>

    <td><a>J@JustinAngel.Net</a></td>

</tr>

 

מול ה-HTML שמפיק אחד הקישורים שלנו:

<a id="A1" href="mailto:J@JustinAngel.Net">HyperLink</a>

 אפשר לראות בבירור שחסר לנו הרנדור בכלל של כל מאפיין ה-Href של תגית ה-<a>!

 

ננסה להבין איך עובד כל הסיפור הזה של טורים ב-GridView. מצד אחד יש לנו מחלקה בשם xxxField ומצד שני יש לנו את המחלקות המוכרות של ASP.Net. במקרה שלנו, יש לנו HyperLinkField שמכיל הרבה מאוד תאים ובכל תא כזה יש HyperLink.

ואיך הקשר הזה ממומש? מסתבר שלכל xxxField יש מתודה בשם InitlizeCell והיא זו שמכניסה את הפקדים לתוך התא ביחס לטור שלנו. לפי Reflector הנה אותה מתודה של HyperLinkField:

public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
    base.InitializeCell(cell, cellType, rowState, rowIndex);
    if (cellType == DataControlCellType.DataCell)
    {
        HyperLink link1 = new HyperLink();
        link1.Text = this.Text;
        link1.NavigateUrl = this.NavigateUrl;
        link1.Target = this.Target;
        if (((rowState & DataControlRowState.Insert) == DataControlRowState.Normal) && base.Visible)
        {
            if ((this.DataNavigateUrlFields.Length != 0) || (this.DataTextField.Length != 0))
            {
                link1.DataBinding += new EventHandler(this.OnDataBindField);
            }
            cell.Controls.Add(link1);
        }
    }
}

אפשר לראות שלכל תא בשורה שהיא שורת מידע (בניגוד למשל לשורה שהיא Header או Footer או סתם שורה ריקה) אנחנו מאתחלים HyperLink ומוסיפים אותו לתא שלנו. בנוסף, אנחנו שואלים כל מיני שאלות מוזרות ואם הם נכונות, אז נקרא למתודה OnDataBindField לכל קישור שלנו שעובר DataBinding.

אז הבנו מה הקשר בין HyperLink ובין HyperLinkField ועכשיו נצטרך למצוא את הסיבה למה אנחנו לא מקבלים Mailto!
כל מה שעשינו עד עכשיו עדיין לא נגע בפועל להכניס HyperLink.NavigateURL, אלא רק אתחול של ה-HyperLink. הקישור עצמו נקבע בזמן ה-DataBinding ולכן אנחנו צריכים להביט לתוך המתודה הזה OnDataBindField.

בתוך אותה מתודה נוכל למצוא את הקטע קוד הבא:

            string text3 = string.Empty;

            if (((this.urlFieldDescs != null) && (num3 > 0)) && (obj1 != null))

            {

                object[] objArray1 = new object[num3];

                for (int num4 = 0; num4 < num3; num4++)

                {

                    if (this.urlFieldDescs[num4] != null)

                    {

                        objArray1[num4] = this.urlFieldDescs[num4].GetValue(obj1);

                    }

                }

                string text4 = this.FormatDataNavigateUrlValue(objArray1);

                if (!CrossSiteScriptingValidation.IsDangerousUrl(text4))

                {

                    text3 = text4;

                }

            }

            if ((base.DesignMode && (this.DataNavigateUrlFields.Length != 0)) && (text3.Length == 0))

            {

                text3 = "url";

            }

            if (text3.Length > 0)

            {

                link1.NavigateUrl = text3;

            }

 

קטע קוד מפחיד ומזעזע לכל הדעות (ועוד בלי השמות האמיתיים של הפרמטרים), אבל נוכל לשים לב כאן בערך מה הולך.
יש משתנה בשם text3 והוא הופך להיות ה-NavigateURL שלנו. אז נביט עליו קצת. הוא מתאתחל בתחילת הקטע קוד הזה ומקבל ערך רק של text4. המשתנה text4 עובר כל מיני מניפולציות מפחידות משהו ובדיקה הגיונית יחסית של CrossSiteScriptingValidation.IsDangerousUrl. בואו נביט על התוכן של המתודה שעושה את הבדיקה הזו:

        internal static bool IsDangerousUrl(string s)

        {

            if (string.IsNullOrEmpty(s))

            {

                return false;

            }

            s = s.Trim();

            int num1 = s.Length;

            string maltioPrefix = "mailto:";

            if (((((num1 > 4) && ((s[0] == 'h') || (s[0] == 'H'))) && ((s[1] == 't') || (s[1] == 'T'))) && (((s[2] == 't') || (s[2] == 'T')) && ((s[3] == 'p') || (s[3] == 'P')))) && ((s[4] == ':') || (((num1 > 5) && ((s[4] == 's') || (s[4] == 'S'))) && (s[5] == ':'))))

            {

                return false;

            }

            int num2 = s.IndexOf(':');

            if (num2 == -1)

            {

                return false;

            }

            return true;

        }

והנה, גילינו את האשם שלנו! לפני קביעת NavigateURL מתבצעת בדיקה שנועדה להגן עלינו שהמידע שאנחנו מכניסים לתוך הקישור, הוא אכן קישור שמתחיל בטקסט "http://". זאת אכן דרך מוזרה משהו לבדוק את זה, אבל ניחא.
אז עכשיו, גילינו שאין לנו שום דרך לעבוד עם HyperLinkField ובאמת להוסיף תחילית של mailto.

אז נעשה מה שתמיד צריך לעשות במצב הזה - נירש את HyperLinkField ונשנה את ההתנהגות שלו.

namespace Controls

{

    public class MailtoHyperLinkField : HyperLinkField

    {

        public MailtoHyperLinkField()

        {

        }

    }

}

ראינו שכל הקוד הבעייתי נמצא במתודה InitlizeCell, אז נדרוס אותה. וניגש לאותו HyperLink שהוסיפה המתודה של InitlizeCell ששייך ל-HyperLinkField המקורי. כלומר, ניתן ל-HyperLinkField.InitlizeCell להתבצע, אבל אחר-כך אנחנו נתפוס שליטה וניגש ל-HyperLink שהיא הוסיפה. למה חטפנו את השליטה? כדי להשיג את היכולת לבצע בעצמנו את OnDataBindField.

public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)

{

    base.InitializeCell(cell, cellType, rowState, rowIndex);

    if (cellType == DataControlCellType.DataCell)

    {

        HyperLink link1 = (HyperLink) cell.Controls[0];

        if (((rowState & DataControlRowState.Insert) == DataControlRowState.Normal) && base.Visible)

        {

            if ((this.DataNavigateUrlFields.Length != 0) || (this.DataTextField.Length != 0))

            {

                link1.DataBinding += new EventHandler(this.OnDataBindFieldWithMailTo);

            }

            cell.Controls.Add(link1);

        }

    }

}

נשנה בגירסה שלנו של OnDataBindField את בדיקת האותיות התחיליות שיבדוק אם המחרוזת היא Mailto או הבדיקה המקורית שלו. 

private static bool IsDangerousUrl(string s)

{

    if (string.IsNullOrEmpty(s))

    {

        return false;

    }

    s = s.Trim();

    int num1 = s.Length;

    string maltioPrefix = "mailto:";

    if ((((((num1 > 4) && ((s[0] == 'h') || (s[0] == 'H'))) && ((s[1] == 't') || (s[1] == 'T'))) && (((s[2] == 't') || (s[2] == 'T')) && ((s[3] == 'p') || (s[3] == 'P')))) && ((s[4] == ':') || (((num1 > 5) && ((s[4] == 's') || (s[4] == 'S'))) && (s[5] == ':'))))

        || ((num1 > maltioPrefix.Length) && (s.StartsWith(maltioPrefix))))

    {

        return false;

    }

    int num2 = s.IndexOf(':');

    if (num2 == -1)

    {

        return false;

    }

    return true;

}

ועכשיו במקום הבדיקה של CrossSiteScriptingValidation.IsDangerousUrl נשתמש ב-IsDangerousUrl שלנו.

if (!IsDangerousUrl(text4))

    text3 = text4;

 

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

אפשרות שנייה ומומלצת בהרבה היא ליצור פרוייקט מסוג Web Control Libary ולהניח שם את הפקד.
נפתח את ה-web.config שלנו ונוסיף את השורה הבאה (כדי לדאוג שנוכל להשתמש בקיצור ה-:asp> ב-designer).

    <system.web>

        <pages>

            <controls>

                <add tagPrefix="asp" namespace="Controls"/>

            </controls>

        </pages>

    </system.web>

השינוי הזה ב-web.config שלנו יאפשר לנו להשתמש בתגית asp בשימוש ב-designer.
(השינוי הזה ל-web.config יתאים רק לפריסה ב-App_code, אם הפריסה היא ב-Web Control Libary יש להוסיף לתגית ה-<add> את שם האסמבלי. למשל אם לפרוייקט קוראים WebControlLibary1 צריך להוסיף "assembly="WebControlLibary1 והפנייה מהאתר שלנו לאותו פרוייקט).

נשנה את הקוד בתוך ה-GridView שלנו:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False">

    <Columns>

        <asp:MailtoHyperLinkField DataNavigateUrlFormatString="mailto:{0}" DataNavigateUrlFields="Link"  DataTextField="Link" DataTextFormatString="{0}"/>

        <asp:HyperLinkField DataNavigateUrlFormatString="mailto:{0}" DataNavigateUrlFields="Link" DataTextField="Link" DataTextFormatString="{0}" />

        <asp:BoundField DataField="Link" />

    </Columns>

</asp:GridView>

ובאמת נקבל שה-GridView שלנו עכשיו מרנדר לינקים מסוג Mailto.

קוד זמין להורדה ב-http://www.JustinAngel.Net/files/MailtoHyperLinkField.zip. 

 

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

Monday Lecture: Upgrading To SQL Server 2005 for developers (level 300), by Justin-Josef Angel

 

  יש יותר מדי חומר מקצועי ללמוד בימינו.

נראה שכל יום יוצאת טכנולוגיה חדשה ושאנחנו, כמפתחים, נדרשים ללמוד אותה ולהתאפס עליה תוך עשרים שניות מקסימום. בשנה האחרונה בלבד הוציאו את דוט נט 2.0, חלונות וויסטה, סיקוול 2005 וכבר הספיקו להוציא את דוט נט 3.0. זה בלתי אפשרי לבן-אדם אחד ללמוד כל-כך הרבה.

סיקוול 2005 זו אפליקציה אדירה שמציעה לנו כמפתחים יכולות מדהימות לשימוש במהלך תהליך הפיתוח. נתנו לנו פונקציות חדשות ב-T-SQL שהיא שפת ה-SQL של סיקוול, נתנו לנו תמיכה מלאה בעבודה מול XML ונתנו לנו אינטגרציה עמוקה ומקצועית מול דוט נט.

בהרצאה זו נצלול לעומק האוקיינוס החדש של ידע טכנולוגי חדש שהצטבר במהלך השנה האחרונה בעבודה עם סיקוול 2005. נעשה יותר מלעבור על הפיצ'רים החדשים, אלא ניכנס בהם הכי חזק שאפשר ואז נגביר את הקצב. אלו הולכות להיות שלוש שעות של חומר טכני אינטנסיבי שבמהלכן נעבור על כל פיצ'ר חדש ומעניין בסיקוול 2005 בסולם-מאמצים של דוגמאות ברמה פשוטה, ברמה בינונית ולבסוף ברמה מתקדמת.

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

תגיעו להרצאה ותראו איך תוך שלוש שעות אתם רוכשים את המקביל של שבועות ניסיון עבודה מול סיקוול 2005.

 

 

Program:

  • 17:30 - 18:00        Gathering

  • 18:00 - 19:20        T-SQL Enhancements

  • [Break]

  • 19:30 - 20:00        New XML Data Type

  • [Break]

  • 20:10 - 21:00        .Net Integration


The Israeli SQL User Group meetings are held at the Microsoft Offices in Ra'anana (Amdocs building), 2 Hapnina St., floor 0, DEKEL meeting room near the reception.

 

Full Subject List:

1. T-SQL Enchantments

· New Max Data Types

· TOP changes

· DML Output

· TableSample & Repeatable

· Pivot & UnPivot

· Except & Intersect

· Row numbering

· DDL Triggers

· Try-Catch blocks

· Service Brokers

2. XML Data Type & XQuery

· New Xml Data Type

· Xml Value method

· Xml Exist Method

· Xml Query Method

· Xml Modify Method

· Transform Xml to Tables

· Transform Tables To Xml

3. .Net Integration

· SQLCLR

· SQL Management objects

· Async Querying methods

· Service Broker Dependency

· Batch Query Optimization

Pre-requisites:  Working knowledge of SQL and Sequel Server, Some understanding of .Net framework & ADO.Net is also helpful.

 

על המרצה:

ג'סטין-יוסף אנג'ל עובד בחמש שנים האחרונות בפיתוח בתחום ה-web, ומתוכם בשלוש שנים האחרונות כמפתח דוט-נט בכיר.

 בשלוש שנים אחרונות אלו הוא עבד וייעץ בפרוייקטים רבים תחת כובעים שונים וביניהם: יועץ, אחראי פרוייקט, ארכיטקט ותוכניתן בכיר. הוא פעיל ועובד בתחומים רבים: ניהול פרוייקטים במתודולוגיות Agile, פיתוח מערכות מבוזרות, ASP.Net, ניתוח, אפיון ופיתוח מונחה ארכיקטורת עצמים, Web 2.0, ניהול והכשרות.

ג'סטין-יוסף אנג'ל מחזיק את הבלוג הוותיק ביותר בעברית בתחום הדוט נט. בבלוג זה הוא מפרסם מאמרים טכניים ו-Webcastים שונים המיועדים הן למפתחים מתחילים והן לגורואים בתחומם כאחד.

ג'סטין-יוסף אנג'ל בעל מספר תארים מקצועיים. הוענק לו תואר MVP לשנת 2006 שניתן ע"י מיקרוסופט למפתחים שהראו ידע מעמיק, מיומנות טכנית רצינית ובעלי היסטוריה של מעורבות קהילתית. בנוסף, הוא בעל תואר MCSD המעיד על יכולותיו המקצועיות והטכניות בפיתוח מגוון אפלקיציות בסביבת דוט נט.

ניתן למצוא את ג'סטין-יוסף אנג'ל בפורומים ואתרים ישראליים בנושא דוט נט ובקבוצות המשתמשים של מיקרוסופט.

Question from Tapuz .Net forum: Building a Tab Control With Microsoft AJAX

שאלה:

 יש לי אתר שעובד עם תגיות <iframe> ב-HTML שמחקה התנהגות של טאבים בדפדפן של הלקוח. הייתי רוצה לבנות אותו טוב יותר, מה הייתם ממליצים לעשות?

 

תשובה:

נשאלת השאלה הבאה: אם אתה רוצה שפעולת שינוי הטאב תהיה שקופה למשתמש או שהיא תדרוש PostBack?

אם אתה רוצה שהיא תדרוש PostBack באמת תוכל לעבוד עם פקד MultiView וסט כפתורים שכל אחד מהם יגרום ל-ActiveViewIndex של ה-MultiView להשתנות בהתאם.

ככה הלקוח בדפדפן שלו לוחץ על כפתור, הלחיצה על הכפתור רצה בחזרה לשרת עם כל הדף, משנה את ה-View הפעיל ב-MultiView והכל חוזר ללקוח מרונדר ויפה. השילוב בין ה-MultiView ובין הסט כפתורים שכל אחד מהם קובע אינדקס אחר ושונה מצדיק לשים את שניהם כחלק מפקד משותף (או Composite Control או User Control) ולהשתמש רק באחד מהם. הפקד עצמו לא מסובך מדי, אבל אם תבחר לכתוב אותו בקוד בלבד (Composite Control) אתה תאבד את היכולת להוסיף Templateים של קוד ASP.Net כ-Viewים. כדי להוסיף Viewים בצורה הזו תצטרך או לרשת את View וליצור את ה-Viewים שלך כ-Custom Controls או לחשוף מחדש יכולת לערוך Templateים.

אם אתה רוצה שהפעולה לא תדרוש PostBack והפעולה תתבצע בצורה שקטה מהמשתמש יש לך כמה אפשרויות איך שאני רואה את זה.
החלטה נוספת שבשלב הזה תצטרך לקבל הוא האם אתה מעוניין שכל ה-HTML לכל הטאבים ישלחו ללקוח או שאתה מעוניין שהלקוח ידבר עם השרת (בצורה שקטה) ויעביר כל פעם רק את ה-HTML הנדרש להצגה באותו רגע.

אם החלטת לשלוח את כל ה-HTML וכל פעם להציג תוכן של טאב אחר אז אנחנו עכשיו פונים לג'אווה סקריפט.
תיקח את כל התכנים השונים של כל הטאבים ותכניס כל אחד מהם ל-<div> נפרד (פקד Panel).
בצד לקוח, יהיו אלמנטים סטטיים (כפתורים, קישורים, תמונות או כל דבר אחר) עם קישור לאירוע onclick של ג'אווה סקריפט. בלחיצה על הכפתור צד-לקוח <div> אחד יחשף וה-<div> שכרגע חשוף יוסתר. מדובר על קביעה פשוטה של this.style.visiability = none.
כאשר הדף מאותחל יש לדאוג שיש בדיוק פקד Panel אחד שהוא עם visibility : inline וכל השאר עם visiability: none.

עכשיו, זאת הייתה הדרך המקובלת לעבוד עד עכשיו. אישית, אני חושב שאפשר לעשות משהו הרבה יותר נקי.
כן ללעבוד עם ג'אווה סקריפט, אבל לא ללערבב את ההתנהגות ואת הבנייה של הפקד צד-שרת בצורה חצי-גמורה כזו שבלתי אפשרי לתחזק.
צריך ליצור פקד אחד כמו שצריך שעושה את כל העבודה הזאת ואפשר לעבוד מולו בצורה שקופה וקלה כמו שנהוג לעבוד מול רכיבים מקצועיים.
ליצור אלמנט בשם Tab שיתרנדר לתגית <div> שבתוכה יהיה תוכן שיקלט מהמשתמש (או באמצעות מערך Controls פנימי או באמצעות חשיפת ITemplate שיאפשר הוספת קוד ASP.Net פנימי בעיצוב).
ליצור אלמנט בשם Tabs שיכיל מערך של Tabים וירנדר את כולם לצד לקוח. בנוסף Tabs ירנדר גם מערך של כפתורים צד-לקוח שיהיו אחראים לאירוע onclick כמו שתיארנו לעיל.

עכשיו אני אקח את זה עוד צעד קדימה - Microsoft AJAX.
במקום לפזר את הג'אווה סקריפט בכל מקום (למשל לכתוב Onclick ברנדור של הכפתורים, למשל לקבוע visiability ברנדור צד-שרת, למשל להתחיל לדאוג לתאימות בין הדפדפנים של כל הסיפור הזה) אפשר לעשות משהו הרבה יותר פשוט.
אפשר ליצור מה שנקרא Behavior צד-לקוח של Ajax. אותה Behavior עובדת על הקונספט הבא "אני למעשה מחלקה. אבל אני בצד-לקוח ואני בג'אווה סקריפט. אני יורשת ממחלקה בשם Behavior, יש לי קונסטרקטור ודיסקטרקטור, ואני יכולה לחשוף מאפיינים ואירועים, אני גם יכולה להירשם לאירועים של הדפדפן".

התפקיד של Behavior כזה כאן לדעתי יהיה מאוד פשוט: להחזיק מערך של Tabים צד-לקוח (שזאת מחלקה צד-לקוח שגם נצטרך לכתוב) ומצביע לפעיל ביניהם. המחלקה צד-לקוח של Tab תאפשר לנו שני דברים: להעלים Tab ולגרום ל-Tab להופיע.
באתחול של ה-Behvaior שלנו נעשה כמה דברים: נעלים את כל ה-Tabים, נקבע טאב פעיל (בין אם לפי קביעה של מאפיין צד-שרת או פשוט נבחר את ראשון), נגרום ל-Tab צד לקוח הראשון להיראות למשתמש, והכי חשוב - נירשם לאירוע ה-onclick של כל הכפתורים שלנו. תקשיב טוב מה נאמר כאן כרגע, נרשם לאירוע צד-לקוח.
במתודה שנרשמה לאירוע צד-לקוח של onclick (ואותה מתודה להזכירך כתובה בג'אווה סקריפט צד-לקוח) תיקח את ה=Tabשמקושר לאותו כפתור ותשנה ב-Behavior את ה-Tab הפעיל (גם את המאפיין שחושף ה-Behavior, גם תעלים את ה-Tab הקודם וגם תגרום ל-Tab הזה להיראות).

אפשר לקחת את זה עוד קדימה - למה בכלל לשלוח ללקוח את כל ה-HTML לכל הטאבים?
באמצעות Ajax אפשר לפנות בנונ-שלנטיות של שורה שורת ג'אווה סקריפט אחת לשרת ולבקש ממנו את ה-HTML שאליו טאב מסויים מתרנדר ואז להציג את ה-HTML הזה במקום קבוע. בצד-שרת, נקלוט את הבקשה הזו, ניקח את האלמנט Tab צד-שרת האחד הזה, נפעיל עליו RenderControl ונשלח בחזרה את ה-HTML ללקוח שיציג אותו בתוך <div>.

 

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

Question from Tapuz .Net forum: Customize GridView's Apperence and Behavior when the DataSource has no rows

שאלה:

 אם ב-GridView של ASP.Net 2.0 אין בכלל ערכים להציג, אז ה-GridView עצמו אינו מוצג כלל.

 האם ניתן להציג כותרת טבלה בלבד? מה נהוג לעשות במצב הזה?

 

תשובה:

מדובר בפיצ'ר מאוד חזק של GridView שהשתנה מאז ימי ה-DataGrid של 1.1.

בדוט נט 1.1 כל פעם של-DataGrid לא היה מידע להציג הוא היה מציג את ה-Header של הטבלה. מה שנראה מאוד מוזר כי יש לך כותרת לטבלה שאין בה שום תוכן.

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

     

protected void Page_Load(object sender, EventArgs e)

    {

        GridView1.DataSource = null; // or some other empty value

        GridView1.DataBind();

    }

 

    protected void GridView1_DataBinding(object sender, EventArgs e)

    {

        if (GridView1.DataSource == null)

        {

            GridView1.DataSource = "x";

        }

    }

 

    protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

    {

        if ((GridView1.DataSource == "x")

            && (e.Row.RowType == DataControlRowType.DataRow))

                e.Row.Style.Add("display", "none");

    }

 

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

 

            <asp:GridView ID="GridView1" runat="server" EmptyDataText="אין נתונים להציג">

            </asp:GridView>

 

    protected void Page_Load(object sender, EventArgs e)

    {

        GridView1.DataSource = null;

        GridView1.DataBind();

    }

 

אפשרות שלישית ואחרונה היא להשתמש ב-EmptyDataTemplate.
תעמדי על ה-GridView ב-Designer, תלחצי על החץ הקטן בצד ימין למעלה, תלחצי על Edit Templates ותבחרי את EmptyDataTemplate. במקום הזה תוכלי לעצב יותר לעומק את המידע שאת רוצה שהמשתמש יראה כאשר אין מידע להציג. כלומר, לכאן תוכלי להכניס גם פקדים ולא רק טקסט.

 

            <asp:GridView ID="GridView1" runat="server">

                <EmptyDataTemplate>

 

                    <script type="text/javascript">

            alert('אין נתונים להציג!');

                    </script>

 

                </EmptyDataTemplate>

            </asp:GridView>

 

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

Question from Tapuz .Net forum: Deploying a Full-Trust .Net Application over LocalInternet

שאלה:

 פיתחנו אפליקציה שנפרסת דרך אינטרנט מקומי במשרד.
אך כאשר אנו מריצים אותה אנחנו מקבלים SecurityException.
לפי MSDN צריך לתת לאסמבלי Full-Trust וזה איכשהו קשור ל-ClickOnce. מישהו יכול להרחיב בנושא?

 

תשובה:

אתה צריך לעבוד עם Code based security של דוט נט.
אני ממליץ בחום שתקרא את ההסבר המפורט יותר על Code Baes Security בדוט נט וכיצד לתת לאסמבלי Full-Trust.

תקרא את המאמר הנ"ל בבקשה ורק אז תקרא את המשך התגובה.

השיטה שהצגתי בהסבר הזה, היא השיטה הידנית לתת Full Trust לאסמבלי על מחשב כלשהו בו מותקנת הפריימוורק. אם אתה רוצה לפרוס אפליקציה ב-FullTrust ולא להיגרר לקנפוג ידני בכל פעם אז ישנן כמה אפשרויות. (ואגב, קשה לי להמאין ש-PrefCalc של הפריימוורק באמת החזיר תוצאה של FullTrust כי זה אומר שאתה מבצע גם גישה לאינטרנט, גם למערכת הקבצים  וגם למערכת ההפעלה).

האפשרות ראשונה והמומלצת ביותר, היא להשתמש ב-ClickOnce שהוא מנגנון פריסה הכולל בתוכו אפשרות מובנית שהלקוח ישיג עדכוני לאפליקציה. אפשר לדבר הרבה על ClickOnce, אבל נקודתית כאן אם תיכנס למאפיינים של הפרוייקט שלך (לחיצה ימנית על הפרוייקט ו-Properties) יש את תגית Security ושם ניתן לקבוע את ההרשאות שתקבל האפליקציה לאחר שתיפרס עם ClickOnce. כברירת מחדל, אפליקציות שנפרסות עם ClickOnce מקבלות FullTrust. כדי לבצע את הפריסה יש עוד כמה צעדים שצריך לעשות שקשורים ל-clickonce (בתקווה לעשות Strong Name לאסמבליס, לפרט את מדיניות העדכונים, לפרט מאיפה מגיעים העדכונים) אבל הרוב מתרכז סביב תגיות Signing, Security ו-Publish.

ClickOnce Deployment and Security

 

האפשרות השנייה היא יצירת פרוייקט התקנה, שזה אומר ליצור פרוייקט מסוג Setup Project ולהוסיף לו את הקבצים (Primary output) של אפליקציות מסויימות והוא יצור קובץ MSI שיתקין אותם על המחשב של הלקוח. היות ואתה רוצה גם לתת Full-trust לאותם DLLים\EXEים שאתה פורס, האפשרות הכי פשוטה היא ליצור Custom Action שמריצה בסיום ההתקנה את כלי ה-caspol.exe של הפריימוורק ולתת את הנתיב של אותם DLLים\EXEים עם הבקשה להרשאת Full-trust. כלי ה-caspol הוא הכלי שמאפשר ברמת ה-Command Line לנהל הרשאות אבטחה של הפריימוורק ובהתאם קיימת לו פקודה שמאפשרת לתת הרשאת Full trust לאסמבלי (ואף אפילו לפרט הרשאות יותר ספציפיות).  

Walkthrough: Creating a Custom Action

How to: Add Assemblies to Security Policy Using Caspol.exe (מכיל דוגמה לשימוש ב-caspol.exe כדי לתת FullTrust לאסמבלי)

 

 

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

Question from .Net Tapuz forum: How to color Rows and Cells in a GridView

שאלה:

יש אפשרות להוסיף javascript ב Data Grid?
נגיד ואני רוצה לעשות onmouseover בטבלת  Data Grid שמקושרת לdatabase ושזה יסמן לי שורה בכל פעם שהסמן עובר, אפשר לעשות כזה דבר?

 

תשובה:

כמו שאמרו קודם לכן, יש אירועים ב-GridView שמאפשרים לתפוס את יצירת השורה החדשה (היות ואין חשיבות למידע ניתן להשתמש באירוע ה-RowCreated ולא בהכרח באירוע RowDataBound שנותן גישה למידע). הכוונה כאן היא שכל פעם שיש שורה חדשה שנוצרת האירוע הזה עולה ודרך e.Row ניתן לקבל את השורה הספציפית המדוברת.

// in myWebForm.aspx

        <asp:GridView ID="GridView1" runat="server" OnRowDataBound="GridView1_RowDataBound" OnRowCreated="GridView1_RowCreated">

        </asp:GridView>


// in myWebForm.aspx.cs

    protected void Page_Load(object sender, EventArgs e)

    {

        GridView1.DataSource = new string[] { "hello", "world" }; // or a DataTable or whatever

        GridView1.DataBind();

    } 

 

    protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

    {

        GridViewRow OhHelloThereIAmTheCurrentlyCreatedRow = e.Row;

    }

 

 

    protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

    {

        GridViewRow OhHelloThereIAmTheCurrentlyDataBoundedRow = e.Row;

    }

כל כזאת שורה של GridViewRow מכילה מערך תאים (דרך e.Row.Cells) ואותו מערך תאים חושף אפשרות לשנות את הרינדור בפועל שלו ל-HTML. ספציפית, אם את רוצה להוסיף Attribute (כלומר להוסיף בתגית התגית זוג של מפתח-ערך כמו Onmouseover כמפתח ו-alert כערך) תוכלי לגשת ל-Attributes של כל תא ובאמת להוסיף את הזוג של מפתחות-ערכים האלו.

 

    protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

    {

        foreach (TableCell curTableCellToAddJavaScriptTo in e.Row.Cells)

            curTableCellToAddJavaScriptTo.Attributes.Add("onmouseover", "alert('Kaboom!');");

    }

בדוגמה הזו עברנו בלולאה על כל התאים בשורה הנוכחית שנוצרה ואמרנו "תוסיף לרנדור צד לקוח מאפיין בשם onmouseover שמבצע עם הערך הזה". בצד לקוח, הדפדפן רואה את זה וידוע לזהות את זה כאירוע ג'אווה סקריפט וכל פעם שנעביר עכבר מעל תא כלשהו נקבל התראה "Kaboom1".
עכשיו נרצה למשל להגיד שכל פעם שהעכבר נכנס לתא מסויים בטבלה נרצה לצבוע את התא המסויים הזה בצבע כלשהו.

    protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

    {

        foreach (TableCell curTableCellToAddJavaScriptTo in e.Row.Cells)

            curTableCellToAddJavaScriptTo.Attributes.Add("onmouseover", "this.style.backgroundColor = 'Pink';");

    }

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

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

{

    foreach (TableCell curTableCellToAddJavaScriptTo in e.Row.Cells)

    {

       curTableCellToAddJavaScriptTo.Attributes.Add("onmouseover", "this.style.backgroundColor = 'Pink';");

      curTableCellToAddJavaScriptTo.Attributes.Add("onmouseout", "this.style.backgroundColor = 'White';");

    }

}

בעיה נוספת שקיימת לנו היא שהכותרת נצבעת, ובהתאם נרצה לדאוג שאנחנו צובעים רק שורות שהם לא מיוחדות (הכותרת, תחתית הרשימה וכך הלאה).

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

{

    if (e.Row.RowType == DataControlRowType.DataRow)

        foreach (TableCell curTableCellToAddJavaScriptTo in e.Row.Cells)

        {

            curTableCellToAddJavaScriptTo.Attributes.Add("onmouseover", "this.style.backgroundColor = 'Pink';");

            curTableCellToAddJavaScriptTo.Attributes.Add("onmouseout", "this.style.backgroundColor = 'White';");

        }

}

 

ה-Enum בשם DataControlRowType מאפשר לנו לבדוק קצת יותר לעומק את השורה הנוכחית שאנחנו עובדים מולה ובאמת לבדוק את האופי היותר ספציפי שלה. למשל גם קיים סוג שורה שהוא "שורת מידע ריקה" שאולי נחליט לא לרנדר אותה.
נרחיב את הדיון לעבור על כל השורה ולא כל תא, ובמקום לולאה על כל התאים נוסיף את הסקריפטים האלו לשורה עצמה.

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

{

    if (e.Row.RowType == DataControlRowType.DataRow)

    {

        e.Row.Attributes.Add("onmouseover", "this.style.backgroundColor = 'Pink';");

        e.Row.Attributes.Add("onmouseout", "this.style.backgroundColor = 'White';");

    }

}

וזהו בגדול על צביעת תאים ושורת ב-GridView.


אפשר ורצוי מאוד לפתח את הדוגמה הזו הלאה כדי שתפעל יותר טוב מבחינת HTML.
-  לכל שורה מתרנדר הסקריפט הזה מחדש וחבל על המקום שזה תופס בדף ה-HTML שלנו. אפשר היה להכניס את הסקריפט הזה לתוך פונקציית ג'אווה סקריפט שמתרנדרת פעם אחת וכל פעם לקרוא לה במקום לכתוב את הסקריפט מחדש.
- אפשר היה להחזיר את השורה לצבע המקורי שלה (עם שמירה של משתנה גלובלי ברמת הדף אצל הלקוח על הצבע האחרון של השורה ששונתה) ולא לצבע שקבוע Hard-coded.
- אפשר להכניס את האפשרויות CSS שאנחנו מכוונים לתוך מחלקת CSS ספציפית שגם היא תתרנדר רק פעם אחת ולקבוע את שם המחלקה (className בצד לקוח, CssClass בצד שרת) של ה-CSS אליו שייכת השורה.
- אפשר להכניס את כל ההיגיון הזה לתוך GridView שהיא רק קובץ מחלקה ולא בדף ספציפי וככה להשתמש ביכולת הזו מחדש בכל פעם בלי להעתיק קוד ברחבי האפליקציה.

 

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

Question from .Net Tapuz forum: How to limit the memory (RAM) my Winforms application uses?

שאלה:

כתבתי תכנית קטנה, מאוד קטנה, והיא תופסת בערך כ-14,000 KB מהזכרון בזמן ההרצה.
האם ניתן לצמצם את הגודל הזה?
התכנית כתובה ב-C# והיא בסביבת WINForm.

 

תשובה:

 הועלה כאן רעיון יפה להשתמש ב-

Process.GetCurrentProcess().MaxWorkingSet = new IntPtr(1000000);


אישית, הייתי מעדיף לא להגביל בשום שלב את הזכרון שתוכנה דורשת היות וייתכן וכי בנקודת זמן ספציפית היא תדרוש הרבה יותר. במצב כזה או שנאלץ לוותר על הפיצ'ר שדורש הרבה יותר זכרון או יותר סביר שנעלה את גבול הזכרון. בכל מקרה, זה יוצר בעיה רצינית והתעסקות עם זכרון.


במצבים שרוצים בהתחלה לצמצם את גודל התוכנה אפשר לבחור בגישה הרבה פחות תקיפה עם ניהול הזכרון ובמקום להגדיר את המקסימום מינימלי שעוד עלולים להגיע אליו, אפשר להגדיר מינימום זכרון מינימלי. במצב הזה, נגיד למשל לתוכנה "תתחילי עם 100KB" מה שיגרום לה בהתחלה לדרוש 256KB זכרון ווירטואלי.

לפי הצורך ככל שהתוכנה נטענת וככל שהתוכנה רצה היא תבקש ממערכת ההפעלה יותר הקצאות לפי הצורך.  הפעולה הזו בהתחלה תוסיף כמה שניות לאתחול התוכנה היות והיא במו"מ ארגסיבי עם עצמה ועם מערכת ההפעלה על הקצאת זכרון ווירטואלי. אם באמת נפתח את Prefmon ונשים Trace על כמות הזכרון הווירטואלי שדורשים תהליכים דוט נטיים אפשר יהיה לראות שבהתחלה יש גרף מדרגות במקום עלייה חדה ומהירה.

// will cause problems (OutOfMemoryException and what not)
Process.GetCurrentProcess().MaxWorkingSet = new IntPtr(10000000);


// will do the same things only by setting the lower bound, will not causes problems, will lengthen loadup time
Process.GetCurrentProcess().MinWorkingSet = new IntPtr(100000);

הנה דוגמה לאפליקציה שנטענת לתוך ה-RAM בלי שום הגבלה:

רק עם הגבלה על הגבול העליון של הזכרון:

רק עם הגבלה על הגבול התחתון של הזכרון:

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

 

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

Question from Tapuz .Net forum: Class Pointers in .Net 101 (with Tree Collection)

שאלה:

 אני מנסה להבין את איך דוט נט עובדת עם מחלקות שמכילות הצבעות למחלקות אחרות מאותו סוג?

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

יש מושג "מצביעים" או תחליף להם בvb.net?האם זו בכלל הגישה הנכונה?

 

תשובה:

    public class SomeClass
    {
        private List<SomeClass> _children = new List<SomeClass>();
        public List<SomeClass> Children
        {
            get { return _children; }
            set { _children = value; }
        }

        private SomeClass _father;
        public SomeClass Father
        {
            get { return _father; }
            set
            {
                _father = value;
                _father.Children.Add(this);
            }
        }
    }

// examples of use:
            SomeClass root = new SomeClass();

            SomeClass son = new SomeClass();
            son.Father = root;

            SomeClass son2 = new SomeClass();
            son2.Father = root;

            // get second soon
            root.Children[1];

            // get brother of son
            son.Father.Children[son.Father.Children.IndexOf(son) + 1];

מדובר בעיקר בהבנה של איך אוספים (גם באנגלית: Collections) עובדים בדוט נט.

יצרנו מחלקה מסוג SomeClass שמכילה הפנייה למחלקה אחרת מסוג SomeClass בתוך מאפיין (גם באנגלית: Property) שנקרא Father. בפועל, כל מה שאמרנו זה "יש לי מאפיין בשם Father שמחזיר ומקבל מחלקה מטיפוס SomeClass" וגם באיזהשהו שלב קבענו את ערך Father ואז באיזהשהו שלב תיחקרנו את הערך הזה.
בנוסף חשפנו מערך ג'נרי (שזה רק אומר שיש מערך עם גודל משתנה מבחינתנו) של מחלקות אחרות מסוג SomeClass ואותו מערך ג'נרי נקרא Children.

היות והמערך הג'נרי הוא Strongly-typed ומיישם את ממשק ה-IList אנחנו מקבלים את היכולת לנווט בתוך האוסף (שימוש ב-IndexOf, add, remove, contains ומה לא) וגם שלתוצאה של אותו ניווט נקבל כמופע של SomeClass ונוכל ישירות לעבוד מולו עם אותם מאפיינים שיצרנו. ולמשל באמת נוכל לקבל את האח הבא של הילד הנוכחי בזה שנחפש את המיקום של הילד הנוכחי בתוך ילדי האב ונוסיף אחד לאותו מיקום.

בדוט נט אין מצביעים פר-סה, אלא אנחנו יודעים שאם יש לי משתנה מסוג מחלקה ישאר שם מצביע על הדבר הזה שנקרא זכרון. לעומת זאת, זה לא משנה לנו כי אנחנו לא מטפלים בזכרון. מבחינתנו, כל עוד אני יכול לקבוע שאני מחזיק משתנה מסוג מחלקה ואז אני יכול לגשת אליו זה מספיק טוב לנו.יש מצבים למשל שלא ישמרו מצביעים אלא העתקים על הזכרון (למשל שנעבוד עם Structים ולא עם Classים).

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

TreeBag<SomeClass> myTree = new TreeBag<SomeClass>();
myTree.Add(root);
myTree.Add(son);
myTree.Add(son2);

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

DTreeNode<string> root = new DTreeNode<string>();
DTreeNode<string> temp;

temp = root.Nodes.Add("Hello");
temp.Nodes.Add("olleH");

temp = root.Nodes.Add("World");
temp.Nodes.AddRange(new string[] { "dWorl", "ldWor", "rldWo", "orldW" } );

כתובות אינטרנט נילוות:
קישור #1: The C5 Generic Collection Library, האתר הרשמי
קישור #2: Generic Tree in C#, ב-codeproject.com

 

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

Question from .Net Tapuz forum: How to iterare over a collection and remove items from it?

שאלה:

יש לי פקד CheckBoxList עם מס' פריטים בתוכו.
אני מעוניין שבאירוע מסויים, כמו למשל CLICK, הפריטים ב-CheckBoxList שלא ניבחרו על ידי המשתמש, יימחקו. כתבתי את הקוד הבא:

           for(int i = 1;i<CheckBoxList1.Items.Count;i++)
            {
                if(!CheckBoxList1.Items[i].Selected)
                {
                    CheckBoxList1.Items.Remove(CheckBoxList1.Items[i]);
                    Label1.Text = CheckBoxList1.Items.Count.ToString();
                }

שאני מריץ לולאת for, אני מקבל כל מיני התנהגויות מוזרות וזה בכלל לא עושה מה שרציתי. איך אפשר לעשות כזה דבר? 

 

תשובה:

 (מוטי מאירי מוסיף כי במהלך ריצת הלולאה Count של המערך קטן)

Count אכן ילך ויקטן במהלך ביצוע הלולאה.

עדיף להימנע מביצוע לולאות for כאשר הערך עליו שואפים הוא גבול משתנה היות ותיווצר התנהגות בלתי-סדירה (כמו זו שהוצגה בתחילת השרשור).
כמו כן, בלולאות foreach לא ניתן להסיר אלמנטים עליהם עוברים.
ובהתאם, הפתרון הוא לולאת while.

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

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);

//// not good!
//for (int i = 0; i < list.Count; i++)
//    list.Remove(i + 1);

int i = 0;
while(i < list.Count)
{
    bool ShouldWeRemoveSomethingFromList = (i%2 == 0);
    if (ShouldWeRemoveSomethingFromList)
        list.Remove(list[i]);
    else
        i++;
}

זה הסינטקס המקובל למעבר על אוספים כאשר משנים את גודלם, לא יהיה קשה להמיר את זה לעבור על מערך של ListItem ולשנות את התנאי ב-ShouldWeRemoveSomethingFromList.
היות ושתי סוגי הלולאות האחרות לא מתאימות מטעם הגדרתן למצב הזה, חובה לעבוד עם While.

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

 

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);

List<int> IntsToRemoveFromList = new List<int>();
foreach(int curIntToCheckIfWeNeedToRemove in list)
       if (curIntToCheckIfWeNeedToRemove % 2 == 0)
              IntsToRemoveFromList.Add(curIntToCheckIfWeNeedToRemove);

foreach (int curIntToRemove in IntsToRemoveFromList)
       list.Remove(curIntToRemove);

 

יאיר נבט מוסיף כי באמצעות הסינטקס של לולאת While ניתן להוריד אלמנטים מסומנים מתוך CheckBoxList כך:

 

       private void Button1_Click(object sender, System.EventArgs e)
        {
            int i = 0;
            while(i < CheckBoxList1.Items.Count)
            {
                if (!CheckBoxList1.Items[i].Selected)
                {
                    CheckBoxList1.Items.Remove(CheckBoxList1.Items[i]);
                }
                else
                {
                    i++;
                    Label1.Text = CheckBoxList1.Items.Count.ToString();
                }

         }

עדכון:

חגי מוסיף כי ניתן לבצע לולאת for יורדת שמורידה איברים לפי מיקום וזה גם פתרון (ואף יותר טוב מאלו שהצעתי כאן) לבעיה:

for(int i = CheckBoxList1.Items.Count-1;i >=0;i--)

    if (!CheckBoxList1.Items[i].Selected)

        CheckBoxList1.Items.RemoveAt(i);

 

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

Question from .Net Tapuz forum: Using HttpWebRequest and HttpWebResponse with non-english content

שאלה:

אני משתמש בStreamReader כדי לקרוא את התוכן של דף Web. כלומר:

        Dim dataStream As Stream = m_response.GetResponseStream()
        Using reader As New StreamReader(dataStream, Text.Encoding.Default)
            Return reader.ReadToEnd()
        End Using

(הm_response הוא מסוג HttpWebResponse.)
הבעיה היא שכל עוד לא קראתי את תוכן הדף אני לא יכול לדעת באיזה קידוד הוא נשלח.
האם קיימת דרך לגלות באיזה קידוד הדף נמצא ולהמיר בהתאם?

 

תשובה:

 תשובתו של דניאל כץ.

מה שאנחנו למעשה רוצים לעשות כאן זה לקרוא את המידע עד הסוף מה-StreamReader ורק לאחר מכן לחפש בתוך התוכן ה-HTMLי ולראות מהו הקידוד של המסמך.

דבר ראשון ניתן לגשת HttpWebResponse.ContentEncoding שאמור לפרט מהו הקידוד של התוכן ה-HTMLי.
אך ייתכן והקידוד הזה יהיה ריק, אז במקרה הזה נפתח את מסמך ה-HTML ונחפש כותרות של Meta Data בתוך המסמך ה-HTMLי שבהן רשום קידוד הקובץ.

לאחר שהשגנו את הקידוד נשנה את קידוד המחרוזת המוחזרת בהתאם. 

זוהי דוגמה לשימוש הרצוי שלנו:

Sub Test()

Dim connection As New CWebConnection(New Uri("http://www.tapuz.co.il/"))
connection.SendRequest()

Dim distortedContent As String = connection.GetContent
Dim readableContent As String = connection.GetDecodedContent

End Sub

 וזוהי מחלקה שממשת את הדרישות והתהליך שתיארנו:

Public Class CWebConnection

Private m_request As HttpWebRequest
Private m_response As HttpWebResponse
Private m_content As String

 

Public Sub New(ByVal url As Uri)
    m_content = Nothing

    m_request = WebRequest.Create(url)
    m_request.Credentials = CredentialCache.DefaultCredentials
    m_request.Timeout = 30000
End Sub

 

Public Sub SendRequest()
    m_response = m_request.GetResponse()
End Sub

 

Public Function GetContent() As String
    If m_content = Nothing Then
        Dim dataStream As Stream = m_response.GetResponseStream()
        Using reader As New StreamReader(dataStream, Encoding.Default) 
            m_content = reader.ReadToEnd() + vbCrLf
        End Using
    End If

    Return m_content
End Function

 

Public Function GetDecodedContent() As String
    Dim raw As String = GetContent()
    Dim dest_enc As Encoding = GetEncoding()

    Return ConvertEncoding(raw, Encoding.Default, dest_enc)
End Function

 

Public Function GetEncoding() As Encoding
    Dim pattern As String = "<\s*(meta\s*http-equiv=\s*["']?)?content-type\s*[:"']?\s*(content\s*=\s*)?["']?\s*[\w/\\]*\s*;\s*charset\s*=\s*(?<encoding>[^"';>]*)\s*["']?\s*/?\s*>"
    Dim m_RegEx As Regex
    Dim encMatch As Match
    Dim enc As Encoding = Encoding.GetEncoding("windows-1255")

    If m_content = Nothing Then
        Throw New Exception("First call GetContent()")
    End If

    If GetEncodingHeader() IsNot Nothing Then
        Return GetEncodingHeader()
    Else
        m_RegEx = New Regex(pattern, RegexOptions.IgnoreCase Or RegexOptions.Compiled)
        encMatch = m_RegEx.Match(m_content)

        If encMatch.Success Then
            Try
                enc = Encoding.GetEncoding(encMatch.Groups("encoding").ToString.ToLower)
            Catch
            End Try
        End If
    End If

    Return enc
End Function

 

Public Function GetEncodingHeader() As Encoding
    Dim output As Encoding = Nothing
    If m_response.ContentEncoding <> "" Then
        Try
            output = Encoding.GetEncoding(m_response.ContentEncoding)
        Catch
            output = Nothing
        End Try
    End If

    Return output
End Function

 

Public Shared Function ConvertEncoding(ByVal raw As String, ByVal src_enc As Encoding, ByVal dest_enc As Encoding) As String
    Return src_enc.GetChars(Encoding.Convert(dest_enc, src_enc, src_enc.GetBytes(raw)))
End Function

End Class

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

4/12/2006 Lecture: Upgrading To SQL Server 2005 for developers (level 300), by Justin-Josef Angel

 

  יש יותר מדי חומר מקצועי ללמוד בימינו.

נראה שכל יום יוצאת טכנולוגיה חדשה ושאנחנו, כמפתחים, נדרשים ללמוד אותה ולהתאפס עליה תוך עשרים שניות מקסימום. בשנה האחרונה בלבד הוציאו את דוט נט 2.0, חלונות וויסטה, סיקוול 2005 וכבר הספיקו להוציא את דוט נט 3.0. זה בלתי אפשרי לבן-אדם אחד ללמוד כל-כך הרבה.

סיקוול 2005 זו אפליקציה אדירה שמציעה לנו כמפתחים יכולות מדהימות לשימוש במהלך תהליך הפיתוח. נתנו לנו פונקציות חדשות ב-T-SQL שהיא שפת ה-SQL של סיקוול, נתנו לנו תמיכה מלאה בעבודה מול XML ונתנו לנו אינטגרציה עמוקה ומקצועית מול דוט נט.

בהרצאה זו נצלול לעומק האוקיינוס החדש של ידע טכנולוגי חדש שהצטבר במהלך השנה האחרונה בעבודה עם סיקוול 2005. נעשה יותר מלעבור על הפיצ'רים החדשים, אלא ניכנס בהם הכי חזק שאפשר ואז נגביר את הקצב. אלו הולכות להיות שלוש שעות של חומר טכני אינטנסיבי שבמהלכן נעבור על כל פיצ'ר חדש ומעניין בסיקוול 2005 בסולם-מאמצים של דוגמאות ברמה פשוטה, ברמה בינונית ולבסוף ברמה מתקדמת.

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

תגיעו להרצאה ותראו איך תוך שלוש שעות אתם רוכשים את המקביל של שבועות ניסיון עבודה מול סיקוול 2005.

 

 

Program:

  • 17:30 - 18:00        Gathering

  • 18:00 - 19:20        T-SQL Enhancements

  • [Break]

  • 19:30 - 20:00        New XML Data Type

  • [Break]

  • 20:10 - 21:00        .Net Integration


The Israeli SQL User Group meetings are held at the Microsoft Offices in Ra'anana (Amdocs building), 2 Hapnina St., floor 0, DEKEL meeting room near the reception.

 

Full Subject List:

1. T-SQL Enchantments

· New Max Data Types

· TOP changes

· DML Output

· TableSample & Repeatable

· Pivot & UnPivot

· Except & Intersect

· Row numbering

· DDL Triggers

· Try-Catch blocks

· Service Brokers

2. XML Data Type & XQuery

· New Xml Data Type

· Xml Value method

· Xml Exist Method

· Xml Query Method

· Xml Modify Method

· Transform Xml to Tables

· Transform Tables To Xml

3. .Net Integration

· SQLCLR

· SQL Management objects

· Async Querying methods

· Service Broker Dependency

· Batch Query Optimization

Pre-requisites:  Working knowledge of SQL and Sequel Server, Some understanding of .Net framework & ADO.Net is also helpful.

 

על המרצה:

ג'סטין-יוסף אנג'ל עובד בחמש שנים האחרונות בפיתוח בתחום ה-web, ומתוכם בשלוש שנים האחרונות כמפתח דוט-נט בכיר.

 בשלוש שנים אחרונות אלו הוא עבד וייעץ בפרוייקטים רבים תחת כובעים שונים וביניהם: יועץ, אחראי פרוייקט, ארכיטקט ותוכניתן בכיר. הוא פעיל ועובד בתחומים רבים: ניהול פרוייקטים במתודולוגיות Agile, פיתוח מערכות מבוזרות, ASP.Net, ניתוח, אפיון ופיתוח מונחה ארכיקטורת עצמים, Web 2.0, ניהול והכשרות.

ג'סטין-יוסף אנג'ל מחזיק את הבלוג הוותיק ביותר בעברית בתחום הדוט נט. בבלוג זה הוא מפרסם מאמרים טכניים ו-Webcastים שונים המיועדים הן למפתחים מתחילים והן לגורואים בתחומם כאחד.

ג'סטין-יוסף אנג'ל בעל מספר תארים מקצועיים. הוענק לו תואר MVP לשנת 2006 שניתן ע"י מיקרוסופט למפתחים שהראו ידע מעמיק, מיומנות טכנית רצינית ובעלי היסטוריה של מעורבות קהילתית. בנוסף, הוא בעל תואר MCSD המעיד על יכולותיו המקצועיות והטכניות בפיתוח מגוון אפלקיציות בסביבת דוט נט.

ניתן למצוא את ג'סטין-יוסף אנג'ל בפורומים ואתרים ישראליים בנושא דוט נט ובקבוצות המשתמשים של מיקרוסופט.

Question from Tapuz .Net forum: Create user friendly and search-engine friendly URLs with ASP.Net

שאלה:

יש לי אתר ASP.net, שאני מעוניינת להפוך את כתובות ה-URL המגיעות ממנו ל-Search Engine Friendly.

 בכל הדוגמאות שמצאתי שמדברות על הנושא, הדרך הבסיסית היא שימוש בפונקציה RewritePath של המחלקה HttpContext.
הפונקציה אמורה "לזייף" את ה-url address של ה-Request כך שאפילו שכתובת העמוד האמיתית היא
 www.mysite.com/mypage.aspx?category=1&product=2 מה שהמשתמש יראה יהיה www.mysite.com/categoryname/productname.html.

 השתמשתי בפונקציה,  קיבלתי שגיאה ולא ברור לי מדוע זה קורה.

 

תשובה:

HttpContext.RewritePath היא מתודה לא סימפטית שצריך להימנע משימוש ישיר בה. למרות היכולות המאוד מוגבלות שלה לבצע סוג יחסית פרימיטיבי של UrlRewriting עדיף להימנע מעבודה מולה היות והיא לא מציגה פתרון כוללני לבעיות שעלולות להיווצר כתוצאה מהשינוי URL. הרבה מאוד פיצ'רים בדוט נט נשברים כאשר מחבלים ישירות ב-HttpContext. למשל, ידוע ש-Themes ו-Master Pages מתחילים לזרוק שגיאות בעבודה עם RewritePath.

לבעיה של UrlRewriting יש שתי פתרונות נפוצים.

הראשון ביניהם מובנה בתוך ASP.Net 2.0 בצורה של תגית <urlMappings> בקובץ ה-web.config. בתוך התגית הזאת (שיושבת כמובן בתוך <system.web>) ניתן להוסיף מיפוי אחד-לאחד של כתובת ספציפית שהלקוח מבקש ולכתובת ספציפית אחרת. כל זה בלי להוריד את השינוי כתובת ללקוח, אלא לבצע הכל שקוף מהלקוח. שזאת למען האמת המטרה. למשל הדוגמה הזאת תפנה כל בקשה מהכתובת Bevrages\Coke.aspx לכתובת פחות ידידותית-למשתמש שתכיל סימונים של המערכת.

        <urlMappings>
            <add url="~\Bevrages\Coke.aspx" mappedUrl="Product.aspx?cat_id=7&prod_id=5" />
        </urlMappings>

מה הבעיה בפתרון המובנה ב-ASP.Net 2.0? הוא מאפשר הפניה רק בין כתובות ספציפיות ולא ביצוע של כללים. מה זה כלל לדוגמה? "תיקח את התיקייה ותחפש אותה כקטגוריה של מוצר ותיקח את השם קובץ ותחפש אותה כשם של מוצר ותזרוק את זה לדף אחר". כלומר, שיש משתנים כלשהם במחרוזת החיפוש, הפריימוורק כבר לא יודע לתמוך בזה.

אגב, הסיבה למגלבה הזו היא מאוד פשוטה, urlMappings נועד לתמוך בפיצ'ר חדש של ASP.Net 2.0 שנקרא app_offline.html וברגע שנשים קובץ כזה בתיקייה של פרוייקט web כל הבקשות לאפליקציה יפנו לדף HTML הסטטי הזה. וככה נוכל למשל להוריד אתר לתחזוקה.

הפתרון הנפוץ לבעיה הזו הוא שימוש בספריית התוכנה החופשית urlrewriting.net. הספרייה מאפשרת כל חיתוך והתאמה בין מחרוזות שנרצה לעשות והכלה של כללים על כל בקשה שהיא מקבלת. הדבר היפה שהיא אפילו מקבלת כללים ב-Regex. למשל הכלל הבא:

    <urlrewritingnet rewriteOnlyVirtualUrls="true" contextItemsPrefix="QueryString" defaultPage="default.aspx" xmlns="http://www.urlrewriting.net/schemas/config/2006/07">
        <rewrites>
            <add name="UrlTest" virtualUrl="^~/Url/([0-9]*)/([0-9]*).aspx"
                 rewriteUrlParameter="ExcludeFromClientQueryString"
                 destinationUrl="~/UrlRewritingTest.aspx?FirstId=$1&SecondId=$2"
                ignoreCase="true"/>
        </rewrites>
    </urlrewritingnet>

הקוד הנ"ל יפנה כל בקשה לכתובת בסגנון Url/1234/4567.aspx לדף UrlRewritingTest.aspx?FirstId=1234&SecondId=45678. העבודה בכללים ולא במוסכמות סטטיות היא חלק מהכוח המאוד רציני של הספריית תוכנה הזו.

ניתן להוריד את הספריית תוכנה הזו כאן:
UrlRewritingNet.UrlRewrite - Home

אם את רוצה גם להיפתר מסיומת ה-ASPX (או כל סיומת אחרת שהפריימוורק מקבל, למשל ASMX) בכדי ליצור כתובת מאוד ידידותית למשתמש, אני ממליץ בחום שתקראי הודעה ישנה שלי בנושא שיתוף בין ה-IIS ל-ASP.Net.

 

שאלת המשך: האם ניתן להשתמש במסד הנתונים בתהליך? למשל ליצור דף בשם myProductName.aspx ושהוא ימצא את ה-ID של myProductName וישלח ל-Product.aspx?id=1234?

שאלה מצויינת.

הכללים שאפשר לראות באתר של UrlRewriting.Net הם הכללים ברירת מחדל, למשל באמת הכלל של Regex שמבצע כל מיני בדיקות, מקבל כל מיני החלטות ואז מבצע בפועל הפנייה. בתוך הכלל הזה יש לוגיקה.

בצורה דומה את יכולה לכתוב לעצמך בקלות רבה RewriteRule משלך. אותם כללים שדיברנו עליהם שה-Regexp הוא דוגמה של כזה כלל שבה עם UrlRewriting, את יכולה לכתוב אחד כזה משלך. אותו RewriteRule מיישם שתי מתודות לא מעניינות ומתודה אחת מאוד מעניינת - RewriteURL שמקבל את ה-Url לשכתוב. בתוך המתודה הזו תוכלי להשתמש למשל ב-Regex.Match כדי להוציא את שתי המילים הרלוונטיות מתוך ה-QueryString, לגשת איתם למסד נתונים ואז להחזיר את ה-URL המתוקן שאת הלוגיקה שלו כתבת במתודה הזו.

העיקר הוא שאת יכולה ליצור לעצמך מחלקה שבאיזהשהו שלב מקבלת את המושכות לידיים ואת יכולה לבצע את ההחלטות שלך ולהכניס את ההיגיון שלך לתוך מנגנון UrlWriting מתפקד לגמרי.
בשביל זה צריך ליישם את אובייקט ה-RewriteRule ו-UrlRewritingProvider שיחזיר את הכלל. בגדול, המקום היחידי שיש בו מחשבה אמיתי זה ב-RewriteRule.RewriteURL ששם את מחליטה לפי ה-URL הנוכחי לאיזה URL להפנות.

עוד על הנושא (ודוגמה על איך הם יישמו את כלל ה-Regexp) אפשר לקרוא במדריך שנמצא בדף הראשי של האתר (הארבעה עמודים האחרונים ב-PDF אאל"ט).

 

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

my Real World Application Development By Example course on MVP Week

תאריך פתיחה: 26-27/11/06 משך הקורס: יומיים

המרצה: ג'סטין-יוסף אנג'ל, www.JustinAngel.Net 


ג'סטין-יוסף אנג'ל עובד ומיעץ בפרוייקטים רבים תחת כובעים שונים וביניהם: יועץ, אחראי פרוייקט, ארכיטקט ותוכניתן בכיר. הוא פעיל ועובד בתחומים רבים: ניהול פרוייקטים במתודולוגיות , פיתוח מערכות מבוזרות, , ניתוח, אפיון ופיתוח מונחה ארכיקטורת עצמים, Web 2.0, ניהול והכשרות. ג'סטין-יוסף אנג'ל בעל מספר תמקצועיים. הוענק לו תואר MVP לשנת 2006 שניתן ע"י מיקרוסופט ארים למפתחים שהראו ידע מעמיק, מיומנות טכנית רצינית ובעלי היסטוריה של מעורבות קהילתית.
בלוג :
http://blogs.Microsoft.co.il/blogs/JustinAngel

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

אל תעמידו פנים, אתם יודעים שכל פעם שאתם צריכים לגשת לקוד שכתבתם לפני יותר משבוע, אין לכם מושג איך לגשת אליו. כותבים 100 שורות קוד בלחיצה של כפתור, פותחים חיבור למסד נתונים בטעינת הדף ועושים חישובים מתמטיים בתוך תא של GridView. יש דרך יותר טובה לכתוב קוד, שבה תוכלו לכתוב קוד ולתחזק אפליקציות כמו שאתם תמיד רציתם.

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

ב-OOP (באנגלית: Object Oriented Programming) יש לנו כלל אחד ויחיד שחוזר על עצמו בכמה צורות: תפתור בעיה פעם אחת ופעם אחת בלבד, אל תעתיק קוד ברחבי האפליקציה שלך, תשים כל אחריות במקום שלה. במהלך ההרצאה נתקוף את הסוגיה של פיתוח אפליקציות לאור הכלל הבא כתיבת קוד נכון, כתיבת קוד חזק, כתיבת קוד שעובד וקל לתחזוקה.

במהלך הקורס נעבור במסע של לימוד באמצעות דוגמאות קוד תוך כדי פיתוח אפליקציות אינטרנט, אפליקציות חלונאיות ו-WebServices. במהלך המסע נרכוש השכלה לדבר החשוב ביותר בתחום פיתוח התוכנה – למה אנחנו מפתחים אפליקציה בצורה שמעבירים בקורס, ונרכוש כלים להשתמש באותו היגיון של "למה ככה?" לעבודה היום-יומית שלנו.

 

Day One - Real World ASP.Net 2.0 Web Application

ASP.Net 2.0 Is an amazing tool and Visual Studio 20005 is a great work environment. We need more than that if we are to develop software. We need order, structure and good architecture. Come join us and gain Real-World expertise on how to develop an ASP.Net application.

  • ASP.Net & ASP.Net 2.0 Server Controls
  • Data Access Layer
  • Business logic Layer
  • 3Tier Project Development – Brining it all together
  • ORM (ActiveRecord & DLinq) – Making your life simpler
  • Implementing a database
  • Code Generation
  • Source Control & MSBuild

Day Two - Real World Smart Client Application

On day two we will learn how to build a Smart Windows Application that can work offline.
.NET Windows forms are meant to work in a connected fashion. Winforms should to be extensions of our main server & database. We can break the chains of connectivity. Join us and see how we develop such a Semi- connected client Real-World application!

  • Windows Forms
  • Web Services
  • Client side Data Access Layer to a Webservice
  • Server side Data Access Layer to a Sql Server 2005
  • 5Tier Client-Server Project Development
  • Caching
  • Refactoring
  • 3rd party productivity tools
  • Cool tools

MVP Week Courses in Israel - MUST BE THERE EVENT

In a couple of years people will ask "Were you in the Israeli MVP Week? because this MVP and that MVP gave really good courses!".

Talking about other MVPs (Microsoft Most Valuable Professionals), They are the cream of the crop of Microsoft Developers & IT Professionals.

Microsoft Israel has done something really great by organizing this week where they bring their top guys to the front & center. People, these presenters & courses are the best of the best. Most courses were specifically written for the Israeli MVP Week by MVPS!

The full list of MVPs & Courses can be found at the Israeli MVP Week Official Website.

 

There are developer courses I think that will be very interesting:

 

You should check of the Israeli MVP Week Official Website and see if anything interests you.

This is going to be one of those events that you will either say "I was there" or you will say "I should have been there".

Question from .Net Tapuz forum: Multi-Threading in Winforms applications

שאלה:

כרגע אני בונה אפליקציית WinForms (שקשורה לרשת) שמטבע הדברים חייבת לעבוד בצורה Multi-threaded (כדי לחסוך את הזמן של הResponse).
קודם יצא לי לעבוד עם Threading רק בסביבת console ובnet 1.1. כואשר ניסיתי ליישם את הטכניקות הישנות לאפליקציית WinForm ב net 2.0 נתקלתי בבעיה שלא ניתן (בדרכים המוכרות לי) להשפיע מתוך thread שונה על הForm. כיצד בכל זאת ניתן לעבוד עם Threadים באפליקציות חלונאיות?

 

תשובה:

יש לי מאמר מתבשל שיהיה מוכן מתישהו החודש בנובמבר על Multi-threading. כהרגלי, מאמר של ג'סטין.

לעבודה א-סינכרונית ב-Winforms מומלץ להשתמש ב-BackgroundWorker.
הקונספט הוא כזה, בכל עבודה א-סינכרונית באפליקציות חלונאיות תמיד יהיו שתי שלבים: תעשה משהו מאוד מאוד ארוך, ואז תשנה משהו קטן וזניח בטופס בעקבותו. הבעיה היא שכפי ששמת לב, לא ניתן לגשת מ-Thread שאינו ה-Thread הראשי לרכיבים הגרפיים של ה-GUI.

למשל אם אני ארצה לחשב את המספר ה-2 מיליון אחרי הנקודה של פאי ולהציג את הספרה הזו בפקד Label.
אם נעבוד בלי Multi-threading, למשל נכתוב את כל הקוד בלחיצה על כפתור ב-GUI, מה שנקבל זה שבזמן שהחישוב המאוד ארוך ומורכב הזה מתבצע הטופס הגרפי שלי יקפא ולא יגיב יותר למשתמש. לא ניתן יהיה להזיז אותו, הוא לא יצטייר מחדש יותר ושום דבר בעצם.

אז נחליט להוציא את כל העיבוד הזה ל-Thread נוסף ונגיד שגם נשלח ל-Thread הזה הפנייה ל-Label ככה שיוכל לשנות את הטקסט בסוף העיבוד הזה. ה-Thread הזה מקבל הקצאת זמן-מעבד שונה מה-Thread הראשי של האפליקציה שלנו ולכן האפליקציה שלנו כרגע רצה לכאורה על שני מעבדים מקביליים. מעבד אחד מריץ את ה-GUI ומעבד אחד מבצע עיבוד. ברגע שה-Thread השני יסיים, הוא יגש ל-Label לנסות לשנות את הטקסט לתוצאה המיוחלת ואז נקבל שגיאת זמן ריצה ששום Thread מלבד ה-Thread הראשי אינו רשאי לגרום לאירוע שמצייר מחדש את ה-GUI.

אז כאן אנחנו נכנסים לדילמה. ברור לנו שצריך לעבוד עם Threadים וברור לנו ש-Threadים לא עובדים איתנו.

במקרה של "נעבוד הרבה הרבה ואז נציג תוצאה" קיים רכיב מיוחד לאפליקציות חלונאיות שתומך בתהליך הזה.
הרכיב הוא BackgroundWorker.
את החישובים המורכבים (או כ-כלל העבודה שלוקחת הרבה זמן עד כדי כך שיש להפרידה ל-Thread נפרד) תשים במתודה כחלק מאירוע ה-DoWork של ה-BackgroundWorker. את התוצאה תכניס לתוך DoWorkEventArgs.Result בתוך המתודה שעושה DoWork.
בסיום תקבל את התוצאה במתודה שנרשמה לאירוע RunWorkerCompleted ושם תשנה את ה-GUI בהתאם.

למשל הדוגמה מ-MSDN:
BackgroundWorker Class

// in form consturctor
          backgroundWorker1.DoWork +=
                new DoWorkEventHandler(backgroundWorker1_DoWork);
            backgroundWorker1.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(
            backgroundWorker1_RunWorkerCompleted);

        // This event handler is where the actual,
        // potentially time-consuming work is done.
        private void backgroundWorker1_DoWork(object sender,
            DoWorkEventArgs e)
        {  
            // Get the BackgroundWorker that raised this event.
            BackgroundWorker worker = sender as BackgroundWorker;
            // Assign the result of the computation
            // to the Result property of the DoWorkEventArgs
            // object. This is will be available to the
            // RunWorkerCompleted eventhandler.
            e.Result = ComputeFibonacci((int)e.Argument, worker, e);
        }

        // This event handler deals with the results of the
        // background operation.
        private void backgroundWorker1_RunWorkerCompleted(
            object sender, RunWorkerCompletedEventArgs e)
        {
                resultLabel.Text = e.Result.ToString();
        }

 

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

More Posts Next page »