DCSIMG
July 2009 - Posts - שלמה גולדברג (הרב דוטנט)

שלמה גולדברג (הרב דוטנט)

מרצה בסלע ויועץ בעולם ה - net.

July 2009 - Posts

Adding control to DetailsView dynamically

 

בהמשך לפוסט הזה, (שהראה איך להוסיף Validator ל - DetailsView שנוצר בצורה דינמית) אני רוצה להדגים איך להוסיף פקדים בצורה דינמית.
 
לפני הכל, את המאמר הבא - ASP.NET Page Life Cycle Overview כל מפתח ב - ASP.NET צריך להכיר בעל פה.
 
 
נניח שבזמן Edit אנחנו רוצים להוסיף CheckBox לתא הראשון.
 
הקוד יראה כך:
 

protected void DetailsView1_ModeChanging(object sender, DetailsViewModeEventArgs e)

{

    DetailsView1.ChangeMode(e.NewMode);

    Bind();

 

    CheckBox chk = new CheckBox();

    chk.ID = "CheckBox1";

    DetailsView1.Rows[0].Cells[1].Controls.Add(chk);

}

 
 
אבל כשנלחץ על - Update לא נמצא את ה - CheckBox ב - Collection של ה - Controls. מהסיבה הפשוטה שהיות שזה נוצר בצורה דינמית אנחנו חייבים לייצר אותו כל פעם מחדש (עם אותו ID).
 
אבל היות שה - ViewState נטען אחרי ה - Init המקום המוקדם שבו נדע מה הסטטוס של ה - DetailsView הוא ב - PreLoad.
 

protected override void OnPreLoad(EventArgs e)

{

    base.OnPreLoad(e);

 

    if (DetailsView1.CurrentMode == DetailsViewMode.Edit)

    {

        CheckBox chk = new CheckBox();

        chk.ID = "CheckBox1";

        DetailsView1.Rows[0].Cells[1].Controls.Add(chk);

    }

}

 
 
ועכשיו באירוע של DetailsView1_ItemUpdating נוכל לשלוף מתוך ה - CheckBox את המידע שלנו.

Creating RequiredFieldValidator for DetailsView dynamically

 

אחד מהפקדים הנחמדים לעבוד עם מידע הוא ה - DetailsView, הוא יודע להציג מידע לאורך במקום לרוחב (כמו Grid) ומתאים מאוד לעידכון של שורה אחת.
 
בדרך כלל מה שאני עושה זה להציג Grid עם כל הנתונים וכשבוחרים שורה מתוך ה - Grid אני מציג את הנתונים של אותה שורה ב - DetailsView.
 
 
לאחרונה רציתי לעבוד איתו בצורה דינמית כלומר - לא להגדיר מראש ב - aspx את כל העמודות. ורציתי להוסיף לאחד העמודות RequiredFieldValidator. נשמע פשוט לא, אז נראה את הקוד.
 
בהתחלה אני מקשר טבלה (עם שורה אחת) ל DetailsView
 

protected void Page_Load(object sender, EventArgs e)

{

    if (!IsPostBack)

    {

        Bind();

    }

}

 

private void Bind()

{

    DataTable dt = GetTable();

 

    DetailsView1.DataSource = dt;

    DetailsView1.DataBind();

}

 

private DataTable GetTable()

{

    if (Session["table"] == null)

    {

        DataTable dt = new DataTable();

        dt.Columns.Add("Id");

        dt.Columns.Add("Name");

        dt.Columns.Add("Age");

        dt.Columns.Add("Salary");

 

        dt.Rows.Add(1, "Ron", 40, 5000);

 

        Session["table"] = dt;

    }

 

    return (DataTable)Session["table"];

}

 
 
ונרשמתי לאירוע של ModeChanging וכתבתי את הקוד הבא.
 

protected void DetailsView1_ModeChanging(object sender, DetailsViewModeEventArgs e)

{

    DetailsView1.ChangeMode(e.NewMode);

    Bind();

 

    TextBox txtId = DetailsView1.Rows[0].Cells[1].Controls[0] as TextBox;

 

    RequiredFieldValidator rfv = new RequiredFieldValidator();

    rfv.ControlToValidate = txtId.ID;

    rfv.ErrorMessage = "Required";

 

    DetailsView1.Rows[0].Cells[1].Controls.Add(rfv);

}

 
 
כשהרצתי את הקוד הזה קבלתי את התוצאה הבאה.
 
"The ControlToValidate property of '' cannot be blank."
 
כשהסתכלתי ב - debugger ראיתי שאמנם המאפיין ID של txtId הוא null, מה שמעניין שברגע שהסתכלתי ב - watch על המאפיין היה לו ערך. כלומר עם הייתי כותב ב - watch את הדבר הבא - txt.ID הייתי מקבל null אבל עם הייתי כותב txt ומתחיל ללחוץ על סימן ה + כדי לראות את המאפיינים הייתי רואה במאפיין ID ערך (וכמובן שמאותו רגע גם הפקודה txt.ID החזירה ערך. ובמילים אחרות ה - ID של הפקד מתרנדר רק כשמסתכלים ב - watch, (או כשהדף נטען) זה נקודה מאוד לא ברורה ואני אחקור אותה כדי להבין מה קורה כאן, מהסתכלות קטנה ב - reflector אני רואה את הקוד הבא
 

public virtual string ID

{

    get

    {

        if (!this.flags[1] && !this.flags[0x800])

        {

            return null;

        }

        return this._id;

    }

    set

    {

        // ...

    }

}

 
אני לא מבין כרגע למה אם המערך במקום 1 וגם במקום 2048 הוא false הם מחזירים null, וזה נקודה שאני אבדוק בהזדמנות אחרת.
 
הפיתרון שכרגע השתמשתי היה לכתוב את הדבר הבא.
 

protected void DetailsView1_ModeChanging(object sender, DetailsViewModeEventArgs e)

{

    DetailsView1.ChangeMode(e.NewMode);

    Bind();

 

    TextBox txtId = DetailsView1.Rows[0].Cells[1].Controls[0] as TextBox;

 

    RequiredFieldValidator rfv = new RequiredFieldValidator();

 

    string id = txtId.ClientID.Substring(txtId.ClientID.LastIndexOf('_') + 1);

    rfv.ControlToValidate = rfv.ControlToValidate = id;

    rfv.ErrorMessage = "Required";

 

    DetailsView1.Rows[0].Cells[1].Controls.Add(rfv);

}

 
מתברר שהמאפיין ClientID כן מאותחל מראש בערך שלו, אז אני פשוט מוציא ממנו את ה - ID שהפקד יקבל בזמן שהוא יתרנדר.
 
נקודה נוספת שגיליתי (ואני צריך להבין אותה) שחייבים להוסיף את ה - Validator ל - Collection הבא: DetailsView1.Rows[0].Cells[1].Controls
אבל עם ננסה להוסיף את ה - Validator ל - Collection של ה - Form. נקבל את השגיאה הבאה.
 
Control 'ctl01' referenced by the ControlToValidate property of '' cannot be validated.
 
 
אני מקווה שאני אצליח למצוא את הסיבות לשני הדברים.
1. מה המשמעות של הקוד במאפיין ID.
2. למה אי אפשר להוסיף את ה - Validator ל - Collection של ה - Form.
 
 
 
דרך אגב. בקוד של המאפיין ID היה כתוב 0x800, ואני הצגתי את הערך העשרוני שלו 2048. השתמשתי עם השירות של גוגל.
פשוט כתבתי בגוגל: 0x800 in decimal וזה החזיר תוצאה.
ממש נחמד לעבוד איתו כדי להמיר מספרים. כמו
10 in binary
 

CSS for Grid, buttons link and menu

 

מצאתי לא מזמן את שני הלינקים הבאים.
 
כאן יש רשימה של עיצובים ל - Grid
 
וכאן יש רשימה גדולה של עיצובים מוכנים ללחצנים לינקים ותפריטים.
 
תהנו
Posted: Jul 27 2009, 11:11 PM by Shlomo | with no comments
תגים:, , , ,

שיטוח של אובייקטים לקובץ - כתיבה לקובץ וקריאה מקובץ

 

הפעם אני רוצה לכתוב פוסט לטובת אלה שנכנסים לעולם ה - Net.
יצא לי בזמן האחרון כמה פעמים להדגים קריאה וכתיבה לקובץ (סירילזיציה ודיסיריליזציה), חשבתי שכדאי לכתוב פוסט על זה, כי מן הסתם יהיו עוד אנשים שלומדים ומחפשים הסבר על הנושא.
 
 
הרעיון בלקחת אובייקט ולשמור אותו בקובץ הוא כדי שנוכל לשמור על המצב של האפליקציה, נניח שיש לנו תוכנה לניהול עובדים, כשהתוכנה תיסגר כל הנתונים שיש לנו בזיכרון יעלמו, ולכן אנחנו רוצים לשמור את הנתונים שלנו בדיסק הקשיח, ולמעשה לשטח את האובייקטים שלנו מהזיכרון הנדיף לזיכרון קבוע - מה שנקרא Serialization.
 
 את דוגמת הקוד אפשר להוריד כאן.
 
אז איך מבצעים את זה,
 
הדרך המורכבת והמסובכת היא - לכתוב לבד את כל המאפיינים של האובייקט לקובץ, זה לא מומלץ כי נצטרך כל הזמן לתחזק את הקוד הזה, כלומר במידה ויש לנו מאפיין חדש נצטרך ללכת לשנות את הקוד של הכתיבה לקובץ.
בכל מקרה אני אדגים גם את השיטה הזאת, דרך נוספת היא להשתמש במנגנונים מובנים של Net, ונוכל לבחור מתוך כמה מימושים של Net. אני אדגים כאן כרגע שניים, הראשון יהיה XmlSerializer והשני יהיה BinaryFormatter. יש עוד כמה, על אחד נוסף (DataContractSerializer) תוכלו לקרוא כאן.
 
נניח שזה מבנה האובייקטים שלנו.
 

public abstract class Employee

{

    public int Id { get; set; }

    public string Name { get; set; }

}

 

public class Programer : Employee

{

    public bool KnowCSharp { get; set; }

}

 

public class Manager : Employee

{

    public int NumberOfProject { get; set; }

}

 
הנה ה - Main שלנו.
 

static void Main(string[] args)

{

    List<Employee> employees = new List<Employee>();

    employees.Add(new Programer() { Id = 1, Name = "Shlomo", KnowCSharp = true });

    employees.Add(new Programer() { Id = 2, Name = "Pini", KnowCSharp = true });

    employees.Add(new Manager() { Id = 1, Name = "Alon", NumberOfProject = 100 });

 

}

 
כעת אנחנו רוצים לשמור את ה - List שלנו בקובץ.
 
הדוגמא הראשונה ממומשת על ידי המתכנת בכתיבה עצמאית לקובץ. (אני לא מדגים כאן כתיבת OO נכונה - כי חלק מהפעולות היו צריכים להיות בתוך המחלקות)
 
כתיבה לקובץ דוגמא 1.
 

static void WriteToFileCustom(List<Employee> employees)

{

    using (StreamWriter writer = File.AppendText(@"C:\a.txt"))

    {

        foreach (var item in employees)

        {

            Programer programer = item as Programer;

            if (programer != null)

            {

                writer.WriteLine("Programer,{0},{1},{2}",

                    programer.Id, programer.Name, programer.KnowCSharp);

                continue;

            }

 

            Manager manager = item as Manager;

            if (manager != null)

            {

                writer.WriteLine("Manager,{0},{1},{2}",

                    manager.Id, manager.Name, manager.NumberOfProject);

                continue;

            }

        }

    }

}

 
 
 וכעת בקובץ יופיע לנו:
 
Programer,1,Shlomo,True
Programer,2,Pini,True
Manager,1,Alon,100
 
כעת נראה איך אפשר לשחזר את המידע מתוך הקובץ.
 

static List<Employee> ReadFromFileCustom()

{

    List<Employee> employees = new List<Employee>();

 

    string[] lines = File.ReadAllLines(@"C:\a.txt");

    foreach (var item in lines)

    {

        string[] props = item.Split(',');

        Employee employee = null;

 

        if (props[0] == "Programer")

        {

            Programer programer = new Programer();

            programer.KnowCSharp = bool.Parse(props[3]);

 

            employee = programer;

        }

        else

        {

            Manager manager = new Manager();

            manager.NumberOfProject = int.Parse(props[3]);

 

            employee = manager;

        }

 

        employee.Id = int.Parse(props[1]);

        employee.Name = props[2];

 

        employees.Add(employee);

    }

 

    return employees;

}

 

 
כמו שכתבתי זו אינה הדרך הממולצת, עדיף להשתמש עם מנגנון מובנה.
 
והנה קוד שמדגים שימוש ב - XmlSerializer.
 
כדי להשתמש בזה אנחנו צריכים לעמוד בשני תנאים.
1. שיהיה לנו בנאי ללא פרמטרים.
2. להוסיף מעל כל המחלקות שלנו את ה - Attribute של Serializable
3. להוסיף מעל האבא את כל הבנים שלו. שיכולים לעבור סירליזציה דרכו.
 
ולכן המחלקות שלנו יראו כך:
 

[Serializable]

[XmlInclude(typeof(Programer))]

[XmlInclude(typeof(Manager))]

public abstract class Employee

{

    public int Id { get; set; }

    public string Name { get; set; }

 

    public Employee()

    {

 

    }

}

 

[Serializable]

public class Programer : Employee

{

    public bool KnowCSharp { get; set; }

 

    public Programer()

    {

 

    }

}

 

[Serializable]

public class Manager : Employee

{

    public int NumberOfProject { get; set; }

 

    public Manager()

    {

 

    }

}

 
 
עכשיו נוסיף מתודה לשמירה לקובץ.
 

static void WriteToFileXml(List<Employee> employees)

{

    XmlSerializer serializer = new XmlSerializer(typeof(List<Employee>));

    using (StreamWriter writer = File.AppendText(@"C:\a.xml"))

    {

        serializer.Serialize(writer, employees);

    }

}

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

<?xml version="1.0" encoding="utf-8"?>

<ArrayOfEmployee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <Employee xsi:type="Programer">

    <Id>1</Id>

    <Name>Shlomo</Name>

    <KnowCSharp>true</KnowCSharp>

  </Employee>

  <Employee xsi:type="Programer">

    <Id>2</Id>

    <Name>Pini</Name>

    <KnowCSharp>true</KnowCSharp>

  </Employee>

  <Employee xsi:type="Manager">

    <Id>1</Id>

    <Name>Alon</Name>

    <NumberOfProject>100</NumberOfProject>

  </Employee>

</ArrayOfEmployee>

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

static List<Employee> ReadFromFileXml()

{

    XmlSerializer serializer = new XmlSerializer(typeof(List<Employee>));

    using (StreamReader reader = new StreamReader(@"C:\a.xml"))

    {

        return (List<Employee>)serializer.Deserialize(reader);

    }

 

}

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

static void WriteToFileBinary(List<Employee> employees)

{

    BinaryFormatter binary = new BinaryFormatter();

    using (Stream writer = new FileStream(@"C:\c.txt", FileMode.Create))

    {

        binary.Serialize(writer, employees);

    }

}

 
 
אני לא יכול להעתיק לכאן את מה שנוצר היות שזה מידע בינארי.
 
הנה הקוד לשיחזור.
 

static List<Employee> ReadFromFileBinary()

{

    BinaryFormatter binary = new BinaryFormatter();

    using (Stream reader = new FileStream(@"C:\c.txt", FileMode.Open))

    {

        return (List<Employee>)binary.Deserialize(reader);

    }

}

 
 

שערי מטבע מבנק ישראל ב - C#

 

ראשית כל אני רוצה להדגיש, שהדוגמא להלן מייבאת את שערי המטבעות מהאתר של בנק ישראל וכדי להציג את התוכן באתר שלכם, אתם צריכים לקבל מהם אישור, כאן יש כתובת מייל (בתחתית העמוד) שניתן לבקש אישורים להצגת התוכן באתר שלכם.
 
 
לעניינינו. לבנק ישראל יש שירות של שערי המטבעות בקבצי xml.
כתבתי class שיודע להחזיק את התוכן של אותו קובץ xml. את ה - class המלא תוכלו להוריד מכאן. בפוסט הזה אני רוצה להראות את המתודות Fill (שמביאות את התוכן מבנק ישראל)
 
 
יש לנו enum בשם CurrencyCode
 

    public enum CurrencyCode

    {

        All = 0,

        SAL = 77,

        USD = 01,

        GBP = 02,

        JPY = 31,

        EUR = 27,

        AUD = 18,

        CAD = 06,

        DKK = 12,

        NOK = 28,

        ZAR = 17,

        SEK = 3,

        CHF = 5,

        JOD = 69,

        LBP = 70,

        EGP = 79

    }

 
הנה ה - class:
 

    public class ExchangeRating

    {

        public string Name { get; set; }

        public int Unit { get; set; }

        public CurrencyCode CurrencyCode { get; set; }

        public string Country { get; set; }

        public double Change { get; set; }

        public double Rate { get; set; }

    }

 
והנה מתודת ה - Fill (ההסברים למטה)
 

public void Fill()

{

    Rates.Clear();

 

 

    WebClient webClient = new WebClient();

 

    string url = "";

 

    if (Code.Contains(CurrencyCode.All))

    {

        url = string.Format("http://www.bankisrael.gov.il/heb.shearim/currency.php?rdate={0}{1}{2}",

            LastUpdate.Year,

            LastUpdate.Month.ToString().Length == 2 ?

                LastUpdate.Month.ToString() :

                "0" + LastUpdate.Month.ToString(),

            LastUpdate.Day.ToString().Length == 2 ?

                LastUpdate.Day.ToString() :

                "0" + LastUpdate.Day.ToString());

 

        InternalFill(webClient, url);

 

    }

    else

    {

        foreach (var item in Code)

        {

            url = string.Format("http://www.bankisrael.gov.il/heb.shearim/currency.php?rdate={0}{1}{2}&curr={3}",

                            LastUpdate.Year,

                            LastUpdate.Month.ToString().Length == 2 ?

                                LastUpdate.Month.ToString() :

                                "0" + LastUpdate.Month.ToString(),

                            LastUpdate.Day.ToString().Length == 2 ?

                                LastUpdate.Day.ToString() :

                                "0" + LastUpdate.Day.ToString(),

                            ((int)item).ToString().Length == 2 ?

                                ((int)item).ToString() :

                                "0" + ((int)item).ToString());

 

            InternalFill(webClient, url);

        }

    }

 

}

 

private void InternalFill(WebClient webClient, string url)

{

    string res = webClient.DownloadString(url);

 

    XmlDocument doc = new XmlDocument();

    doc.LoadXml(res);

 

    foreach (XmlNode item in doc["CURRENCIES"].GetElementsByTagName("CURRENCY"))

    {

        Rates.Add(new ExchangeRating()

        {

            Change = double.Parse(item["CHANGE"].InnerText),

            Country = item["COUNTRY"].InnerText,

            CurrencyCode = (CurrencyCode)Enum.Parse(typeof(CurrencyCode),

                            item["CURRENCYCODE"].InnerText),

            Name = item["NAME"].InnerText,

            Unit = int.Parse(item["UNIT"].InnerText),

            Rate = double.Parse(item["RATE"].InnerText)

        });

    }

 

    if (Rates.Count == 0)

    {

        StringBuilder builder = new StringBuilder();

        foreach (XmlNode item in doc["CURRENCIES"])

        {

            if (item.Name.Contains("ERROR"))

            {

                builder.Append(item.InnerText);

                builder.Append(Environment.NewLine);

            }

        }

        if (builder.Length > 0)

        {

            throw new Exception(builder.ToString());

        }

    }

}

 
מתודת ה - Fill בודקת האם רוצים להביא את כל המטבעות או רק חלק מהם.
במידה ורוצים להביא את כל המטבעות, צריך לפנות את האתר רק עם התאריך (והוא צריך להיות בפורמט YYYYMMDD - ולכן יש שם קוד טיפה ארוך כדי להחזיק את הפורמט המתאים)
במידה ורוצים רק חלק מהמטבעות, רצים בלולאה על כל המטבעות הרצויים, ומפעילים את internalFill.
 
ב - InternalFill  פונים לאתר דרך WebClient ומורידים את קובץ ה - xml, טוענים אותו ל XmlDocument ומתחילים למלא את האובייקטים מקובץ ה - xml.
 
במידה ואין שום data, בודקים האם ב - xml מופיע שגיאות, במידה וכן זורקים exception עם השגיאות.
 

Addins for Internet Explorer Context Menu

 

כבר יש לי כמה תוספות נחמדות עבור IE שמופעלות באמצעות ה - Context Menu - חשבתי לכתוב פוסט אחד שירכז את כולם ובכל פעם שאכתוב AddIn נוסף אני אעדכן את הפוסט הזה.
 
  • אפליקציה לניהול ה - Context Menu ניתן להוריד כאן, ולקרוא על זה כאן.
     
  • ביטול בדיקת ה - CAPTCHA במערכת הבלוגים של מייקרוסופט. את הסקריפט ניתן להוריד כאן, ולקרוא על זה כאן.
     
  • כתיבה מימין לשמאל או משמאל לימין במערכת הבלוגים של מייקרוסופט. את הסקריפטים ניתן להוריד כאן, ולקרוא על זה כאן.
     
  • כניסה אוטומטית ל - Microsoft Outlook Web Access. את הסקריפט ניתן להוריד כאן, ולקרוא על זה כאן.
     
  • פתיחת Notepad ושליחת הטקסט המסומן. את התוכנה עם הסקריפט ניתן להוריד כאן, ולקרוא על זה כאן.
     
     
     
בעזרת השם כל AddIn נוסף יעודכן כאן (רעיונות יתקבלו בברכה)

Export selected text from IE to notepad using Context Menu

 

מי שזוכר, כתבתי אפליקציה נחמדה להוספת פעולות ל - Context Menu של IE.
 
מישהו בשם חיים כתב לי את ההערה הבאה:
 

לא ממש הבנתי איך להשתמש בה.

אני מעוניין לפתוח notepad עם אותו קטע טקסט שבחרתי.

איך עושים את זה?

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

class Program

{

    [DllImport("user32.dll", EntryPoint = "FindWindowEx")]

    public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,

                                            string lpszClass, string lpszWindow);

 

    [DllImport("User32.dll")]

    public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);

 

    static void Main(string[] args)

    {

        if (args.Length == 1)

        {

            Process notepad = Process.Start("Notepad.exe");

            Thread.Sleep(1000);

 

            IntPtr child = FindWindowEx(notepad.MainWindowHandle, new IntPtr(0), "Edit", null);

            SendMessage(child, 0x000C, 0, args[0]);

        }

    }

}

 
במידה ומפעילים את האפליקציה וישלח ערך כלשהו כארגומנט אזי Notepad יופעל והטקסט ישלח פנימה.
 
כעת כתבתי את קובץ ה - JS הבא:
 

<SCRIPT LANGUAGE = "JavaScript">

 

    var obShell = new ActiveXObject("Shell.Application");

 

    var exe = "C:\\InternetNotepad\\bin\\Debug\\InternetNotepad.exe";

 

    var oWindow = window.external.menuArguments;

    var oDocument = oWindow.document;

    var oSelect = oDocument.selection;

    var oSelectRange = oSelect.createRange();

    var text = '"' + oSelectRange.text + '"';

 

    obShell.ShellExecute(exe, text, "", "open", 1);

</SCRIPT>

 
 
מה שזה עושה - מוצא את הטקסט הנבחר מוסיף לו גרשיים משני הצדדים ומפעיל את האפליקציה שמקודם כתבנו עם הפרמטרים (במידה ובטקסט יהיה גרשיים אני מניח שזה לא יעבוד - ואני מתעצל בשעה כזאת לכתוב את הסקריפט בצורה יותר טובה, שחיים יעשה את זה)
 
כעת מה שנשאר זה רק להפעיל את התוכנה לעריכת ה - Context Menu ולייצר פריט חדש בשם Export to Notepad לסמן שרק בזמן Text Selection לתת את קובץ ה - html (לא לשכוח לשנות את הנתיב של קובץ ההפעלה) וזהו.
 
 
 
שלב ראשון: יצירת הפריט החדש:
 
Export 1
 
שלב שני: בחירת טקסט ולחיצה על - Export to Notepad.
 
Export2
 
שלב שלישי: Notepad מופעל והטקסט שבחרנו מועתק לשם.
 
Export 3

הודעות שגיאה אנושיות - בבקשה

 

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

New Resource Editor - development time as well as after deployment.

 

דרך נפוצה מאוד לשמור הודעות/תמונות וכד' היא ב - Resource file. הסבר על הנושא.
 
הבעייה הגדולה שלהם שמי שיכול לטפל ב - Resources זה רק המפתחים (או למי שיש Visual Studio) מה שגורם לכך שכשהבודק מוצא הודעת שיש בה שגיאה הוא צריך לעבור את כל התהליך של באג עד שימצא המפתח שיואיל בטובו לתקן את הודעת השגיאה, ואותו דבר יקרה עם החברה תביא אדם מיוחד לכתיבת ההודעות.
 
גרוע מכך - נניח שיצרנו גרסה ראשונית של המוצר - מנהלי המוצר טסים לחו"ל כדי להתקין שם את הגרסה - הכל בסדר עד שאחד מקוני המוצר אומר "בסוף המשפט צריך לכתוב נקודה" או משהו בסגנון. היות שהכול נכתב ב - Embedded Resource צריך לייצר גרסה חדשה (וזה אחד הסיבות שהרבה נוהגים לשמור את ה - resource ב - DB או בקבצי XML אפילו ש - Embedded הוא הכי יעיל).
 
עבור כל הבעיות הללו כתבתי Resource Editor, אפשר להוריד אותו מכאן.
 
ה - Resource Editor נותן יותר פונקציונליות מה - Resource Editor של VS. וכמובן נותן את היכולת לשנות Resources אחרי קומפיליציה.
 
בשלב זה אי אפשר להוסיף קבצי Resource חדשים רק לערוך תוכן של קבצי Resource קיימים (אני אוסיף את התכונה הזאת בעתיד)
 
המסך הראשי:
 
Main
 
כמו שאפשר לראות זה מחולק לשלושה חלקים.
 
החלק העליון מתייחס לכל ה - dll/exe
 
החלק האמצעי מתייחס ל - Resources שנמצאים בו.
 
והחלק התחתון מתייחס לכל Resource file בנפרד (כשלכל אחד יש כמה סוגי resources - כמו ב - VS)
 
 
כשנלחץ על Open נוכל לבחור את הקובץ שלנו שיש בו Resources - לדוגמא:
 
String Editor
 
בכל אחד מהטאבים נראה את ה - Resources  המתאימים ובכל טאב יש את הפונקציונליות המתאימה.
 
לחצן ה - Save הראשון שומר את כל השינויים בתוך ה - dll
 
לחצן ה - Save השני יוצר קובץ resx עבור המפתחים (כך שכולם יהיו מסונכרנים).
 
הלחצן הבא הוא - Show Changes - מראה (עבור String, Bitmap and Icons) את השינויים שנעשו, ועבור String אפשר לעשות undo.
 
הלחצן האחרון - Find יודע לחפש keys או values (עבור Strings).
 
 
 
מי שישתמש באפליקציה צריך לזכור שלמעשה לחצן ה - Save עושה קומפילציה מחדש.

UnmanagedMemoryStream and PinnedBufferMemoryStream

 

יש class מעניין בשם UnmanagedMemoryStream, התפקיד שלו בחיים זה לספק גישה לבלוקים בזיכרון קוד שאינו מנוהל. בדוגמא ב - MSDN יש את דוגמת הקוד הבאה: (שם זה מופיע עם הערות)
 

unsafe

{

    byte[] message = UnicodeEncoding.Unicode.GetBytes("Here is some data.");

    IntPtr memIntPtr = Marshal.AllocHGlobal(message.Length);

    byte* memBytePtr = (byte*)memIntPtr.ToPointer();

 

    UnmanagedMemoryStream writeStream = new UnmanagedMemoryStream(memBytePtr, message.Length,

                                                                    message.Length, FileAccess.Write);

    writeStream.Write(message, 0, message.Length);

    writeStream.Close();

 

    UnmanagedMemoryStream readStream = new UnmanagedMemoryStream(memBytePtr, message.Length,

                                                                message.Length, FileAccess.Read);

    byte[] outMessage = new byte[message.Length];

    readStream.Read(outMessage, 0, message.Length);

    readStream.Close();

 

    Console.WriteLine(UnicodeEncoding.Unicode.GetString(outMessage));

    Marshal.FreeHGlobal(memIntPtr);

}

 
בגדול מה שזה עושה, מייצר מערך של בתים שמייצג את המחרוזת.
הגדרת מצביע למקום בזיכרון בגודל המערך.
יצירת מצביע למקום הזה.
יצירת אובייקט מסוג UnmanagedMemoryStream שמקבל את המצביע.
כתיבה לתוך ה - Stream
סגירת ה - Stream.
יצירת Stream חדש לקריאה.
קריאה של ה - Stream והדפסה.
שיחרור המצביע.
 
פשוט וקל.
 
איפה הבעייה ? שימו לב לקוד הבא:
 

static void Main(string[] args)

{

    UnmanagedMemoryStream stream = WriteInStream();

    ReadFromStream(stream);

}

 

static UnmanagedMemoryStream WriteInStream()

{

    unsafe

    {

        byte[] message = UnicodeEncoding.Unicode.GetBytes("Here is some data.");

        IntPtr memIntPtr = Marshal.AllocHGlobal(message.Length);

        byte* memBytePtr = (byte*)memIntPtr.ToPointer();

 

        UnmanagedMemoryStream writeStream = new UnmanagedMemoryStream(memBytePtr, message.Length,

                                                                        message.Length, FileAccess.Write);

        writeStream.Write(message, 0, message.Length);

 

        Marshal.FreeHGlobal(memIntPtr);

        return writeStream;

    }

}

 

static void ReadFromStream(UnmanagedMemoryStream stream)

{

    unsafe

    {

        IntPtr memIntPtr = Marshal.AllocHGlobal((int)stream.Length);

        byte* memBytePtr = (byte*)memIntPtr.ToPointer();

 

        UnmanagedMemoryStream readStream = new UnmanagedMemoryStream(memBytePtr, stream.Length,

                                                                        stream.Length, FileAccess.Read);

        byte[] outMessage = new byte[stream.Length];

        readStream.Read(outMessage, 0, (int)stream.Length);

        readStream.Close();

 

        Console.WriteLine(UnicodeEncoding.Unicode.GetString(outMessage));

        Marshal.FreeHGlobal(memIntPtr);

    }

}

 

 
הקוד הוא אותו דבר, חוץ מן העובדה שחילקתי את העבודה לשני פונקציות שונות והחזקתי מצביע לאותו אובייקט מסוג UnmanagedMemoryStream.
 
מסתבר שכשנריץ את הקוד נקבל כל פעם תוצאות שונות (בדוגמה הספציפית התוצאה תהיה כמעט זהה אבל לא לגמרי) אבל אם ה - stream יהיה מספיק גדול נניח קובץ תמונה) ה - Stream יתאפס ולא נוכל לשחזר את התמונה מהזיכרון.
 
אני מניח שזה קורה בגלל ה - GC אני לא סגור בדיוק על הסיבות אבל זה נראה לי כמו הזזות של הזיכרון.
 
אז איך ניתן לפתור את זה ? מסתבר שיש class שנקרא PinnedBufferMemoryStream שיורש מ - UnmanagedMemoryStream ותפקידו בחיים זה לנעוץ את הזיכרון כך שאף אחד לא יוכל להזיז אותו.
אבל איכשהו החליטו להגדיר אותו כ - internal כך שאי אפשר לייצר ממנו מופע.
דרך אחת לפתור את הבעייה זה להסתכל ב - reflector ולהעתיק את הקוד (לא כזה מסובך) הבעייה עם הדרך הזאת זה כשאנחנו מקבלים אובייקטים מהסוג הזה מה - framework (לדוגמא בעבודה עם resources) ואז לא נוכל להחזיק אותם עם מצביע מהאובייקט שלנו, ולכן כתבתי את הפונקצייה הבאה.
 

private UnmanagedMemoryStream GetPinnedBufferMemoryStream(byte[] buffer)

{

    Module module = typeof(UnmanagedMemoryStream).Module;

    Type pinnedBufferMemoryStreamType = module.GetTypes().

            Single(item => item.Name == "PinnedBufferMemoryStream");

 

    ConstructorInfo ctor = pinnedBufferMemoryStreamType.

                        GetConstructor(

                            BindingFlags.NonPublic | BindingFlags.Instance,

                            null,

                            new Type[] { typeof(byte[]) },

                            null);

 

    UnmanagedMemoryStream pinnedBufferMemoryStream =

        (UnmanagedMemoryStream)ctor.Invoke(new object[] { buffer });

 

    return pinnedBufferMemoryStream;

}

 
זה יודע להחזיר לנו מופע של האובייקט PinnedBufferMemoryStream באמצעות reflection כשהמצביע הוא מסוג UnmanagedMemoryStream (שהוא האבא שלו) וזה פותר לנו את כל הבעיות

C# 4.0 - Table Of Content

 

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

תוכן העניינים:

 
C# 4.0 Part 1 - First glance on the dynamic - בפרק זה נכיר לראשונה את המושג dynamic ונראה כמה דוגמאות פשוטות ושימושיות.
 
C# 4.0 Part 2 - The GetCalculator program - פרק זה ידגים גישה על ידי dynamic למחשבון שנכתב ב - net ומחשבון שנכתב ב - JS עטוף באובייקט COM.
 
C# 4.0 Part 3 - Behind The Scenes Part 1 - נבין מה קורה מאחורי הקלעים כשעושים השמה לתוך dynamic, והאם קיים באמת טיפוס dynamic.
 
C# 4.0 Part 4 - Understanding Expression Tree - כדי שנוכל להבין מה קורה כשעושים פעולות על dynamic צריך הבנה בסיסית ב - Expression Tree.
 
C# 4.0 Part 5 - Behind The Scenes Part 2 - נבין מה קורה מאחורי הקלעים כשעושים פעולות על dynamic.
 
C# 4.0 Part 6 - DynamicObject class and IDynamicMetaObjectProvider interface - איך משנים את ההתנהגות של dynamic על אובייקטים ספציפיים.
 
C# 4.0 Part 7 - dynamic Limitations - מה אי אפשר לעשות בעזרת dynamic.
 
C# 4.0 Part 8 - First glance on the Optional Parameters - מה ואיך אפשר להשתמש בפרמטרי רשות.
 
C# 4.0 Part 9 - Spell Checking with word using Optional Parameters - הדגמה של בדיקת איות באמצעות word בעזרת פרמטרי רשות.
 
C# 4.0 Part 10 - Optional Parameters - Behind The Scenes - מה קורה מאחורי הקלעים כשמשתמשים עם פרמטרי רשות, ואיך אפשר "לעבוד" על המערכת.
 
C# 4.0 Part 11 - Named Parameters - שליחת פרמטרים לפי השם שלהם ולא לפי המיקום.
 
C# 4.0 Part 12 - Covariance and Contravariance - נכיר את המושגים החדשים/ישנים הללו, ונבין איך נוכל לכתוב קוד יותר יעיל בעזרתם.
 
 
 
 
 
 
 
 
 

Differece between DataRow.Delete and DataTable.Rows.Remove

 

שימו לב לאיך המתודה Remove ממומשת:
 

    public void Remove(DataRow row)

    {

        if (((row == null) || (row.Table != this.table)) || (-1L == row.rowID))

        {

            throw ExceptionBuilder.RowOutOfRange();

        }

        if ((row.RowState != DataRowState.Deleted) && (row.RowState != DataRowState.Detached))

        {

            row.Delete();

        }

        if (row.RowState != DataRowState.Detached)

        {

            row.AcceptChanges();

        }

    }

 
 
המתודה קוראת ל - row.Delete ולאחר מכן מפעילה את AcceptChanges מה שאומר שהשורה תמחק לצמיתות מהטבלה.
 
כדי למנוע את החוכמה של המפתח שכתב את המתודה הנ"ל, צריך להפעיל את מתודת Delete במקום את מתודת Remove
Posted: Jul 02 2009, 12:51 PM by Shlomo | with 1 comment(s)
תגים:, ,

Get Image's type using RawFormat property

 

למי שיוצא לו לעבוד עם אובייקט מסוג Image יודע שלפעמים אנחנו רוצים להציג את סוג התמונה, לכאורה מה הבעייה, יש מאפיין בשם RawFormat שה - ToString שלו אמור להחזיר את סוג התמונה.
 
מסתבר שזה לא עובד, כי האובייקט עושה override ל - Equal כדי לדעת מה סוג התמונה, אבל במתודה ToString השתמשו ב - == כדי לבדוק, הנה הקוד של Equal.
 

    public override bool Equals(object o)

    {

        ImageFormat format = o as ImageFormat;

        if (format == null)

        {

            return false;

        }

        return (this.guid == format.guid);

    }

 
והנה הקוד של ToString:
 

    public override string ToString()

    {

        if (this == memoryBMP)

        {

            return "MemoryBMP";

        }

        if (this == bmp)

        {

            return "Bmp";

        }

        if (this == emf)

        {

            return "Emf";

        }

        if (this == wmf)

        {

            return "Wmf";

        }

        if (this == gif)

        {

            return "Gif";

        }

        if (this == jpeg)

        {

            return "Jpeg";

        }

        if (this == png)

        {

            return "Png";

        }

        if (this == tiff)

        {

            return "Tiff";

        }

        if (this == exif)

        {

            return "Exif";

        }

        if (this == icon)

        {

            return "Icon";

        }

        return ("[ImageFormat: " + this.guid + "]");

    }

 
 
כדי לפתור את הבעייה כתבתי את ה -  Extension Method הבא:
 

    public static string ImageType(this Image image)

    {

        if(image.RawFormat.Equals(ImageFormat.Bmp))

        {

            return "Bmp";

        }

        else if (image.RawFormat.Equals(ImageFormat.MemoryBmp))

        {

            return "MemoryBMP";

        }

        else if(image.RawFormat.Equals(ImageFormat.Wmf))

        {

            return "Emf";

        }

        else if(image.RawFormat.Equals(ImageFormat.Wmf))

        {

            return "Wmf";

        }

        else if(image.RawFormat.Equals(ImageFormat.Gif))

        {

            return "Gif";

        }

        else if (image.RawFormat.Equals(ImageFormat.Jpeg))

        {

            return "Jpeg";

        }

        else if (image.RawFormat.Equals(ImageFormat.Png))

        {

            return "Png";

        }

        else if (image.RawFormat.Equals(ImageFormat.Tiff))

        {

            return "Tiff";

        }

        else if (image.RawFormat.Equals(ImageFormat.Exif))

        {

            return "Exif";

        }

        else if (image.RawFormat.Equals(ImageFormat.Icon))

        {

            return "Icon";

        }

 

        return "";

    }

Posted: Jul 02 2009, 10:46 AM by Shlomo | with 1 comment(s)
תגים:,

C# 4.0 Part 12 - Covariance and Contravariance

 

המושג הזה הוא קצת וירטואלי למי שלא מכיר אותו, אז כדאי שנעבור על המושג לפני שנראה מה התחדש ב - C# 4.0.
 
 
ב - C# 2.0 יצא מושג חדש Covariance and Contravariance in Delegates 
 
Covariance אומר את הדבר הבא: אפשר להגדיר delegate שמחזיר אובייקט ולתת מתודה שבפועל מחזירה מישהו שיורש ממנו, כלומר:
 

    delegate object CovarianceDelegate();

 

    static void Main()

    {

        CovarianceDelegate cd = CovarianceMethod;

    }

 

    static Person CovarianceMethod()

    {

 

    }

 
וזה הגיוני כי זה עומד בחוקי ה - Object Oriented (שאפשר לשלוח בן לאבא).
 
 
Contravariance אומר את הדבר הבא: אפשר להגדיר delegate שמקבל אובייקט ולתת מתודה שבפועל מקבלת את האבא, כלומר:
 

    delegate void ContravarianceDelegate(Person obj);

 

    static void Main()

    {

        ContravarianceDelegate cva = ContravarianceMethod;

    }

 

    static void ContravarianceMethod(object obj)

    {

    }

 
 
וכמו שאמרתי זה היה אחד מהחידושים ב - C# 2.0, היום ב - (C# 4.0) הרחיבו את המושג Covariance and Contravariance גם עבור Generic Interface ו - Generic Delegate. (מומלץ לקרוא את ההסבר ב - MSDN).
 
למעשה Generic Interface או Generic Delegate נקראים בשם Variant אם ה - Generic Parameter (כלומר ה - T) מוגדר כ - Covariance או כ - Contravariance.
 
 
דוגמא ל - Generic Interface שה - T שלו מוגדר כ - Covariance הוא IEnumerable. הקוד הבא חוקי רק ב - C# 4.0
 

    List<string> strings = new List<string>();

    IEnumerable<object> objects = strings;

 
למעשה ה - T של IEnumerable מוגדר שהוא יכול להחזיר כל מי שיורש מ - T ולכן השורה השנייה הינה חוקית, שימו לב איך הוגדר ה - Interface.
 

   public interface IEnumerable<out T> : IEnumerable

   {

       IEnumerator<T> GetEnumerator();

   }

 
ה - out ל - T מגדיר שכל מה שיורש מ - T יכול לחזור בפונקציה GetEnumerator (וזה לא פשוט, מכיון ש - IEnumerable של string לא יורש מ - IEnumerable של object - אבל ה - out מגדיר שמסתכלים על ה - T והיות ש - string יורש מ - object אפשר לעשות את זה).
 
 
דומגא ל Interface שהוגדר כ - Contravariance הוא ICompare. הקוד הבא הינו חוקי רק ב - C# 4.0
 

    public class Class1

    {

        static void Main()

        {

            IComparer<Employee> abc = new Person();

        }

    }

    class Employee : Person

    {

    }

 

    class Person : IComparer<Person>

    {

    }

 
 
זאת אומרת שאני יכול לשלוח לאובייקט מסוג IComparer של Employee אובייקט שמממש את IComparer של Person היות ש - Person הוא האבא של Employee.
(זה הגיוני, כי אם Employee יורש מ - Person, אז ברור שמתודה שיודעת להשוות בין שני Person תדע גם להשוות בין שני Employee)
 
ה - Interface מוגדר כך:
 

    public interface IComparer<in T>

    {

        int Compare(T x, T y);

    }

 
ברגע שה - T מוגדר כ - in אפשר לשלוח פנימה כל מי ש - T יורש ממנו.
 
ב - MSDN יש כמה דוגמאות שימושיות לזה. כאן וכאן והסבר מקיף נרחב נמצא כאן
Posted: Jul 01 2009, 09:57 AM by Shlomo | with no comments |
תגים:, , ,