Blog

posts by Fons Sonnemans

SmartPart for SharePoint

0 Comments
By Fons Sonnemans, 10-jun-2004

Together with Jan Tielens, I have created a improved version of my User Control Container Web Part called the SmartPart for SharePoint.

Jan has written an introduction and even created a video demonstrating it in action.

We have created a GotDotNet Workspace for the SmartPart. Over there you can download an installation package that will install the SmartPart, the source code and an example user control. At this point there isn’t very much documentation, but I will work on that. :-) There are a lot of cool ideas which (I hope) will be added to the SmartPart (for example a connectable SmartPart). I’ve IM-ed with Patrick today and he has another cool idea which he discussed in one of his latest posts: using the User Interface Process block together with web parts. Anyway, some nice ideas are waiting to be implemented!

Tags: SharePoint

READ MORE

I will be at TechEd 2004 Amsterdam

0 Comments
By Fons Sonnemans, 12-mei-2004

This will be my 6th TechED (97 and 98 in Nice, 99 and 2000 in Amsterdam, 2002 in Barcelona).

It is a great event in which I learn a lot and get lot's of inspiration. It's not cheap but it is worth every peny!

Tags: TechEd

READ MORE

InitialFocus on a ASP.NET WebForm

0 Comments
By Fons Sonnemans, 28-apr-2004

Download InitialFocusDemo.zip

Introduction

The PageUtil class has a static method SetInitialFocus(control) which can be used to generate a JavaScript for an ASP.NET page (WebForm), which sets the focus on a (given) control.

        private voidPage_Load(objectsender, System.EventArgs e)
        {
            // Set the InitialFocus on TextBox1
            ReflectionIT.Web.PageUtil.SetInitialFocus(TextBox1);
        }

TextBox1: 

PageUtil Class

using System;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace ReflectionIT.Web {
    /// <summary>
    /// Utility class for a ASP.NET page
    ///
    /// Be sure to notice that this code is provided as a technology sample,
    /// 'as is' and no warranties are made by the author.
    ///
    /// For questions and comments: Fons.Sonnemans@reflectionit.nl
    ///
    /// </summary>
    public class PageUtil {

        /// <summary>
        /// Set the InitialFocus to the given control. Only works when JavaScript is supported.
        /// </summary>
        /// <param name="control">Control to set the InitialFocus on.</param>
        publicstaticvoid SetInitialFocus(Control control){
            // Postpone setting the InitialFocus to the PreRender event of the control.
            // The control might not yet have a Page property, in PreRender it always has!
            control.PreRender += new EventHandler(InitialFocusControl_PreRender);
        }

        /// <summary>
        /// Set the InitialFocus to the control that notified this EventHandler (sender)
        /// </summary>
        /// <param name="sender">The source of the event. </param>
        /// <param name="e">An EventArgs that contains the event data. </param>
        privatestaticvoid InitialFocusControl_PreRender(object sender, EventArgs e){
            Control control=senderas Control;

            if(control.Page == null){
                thrownew ArgumentException(
                    "The Control must be added to a Page before you can set the IntialFocus to it.");
            }
            if(control.Page.Request.Browser.JavaScript ==true) {
                // Create JavaScript
                StringBuilder s =new StringBuilder();
                s.Append("\n<SCRIPT LANGUAGE='JavaScript'>\n");
                s.Append("<!--\n");
                s.Append("function SetInitialFocus()\n");
                s.Append("{\n");
                s.Append(" document.");

                // Find the Form
                Control p =control.Parent;
                while(!(p is System.Web.UI.HtmlControls.HtmlForm))
                    p = p.Parent;
                s.Append(p.ClientID);

                s.Append("['");
                s.Append(control.UniqueID);

                // Set Focus on the selected item of a RadioButtonList
                RadioButtonList rbl= controlas RadioButtonList;
                if(rbl!= null){
                    stringsuffix ="_0";
                    int t = 0;
                    foreach(ListItem liinrbl.Items){
                        if(li.Selected) {
                            suffix= "_"+ t.ToString();
                            break;
                        }
                        t++;
                    }
                    s.Append(suffix);
                }

                // Set Focus on the first item of a CheckBoxList
                if(controlis CheckBoxList){
                    s.Append("_0");
                }

                s.Append("'].focus();\n");
                s.Append("}\n");

                if(control.Page.SmartNavigation)
                    s.Append("window.setTimeout(SetInitialFocus, 500);\n");
                else
                    s.Append("window.onload = SetInitialFocus;\n");

                s.Append("// -->\n");
                s.Append("</SCRIPT>");

                // Register Client Script
                control.Page.RegisterClientScriptBlock("InitialFocus", s.ToString());
            }
        }
    }
}

Example

Textbox1 has the focus after the page is loaded.

 WebForm1.aspx

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="InitialFocusDemo.WebForm1"smartNavigation="False"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
    <HEAD>
        <metacontent="Microsoft Visual Studio 7.0"name="GENERATOR">
        <metacontent="C#"name="CODE_LANGUAGE">
        <metacontent="JavaScript (ECMAScript)"name="vs_defaultClientScript">
        <metacontent="http://schemas.microsoft.com/intellisense/ie5"name="vs_targetSchema">
    </HEAD>
    <body>
        <formid="Form1"method="post"runat="server">
            <table>
                <tr>
                    <td>
                        <asp:labelid="Label1"runat="server">Label</asp:label>
                    </td>
                    <td>
                        <asp:textboxid="TextBox1"runat="server"></asp:textbox>
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:labelid="Label2"runat="server">Label</asp:label>
                    </td>
                    <td>
                        <asp:textboxid="TextBox2"runat="server"></asp:textbox>
                    </td>
                </tr>
                <tr>
                    <td>
                    </td>
                    <td>
                        <asp:Button id="Button1"runat="server" Text="Button"></asp:Button>
                    </td>
                </tr>
            </table
        </form>
    </body>
</HTML>

 WebForm1.aspx.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace InitialFocusDemo
{
    /// <summary>
    /// Summary description for WebForm1.
    /// </summary>
    public class WebForm1: System.Web.UI.Page
    {
        protected System.Web.UI.WebControls.Label Label1;
        protected System.Web.UI.WebControls.Label Label2;
        protected System.Web.UI.WebControls.TextBox TextBox1;
        protected System.Web.UI.WebControls.Button Button1; 
        protected System.Web.UI.WebControls.TextBox TextBox2;
    
        public WebForm1()
        {
            Page.Init +=new System.EventHandler(Page_Init);
        }

        privatevoid Page_Load(objectsender, System.EventArgs e)
        {
            // Set the InitialFocus on TextBox1
            ReflectionIT.Web.PageUtil.SetInitialFocus(TextBox1);
        }

        privatevoid Page_Init(objectsender, EventArgs e)
        {
            //
            // CODEGEN: This call is required by the ASP.NET Web Form Designer.
            //
            InitializeComponent();
        }

        #region Web Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        privatevoid InitializeComponent()
        {    
            this.Load +=new System.EventHandler(this.Page_Load);

        }
        #endregion

    }
}

Thanks to:

  • Philip Quirke who reported a bug in an earlier version. I used the controls 'UniqueID' property instead of 'ClientID' in the SetInitialFocus() method.
  • S Patil who reported that it didn't work with Netscape 4.7x browser. I have therfore changed the JavaScript.
  • Scott Dinwiddie who reported the SmartNavigation problem.
  • Waylon Campbell who reported the RadioButtonList problem.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

Tags: Web, ASP.NET

READ MORE

String Array Sorting

0 Comments
By Fons Sonnemans, 27-apr-2004

It took me today some time to figure out how to sort an array of strings case-sensitively. The default behavior of the Array.Sort() uses an default Comparer object which should be case-sensitive. When I tested this I found out that I had misinterpreted the definition of 'case-sensitive'. What I wanted was 'Oridinal' sorting. So I created an OrdinalStringComparer class which implements IComparer and I got what I wanted.

using System;
using System.Collections;

namespace ReflectionIT.Test {

    class Class1 {

        [STAThread]
        static void Main(string[] args) {

            Test(new CaseInsensitiveComparer());
            Test(Comparer.Default);        
            Test(new OridinalStringComparer());
        
            Console.ReadLine();
        }

        private static void Test(IComparer comparer) {

            string[] words = new string[] { "c", "a", "A", "aB", "ab", "Ab" };

            Array.Sort(words, comparer);
        
            Console.WriteLine(comparer.GetType().Name + ":");
            foreach (string word in words) {
                Console.WriteLine(word);
            }
            Console.WriteLine();
        }
        
    }
        
    public class OridinalStringComparer : IComparer {
        
        int IComparer.Compare(object x, object y ) {
            return string.CompareOrdinal((string)x, (string)y);
        }
        
    }
        
}

And when you run this program you get the following output:

CaseInsensitiveComparer:
A
a
Ab
ab
aB
c

Comparer:
a
A
ab
aB
Ab
c

OridinalStringComparer:
A
Ab
a
aB
ab
c

READ MORE

User Control Container Web Part

0 Comments
By Fons Sonnemans, 20-apr-2004

Download ReflectionIT.SharePoint.WebParts.zip

Introduction

SharePoint is a great product. In this article I will demonstrate this by creating a Web Part which acts like a container for normal ASP.NET User Controls (.ascx files). This makes it very easy to customize a portal page by adding normal ASP.NET User Controls to it.

UserControlContainer Web Part

The UserControlContainer Web Part is created using the Web Part Templates for Microsoft Visual Studio .NET. The real magic in the UserControlContrainer class happens in the CreateChildControls() method. In this method the User Control is loaded and added to the Controls collection. The RenderWebPart() method renders the User Control to the HtmlTextWriter.


using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;

namespace ReflectionIT.SharePoint.WebParts
{
    /// <summary>
    /// Description for UserControlContainer.
    /// </summary>
    [DefaultProperty("Text"),
        ToolboxData("<{0}:UserControlContainer runat=server></{0}:UserControlContainer>"),
        XmlRoot(Namespace="ReflectionIT.SharePoint.WebParts")]
    publicclass UserControlContainer : Microsoft.SharePoint.WebPartPages.WebPart
    {
        privateconststring defaultText="";
        privatestring_userControl=defaultText;
        private Control _control=null;

        [Browsable(true),
            Category("User Control"),
            DefaultValue(defaultText),
            WebPartStorage(Storage.Personal),
            FriendlyName("User Control (.ascx)"),
            Description("Path to the User Control (.ascx)")]
        publicstring UserControl
        {
            get
            {
                return_userControl;
            }

            set
            {
                _userControl= value;
            }
        }
        
        /// <summary>
        ///    This method gets the custom tool parts for this Web Part by overriding the
        ///    GetToolParts method of the WebPart base class. You must implement
        ///    custom tool parts in a separate class that derives from
        ///    Microsoft.SharePoint.WebPartPages.ToolPart.
        ///    </summary>
        ///<returns>An array of references to ToolPart objects.</returns>
        publicoverride ToolPart[] GetToolParts(){
            ToolPart[] toolparts=new ToolPart[3];
            WebPartToolPart wptp= new WebPartToolPart();
            CustomPropertyToolPart custom= new CustomPropertyToolPart();
            toolparts[0]=custom;
            toolparts[1]=wptp;
            toolparts[2]=new CopyrightToolpart();

            wptp.Expand(Microsoft.SharePoint.WebPartPages.WebPartToolPart.Categories.Appearance);
            custom.Expand("User Control");

            returntoolparts;
        }

        /// <summary>
        /// Load the UserControl
        /// </summary>
        protectedoverridevoid CreateChildControls() {
            base.CreateChildControls ();

            try{
                if(_userControl!= defaultText){
                    _control= this.Page.LoadControl(_userControl);
                }else {
                    _control= new LiteralControl(string.Format("To link to content, <a href=\"javascript:MSOTlPn_ShowToolPaneWrapper('{0}','{1}','{2}');\">open the tool pane</a> and then type a URL in the Link text box.",1, 129,this.ID));
                }
            }catch(System.Exception ex){
                _control= new LiteralControl(string.Format("<b>Error:</b> unable to load {0}<br /><b>Details:</b> {1}",_userControl,ex.Message));
            }
            
            if(_control!= null){
                // Add to the Controls collection to support postback
                this.Controls.Add(_control);
            }
        }

        
        /// <summary>
        /// Render this Web Part to the output parameter specified.
        /// </summary>
        /// <param name="output"> The HTML writer to write out to </param>
        protectedoverridevoid RenderWebPart(HtmlTextWriter output)
        {
            EnsureChildControls();
            if(_control!= null){
                _control.RenderControl(output);
            }
        }
    }
}

Installation

STSADM is a command line tool in SharePoint which can be used to install the Web Part package. A Web Part package is to be created in the form of a CAB file. You do this in Visual Studio.NET by adding the DWP file (C#'rs.. make sure your DPW file is marked as content file) and the DLL along with a third file, the manifest.xml (which is normally there if you started from the Web Part template) to the CAB file. The manifest will contain the registration of the safe control that needs to be done in the web.config.

I run STSADM using a little Install.bat file which looks like this:

"%programfiles%\Common Files\Microsoft Shared\web server extensions\60\BIN\stsadm"-o addwppack-force -globalinstall-filename Setup\Release\Setup.cab

Updates can now easily be done by recompiling your projects and simply double-clicking the install.bat file or for the C#'rs call it as a post-build event.

Sample User Control

I will demonstrate the usage of my web part using a simple Calculator user control which can be used to calculate the sum of two numbers. The control is just a normal UserControl which I created using Visual Studio.NET. I have copied the calculator.ascx file to a new folder called 'SharepointTestControls' in my portal, in my case c:\inetpub\DemoPortal\SharepointTestControls. I have placed the SharepointTestControls.dll which contains the compiled codebehind class in the 'bin' folder of my potal, in my case c:\inetpub\DemoPortal\bin.

<%@ Control Language="c#" AutoEventWireup="false" Codebehind="Calculator.ascx.cs" Inherits="SharepointTestControls.Calculator" TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
<TABLEid="Table1"cellSpacing="1"cellPadding="1"border="0">
    <TR>
        <TD>A:</TD> 
        <TD> 
            <asp:TextBoxid="TextBoxA"runat="server"Width="80px"></asp:TextBox></TD> 
    </TR>
    <TR
        <TD>B:</TD> 
        <TD> 
            <asp:TextBoxid="TextBoxB"runat="server"Width="80px"></asp:TextBox></TD> 
    </TR>
    <TR
        <TD> 
            <asp:Button id="ButtonTotal"runat="server"Text="Total"Width="56px"></asp:Button></TD>
        <TD>
            <asp:TextBoxid="TextBoxTotal"runat="server"Width="80px"></asp:TextBox></TD>
    </TR>
</TABLE>
UserControl file: Calculator.ascx

namespace SharepointTestControls
{
    using System;
    using System.Data;
    using System.Drawing;
    using System.Web;
    using System.Web.UI.WebControls;
    using System.Web.UI.HtmlControls;

    /// <summary>
    /// Summary description for Calculator.
    /// </summary>
    public class Calculator : System.Web.UI.UserControl
    {
        protected System.Web.UI.WebControls.TextBox TextBoxA;
        protected System.Web.UI.WebControls.TextBox TextBoxB;
        protected System.Web.UI.WebControls.TextBox TextBoxTotal;
        protected System.Web.UI.WebControls.Button ButtonTotal;

        privatevoid Page_Load(objectsender, System.EventArgs e)
        {
            // Put user code to initialize the page here
        }

        #region Web Form Designer generated code
        overrideprotectedvoid OnInit(EventArgs e)
        {
            //
            // CODEGEN: This call is required by the ASP.NET Web Form Designer.
            //
            InitializeComponent();
            base.OnInit(e);
        }
        
        /// <summary>
        ///        Required method for Designer support - do not modify
        ///        the contents of this method with the code editor.
        /// </summary>
        privatevoid InitializeComponent()
        {
            this.ButtonTotal.Click += new System.EventHandler(this.ButtonTotal_Click);
            this.Load +=new System.EventHandler(this.Page_Load);

        }
        #endregion

        privatevoid ButtonTotal_Click(objectsender, System.EventArgs e){
            try{
                TextBoxTotal.Text = (Convert.ToDouble(TextBoxA.Text) +
                    Convert.ToDouble(TextBoxB.Text)).ToString();
            }catch(FormatException){
                TextBoxTotal.Text = "NAN";
            }
        }
    }
}
Codebehind file: Calculator.ascx.cs

Using the Web Part

You can use the User Control Container Web Part by adding it to your page. In my example I have added it to the Left zone.

Next I have set the User Control property to '~\SharepointTestControls\Calculator.ascx' and changed the Title into 'Calculator'.

The Calculator UserControl now lives inside the UserControlContainer WebPart. When you enter two numeric values into textboxes 'A' and 'B' and click the 'Total' button you will see it really works.

Setting Custom Properties in DWP

I learned a trick from Mads Haugb?? Nissen which makes this WebPart even more powerfull. You can create an separate DWP file in which you predefine the value for the UserControl property. This way you don't have to set it manually.

<?xmlversion="1.0"encoding="utf-8"?>
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" >
    <Title>Calculator User Control</Title>
    <Description>Calculator controlusedtodemonstratesettingcustom properties</Description>
    <Assembly>ReflectionIT.SharePoint.WebParts</Assembly>
    <TypeName>ReflectionIT.SharePoint.WebParts.UserControlContainer</TypeName>
    <!--
Specify initialvalues for any additional base class or custom properties here. Don't forget the 'xmlns' attribute pointing to the XmlRoot namespace value of your WebPart.-->
    <UserControlxmlns="ReflectionIT.SharePoint.WebParts">~/SharepointTestControls/Calculator2.ascx</UserControl>
</WebPart>

Finally you must copy the calculator.dwp file to the 'wpcatalog' folder of your portal, in my case c:\inetpub\DemoPortal\wpcatalog. The Virtual Server Gallery will show the Calculator User Control as if it was a normal WebPart. This makes creating WebParts very easy.

Related Reading

A Developer's Introduction to Web Parts
Packaging and Deploying Web Parts for Microsoft Windows SharePoint Services
Building Web Parts for Microsoft SharePoint Products and Technologies, Part I - The Basics
Building Web Parts for Microsoft SharePoint Products and Technologies, Part II - Web Parts and User Controls
Building Web Parts for Microsoft SharePoint Products and Technologies, Part III - Connectable Web Parts

Conclusion

Microsoft SharePoint Portal Server and Windows SharePoint Services is, in combination with the VS.NET and FrontPage 2003, an excellent development platform for building custom portal sites. I hope this article gave you an impression of how easy it is to add User Controls to a portal page.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

Tags: SharePoint

READ MORE

System.Collections and System.Collections.Specialized

0 Comments
By Fons Sonnemans, 08-mrt-2004

This table gives you an overview of all collections in the System.Collections and System.Collections.Specialized namespaces.

Class Key Value Remarks
System.Array   ? Predefined size, used for passing data around.
ArrayList   Object Easy to work with, provides basic collection functionality and can easily be converted to an Array.
BitArray   Boolean
Manages a compact array of bit values, which are represented as Booleans
CollectionBase   ? Abstract base for building your own collection classes.
DictionaryBase Object ? Abstract base for building your own collection classes using a key-value pair.
HashTable Object Object Provides high performance access to items in the key-value pair collection, by a specific key.
Queue   Object Implements the FIFO mechanism.
ReadOnlyCollectionBase   Object Abstract base for building your own read-only collection classes.
SortedList Object (sorted) Object Provides access to items in the collection by a specific key or an index (GetByIndex(int)).
Stack   Object Implements the LIFO mechanism.
Specialized.HybridDictionary
Object Object Uses internally a ListDictionary class when the collection is small, and switches automatically to a Hashtable class when the collection gets large.
Specialized.ListDictionary Object Object Is designed to be used for collections with 10 or less items, while the Hashtable is designed to contain more items.
Specialized.NameObjectCollectionBase String ? Abstract base for building your own collection classes using a key-value pair.
Specialized.NameValueCollection String (sorted) String NameValueCollection class is the strong type equivalent of the SortedList class, for the String class.
Specialized.StringCollection   Object Strongly typed ArrayList for the String class.
Specialized.StringDictionary String String Strongly typed Hashtable for the String class, not sorted.

Read also Jan Tielens article on the MSDN Belux website.

READ MORE

EventsHelper using Fire and Forget

0 Comments
By Fons Sonnemans, 07-mrt-2004

I have used the EventsHelper class to fire events asynchronical. I came across this class in the TechEd 2003 presentation C# Best Practices from Eric Gunnerson and Juval Löwy. Today I noticed a bug. The FireAsync() method uses the 'Fire and Forget' pattern incorrectly.

The 'Fire and Forget' pattern is used when the return value and returned parameters of the call are not required, and when there is no need to synchronize with the asynchronously executing method. In this case, the caller simply needs to call BeginInvoke, passing any normal and ref parameters, and null for the callback and asyncState.

This has been a commonly used pattern. However, there has been a documentation change in version 1.1 of the .NET Framework that has big ramifications for this technique of making async calls. The documentation now states that EndInvoke must be called for a corresponding BeginInvoke--otherwise Microsoft say they may now, or in the future, leak resources. It appears that no resources are leaked under version 1.0 of the framework; however, with this type of warning in place, it is recommended that a call to EndInvoke be made even if the return values of an async call are not required.

It is relatively straightforward to create a helper class that handles this for you--one example I found here.

public class AsyncHelper {

    delegate void DynamicInvokeShimProc(Delegate d, object[] args);

    static DynamicInvokeShimProc dynamicInvokeShim = new
        DynamicInvokeShimProc(DynamicInvokeShim);

    static AsyncCallback dynamicInvokeDone = new
        AsyncCallback(DynamicInvokeDone);

    public static void FireAndForget(Delegate d, params object[] args) {
        dynamicInvokeShim.BeginInvoke(d, args, dynamicInvokeDone, null);
    }

    static void DynamicInvokeShim(Delegate d, object[] args) {
        d.DynamicInvoke(args);
    }

    static void DynamicInvokeDone(IAsyncResult ar) {
        dynamicInvokeShim.EndInvoke(ar);
    }
}

My improved EventsHelper class now looks like this:

public class EventsHelper {

    public static void FireAsync(Delegate del, params object[] args) {
        if (del == null) {
            return;
        }
        Delegate[] delegates = del.GetInvocationList();
        AsyncFire asyncFire;
        foreach (Delegate sink in delegates) {
            asyncFire = new AsyncFire(InvokeDelegate);
            AsyncHelper.FireAndForget(asyncFire, sink, args);
        }
    }

    delegate void AsyncFire(Delegate del, object[] args);

    [OneWay]
    static void InvokeDelegate(Delegate del, object[] args) {
        del.DynamicInvoke(args);
    }

    public static void Fire(Delegate del, params object[] args) {
        if (del == null) {
            return;
        }
        Delegate[] delegates = del.GetInvocationList();
        foreach (Delegate sink in delegates) {
            try {
                sink.DynamicInvoke(args);
            }
            catch {}
        }
    }
}

READ MORE

DateDiff() function in C#

4 Comments
By Fons Sonnemans, 11-feb-2004

I have written the following DateDiff() function in C#. VB.NET users already had it using the Micrsoft.VisualBasic.dll assembly. Now you can use it without referencing this 'ugly' extra assembly.

using System;


namespace ReflectionIT.System {

    public enum DateInterval {
        Year,
        Month,
        Weekday,
        Day,
        Hour,
        Minute,
        Second
    }

    public class DateTimeUtil {

        public static long DateDiff(DateInterval interval, DateTime date1, DateTime date2) {

            TimeSpan ts = date2 - date1;

            switch (interval) {
                case DateInterval.Year:
                    return date2.Year - date1.Year;
                case DateInterval.Month:
                    return (date2.Month - date1.Month) + (12 * (date2.Year - date1.Year));
                case DateInterval.Weekday:
                    return Fix(ts.TotalDays) / 7;
                case DateInterval.Day:
                    return Fix(ts.TotalDays);
                case DateInterval.Hour:
                    return Fix(ts.TotalHours);
                case DateInterval.Minute:
                    return Fix(ts.TotalMinutes);
                default:
                    return Fix(ts.TotalSeconds);
            }
        }

        private static long Fix(double Number) {
            if (Number >= 0) {
                return (long)Math.Floor(Number);
            }
            return (long)Math.Ceiling(Number);
        }
    }
}


READ MORE

Integrate NDoc HTML Help in VS.NET

0 Comments
By Fons Sonnemans, 30-dec-2003

Download NDocSample.zip

Introduction

NDoc 1.2 generates class libraries documentation from .NET assemblies and the XML documentation files generated by the C# compiler. NDoc uses add-on documenters to generate documentation in several different formats, including the MSDN-style HTML Help format (.chm), the Visual Studio .NET Help format (HTML Help 2), and MSDN-online style web pages.

This article explains the four steps to take in order to integrate the HTML Help 2 file you generated into Visual Studio.NET.

  1. Comment the Library using XML documentation tags
  2. Create HTML Help 2 using NDoc
  3. Registering the Help File using H2Reg
  4. Include Help Collection to VSCC

Step 1: Comment the Library using XML documentation tags

I have created a sample library with two classes Employee and EmployeeCollection. These classes are documented using the XML documentation tags (///). The assembly is named ReflectionIT.NDocSample.

using System;

namespace ReflectionIT.NDocSample
{
    /// <summary>
    /// Employee class used to demonstrate NDoc and Visual Studio.NET integration.
    /// </summary>
    /// <example>
    /// Some sample:
    /// <code>
    /// Employee emp = new Employee("Jim", 4000);
    /// emp.RaiseSalary();
    /// Console.WriteLine(emp.GetYearSalary);</code>
    /// </example>
    public class Employee
    {
        privatestring_name;
        privateint_salary;

        /// <summary>
        /// Initializes a new instance of the Employee class with a name and salary.
        /// </summary>
        /// <param name="name">The name of the employee</param>
        /// <param name="salary">The salary of the employee</param>
        public Employee(stringname, intsalary)
        {
            this.Name = name;
            this.Salary = salary;
        }

        /// <summary>
        /// Gets or sets the name of the employee.
        /// </summary>
        publicstring Name {
            get{return this._name;}
            set{this._name= value;}
        }

        /// <summary>
        /// Gets or sets the salary of the employee.
        /// </summary>
        publicint Salary {
            get{return this._salary;}
            set{this._salary= value;}
        }

        /// <summary>
        /// Returns the year salary for the employee using 12 months
        /// </summary>
        /// <returns>The year salary</returns>
        publicvirtualint GetYearSalary() {
            return Salary *12;
        }

        /// <summary>
        /// Raise the salary with 10%
        /// </summary>
        publicvirtualvoid RaiseSalary() {
            Salary += Salary *10/100;
        }
    }
}

The assembly is named ReflectionIT.NDocSample. I have set the name of the xml file in which the documentation comments are processed to ReflectionIT.NDocSample.xml. Make sure this is the same name as the assembly otherwise IntelliSense will not work correctly. Don't forget to compile the assembly; this will create the xml file.

Step 2: Create HTML Help 2 using NDoc

NDoc comes with a GUI front end (NDocGUI.exe) and a console application (NDocConsole.exe). I have used the NDocGui to create a NDoc project for my NDocSample library. You can easily do this using the 'New from Visual Studio Solution...' menu option.


Next you have to set the Documentation Type to 'HtmlHelp2'. I have also changed some extra properties like: CopyrightHref, CopyrightText, HtmlHelpName and Title.

Then you are ready to build the help file. Before you can do this you have to install Microsoft Html Workshop and Microsoft Visual Studio .NET Help Integration Kit (VSHIK2003). You build the help file using the Documentation, Build menu option (Ctrl+Shift+B). The result is an ReflectionIT.NDocSample.chm help file which NDoc converted to compiled Html Help.

Step 3: Registering the Help File using H2Reg

I have used H2Reg to register the NDocSample help file. H2Reg.exe is a small utility (177K), that allows you to register MS Help 2.x Collections: Namespaces, Titles, Plug-ins, and Filters without using MSI (Microsoft Installer).

To use H2Ref you have to create an ini file in which you describe what you want to register. Below you see the H2Reg_cmd.ini file I used to register the NDocSample help file.


; - - - - - - - Register -r switch

[Reg_Namespace]
;<nsName>|<nsColfile>|<nsDesc>
ReflectionIT.NDocSample|ReflectionIT.NDocSample.HxC|ReflectionIT NDocSample

[Reg_Title]
;<nsName>|<TitleID>|<LangId>|<HxS_HelpFile>|<HxI_IndexFile>|<HxQ_QueryFile>|
;<HxR_AttrQueryFile>|<HxsMediaLoc>|<HxqMediaLoc>|<HxrMediaLoc>|<SampleMediaLoc>
ReflectionIT.NDocSample|ReflectionIT.NDocSample|1033|ReflectionIT.NDocSample.HxS|||||||

;------- UnRegister -u switch

[UnReg_Namespace]
;<nsName>
ReflectionIT.NDocSample

[UnReg_Title]
;<nsName>|<TitleID>|<LangId>
ReflectionIT.NDocSample|ReflectionIT.NDocSample|1033


I have placed this ini file together with the ReflectionIT.NDocSample.HxC and ReflectionIT.NDocSample.HxC files into a folder called Help. In this folder you have to start H2Reg with the -R switch which I have automated using the following register.bat batch file.

copy . .\Ndoc\doc\*.hxS
copy..\Ndoc\doc\*.hxC
"C:\Program Files\Helpware\H2Reg\H2Reg.exe" -r cmdfile="C:\My Documents\Visual Studio Projects\NDocSample\Help\H2Reg_cmd.ini"

Step 4: Include Help Collection to VSCC

The last step is to include the help collection in Visual Studio .NET 2003 Combined Help Collection (VSCC). Therefore you have to open the 'Visual Studio .NET Combined Help Collection Manager' page using the following hyperlink: ms-help://MS.VSCC.2003/VSCCCommon/cm/CollectionManager.htm. In this page you have to select the 'ReflectionIT.NDocSample' checkbox and click the 'Update VSCC' button.


You can change the Title attribute of HelpCollection node in the .HxC file when you want a more descriptive name.

Result

The result off all these steps is great. You will see the help file integrated into Visual Studio.NET. It is context sensitive (F1) and will also appear in the Dynamic Help.



Conclusion

The Xml documentation comments of C# are great. With NDoc and H2Reg you can generate an Html Help 2 file and integrate it in Visual Studio.NET. This will make your code libraries really (re)usable.

I haven't figured out how to add Filters and Contents (TOC). I hope to update this article soon with these options. When you find this all to difficult you should look at DocumentX which is a lot easier to use, but not for free.

Any suggestions and feedback for improving this Add-In is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

READ MORE

.NET Quick Reference Card

0 Comments
By Fons Sonnemans, 07-nov-2003

Download DotNetRefCard.pdf

I have created a small quick reference card for Microsoft .NET with some hard to remember details. My printer supports two pages per side and double sided printing. This makes it possible to print the 4 pages on a single sheet which makes it easier to use.

Contents:

  • C# String escape sequence
  • C# Numeric & Char Literals
  • String Formatting
  • Standard Numeric Format Strings
  • Custom Numeric Format Strings
  • Standard DateTime Format Strings
  • Custom DateTime Format Strings
  • Regular Expressions

I hope you will find it useful.

Any suggestions and feedback for improving this reference card is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

READ MORE

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.