Second Stanza

October 19, 2008

Using the Report Viewer Component with .RDL Files

Filed under: .NET Examples, SQL Server — Tags: , — dfbaskin @ 9:30 pm

You can use Visual Studio to create Report Definition Language (.RDL) files that can be used within SQL Server Reporting Services. The editor for .RDL files allows you to put together the SQL Query, design the report layout, and preview the report all within Visual Studio.

Microsoft has also provided a Report Viewer control that allows you to display reports within your application. This control is not tied to SQL Server since you can use your own data sources as input to the report (though it can also display reports that originate from SQL Server Reporting Services). Reports are defined using the .RDLC format, which is the same format as .RDL files, but ignores certains aspects of the definitions (more details here).

Of course, since a data source can be defined within a Visual Studio project, you lose the ability to preview your report easily. Generally, you have to compile and run the application in order to view your report and verify that it is correct. Being able to quickly view your report output was a nice feature of working with the SQL Server Reporting Services version of the report editor.

However, since the .RDL and .RDLC formats are the same, you can use an .RDL specification as input to the Report Viewer control. All you need to do is to implement some code that runs the query embedded in the .RDL file. By doing so, you may use the Visual Studio 2005 version of the Reporting Services editor (with its handy preview mode) but still use the Report Viewer component within your application.

Here’s source code that shows this technique:



using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using Microsoft.Reporting.WinForms;

namespace ReportView
{
    static class Program
    {
        [STAThread]
        static void Main( string[] argList )
        {
            try
            {
                if( argList.Length != 1 )
                    throw new ArgumentException( "Must specify name of .RDL file." );
                string rdlFileName = argList[0];
                if( !File.Exists( rdlFileName ) )
                    throw new FileNotFoundException( "Could not find .RDL file." );

                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault( false );
                Application.Run( new ReportViewForm( rdlFileName ) );
            }
            catch( Exception ex )
            {
                StringBuilder errMsg = new StringBuilder();
                errMsg.Append( "Application Error generated:\n" );
                for( Exception x = ex; x != null; x = x.InnerException )
                    errMsg.AppendFormat( "   {0} - {1}\n", x.GetType().FullName, x.Message );
                errMsg.AppendFormat( "Stack Trace:\n{0}", ex.StackTrace );
                MessageBox.Show( errMsg.ToString(), "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Error );
            }
        }
    }

    class ReportViewForm : Form
    {
        private Microsoft.Reporting.WinForms.ReportViewer rptView;
        private string reportFileName;

        public ReportViewForm( string fileName )
        {
            reportFileName = fileName;
            rptView = new Microsoft.Reporting.WinForms.ReportViewer();

            SuspendLayout();

            rptView.Dock = System.Windows.Forms.DockStyle.Fill;
            rptView.Name = "rptView";
            rptView.TabIndex = 0;
            Controls.Add( rptView );

            const int border = 20;
            System.Drawing.Size winSize = Screen.PrimaryScreen.WorkingArea.Size;
            Location = new System.Drawing.Point( border, border );
            Size = new System.Drawing.Size( winSize.Width - ( 2 * border ), winSize.Height - ( 2 * border ) );
            StartPosition = FormStartPosition.Manual;
            Name = "ReportViewForm";
            Text = Path.GetFileName( reportFileName );

            ResumeLayout( false );
        }

        const string ReportDefinitionNS = "http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition";
        const string ReportDesignerNS = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";

        protected override void OnLoad( EventArgs e )
        {
            base.OnLoad( e );

            rptView.ProcessingMode = ProcessingMode.Local;
            using( Stream readStream = new FileStream( reportFileName, FileMode.Open, FileAccess.Read, FileShare.Read ) )
                rptView.LocalReport.LoadReportDefinition( readStream );

            XPathDocument xpDoc;
            using( Stream readStream = new FileStream( reportFileName, FileMode.Open, FileAccess.Read, FileShare.Read ) )
                xpDoc = new XPathDocument( readStream );

            ConnectionStringSettings connSett = ConfigurationManager.ConnectionStrings["Default"];
            using( SqlConnection sqlConn = new SqlConnection( connSett.ConnectionString ) )
            {
                sqlConn.Open();

                XPathNavigator xpNav = xpDoc.CreateNavigator();
                XPathExpression xpe = xpNav.Compile( "/r:Report/r:DataSets/r:DataSet" );
                XmlNamespaceManager nsMgr = new XmlNamespaceManager( xpNav.NameTable );
                nsMgr.AddNamespace( "r", ReportDefinitionNS );
                nsMgr.AddNamespace( "rd", ReportDesignerNS );
                xpe.SetContext( nsMgr );
                foreach( XPathNavigator xpNode in xpNav.Select( xpe ) )
                {
                    string dsName = Convert.ToString( xpNode.Evaluate( "string( @Name )", nsMgr ) );
                    string cmdTxt = Convert.ToString( xpNode.Evaluate( "string( r:Query/r:CommandText )", nsMgr ) );
                    if( String.IsNullOrEmpty( dsName ) || String.IsNullOrEmpty( cmdTxt ) )
                        throw new Exception( "Could not obtain query for report definition." );

                    SqlCommand sqlCmd = sqlConn.CreateCommand();
                    sqlCmd.CommandType = CommandType.Text;
                    sqlCmd.CommandText = cmdTxt;

                    DataTable dataTable = new DataTable();
                    SqlDataAdapter sqlAdapter = new SqlDataAdapter( sqlCmd );
                    sqlAdapter.Fill( dataTable );
                    rptView.LocalReport.DataSources.Add( new ReportDataSource( dsName, dataTable ) );
                }
            }

            rptView.RefreshReport();
        }
    }
}


February 28, 2008

Single Instance Windows Form Application

Filed under: .NET Examples, GUI — Tags: , , — dfbaskin @ 1:10 am

If you have a .NET Windows Form application where you only want a single instance to ever be running, you can do this by using two pieces of functionality, a global mutex and a .NET remoting call. The global mutex is used to determine if another instance of the application is already running. This solves part of the problem, but it is helpful to the end user to show them the initial instance of the application. This is where the .NET remoting call comes in. The new instance uses a .NET remoting call to the initial instance of the application to request that it show itself.

Here’s a simplified example of a single instance Windows Form application:


using System;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Security.Permissions;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.InteropServices;

namespace SingleInstanceWebForm
{
    public interface IShowApplication
    {
        void ShowApp();
    }

    static class Program
    {
        public const string ApplicationMutexName = "SingleInstanceForm.Mutex";
        public const string IpcChannelName = "SingleInstanceForm.Channel";
        public const string ServiceName = "ShowApp";

        private static SingleInstanceForm formInstance;

        [SecurityPermission( SecurityAction.Demand )]
        private class RemotingObject : MarshalByRefObject, IShowApplication
        {
            void IShowApplication.ShowApp()
            {
                formInstance.ShowApp();
            }
        }

        [SecurityPermission( SecurityAction.Demand )]
        private class RemotingServer : IDisposable
        {
            private IpcChannel ipcServer;

            public RemotingServer()
            {
                this.ipcServer = new IpcChannel( Program.IpcChannelName );
                ChannelServices.RegisterChannel( this.ipcServer, false );

                RemotingConfiguration.ApplicationName = Program.IpcChannelName;
                RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemotingObject ),
                                                                    Program.ServiceName,
                                                                    WellKnownObjectMode.Singleton );
            }

            public void Dispose()
            {
                ChannelServices.UnregisterChannel( this.ipcServer );
            }
        }

        [SecurityPermission( SecurityAction.Demand )]
        private class RemotingClient : IDisposable
        {
            public static string IpcChannelAddress
            {
                get { return String.Format( "ipc://{0}/{1}", IpcChannelName, ServiceName ); }
            }

            private IpcChannel ipcClient;

            public RemotingClient()
            {
                ipcClient = new IpcChannel();
                ChannelServices.RegisterChannel( ipcClient, false );
            }

            public T GetInterface<T>() where T : class
            {
                T objInst = Activator.GetObject( typeof( T ), IpcChannelAddress ) as T;
                if( objInst == null )
                    throw new ArgumentException( String.Format( "Error trying to get instance of {0} object.", typeof( T ).FullName ) );
                return objInst;
            }

            public void Dispose()
            {
                ChannelServices.UnregisterChannel( this.ipcClient );
            }
        }

        [STAThread]
        static void Main()
        {
            try
            {
                bool newMutex;
                using( Mutex appMutex = new Mutex( true, Program.ApplicationMutexName, out newMutex ) )
                {
                    if( newMutex )
                    {
                        using( new RemotingServer() )
                        {
                            Application.EnableVisualStyles();
                            Application.SetCompatibleTextRenderingDefault( false );
                            Application.Run( formInstance = new SingleInstanceForm() );
                        }
                    }
                    else
                    {
                        using( RemotingClient remClient = new RemotingClient() )
                        {
                            IShowApplication showApp = remClient.GetInterface<IShowApplication>();
                            showApp.ShowApp();
                        }
                    }
                }
            }
            catch( Exception ex )
            {
                StringBuilder errTxt = new StringBuilder();
                for( Exception x=ex; x != null; x = x.InnerException )
                    errTxt.AppendFormat( "{0} - {1}\n", x.GetType().FullName, x.Message );
                errTxt.AppendFormat( "Stack Trace:\n{0}", ex.StackTrace );
                MessageBox.Show( errTxt.ToString(), "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Error );
            }
        }
    }

    class SingleInstanceForm : Form, IShowApplication
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct FLASHWINFO
        {
            public UInt32 cbSize;
            public IntPtr hwnd;
            public UInt32 dwFlags;
            public UInt32 uCount;
            public UInt32 dwTimeout;
        }

        [DllImport("user32.dll")]
        static extern Int32 FlashWindowEx( ref FLASHWINFO pwfi );

        [System.Flags]
        public enum FLASHW_FLAGS
        {
            FLASHW_ALL = 0x00000003,
            FLASHW_CAPTION = 0x00000001,
            FLASHW_STOP = 0x00000000,
            FLASHW_TIMER = 0x00000004,
            FLASHW_TIMERNOFG = 0x0000000C,
            FLASHW_TRAY = 0x00000002
        }

        private Label txtBox;
        private int initCounter;

        public SingleInstanceForm()
        {
            Text = "Single Instance Form";
            StartPosition = FormStartPosition.CenterScreen;
            Size = new System.Drawing.Size( 400, 300 );

            Controls.Add( this.txtBox = new Label() );
            this.txtBox.Dock = DockStyle.Fill;
            this.txtBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            this.txtBox.Text = "Initialized.";
            this.txtBox.Font = new System.Drawing.Font( "Verdana", 22, System.Drawing.FontStyle.Bold );
        }

        public void ShowApp()
        {
            if( InvokeRequired )
                BeginInvoke( new MethodInvoker( delegate() { ShowApp(); } ) );
            else
            {
                Activate();

                FLASHWINFO flashInfo = new FLASHWINFO();
                flashInfo.cbSize = Convert.ToUInt32( Marshal.SizeOf( flashInfo ) );
                flashInfo.hwnd = Handle;
                flashInfo.dwFlags = Convert.ToUInt32( FLASHW_FLAGS.FLASHW_ALL );
                flashInfo.uCount = 5;
                flashInfo.dwTimeout = 500;
                FlashWindowEx( ref flashInfo );

                this.txtBox.Text = String.Format( "Called {0} time(s).", ++this.initCounter );
            }
        }
    }
}

The FlashWindowEx() function is used to help the user find the window by flashing both the window itself and the task bar button. I’m not sure why this function is not included in the .NET framework, but we can use P/Invoke to access it.

The .NET remoting call uses an IpcChannel, which is a remoting channel that uses the Windows Interprocess Communications system. I couldn’t find documentation about the format of the channel name for an IpcChannel, but other references (such as here) indicate that the name is in the form, “ipc://ChannelName/ServiceName”.

Also note that the MethodInvoker delegate is part of the .NET framework and serves as a general-purpose delegate where no parameters are passed to the method and no return value is expected. Here it is helpful to make sure the Form object is updated on the correct thread when the request to show the application is received.

February 18, 2008

Paged and Sorted ASP.NET Data Grids

Filed under: .NET Examples, ASP.NET — Tags: , , — dfbaskin @ 7:35 pm

The ASP.NET class, ObjectDataSource, performs most of the dirty work of coordinating activity between a data source and a data grid presented on an web page, including handling sorting and paging issues.

When the data source is SQL Server, it is, of course, more efficient to return only the subset of data to be displayed, rather than returning the entire data set to the ASP.NET application and extracting the subset there. One technique to handle this scenario, is to make use of a temporary table containing the index to the records in the order they are to be displayed to the user. Then a query and a join can be used to extract the subset of records for the page of records currently displayed within the grid.

The source code below demonstrates this technique using the AdventureWorks sample database included with SQL Server 2005. This code includes a data grid using the GridView web control and one using Infragistic’s UltraWebGrid control. Note that you can sort by any of the column headings and page through the sorted list without coding additional sorting or paging functionality.

Download / View Source Code
(View requires Microsoft Silverlight 1.0 and Internet Explorer)

January 13, 2008

Combination Console and GUI .NET App

Filed under: .NET Examples — Tags: , — dfbaskin @ 9:57 pm

Here’s a simple example of how to create a .NET application that can be run either as a console application or a GUI application.


using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace ConsoleGUIApp
{
    static class Program
    {
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        private static extern bool FreeConsole();

        [STAThread]
        static void Main( string[] argLst )
        {
            if(( argLst.Length > 0 ) && Regex.IsMatch( argLst[0], "^[-/]gui$", RegexOptions.IgnoreCase ))
            {
                FreeConsole();
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault( false );
                Application.Run( new GUIMode() );
            }
            else
            {
                ConsoleMode app = new ConsoleMode();
                app.Run();
            }
        }
    }

    public class ConsoleMode
    {
        public void Run()
        {
            Console.WriteLine( "Running from console ..." );
        }
    }

    public class GUIMode : Form
    {
        public GUIMode()
        {
            Text = "My GUI Form";
        }
    }
}

Make sure you compile the source as a “Console” application. By default, the application will run in console mode.  Add the “/gui” option to the command to run in GUI mode.

Blog at WordPress.com.