February 2010 - Posts
נניח שרוצים לקשר בין TreeView לבין קובץ xml
ונרצה להציג אותו ב - Tree
כשנכתוב קוד כזה
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="XmlDataSource1">
</asp:TreeView>
<asp:XmlDataSource ID="XmlDataSource1" runat="server"
DataFile="~/currency.xml"></asp:XmlDataSource>
נקבל תוצאה כזאת
כלומר נקבל את המבנה ולא את המידע בעצמו.
כדי לפתור את זה נצטרך להוסיף Binding ל - TreeView
הכי קל זה בעזרת ה - Designer
נוסיף את המאפיינים שאנחנו רוצים לראות ונבחר ב - TextField את הערך המתאים.
בסופו של דבר נקבל
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="XmlDataSource1">
<DataBindings>
<asp:TreeNodeBinding DataMember="NAME" TextField="#InnerText" />
<asp:TreeNodeBinding DataMember="UNIT" TextField="#InnerText" />
<asp:TreeNodeBinding DataMember="CURRENCYCODE" TextField="#InnerText" />
<asp:TreeNodeBinding DataMember="COUNTRY" TextField="#InnerText" />
<asp:TreeNodeBinding DataMember="RATE" TextField="#InnerText" />
<asp:TreeNodeBinding DataMember="CHANGE" TextField="#InnerText" />
</DataBindings>
</asp:TreeView>
מה שיחזיר
לאחרונה בשיטוטים שלי מצאתי dll שיש לו יכולות מדהימות של השמעת טקסט (והקלטה)
צריך להוסיף reference ל - System.Speech
שימו לב לקוד הבא:
string textToSpeak = Console.ReadLine();
SpeechSynthesizer ss = new SpeechSynthesizer();
ss.Speak(textToSpeak);
ומהרמקולים של המחשב נשמע את הטקסט שהמשתמש הקליד.
למחלקה הזאת יש המון יכולות.
החל משליטה על העוצמה והמהירות, האפשרות להחליט להיכן ינותב הפלט (האם לרמקול או לקובץ wav)
אפשר לקבל events מה הסטטוס של השמעת הטקסט, בעזרת רישום לאירוע SpeakProgress (ויש עוד כמה אירועים מעניינים)
אפשר לבנות מחרוזת להשמעה עם סגנונות (כלומר חלק מהטקסט יותר גבוה חלק מהר יותר וכדו') בעזרת מחלקה שנקראת PromtBuilder
אפשר להפעיל את ההשמעה בצורה אסינכרונית בעזרת המתודה SpeakAsync (וכמובן שאפשר לבטל אותה באמצע)
בקיצור - dll עם יכולות מאוד מעניינות - ותשחקו איתו ותהנו.
כל Console Application שאנחנו כותבים נראה כך:
static void Main(string[] args)
{
}
הפוסט הזה ידבר על "מה זה ה - args שאנחנו רואים ואיך שולחים פרמטרים לשם ואיך ניתן לגשת לפרמטרים ממתודות אחרות (בלי לשלוח את ה - aegs כפרמטר)."
הרבה תוכניות מסוג Console כשהן עולות צריכות לקבל מהמשתמש ארגומנטים כדי לדעת מה לעשות (בדרך כלל שם הפקודה בתוספת ? / תציג את כל האפשרויות. לדוגמא - אני מניח שכולם מכירים את הפקודה dir שמציגה רשימת קבצים ותיקיות - אם נכתוב
dir /?
נקבל את כל האפשרויות להפעלה - לדוגמא
dir /a r
תציג רק את הקבצים שהם ReadOnly.
אז איך שולחים פרמטרים ?
בחיים האמיתיים (כלומר כהלקוח מריץ את התוכנית) לאחר השם של התוכנית - כל מה שנכתוב יכנס כפרמטר למערך לפי התו רווח, לדוגמא:
myApp.exe abc 123 "abc 123"
נקבל מערך בעל שלושה כניסות
abc
123
abc 123
בזמן פיתוח כדי לשלוח ערכים נוכל במאפיינים של הפרוייקט תחת הטאב של Debug להכניס בתיבת הטקסט Command Line Args את הערכים.
כדי להגיע בכל מקום לפרמטרים - נוכל בעזרת
שיחזיר מחרוזת שתראה כך
myApp.exe abc 123 "abc 123"
או בעזרת
string[] arr = Environment.GetCommandLineArgs()
שיחזיר מערך בעל 4 כניסות
MyApp.exe
abc
123
abc 123
דימה הראה לי טריק מגניב של Windows 7 שלא הכרתי.
לחיצה עם הלחצן האמצעי בעכבר על אחד מהחלונות ב - TaskBar ישכפל את החלון.
לדוגמא - אם אני רוצה לפתוח Internet Explorer חדש - הדרך הכי מהירה היא ללחוץ עם הלחצן האמצעי של העכבר על האיקון של IE.
תנסו ותהנו.
אני מניח שיצא לכם לכתבו קוד כזה
<asp:DropDownList ID="ddl" runat="server">
<asp:ListItem Text="Select Item"></asp:ListItem>
</asp:DropDownList>
ואם רציתם לקשר רשימה כלשהי בצד השרת
IEnumerable<int> list = Enumerable.Range(0, 10);
ddl.DataSource = list;
ddl.DataBind();
הבעייה בקוד הזה שזה מוחק את ה - Select Item והבעייה הגדולה בדרך כלל במקרה הזה שאם נרשמתם לאירוע של SelectIndexChanged כדי לבחור את הראשון תצטרכו קודם לבחור את השני ורק אחרי זה תוכלו לבחור את הראשון.
מה שגיליתי לא מזמן - זה את המאפיין AppendDataBoundItems וזה נראה כך
<asp:DropDownList ID="ddl" runat="server" AppendDataBoundItems="true">
<asp:ListItem Text="Select Item"></asp:ListItem>
</asp:DropDownList>
ועכשיו ה - Bind יוסיף את הערכים ולא ידרוס.
בתפוז עלתה שאלה, איך ניתן לקרוא מידע מקובץ קונפיג חיצוני (כלומר - לא הקונפיג של ה - exe שמריץ את התוכנית)
הנה דוגמא
string path = @"bin\Debug\ConsoleApplication1.exe";
Configuration config = ConfigurationManager.OpenExeConfiguration(path);
var a = config.AppSettings.Settings["MyKey"].Value;
ה - path הינו לקובץ ה - exe האחר שיש לו קונפיג.
כדי שזה יעבוד צריך להוסיף referenct ל - System.Configuration
מה קורה כשאתם עובדים בסביבת X64 ואתם מריצים פונקציות מ - dll חיצוני ואתם מקבלים את השגיאה (או משהו בסגנון)
is not a valid Win32 application. (Exception from HRESULT: 0x800700C1)
זה קורה בדרך כלל שאותו dll חיצוני מפעיל native code והוא לא יודע לרוץ בסביבת X64.
מה שאני עושה זה משהו כזה (טיפה עקום אבל הפיתרון הכי פשוט ומהיר)
כותב אפליקצייה Console Application נפרדת שתפעיל את אותם פונקציות - ומקמפל אותה ב - X86.
כעת באפליקצייה שלי (שחייבת כאמור לרוץ ב - X64) אני כותב כך:
var process = System.Diagnostics.Process.Start("process name", "args");
process.WaitForExit();
כעת - אני מפעיל את אותם פונקציות במוד X86 וכשהם מסיימים אני יכול להמשיך לרוץ.
כמובן שבמידה וזה לא סתם הפעלה של פונקציות שעושות משהו, אלא צריך לקבל תשובות ולפי זה להחליט מה לעשות,
מן הסתם צריך פתרון יותר מורכב כמו remoting וכד'.
כשרוצים לרוץ בלולואה על כל ה - האפשרויות ב - enum כלשהו, בדרך כלל רואים קוד כזה
string[] names = Enum.GetNames(typeof(MyEnum));
foreach (string item in names)
{
MyEnum myEnum = (MyEnum)Enum.Parse(typeof(MyEnum), item);
// logic...
}
מה שעשיתי היה לקבל את כל מה שיש ב - enum כמערך של מחרוזות ואז בלולאה להמיר כל אחד ממנו למופע של ה - enum.
כדי לא לכתוב את הקוד (המכוער) כל הזמן אני מעדיך לכתוב כך:
MyEnum[] names = typeof(MyEnum).GetNames<MyEnum>();
foreach (MyEnum item in names)
{
// logic
}
וזה מתאפשר לי בזכות ה - Extension Method הבא:
public static T[] GetNames<T>(this Type enumType) where T : struct
{
string[] names = Enum.GetNames(enumType);
T[] returnArray = names.Select<string, T>(x => (T)Enum.Parse(enumType, x)).ToArray();
return returnArray;
}
בהמשך
לפוסט שהראיתי איך להוסיף Windows User Control לדפי Web (בעזרת ActiveX).
אני רוצה להדגים גישה למאפיינים ולמתודות של ה - User Control.
גישה למאפיינים היא מאוד פשוטה.
כל מאפיין שהוא public אפשר לגשת אליו בעזרת param - לדוגמא:
<object id="UserControl1"
classid="http://localhost/MyApp/ActiveXControls.dll#ActiveXControls.UserControl1">
<param name="BorderStyle" value="FixedSingle" />
<param name="Enabled" value="false" />
</object>
שימו לב שלמאפיין BorderStyle נותנים ערך בלי ה - namespace.
כדי שתוכלו להפעיל מתודות צריך לשנות ב - assembly info את ה - atttibute של ComVisible ל - true
[assembly: ComVisible(true)]
כעת ניתן להפעיל ב - java script מתודות.
נניח שיש את המתודה הבאה ב - User Control
public int Add(int a, int b)
{
return a + b;
}
ניתן להפעיל את המתודה כך:
function func() {
var obj = document.getElementById('UserControl1');
var res = obj.Add(10, 20);
alert(res);
}
בתפוז עלתה שאלה האם ניתן להוסיף Windows Form Control ל - Web Application.
התשובה היא שזה אפשרי בעזרת ActiveX בצורה הבאה.
מייצרים פרוייקט מסוג Windows Form Conrols
מייצרים את ה - Contorl שלכם (זה יכול להיות עטיפה ל - button פשוט)
אחרי הקימפול מעתיקים את ה - dll לתיקייה של ה - web (לא להוסיף reference - אלא להעתיק את ה - dll לאותה תיקייה שבה יש את הדפים).
כעת ב - aspx שלכם תכתבו כך:
<object id="UserControl1"
classid="http://localhost/MyWebApp/ActiveXControls.dll#ActiveXControls.UserControl1">
</object>
מה שחשוב הוא שה - classid יהיה אל ה - dll ובסוף מוסיפים אנקור # ל - class.
סטודנט שאל אותי איך מייצרים Windows Service.
הנה השלבים:
1. תוסיפו פרויקט מסוג Windows Service.
2. תכתבו את כל הקוד שלכם.
אם תנסו להריץ אותו תקבלו את ההודעה הבאה
Cannot start service from the command line or a debugger. A Windows Service must first be installed (using installutil.exe) and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.
מה שצריך לעשות זה כך:
1. קליק ימין על השטח האפור ב - Service
2. לחיצה על Add Installer
3. תקמפלו את הפרויקט
4. תפתחו Visual Studio 2008 Command Promt
5. תכתבו installutil -i [project exe (כמובן שה - project exe מתחלף בשם של הפרויקט עם הנתיב המלא - לדוגמא installutil -i c:\projects\MyProject\bin\debug\win.exe
6. תפתחו את ה - services (ע"י הקלדה ב - Run את services.msc)
7. תעשו Start ל - Service שלכם.
בהצלחה
עדי הראה לי רעיון מאוד מעניין למימוש שמירת אובייקט זמני בזיכרון. למה הכוונה ?
נניח שיש לנו קוד כזה
public class FileLogger : ILogger
{
}
public class MockLogger : ILogger
{
}
public class Person
{
public ILogger Logger { get; set; }
}
ויש לנו את פונקצייה שמקבלת כפרמטר Person אבל בחלק מהקוד של הפונקצייה לא רוצים שה - Logger יהיה FileLogger אלא MokeLogger - מן הסתם נעשה קוד כזה
public void Func(Person person)
{
ILogger original = person.Logger;
person.Logger = new MockLogger();
//work.....
person.Logger = original;
}
מה שעדי הציע - זה לעשות את הקוד הבא
using (DisposableProperty<ILogger>.Set(person, new MockLogger()))
{
// work..
}
וכעת באורח פלא בתוך בלוג ה - using ה - Logger יהיה Mock וכשנצא מהבלוק זה יחזור להיות ה - Logger המקורי.
הרעיון הזה הוא נהדר בסביבות Test - תחשבו על מקרים שאתם לא באמת מעוניינים לגשת לבסיס הנתונים למשל וכו', נכון שתמיד אפשר לכתוב כמו בדוגמא הראשונה אבל הקוד הרבה יותר אלגנטי בצורה הזאת.
אז איך זה עובד ?
public class DisposableProperty<T> : IDisposable where T : class
{
private T _original;
private PropertyInfo _property;
private object _obj;
private DisposableProperty(object obj, T replaceValue)
{
if (obj == null)
throw new ArgumentNullException();
this._obj = obj;
_property = obj.GetType().GetProperties().
FirstOrDefault(p => p.PropertyType == typeof(T));
if (_property == null)
throw new ArgumentException();
_original = _property.GetValue(obj, null) as T;
_property.SetValue(obj, replaceValue, null);
}
public void Dispose()
{
_property.SetValue(_obj, _original, null);
}
public static IDisposable Set(object obj, T replaceValue)
{
return new DisposableProperty<T>(obj, replaceValue);
}
}
אמנם לא הכי יעיל מבחינת ביצועים - אבל בדרך כלל קוד כזה ירוץ בסביבות Tests אז לא כל כך נורא.
המתודה Set מחזירה מופע חדש של המחלקה, בבנאי שומרים את המקורי וב - Dispose מחזירים את המקורי.
בהתבסס על
הפוסט הקודם אדגים כאן איך ניתן לכתוב ולקרוא מה - Cookie בצד הלקוח.
למעשה בפוסט הקודם הצגתי איך יוצרים Cookie בצד השרת שיש לו הרבה ערכים - ככה:
HttpCookie cookie = new HttpCookie("ClientColumns");
for (int j = 0; j < cbl.Items.Count; j++)
{
cookie.Values.Add("name", "value");
}
ברור שאפשר גם לייצר Cookie שיש לו רק ערך אחד
HttpCookie cookie = new HttpCookie("ClientColumns");
cookie.Value = "value";
שימו לב להבדל בין שימוש ב - Values לבין שימוש ב - Value.
בצד הלקוח גם כן אפשר לייצר Cookie שיש לו ערך אחד או יותר.
הנה הפונקצייה לייצר ולקרוא מ - Cookie (הפונקצייה הועתקה
מכאן - מומלץ לקרוא)
function CreateCookie(name, value, expiredays) {
var expires = '';
if (expiredays != undefined) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + expiredays);
expires = exdate.toGMTString();
}
document.cookie = name + "=" + value + (expires == '' ? '' : ";expires=" + expires) + "; path=/";
}
function ReadCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
הפונקצייה CreateCookie מקבלת את השם, הערך, ובעוד כמה ימים זה יפוג.
דוגמא לשימוש עם ערך אחד
CreateCookie('name', 'value', 100);
CreateCookie('name', 'value');
במקרה שלא נשלח ערך לפרמטר שמייצג את זמן התפוגה ה - Cookie יעלם כשיסגרו את הדפדפן.
דוגמא לשימוש עם יותר מערך אחד
CreateCookie('name', 'Id=1&First Name=Shlomo&Work=true');
קריאה ל - ReadCookie תמיד תחזיר את מה שיש ב - value של ה - Cookie.
כלומר לא באמת קיים כזה דבר Cookie שיש לו יותר מערך אחד - אלא שבמקרה שה - Value של ה - Cookie מכיל את התו & האובייקט בצד השרת יודע לפרק את ה - value של ה - cookie להרבה ערכים.
לעומת זאת בצד הלקוח אנחנו צריכים לעשות את זה לבד.
נראה כעת את הדוגמא מהפוסט הקודם איך זה יעבוד עם שימוש בצד הלקוח .
ל - CheckListBpx נוסיף טיפול בצד הלקוח
<asp:CheckBoxList ID="cbl" runat="server" RepeatDirection="Horizontal" onclick="Change(this)">
המתודה:
function Change(tbl) {
var value = CookieString(tbl);
CreateCookie("ClientColumns", value, 100);
ChangeGrid(false);
}
המתודה CookieString מקבלת את ה - CheckListBox ומחזירה מחרוזת מתאימה ל - Cookie.
המתודה CreateCookie שומרת את ה - Cookie.
המתודה ChangeGrid מעלימה את העמודות בגריד, ומקבלת את הערך false כדי להגיד למתודה שלא צריך לשנות את הערך ב - CheckBox (מכיון שכשהעמוד נטען צריך להפעיל את המתודה הזאת כדי לטעון את הערכים מה - Cookie ואז צריך לשנות את ה - CheckBox).
נוסיף ל - body
<body onload="ChangeGrid(true)">
כעת נראה את אותם מתודות.
function CookieString(tbl) {
var inputArr = tbl.getElementsByTagName('INPUT');
var str = '';
for (var i = 0; i < inputArr.length; i++) {
str += inputArr[i].nextSibling.innerText + '=' + inputArr[i].checked + '&';
}
if (str != '') {
str = str.substr(0, str.length - 1);
}
return str;
}
אני מקבל מערך של כל ה - CheckBox שקיים ב - CheckBoxList.
רץ על כולם ומייצר מחרוזת מתאימה.
אחרי הלולאה אני מוחק את התו האחרון &.
function ChangeGrid(setCheckBox) {
var value = ReadCookie('ClientColumns');
if (value != null) {
var values = value.split('&');
var grid = document.getElementById('<%= grid1.ClientID %>');
var clb = document.getElementById('<%= cbl.ClientID %>');
var inputs = clb.getElementsByTagName('INPUT');
for (var m = 0; m < values.length; m++) {
var display = values[m].split('=')[1] == "true" ? 'block' : 'none';
for (var i = 0; i < grid.rows.length; i++) {
grid.rows[i].cells[m].style.display = display;
}
if (display == 'none' && setCheckBox) {
inputs[m].checked = false;
}
}
}
}
אני מנסה לקרוא את ה - Cookie
עושה מה - value מערך לפי סימן ה - &
אני מקבל את הגריד, ה - CheckBoxList ומערך של כל ה - CheckBox שקיימים בו.
אני רץ בלולאה על כל הערכים ב - Cookie.
השורה הבאה:
var display = values[m].split('=')[1] == "true" ? 'block' : 'none';
במידה והערך הוא true נקבל את המילה block אחרת נקבל none
אני רץ על כל השורות בגריד ומגדיר לעמודה האם יראו אותה או לא.
במידה ומוגדר שלא יראו את העמודה והגענו אחרי ה - load של ה - body - נשנה גם את הערך של ה - checkBox.
כעת כשנריץ את העמוד - נגלה שהכול קורה בצד הלקוח וכמובן כשנסגור את הדפדפן ונפתח אותו מחדש הוא יזכור איזה עמודות רצינו לראות.
בפוסט הזה אני רוצה להדגים איך משתמשים ב - Cookie בצד השרת.
Cookie הוא אחד מהדרכים הותיקות ביותר לשמור מידע בצד הלקוח שניתן להשתמש בו גם בצד השרת.
נניח שיש לכם את קוד שמציג גריד עם מספר עמודות גדול מאוד ויש על הדף CheckListBox שמאפשר למשתמש להחליט איזה עמודות הוא מעוניין לראות.
הקוד נראה כך:
בצד הלקוח:
<asp:CheckBoxList ID="cbl" runat="server" RepeatDirection="Horizontal">
<asp:ListItem Text="Id" Selected="True"></asp:ListItem>
<asp:ListItem Text="First Name" Selected="True"></asp:ListItem>
<asp:ListItem Text="Last Name" Selected="True"></asp:ListItem>
<asp:ListItem Text="Age" Selected="True"></asp:ListItem>
<asp:ListItem Text="Salaty" Selected="True"></asp:ListItem>
<asp:ListItem Text="Seniority" Selected="True"></asp:ListItem>
</asp:CheckBoxList>
<br />
<asp:GridView ID="grid1" runat="server" OnRowDataBound="grid1_RowDataBound">
</asp:GridView>
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
יש לנו CheckListBox עם השמות של כל העמודות.
Grid ולחצן (כדי שנוכל לעשות PostBack).
צד השרת:
protected void Page_Load(object sender, EventArgs e)
{
BindGrid();
}
private void BindGrid()
{
DataTable table = new DataTable();
table.Columns.Add("Id");
table.Columns.Add("First Name");
table.Columns.Add("Last Name");
table.Columns.Add("Age");
table.Columns.Add("Salary");
table.Columns.Add("Seniority");
table.Rows.Add(1, "Noam", "Gold", 30, 5000, 2.5);
table.Rows.Add(1, "Pini", "Area", 25, 4500, 1);
table.Rows.Add(1, "Alon", "Tamari", 20, 7000, 4);
grid1.DataSource = table;
grid1.DataBind();
}
protected void grid1_RowDataBound(object sender, GridViewRowEventArgs e)
{
for (int j = 0; j < cbl.Items.Count; j++)
{
e.Row.Cells[j].Visible = cbl.Items[j].Selected;
}
}
ב - Page_Load אנחנו נותנים Data ל - Grid (יוצרים טבלה עם סתם נתונים)
ובאירוע RowDataBound אנחנו מסתירים את העמודות שהמשתמש לא מעוניין לראות.
כמובן שבפעם הראשונה שהדף יעלה, המשתמש יראה את כל העמודות, הוא יוכל להוריד מה - CheckBoxList את הסימון עבור אותם עמודות שהוא אינו מעוניין לראות, לרפרש את העמוד ולראות רק את העמודות שהוא מעוניין.
הבעייה - המשתמש לא רוצה בכל פעם שהוא מעלה את הדף לסמן את העמודות שהוא רוצה לראות, הוא רוצה שהמחשב יזכור את הבחירה שלו מפעם קודמת.
הפיתרון - שימוש ב - Cookie.
נוסיף את הקוד הבא ללחצן שמרפרש את העמוד (אחרי שהמשתמש משנה את הבחירה שלו)
protected void Button1_Click(object sender, EventArgs e)
{
HttpCookie cookie = new HttpCookie("ClientColumns");
for (int j = 0; j < cbl.Items.Count; j++)
{
cookie.Values.Add(cbl.Items[j].Value, cbl.Items[j].Selected.ToString());
}
cookie.Expires = DateTime.Now.AddYears(1);
Response.Cookies.Add(cookie);
}
אנחנו יוצרים Cookie נותנים לו את השם ClientColumns.
מוסיפים לו את רשימה של ערכים כשכל אחד מהם מורכב מ - Key ו - Value, במקרה שלנו בסופו של דבר אנחנו יוצרים משהו כזה:
Id=true&First Name=true&Last Name=false&Age=false&Salary=true&Seniority=true
לאחר מכן אנחנו מגדירים שתאריך התפוגה של ה - Cookie יהיה בעוד שנה (סתם תאריך)
והכי חשוב לא לשכוח להוסיף את ה - Cookie ל - Response.
כעת נוסיף ב - Page_Load את הקוד הבא (לפני הקריאה ל - BindGrid)
if (!IsPostBack)
{
HttpCookie cookie = Request.Cookies["ClientColumns"];
if (cookie != null)
{
foreach (string item in cookie.Values.Keys)
{
ListItem li = cbl.Items.FindByValue(item);
li.Selected = bool.Parse(cookie.Values[item]);
}
}
}
אנחנו מנסים להוציא מה - Request את ה - Cookie (במידה והוא קיים)
רצים בלולאה על כל ה - Keys (כזכור כל Key הוא כשם אחד מה - items ב - CheckBoxList)
ומגידירים האם הוא יהיה מסומן לפי הערך שנשמר ב - Cookie.
כעת במידה והמשתמש יבחר לראות רק חלק מהעמודות - בפעם הבאה שהוא יגיע לעמוד המחשב יזכור את הבחירה שלו.
בפוסט הבא אני אדגים שימוש ב - Cookie בצד הלקוח.
בשבוע האחרון קבלתי פרויקט קטן ומעניין.
במכללת סלע הציבו לא מזמן פלזמה שמריצה מצגת (pptx) שמציגה חדשות טכנולוגיה וכד'.
התבקשתי לייצר קוד שתשלוף מהפוסטים של הבלוגרים שלנו את הפוסט האחרון של כל אחד מהם (יש לנו למעלה משלושים בלוגרים) ולהציג את זה במצגת.
בהתחלה התחלתי לכתוב VBA, די נחמד יש לו את היתרונות שלו אבל על זה אני אדבר בפוסט נפרד,
מהר מאוד עברתי בהמלצת
שי רייטן ל - VSTO.
השלב הראשון היה איכשהו להתחבר ל - RSS של הפוסטים, לא הייתי צריך לכתוב קוד בעצמי, השתמשתי
בזה - קוד נחמד שהורדתי מ -
Code Project).
שימוש בקוד הוא מאוד פשוט
Uri uri = new Uri(item.RssUrl);
RssChannel myRssChannel = new RssChannel(uri);
RssItem item = myRssChannel.Items[0];
הבעייה העיקרית הייתה אחרי שקבלתי את הנתונים, הייתי אמור להציג אותם על תיבות טקסט בשקף.
את הכותרת והתאריך זה היה מאוד פשוט.
Pres.Slides[1].Shapes[1].TextFrame.TextRange.Text = item.Title;
(את המופע של Pres קבלתי דרך רישום ל - Application_AfterPresentationOpen וכמובן המיקום של השקפים והמיקום של תיבות הטקסט הגיע
מקובץ קונפיג.)
כעת רציתי להציג את התוכן של הפוסט - מה שניסיתי לעשות היה:
Pres.Slides[1].Shapes[2].TextFrame.TextRange.Text = item.Description;
הבעייה שהתוכן של ה - Description מכיל גם את התגים של ה - html - כלומר הייתי מקבל בתיבת הטקסט:
< B >Shlomo< /B >
במקום לקבל
Shlomo
מה שאכן הוכיח את עצמו, שי כתב קוד שמקבל מחרוזת מעתיק אותה בצורה נכונה ל - Clipboard ומדביק אותה על תיבת הטקסט, וזה נראה כמו שצריך.
הקוד של שי (ניתן להוריד את המחלקה
מכאן)
public static class InsertHtml
{
public static void InsertHtmlToShape(Shape shape, string html)
{
// Copy the HTML to clipboard (Pay attention,
// no <HTML> nor <BODY> tags are needed here
CopyHtmlToClipBoard(html);
// Paste the HTML into the shape
shape.TextFrame.TextRange.Paste();
}
private static void CopyHtmlToClipBoard(string html)
{
Encoding enc = Encoding.UTF8;
string begin = "Version:0.9\r\nStartHTML:{0:000000}\r\nEndHTML:{1:000000}"
+ "\r\nStartFragment:{2:000000}\r\nEndFragment:{3:000000}\r\n";
string html_begin = "<html>\r\n<head>\r\n"
+ "<meta http-equiv=\"Content-Type\""
+ " content=\"text/html; charset=" + enc.WebName + "\">\r\n"
+ "<title>HTML clipboard</title>\r\n</head>\r\n<body>\r\n"
+ "<!--StartFragment-->\r\n";
string html_end = "\r\n<!--EndFragment-->\r\n</body>\r\n</html>\r\n";
string begin_sample = String.Format(begin, 0, 0, 0, 0);
int count_begin = enc.GetByteCount(begin_sample);
int count_html_begin = enc.GetByteCount(html_begin);
int count_html = enc.GetByteCount(html);
int count_html_end = enc.GetByteCount(html_end);
string html_total = String.Format(
begin
, count_begin
, count_begin + count_html_begin + count_html + count_html_end
, count_begin + count_html_begin
, count_begin + count_html_begin + count_html
) + html_begin + html + html_end;
DataObject obj = new DataObject();
obj.SetData(DataFormats.Html, new MemoryStream(enc.GetBytes(html_total)));
Clipboard.SetDataObject(obj, true);
}
}
(קוד מעניין למדתי ממנו כל מיני דברים חדשים)
בכל מקרה זה עבד, כעת רק נשאר לי להריץ את הקוד ב - Timer כל כמה שעות כדי להתעדכן, אבל אז קבלתי את ההודעה המרגיזה
Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.
לאחר מכן החלטנו להציג רק 500 תווים ראשונים מכל פוסט ואז נתקלתי בבעיה איך אני חותך מה - Description את שאר הטקסט, הרי אם אני יחתוך לפני שאני מדביק לתיבת הטקסט אני מאבד את רוב הפוסט (מכיוון שהוא סופר גם את התגים כתווים) ואם אני אכתוב קוד כזה
shape.TextFrame.TextRange.Text = shape.TextFrame.TextRange.Text.Remove(500)
אני מאבד את העיצוב ה - html.
ולכן כתבתי כך:
shape.TextFrame.TextRange.Characters(500, shape.TextFrame.TextRange.Text.Length).Delete();
shape.TextFrame.TextRange.InsertAfter("...");
מה שאני עדיין לא יודע כרגע (אני מבטיח לחפש ולפרסם) איך אפשר לשנות את קבצי הקונפיג אחרי ההתקנה הסופית.
כלומר - אחרי שאני מסיים לכתוב את הקוד שלי - אני שומר חלק מהמידע בקובץ קונפיג (למשל - כתובות RSS של הבלוגים) אחרי שמתקינים במחשב את ה - AddIn אי אפשר לשנות את הקונפיג - ולכן כל פעם אני צריך לקמפל מחדש.
More Posts
Next page »