C#: Save a Form’s Size and Location

In an attempt to enhance user experience it may be desirable to save the size and location of a Form. When dealing with customizable user settings, it is important to determine where to persist the user’s applications settings. The Application class has a UserAppDataRegistry property that references the HKEY_CURRENT_USER\Software\[Control.CompanyName]\[Control.ProductName]\[Control.ProductVersion]. registry key in the user’s profile (If this key does not exist it is created). Here is a basic example of storing a form's state in the user's registry profile:
	private void Form_Load(object sender, EventArgs e)
        {
            this.WindowState = (FormWindowState)FormWindowState.Parse(WindowState.GetType(), Application.UserAppDataRegistry.GetValue("WindowState", FormWindowState.Normal).ToString());
            if (this.WindowState == FormWindowState.Normal)
            {
                int x = (int)Application.UserAppDataRegistry.GetValue("LocationX");
                int y = (int)Application.UserAppDataRegistry.GetValue("LocationY");
                this.DesktopLocation = new Point(x, y);
                int w = (int)Application.UserAppDataRegistry.GetValue("WindowSizeW");
                int h = (int)Application.UserAppDataRegistry.GetValue("WindowSizeH");
                this.Size = new Size(w, h);
            }
        }

        private void Form_FormClosing(object sender, FormClosingEventArgs e)
        {
            Application.UserAppDataRegistry.SetValue("WindowState", this.WindowState);
            Application.UserAppDataRegistry.SetValue("WindowSizeH", this.Size.Height);
            Application.UserAppDataRegistry.SetValue("WindowSizeW", this.Size.Width);
            Application.UserAppDataRegistry.SetValue("LocationX", this.DesktopLocation.X);
            Application.UserAppDataRegistry.SetValue("LocationY", this.DesktopLocation.Y);
        }



   

ASP.NET Membership Using a Custom Profile

ASP.NET Membership Custom Profile

The ASP.NET Membership is an easy way to manage user credentials and security within ASP.NET Web Application (Web Site).  ASP.NET Membership will not only handle user authentication, it can also be used to manage user profile information.  To add a Custom Profile for the ASP.NET Membership users you need to enable profiles, specify a profile provider and add the profile properties in the system.web section of the web.config file:
<profile enabled="true">
			<providers>
				<remove name="AspNetSqlProfileProvider"/>
				<add name="AspNetSqlProfileProvider" connectionStringName="SqlMembership" applicationName="appProfile" type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
			</providers>
			<properties>
				<add name="Gender" type="System.String"/>
				<add name="favoritenumber" type="System.Int32"/>
				<add name="notification" type="System.Boolean"/>
				<add name="BirthDate" type="System.DateTime"/>
			</properties>
		</profile>
Once the profile information has been enable and configured a user’s profile information can be read and saved through the dynamic ProfileCommon class. The ProfileCommon class will the properties specified in the configuration file.  A simplified code example for reading and saving profile values:
protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            if (User.Identity.IsAuthenticated == true)
            {
                MembershipUser user = Membership.GetUser();
                ((TextBox)LoginView1.FindControl("Email")).Text = ((TextBox)LoginView1.FindControl("Email2")).Text = user.Email;
                ((TextBox)LoginView1.FindControl("txtNumber")).Text = Profile.favoritenumber.ToString();
                DropDownList ddl = (DropDownList)LoginView1.FindControl("ddlGender");
                ddl.SelectedIndex = ddl.Items.IndexOf(ddl.Items.FindByValue(Profile.Gender));
                ((CheckBox)LoginView1.FindControl("chkNotification")).Checked = Profile.notification;
            }
        }
    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
        if (Page.IsValid)
        {
            TextBox Email;
            MembershipUser user = Membership.GetUser();
            Email = (TextBox)LoginView1.FindControl("Email");
            if (user.Email != Email.Text)
            {
                user.Email = Email.Text;
            }
            Membership.UpdateUser(user);
            
            Profile.Gender = ((DropDownList)LoginView1.FindControl("ddlGender")).SelectedValue;
            Profile.notification = ((CheckBox)LoginView1.FindControl("chkNotification")).Checked;
            Profile.favoritenumber = System.Convert.ToInt32(((TextBox)LoginView1.FindControl("txtNumber")).Text);
            Profile.Save();
        }
    }

ASP.NET Membership Custom Profile



   

ASP.NET GridView: Multiple Column Sort

The native ASP.NET GridView allows for the sorting of data by setting the Grid's AllowSorting property to True.  With AllowSorting enabled, header row cells contain hyperlinks, which when clicked, sort the GridView.  There are several GridView properties and events available which can be used for the customization of the GridView's sort.

  • The Sort method, defined as Sort ( string sortExpression, SortDirection sortDirection ), sorts the GridView based upon the specified sort expression and direction.
  • The Sorting event occurs when the hyperlink column heading is clicked, but before the GridView has handled the sorting of the data.
  • The Sorted event occurs when the hyperlink column heading is clicked, but after the GridView has sorted the datasource.
  • The RowCreated event occurs when a row is created in the GridView.
  • The SortDirection property gets the sort direction of the column being sorted.
  • The SortExpression property gets the sort expression associated with the column being sorted.



The default sort action of the GridView sorts only by a single column.  Using the available sort related events and properties it is rather easy to create a GridView that can be sorted by multiple columns.  The following example is for a GridView than can be sorted by multiple columns.  In this example the columns are sorted in the order they are clicked by a tri-state function.  Clicking on a column heading will set a columns sort state to be Ascending, Descending or Off.  The columns in the GridView are sorted based upon in which they are click or turned on.  When the sort state of a column is Off it is removed from the sort.

using System;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

/// <summary>
/// MySortItem is a class that is used to in a List to store the sort columns and thier order
/// </summary>
public class MySortItem
{
    private string columnname;
    public string ColumnName
    {
        get
        {
            return columnname;
        }
        set
        {
            columnname = value;
        }
    }
    private SortDirection columnsort;
    public SortDirection ColumnSort
    {
        get
        {
            return columnsort;
        }
        set
        {
            columnsort = value;
        }
    }
    public string SortToString( SortDirection cs )
    {
        string s = ( cs == SortDirection.Ascending ) ? "asc" : "desc";
        return s;
    }

}

public partial class _Default : System.Web.UI.Page
{
    DataSet ds = new DataSet ();
    // List for holding sorted columns, their order and their direction
    List<MySortItem> sl;

    protected void Page_Load( object sender, EventArgs e )
    {
        // If the Column Sort List does not exist, create it. the list is stored in a session variable
        if ( Session["sortitems"] == null )
        {
            Session["sortitems"] = new List<MySortItem> ();
        }
        sl = (List<MySortItem>) Session["sortitems"];

        if ( !Page.IsPostBack )
        {
            // Procedure for binding the datasource with the DataGrid
            BindDataGrid ( GridView1 );
        }
    }

    /// <summary>
    /// Called from within the application to Bind the DataGrid to a datasource. in this cases it is the default view of a table. 
    /// </summary>
    /// <param name="gv"></param>
    protected void BindDataGrid( GridView gv )
    {
        // Read data from an XML file
        ds.ReadXml ( Server.MapPath ( "~/App_Data/Data.xml" ) );

        // build the sort expression
        string sortexp = "";
        for ( int i = 0; i < sl.Count; i++ )
        {
            sortexp = ( i == 0 ) ? sl[i].ColumnName + " " + sl[i].SortToString ( sl[i].ColumnSort ) : sortexp + "," + sl[i].ColumnName + " " + sl[i].SortToString ( sl[i].ColumnSort );
        }

        //Set label text to display the sort expression on the page for the user
        Label3.Text = sortexp;
        // set the datasource's table default view to indicate sorting; a datatable could also be used directly. In this example a datasource
        // was used due to dynmaic XML data loading
        ds.Tables[0].DefaultView.Sort = sortexp;
        // set the datagrid's datasource
        gv.DataSource = ds.Tables[0].DefaultView;
        gv.DataBind ();
    }

    /// <summary>
    /// Assigned to the GridView's PagIndexChanging event. This is used if the GridView allows for paging. 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void GridView1_PageIndexChanging( object sender, GridViewPageEventArgs e )
    {
        try
        {
            if ( sender is GridView )
            {
                ( (GridView) sender ).PageIndex = e.NewPageIndex;
                BindDataGrid ( GridView1 );
                e.Cancel = false;
            }
        }
        catch
        {

            e.Cancel = true;
        }
    }

    /// <summary>
    /// Assigned to the GridView's Sorting event. 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void GridView1_Sorting( object sender, GridViewSortEventArgs e )
    {
        try
        {
            // Scroll through the list and see if the clicked GridView column exists in the sort list.
            // If there is an entry for the column then change the order or remove the column from the list (tri-state)
            bool found = false;
            for ( int i = 0; i < sl.Count; i++ )
            {
                if ( sl[i].ColumnName == e.SortExpression.ToString () )
                {
                    found = true;
                    switch ( sl[i].ColumnSort )
                    {
                        case SortDirection.Descending:
                            sl[i].ColumnSort = SortDirection.Ascending;
                            break;
                        case SortDirection.Ascending:
                            sl.RemoveAt ( i );
                            break;
                    }
                }
            }
            if ( !found )
            {
                MySortItem si = new MySortItem ();
                si.ColumnName = e.SortExpression.ToString ();
                si.ColumnSort = SortDirection.Descending;
                sl.Add ( si );
            }

            BindDataGrid ( GridView1 );
            //accept column sort
            e.Cancel = false;
        }
        catch ( Exception )
        {
            //error so cancel
            e.Cancel = true;
        }
    }

    /// <summary>
    /// Assigned to the GridView's RowCreated event.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void GridView1_RowCreated( object sender, GridViewRowEventArgs e )
    {
        // add images to the column header to indicate sort of each column
        if ( e.Row.RowType == DataControlRowType.Header )
        {
            foreach ( TableCell tc in e.Row.Cells )
            {
                if ( tc.HasControls () )
                {
                    // search for the header link
                    LinkButton lnk = (LinkButton) tc.Controls[0];
                    if ( ( lnk != null ) & ( sl != null ) )
                    {
                        // Get the position of the column in the List 
                        int i = sl.FindIndex ( delegate ( MySortItem s )
                        {
                            return s.ColumnName == lnk.CommandArgument;
                        } );
                        // if the column is found in the list
                        if ( i >= 0 )
                        {
                            System.Web.UI.WebControls.Image img = new System.Web.UI.WebControls.Image ();
                            //set the appropriate image
                            img.ImageUrl = "~/images/" + ( sl[i].ColumnSort == SortDirection.Ascending ? "asc" : "desc" ) + ".gif";
                            // add space and the sort image to header
                            if ( sl[i].ColumnName == lnk.CommandArgument )
                            {
                                tc.Controls.Add ( new LiteralControl ( " " ) );
                                tc.Controls.Add ( img );
                            }
                        }
                    }
                }
            }
        }
    }
}
This is all it takes to have an ASP.NET GridView that can be sorted by multiple columns.



   

A little System.Diagnostics

I am not going to get overly verbose here; however the importance of application logging is immeasurable. When applications are deployed, obtaining ‘real life’ application can assist in diagnostic and overall application health situations. Excessive logging can be ineffective (too much to analyze) and burdensome (logging to a text file that grows forever) if not implemented appropriately.

When developing and deploying an application having the flexibility of what, when and how to log can alleviate the burden and ineffectiveness of logging. The System.Diagnostics namespace has a number of classes available that allow for this type of flexible logging. The applied use of these classes allows a developer to specify what to log in an application at various levels of detail within one application without the need to deploy diagnostic builds. Depending on diagnostic needs, the application’s config file can specify the level of diagnostic information to write and where. A simple Logging class (this is the class I created to use this functionality) can be developed as follows:

using System;
using System.Diagnostics;

namespace BPSoftware
{
    public class Logging
    {
        /// <summary>
        /// strNameSpace is the base namespace for use in the logging message.
        /// </summary>
        public static string strNameSpace;

        /// <summary>
        /// GeneralTraceSwitch is a traceswitch object for logging.  Switch level is set through 
        /// the application config switches section.  The destination listener is also set through
        /// the application config file.
        /// </summary>
        //public static TraceSwitch GeneralTraceSwitch = new TraceSwitch ( "TraceSwitch", "Config file TraceSwitch" );
        public static TraceSwitch GeneralTraceSwitch = new TraceSwitch ( "General", "General Trace Switch" );

        #region Methods
        /// <summary>
        /// Writeline is the base logging method. Messages are logged to the listener specified
        /// in the application config file.
        /// </summary>
        /// <param name="strClassName">Calling class name.</param>
        /// <param name="strMethodName">Calling method name.</param>
        /// <param name="strMessage">The message sent to the listener.</param>
        private static void WriteLine( string strClassName, string strMethodName, string strMessage )
        {
            lock ( typeof ( Logging ) )
            {
                Trace.WriteLine ( string.Concat (
                    new object[] { 
                        "(", DateTime.Now, ") ", 
                        strNameSpace, ".", strClassName, ".", strMethodName, " -->", strMessage 
                    }
                 ) );
            }
        }

        /// <summary>
        /// Pass information to the base WriteLine if the TraceError switch is set.
        /// </summary>
        /// <param name="strClassName">Calling class name.</param>
        /// <param name="strMethodName">Calling method name.</param>
        /// <param name="strMessage">The message sent to the listener.</param>
        public static void WriteLineTraceError( string strClassName, string strMethodName, string strMessage )
        {
            if ( Logging.GeneralTraceSwitch.TraceError )
                WriteLine ( strClassName, strMethodName, strMessage );
        }

        /// <summary>
        /// Pass information to the base WriteLine if the TraceWarning switch is set.
        /// </summary>
        /// <param name="strClassName">Calling class name.</param>
        /// <param name="strMethodName">Calling method name.</param>
        /// <param name="strMessage">The message sent to the listener.</param>
        public static void WriteLineTraceWarning( string strClassName, string strMethodName, string strMessage )
        {
            if ( Logging.GeneralTraceSwitch.TraceWarning )
                WriteLine ( strClassName, strMethodName, strMessage );
        }

        /// <summary>
        /// Pass information to the base WriteLine if the TraceInfo switch is set.
        /// </summary>
        /// <param name="strClassName">Calling class name.</param>
        /// <param name="strMethodName">Calling method name.</param>
        /// <param name="strMessage">The message sent to the listener.</param>
        public static void WriteLineTraceInfo( string strClassName, string strMethodName, string strMessage )
        {
            if ( Logging.GeneralTraceSwitch.TraceInfo )
                WriteLine ( strClassName, strMethodName, strMessage );
        }

        /// <summary>
        /// Pass information to the base WriteLine if the TraceVerbose switch is set.
        /// </summary>
        /// <param name="strClassName">Calling class name.</param>
        /// <param name="strMethodName">Calling method name.</param>
        /// <param name="strMessage">The message sent to the listener.</param>
        public static void WriteLineTraceVerbose( string strClassName, string strMethodName, string strMessage )
        {
            if ( Logging.GeneralTraceSwitch.TraceVerbose )
                WriteLine ( strClassName, strMethodName, strMessage );
        }
        #endregion
    }
}

Throughout the application logging messages can be added:
catch ( Exception ex)
        {
            Logging.WriteLineTraceError( "ClassName", "Method", "Exception: " + ex.Message );
        }

        Logging.WriteLineTraceInfo( "ClassName", "Method",,
            String.Format( "{0}: {1}", e.ActionType.ToString(), e.Message )



The application’s config file can then be altered to determine the appropriate level and location of the logging based upon certain diagnostic needs. The switches node specifies what level of logging should occur. The switches operate in a hierarchial fashion (3 includes 3, 2 and 1; 2 includes 2 and 1):

1 <system.diagnostics>
2 <trace autoflush="true" indentsize="4">
3 <!-- Specify listener output -->
4 <listeners>
5 <remove name="Default" />
6 <!-- Write to a log file -->
7 <add name="Default"
8 type="System.Diagnostics.TextWriterTraceListener"
9 initializeData="AFileSync.log" />
10 <!-- Write to the Event Log-->
11 <!-- <add name="Default"
12 type="System.Diagnostics.EventLogTraceListener"
13 initializeData="Application" /> -->
14 </listeners>
15 </trace>
16 <switches>
17 <!-- Off = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4 -->
18 <add name="General" value="3" />
19 </switches>
20 </system.diagnostics>



Using the application’s config file to specify the level of logging allows for the capture of diagnostic information of a stable build to capture ‘real world’ information. The flexibility of the listener to write to (you can create customer listeners) is an added bonus!



   

Control and List Windows Services

As I whipped up my file synchronizing service application, to synchronize files between my computer and my NAS), I worked with the System.ServiceProcess Namespace. The System.ServiceProcess Namespace "provides classes that allow you to implement, install, and control Windows service applications".

My file synchronization needs did not require that my application service to be "running" all the time. In, fact I only needed the service to run under certain situations. It was easy enough to get the service written and installed and now I wanted to 'automatically' (programmatically) start and stop my service when certain conditions were met.

Entrance - the ServiceController Class. The ServiceController Class "Represents a Windows service and allows you to connect to a running or stopped service, manipulate it, or get information about it." In order to get acclimated to the ServiceController Class I created a simple application to list and display information about the installed services.

 

using System;
using System.ServiceProcess;
using System.Windows.Forms;

namespace Services1
{
    public partial class Form1 : Form
    {
        ServiceController controller;

        public Form1()
        {
            controller = new ServiceController ();
            controller.MachineName = ".";
            InitializeComponent ();
            lstServices.DisplayMember = "DisplayName";
            lstServices.ValueMember = "ServiceName";
            lstServices.DataSource = ServiceController.GetServices ();
        }

        private void StartService( string servicename )
        {
            controller.ServiceName = servicename;
            controller.Start ();

        }

        private void PauseService( string servicename )
        {
            controller.ServiceName = servicename;
            if ( controller.CanPauseAndContinue )
                controller.Pause ();
        }

        private void StopService( string servicename )
        {
            controller.ServiceName = servicename;
            if ( controller.CanStop )
                controller.Stop ();
        }


        private void listBox1_SelectedIndexChanged( object sender, EventArgs e )
        {
            if ( lstServices.SelectedItems.Count > 0 )
            {
                try
                {
                    controller.ServiceName = lstServices.SelectedValue.ToString ();
                    textBox1.Text = String.Format (
                        "Service Name: {0}\r\nDisplay Name: {1}\r\nType: {2}\r\nStatus: {3}",
                        controller.ServiceName,
                        controller.DisplayName,
                        controller.ServiceType.ToString (),
                        controller.Status.ToString ()
                        );
                }
                catch ( Exception ex )
                {
                    textBox1.Text = String.Format ( "Error: \r\n{0}", ex.Message );
                }
            }
            else
            {
                textBox1.Clear ();
            }
        }

        private void btnStart_Click( object sender, EventArgs e )
        {
            StartService ( lstServices.SelectedValue.ToString () );
        }

        private void btnPause_Click( object sender, EventArgs e )
        {
            PauseService ( lstServices.SelectedValue.ToString () );
        }

        private void btnStop_Click( object sender, EventArgs e )
        {
            StopService ( lstServices.SelectedValue.ToString () );
        }
    }
}