2008/12/16

Gridview: automatically change focus from one TextBox to the other

In a word, I would like to do something like Excel can do:
1. After inputing data and pressing the "Enter" key, the focus should change to the next TextBox.
2. After focus changed, the content should be selected automatically so that users can repeat step 1.
3. Each "Enter" you press will trigger the postback and do some calculations.
4. If reach the end of the Gridview, do nothing.

Environment:
1.A GridView control + ASP.Net 2.0 application.
2.GridView has an ItemTemplate field, which contains TextBox.
3.GridView is inside an UpdatePanel.
protected void TextBox_TextChanged(object sender, EventArgs e)
{
  //get the parent gridview
  GridViewRow gvr = (GridViewRow)((TextBox)sender).NamingContainer;
  GridView gv = (GridView)gvr.NamingContainer;

  //automatically change focus to the next row's textbox
  if (gvr.RowIndex != gv.Rows.Count - 1)
   ChangeFocus((TextBox)gv.Rows[gvr.RowIndex + 1].FindControl(((TextBox)sender).ID), ScriptManager1);

  gvr = null;
  gv = null;
}

private static void ChangeFocus(TextBox tb, ScriptManager scriptMgr)
{
  tb.Attributes.Add("onfocus", "javascript:this.select();");
  scriptMgr.SetFocus(tb.ClientID);
}

2008/11/07

Dynamically enable/disable ASP.Net AJAX controls

The ASP.Net AJAX Toolkit is a good stuff because it can let you achieve some very cool functions without doing too much hard works. However, how to do it if I want the AJAX control works only in some conditions? For example, I want to have the same function like this one. After many times try and error, I finally figure out the correct procedures. Here are the steps:

Tips: Storing data in the web.config file

You can store any string you want.
<appSettings>
  <add key="SSRS" value="http://localhost/Reports/Pages/Report.aspx" />
  <add key="FTPServer" value="ftp.myftp.com" />
</appSettings>
Here is how to use:
string strSSRS = ConfigurationManager.AppSettings["SSRS"];

2008/11/04

Encode Query String by using Base64 (6/24/2009 updated)

Recently, I read a post about encrypting the Query String, and it's quite interested. You can read it here. However, we can use a simpler way to do the same thing like this:
public static string EncryptString(string toEncode)
{
  try
  {
    byte[] toEncodeAsBytes = Encoding.UTF8.GetBytes(toEncode);
    return Convert.ToBase64String(toEncodeAsBytes);
  }
  catch (Exception ex)
  {
    //do your error handling here
  }
}

public static string DecryptString(string toDecrypt)
{
  try
  {
    byte[] encodedDataAsBytes = Convert.FromBase64String(toDecrypt.Replace(" ", "+"));
    return Encoding.UTF8.GetString(encodedDataAsBytes);
  }
  catch (Exception ex)
  {
    //do your error handling here
  }
}

You can apply some other encryption algorithms like TripleDES before applying the Base64 encoding.

PS: Be careful of the URL length restriction.

2008/10/22

Tips: Closing browser without asking users

In some cases I need to close browser without popping up a window to asking, and here is what I found that really works:
window.open('','_self','');
window.close();
or

window.open('','_parent','');
window.close();

The use of _self or _parent will depend on the situation. When users click the "Exit" button on a page, I will use the _self. When the user select "Yes" to leave on a confirm window pop up, I will use the _parent so that the browser will close correctly.

Tips: Converting DataTable's DataType

Here is the way to convert a DataTable's DataType from whatever types to the String type.
private static DataTable ConvertTableType(DataTable dt)
{
  DataTable newDt = dt.Clone();

  //convert all columns' datatype
  foreach (DataColumn dc in newDt.Columns)
  {
    dc.DataType = Type.GetType("System.String");
  }

  //import data from original table
  foreach (DataRow dr in dt.Rows)
  {
    newDt.ImportRow(dr);
  }
  dt.Dispose();

  return newDt;
}

2008/10/20

Tips: Merge two DataTables (6/16/2009 updated)

How to merge two DataTable into one? Here is what I did:
private static DataTable AppendDataTable(DataTable hostDt, DataTable clientDt)
{
  if (hostDt != null && hostDt.Rows.Count > 0)
  {
    DataRow dr;

    for (int i = 0; i < clientDt.Columns.Count; i++)
    {
      hostDt.Columns.Add(new DataColumn(clientDt.Columns[i].ColumnName));

      if (clientDt.Rows.Count > 0)
        for (int j = 0; j < clientDt.Rows.Count; j++)
        {
          dr = hostDt.Rows[j];
          dr[hostDt.Columns.Count - 1] = clientDt.Rows[j][i];
          dr = null;
        }
    }
  }

  return hostDt;
}
You don't have to return the hostDt because the reference type object is passed by reference. However, if your source tables will be disposed after merging, you have to replace the return statement by this:
return hostDt.Copy();

2008/10/19

A generic method to call Stored Procedures

Scenario:
1.You need to call many Stored Procedures (Sproc), but you don't want to create a method for each of them.
2.You have some Sprocs, and each of them has different numbers of input parameters.

Here is what I did:
private DataSet DBConnectionForGettingData(string sprocName, Dictionary<string, string> paramList)
{
  using (SqlConnection conn = new SqlConnection(str_ConnectionString))
  {
    SqlCommand cmd = new SqlCommand();
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = sprocName;
    cmd.Connection = conn;

    //loop through the dictionary
    foreach (string key in paramList.Keys)
    {
      cmd.Parameters.AddWithValue(key, paramList[key]);
    }

    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);

    //release the resource
    cmd = null;
    da = null;

    return ds;
  }
}
The paramList is the collection of parameters. All you need to do is create a Dictionary object and add all the parameters to this object. You can also adjust the arguments of this method to make it more flexible.

2008/10/12

Drawing charts on your ASP.Net page by using OWC

Here is the steps that how I draw and put a chart on my ASP.Net page by using Microsoft Office Web Component (OWC):
My environment: VS 2005, MS Office 2003.
  1. Adding the reference. Right click on your web project -> select add a reference -> select "COM" tab -> find and add the "Microsoft Office Web Component 11".
    PS: The OWC version is depending on the version of MS Office you are using. I am using Office 2003 so the OWC version will be 11.
  2. In your code-behind file, add this
    using Microsoft.Office.Interop.Owc11;

2008/10/07

Refresh the ASP.Net Image control (6/17/2009 updated)

A very simple request: Refresh the Image control on your aspx page when you want to.
Scenario:
1.You want to refresh a specific image when a user clicks a button.
2.You want to refresh a specific image when that image has been changed.
3.You want to refresh a specific image periodically.

This won't work:
//strImagePath is a variable which stores the image path and filename
Image1.ImageUrl = strImagePath;

This will:
Image1.Attributes.Add("src", "strImagePath?ts=" + System.DateTime.Ticks);

In order to avoid the browser sending the old/cached image to client, we can change the value of the image's "src" property by giving a timestamp. We can also change the image file name (like image20090617.jpg -> image20090618.jpg -> image20090619.jpg), but this will require more steps and increase the complexity.

2008/09/30

Tips: Get the GridView control from the TextBox insides the GridView (7/21/09 updated)

Scenario:
1.You have one or more than one GridView(s) on your page.
2.Each GridView has one or more TextBox(s) in it.
3.At the TextChanged() event, you want to/have to know which GridView owns the event.
Here is the way to get the parent control of that TextBox:
//get the parent gridview
GridViewRow gvr = (GridViewRow)(((TextBox)sender).Parent).Parent;
GridView gv = (GridView)(gvr.Parent).Parent;

Once you get the GridViewRow, you can get the RowIndex value. Once you get the GridView, you get everything...

7/21/09 updated:
A better way to get the same thing by using NamingContainer property:
GridViewRow gvr = (GridViewRow)((TextBox)sender).NamingContainer;
GridView gv = (GridView)gvr.NamingContainer;

2008/09/21

Set the value of a DropDownList control by a given text or value

Usually we will set the default of a DropDownList control by using this command:

ddlCompany.SelectedIndex = 1;
However, what should we do if we only have a text string or a value of that text? For example, I want to bind this table to a DropDownList control:
ID  Name
--  -----
1   HP
2   IBM
3   Microsoft

And we want dynamically set the default value when the QueryString is presented, like http://xxxxxx/company.aspx?ID=2 or http://xxxxxx/company.aspx?Name=IBM.  In this scenario, we have to use the following codes to set the default value of the DropDownList control:

foreach (ListItem li in ddlCompany.Items)
{
if (li.Value == "2")
{
li.Selected = true;
break;
}
}
or

foreach (ListItem li in ddlCompany.Items)
{
if (li.Text == "IBM")
{
li.Selected = true;
break;
}
}

2008/09/18

Tips: Pass parameters to URL from Reporting Services

Here is how to embed parameters into URL:

="http://xxx.xxx.com/show.aspx?pid=" & Parameters!pid.Value
You can use this format to embed as many parameters as you want.

How to jump to the link without leaving the current page? (that is to say, open a new window) Here is how:
="javascript:void(window.open('http://google.com','_blank'))"

2008/09/15

Check dirty form: Server-Side (Code-Behind) method combines with Client-Side javascript

There are many ways to check whether the form is dirty or not, and most of them are client side (javascript) codes. My situation is:
1. I have to do a Server-Side condition check first, and then decide whether to call the client side javascript function.
2. My TextBox are all inside an UpdatePanel.

The pure client side solution (like this one) doesn't seem to be a good fit for me. The reason is because the "onunload" event will be called every time when a postback is fired, and the javascript confirm window will always show up. Here is my solution:

1. Setting a mark in the TextBox TextChanged event. This means the data has been modified by client.
protected void gvColumn1_TextChanged(object sender, EventArgs e)
{
  int tempValue;

  //check the input value
  if (int.TryParse(((TextBox)sender).Text, out tempValue))
  {
    //your logic here
    .....

    //here is the mark
    ViewState["DataChanged"] = true;
  }
  else
  {
    //your logic here
    .....
  }
}

2. Clear the mark at the click event of the "Save" button.
protected void btnSave_Click(object sender, EventArgs e)
{
  //your logic here
  .....

  if (ViewState["DataChanged"] != null)
    ViewState["DataChanged"] = null;
}

3. Check the mark if the "Exit", "Next Page", or "Previous Page" button is clicked. If the mark exists, call the javascript to show the confirm window.
protected void btnExit_Click(object sender, EventArgs e)
{
  if (ViewState["DataChanged"] != null && (bool)ViewState["DataChanged"]
    == true)
  {
    //your logic here
    .....

    //build the javascript script
    string script = "<script>";
    script += "if (confirm('Leave without saving your modifications?'))";
    script += "window.location='Default.aspx';";  //redirect to homepage
    script += "</script>";

    //giving user a chance to save before leaving
    ScriptManager.RegisterClientScriptBlock(this, this.GetType(),
      "DataNotSave", script, false);
  }
  else
    Response.Redirect("~/Default.aspx");  //redirect to homepage
}

PS: This approach cannot solve the problem if users click the "Previous" or "Next" button on their browsers.

2008/08/25

A simple Asp.Net Counter [2/25/2011 updated]

2/25/2011 Updated:
Added the Thread-Safe lock.

I create a very simple page counter for some purposes:
  1. It will store the counter data (visit times, last visit date) in a file for easy manipulation.
  2. If the file doesn't exist, then program should create it automatically.
  3. One file can store as many counters info as I want. It I have 5+ pages (or even the whole website) need to be counted, this file can store than all.
  4. Data should be read/wrote easily.
  5. The file should be read by other programs/software easily.
  6. The counter can be reset by program without editing the file manually.
I chose XML as my file format. Here is the code:
static object LockMe = new object();
public static string CounterInfo(string tag, bool resetCounter)
{
  lock (LockMe)
  {
    string counterValue = "1";

    try
    {
      bool tagNotFound = true;
      XmlDocument xDoc = new XmlDocument();
      string filename = @"~/CounterData.xml"; //file to store the counter value
      FileInfo oFile = new FileInfo(HttpContext.Current.Server.MapPath(
      filename));

      //create one if the file doesn't exist
      if (!file.Exists)
        xDoc.LoadXml("");
      else
      {
        //load file which contains counter info
        xDoc.Load(HttpContext.Current.Server.MapPath(filename));

        foreach (XmlNode node in xDoc.GetElementsByTagName(tag))
        {
          if (!resetCounter)
          {
            //show counter value
            counterValue = (Convert.ToInt16(node.InnerText) + 1).ToString();

            //update counter value
            node.InnerText = counterValue;
          }
          else
          {
            //set the value to zero
            node.InnerText = "0";
          }

          //update modified date
          node.Attributes["modifieddate"].Value = DateTime.Now.ToShortDateString();
          tagNotFound = false;
          break;
        }
      }

      //if we can't find the tag, then create one
      if (tagNotFound)
      {
        XmlElement elem = xdoc.CreateElement(tag);
        elem.SetAttribute("modifieddate", DateTime.Now.ToShortDateString());
        elem.InnerText = "1";
        xDoc.DocumentElement.AppendChild(elem);
        elem = null;
      }

      //save back to the xml file
      xDoc.Save(HttpContext.Current.Server.MapPath(filename));
      xDoc = null;
      oFile = null;
    }
    catch (Exception e)
    {
      //log your error message here
    }

    return counterValue;
  }
}

2008/07/21

My easy and simple way to raise an error in T-SQL (4/21/2010 updated)

Sometimes I need to know if a database operation is success or failure in my code (not in stored procedures) and perform different behaviors. My easy and simple way is raising an error in stored procedures and return a specific error message to my application, and I will check if that message is presented in the returning result. The syntax is:
Begin Try
  Update xxxx
  Set yy = zzz
  Where aa = 123
  Select @updateRowcount = @@Rowcount, @ErrorCode = @@Error

  If (@updateRowcount = 0)
    Raiserror('0 row(s) affected', 11, 1)
  If (@ErrorCode != 0)
    Raiserror('Update failed.', 11, 1)

End Try

Begin Catch
  Select Error_number(), Error_line(), Error_message()
  return
End Catch
The string in the Raiserror() function is the "specific message".

2008/07/11

Wrapping statements

Many people (me also) like to wrap many statements into a single statement so that it looks short and clean. However, it's not a good idea in the following LINQ to SQL query:
string[] names = { "Jim", "John", "Kenny", "Mary", "Jay" };

IEnumerable<string> query = 
from n in names
where n.Length == names.Max(n1 => n1.Length)
select n;  //the output is Kenny
This query is not so efficiency because the subquery is recalculated on each outer loop and will create one more round trip to the database each time. We can improve it by separating the subquery like this:
int max = names.Max (n => n.Length);

IEnumerable<string> query = 
from n in names
where n.Length == max
select n;


PS: If you want to understand more, please refer to the C# 3.0 in a nutshell.

2008/05/30

Windows Form: Control the Closing icon ("X" button)

If you set the control box property to true (Fig.1), the UI will then show the control box which contains MinimizeBox, MaximizeBox, and the "X" button.
Fig.1
The "X" button usually means close/exit this windows form (or application). Can we have more control of this behavior (or the "X" button)?

Yes, we can. In your code behind, create an event for the form you want to control like this:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
  DialogResult dr = MessageBox.Show("Do you really want to leave?", "Confirm Window", MessageBoxButtons.YesNoCancel);

  if (dr == DialogResult.No || dr == DialogResult.Cancel)
  {
    MessageBox.Show("That's my boy~");
    e.Cancel = true; //means cancel this event
  }
  else
    MessageBox.Show("Bad boy!");
}

Every time when user clicks on the "X" button to close the form, it will trigger the above event and perform whatever you design in there.

2008/05/16

Use XmlDocument to generate XML

This way is much simpler than XmlTextWriter:
public static String createItem(DataTable dt)
{
  XmlDocument XmlDoc = new XmlDocument();
  XmlDoc.LoadXml("");
  XmlElement XmlElem;

  foreach (DataRow dr in dt.Rows)
  {
    XmlElem = XmlDoc.CreateElement("customer");
    XmlElem.SetAttribute("Name", dr["custName"].ToString());
    XmlElem.SetAttribute("ID", dr["custID"].ToString());
    XmlElem.SetAttribute("Address", dr["address"].ToString());
    XmlElem.SetAttribute("ZipCode", dr["zip"].ToString());
    XmlElem.SetAttribute("Phone", dr["phone"].ToString());
    XmlElem.InnerText = "Kenny";

    XmlDoc.DocumentElement.AppendChild(XmlElem);
  }

  return XmlDoc.FirstChild.InnerXml;
}

Use XmlTextWriter to generate XML

using (MemoryStream ms = new MemoryStream())
{
  XmlTextWriter Xwriter = new XmlTextWriter(ms, Encoding.UTF8);

  Xwriter.WriteStartDocument();  //Put the XML declaration
  Xwriter.Formatting = Formatting.Indented;  //Format the XML document

  Xwriter.WriteComment("XML sample file for ch2.2"); //Put a comment
  Xwriter.WriteStartElement("po");  //This is the root element
  Xwriter.WriteAttributeString("id", "PO1456");

  Xwriter.WriteStartElement("address");
  Xwriter.WriteAttributeString("type", "shipping");
  Xwriter.WriteElementString("name", "Frits Mendels");
  Xwriter.WriteElementString("street", "152 Cherry St");
  Xwriter.WriteElementString("city", "San Francisco");
  Xwriter.WriteElementString("state", "CA");
  Xwriter.WriteElementString("zip", "94045");
  Xwriter.WriteEndElement();

  Xwriter.WriteStartElement("items");
  Xwriter.WriteStartElement("item");
  Xwriter.WriteAttributeString("quantity", "1");
  Xwriter.WriteAttributeString("productCode", "R-273");
  Xwriter.WriteAttributeString("description", "14.4 Volt Cordless Drill");
  Xwriter.WriteAttributeString("unitCost", "189.95");
  Xwriter.WriteEndElement();

  Xwriter.WriteStartElement("item");
  Xwriter.WriteAttributeString("quantity", "1");
  Xwriter.WriteAttributeString("productCode", "1632S");
  Xwriter.WriteAttributeString("description", "12 Piece Drill Bit Set");
  Xwriter.WriteAttributeString("unitCost", "14.95");
  Xwriter.WriteEndElement();
  Xwriter.WriteEndElement();

  Xwriter.WriteEndElement();
  Xwriter.WriteEndDocument();
  Xwriter.Flush();  //flush everything from the buffer
  ms.Position = 0;
  StreamReader sr = new StreamReader(ms);
  return sr.ReadToEnd();
}

The output will be



Frits Mendels 152 Cherry St San Francisco CA 94045

We can also use StreamWriter class instead of MemoryStream, just replace this line of code
XmlTextWriter Xwriter = new XmlTextWriter(ms, Encoding.UTF8);
with
StreamWriter sw = new StreamWriter();
XmlTextWriter Xwriter = new XmlTextWriter(sw);
and remove the using of MemoryStream. Also replace this part
Xwriter.Flush();
ms.Position = 0;
StreamReader sr = new StreamReader(ms);
return sr.ReadToEnd();
with
Xwriter.Flush();
Xwriter.Close();
return sw;

Here is an example from Microsoft Technet.

2008/03/03

Another way to bulk update data in SQL (Updated)

In a small application, sometimes we need to update some among of data in database frequently. I ever mentioned how to convert xml into a table in SQL, so that we can convert those data into a XML file and send it to SQL server.
What if those data that need to be updated contain many duplicate columns? We can send those duplicate columns just once, and concatenate those non-duplicate columns into a string and send it to SQL as a parameter. It works like this:
1.create a method to fetch data from List and merge them into a string by using a delimiter to seperate them.
For example:
//This is a new feature of C# 3.0. Take a look of this:
//"http://blogs.msdn.com/abhinaba/archive/2005/09/17/470358.aspx".
List<int> CustomerIdList = new List<int>{1001,1002,1003,1004};
string CustomerIds = MergeData(CustomerIdList);
Create your own MergeData() method to merge the input List<int> and output as a single string like this: "1001,1002,1003,1004".
2.Send this string as a parameter to the SQL server.
3.At SQL server, we need to create a Table-Valued function to parse this string into a table.
CREATE FUNCTION [dbo].[fxnParseCommaDelmitedList]
(
@CommaDelimitedList varchar(8000)
)
RETURNS @TableVar TABLE (ItemID int NOT NULL )
AS
BEGIN
  DECLARE @IDListPosition int
  DECLARE @IDList varchar(4000)
  DECLARE @ArrValue varchar(4000)
  SET @IDList = COALESCE(@CommaDelimitedList, '')
  IF @IDList <> ''
  BEGIN
  -- Add comma to end of list so user doesn''t have to
  SET @IDList = @IDList + ','
  -- Loop through the comma demlimted string list
  WHILE PATINDEX('%,%' , @IDList ) <> 0
    BEGIN
      SELECT @IDListPosition = PATINDEX('%,%' , @IDList)
      SELECT @ArrValue = LEFT(@IDList, @IDListPosition - 1)
      -- Insert parsed ID into TableVar for "where in select"
      INSERT INTO @TableVar (ItemID) VALUES (CONVERT(int, @ArrValue))
      -- Remove processed string
      SELECT @IDList = STUFF(@IDList, 1, @IDListPosition, '')
    END
  END
  RETURN
END

This sproc will return a table like this:
ItemID (column name)
1001
1002
1003
1004

A lighter way than using XML file.

2008/01/28

What's the different among Parse(), TryParse(), and ConvertTo() ?

These three methods are all converting data from one type to another, but why we have three ways (methods) for the same purpose? What's the reason for that? What's the differences? Here is a very good article for all these question: Performance Profiling Parse vs. TryParse vs. ConvertTo.
After reading this, I finally know what's the differences and when to use them. There are always people doing such a detail research for us. Thanks!