Second Stanza

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.

Advertisement

February 21, 2008

Team Foundation Server Installation Problem

Filed under: Team Foundation Server — Tags: — dfbaskin @ 5:03 pm

While setting up Team Foundation Server 2008 in a dual-server configuration, the installation stopped towards the end with the error message:

Error 28805: The setup program cannot complete the request to the server that is running SQL Server Reporting Services. Verify that SQL Server Reporting Services is installed and running on the Team Foundation app tier and that you have sufficient permissions to access it. For more information, see the setup log.

The actual error recorded in the setup log was:

Cannot finish the request on Microsoft SQL Server Reporting Server. Please check that Microsoft SQL Server Reporting Services and Windows service are running and you have sufficient privileges on the data tier:
System.Web.Services.Protocols.SoapException: The report server installation is not initialized. (rsReportServerNotActivated) —> The report server installation is not initialized. (rsReportServerNotActivated)
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object parameters)
at Microsoft.TeamFoundation.Proxy.Reporting.ReportingService.CreateDataSource(String DataSource, String Parent, Boolean Overwrite, DataSourceDefinition Definition, Property Properties)
at Microsoft.TeamFoundation.Client.Tools.createds.Program.Run(String args)

Data source creation failed.

So, according to the installation instructions, the TFS installation is supposed to initialize (and I assume activate) the report server.

So what was the problem? It turns out that to fix it, I had to go into Report Services Configuration Manager, and on the Encryption Keys tab, I had to select Delete. After that, the installation continued on with no problem. Go figure.

February 20, 2008

SharePoint Errors

Filed under: SharePoint — Tags: , — dfbaskin @ 3:27 pm

I had been getting the following two errors in the SharePoint server’s event logs:

Event Type:     Error 
Event Source:   Windows SharePoint Services 3 Search 
Event Category: Gatherer 
Event ID:       2424 
Description: 
The update cannot be started because the content sources cannot be 
accessed. Fix the errors and try the update again.

and

Event Type:     Error 
Event Source:   DCOM 
Event Category: None 
Event ID:       10016 
Description: 
The application-specific permission settings do not grant 
Local Activation permission for the COM Server application with CLSID 
{61738644-F196-11D0-9953-00C04FD919C1} to the user ....  
This security permission can be modified using the Component Services 
administrative tool.

The first error appears to be fixed using the technique described here:

http://guru-web.blogspot.com/2007/08/wss-30-content-indexing-update-cannot.html

The second error was fixed by using the technique described here:

http://geekswithblogs.net/mhamilton/archive/2006/12/19/101568.aspx

(the GUID in the error message corresponds to the “IIS WAMREG admin service”). A knowledgebase article, http://support.microsoft.com/kb/920783, regarding this issue says that you can safely ignore this issue, but it does give you the method to stop receiving the messages.

February 19, 2008

SharePoint Flash Audio Player Custom Web Part

Filed under: SharePoint — Tags: , — dfbaskin @ 10:31 pm

I needed to include lecture audio in a SharePoint (v3) site that I manage, using Flash as the audio player. At this point, SharePoint doesn’t include any native support for Flash files, but does allow you to create your own custom controls. Fortunately, the Developer’s Guide To Windows SharePoint Services 3.0 gives a good overview of creating a custom control (see Chapter 10, Custom Web Parts).

First, I used the MP3 to SWF Convertor (from hootech.com) to create the Flash audio player.  This utility creates two files, one containing the audio itself and another containing the player component. Both of the files should be put into a documents collection within SharePoint. (I realized later that the player component will be the same for each conversion, if the same settings are used, so you really only need one copy of the player, even though the conversion program creates a new copy for each MP3 file converted. So I end up duplicating the player for each audio file, even thought I really only needed a single player.)

Next, I installed the custom SharePoint component and created a list that contained a field built on my custom web part, which references the Flash files in my documents collection.

When editing the list item, the web part generates HTML code that allows you to enter the four pieces of information about the audio, the SharePoint URL of the Flash player component, the duration of the audio, and the width and height of the player control:

 Web part in edit mode.

When the item is viewed, then the web part generates the necessary HTML for displaying the Flash audio player:

Web part in Play mode.

The web part removes the “.player” portion of the URL to get the URL of the audio file.

This web part is a fairly crude solution since it is tied to the output of the MP3/Flash convertor and requires the user to enter the dimensions and duration of the audio, but it works nicely for my needs.

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

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)

February 5, 2008

Silverlight 1.0 Source Code Viewer

Filed under: Silverlight — Tags: , , — dfbaskin @ 8:48 pm

One of the purposes for this blog is to post source code that might be helpful to the development community. I wanted to create a simple viewer for the source code I will be posting (I get tired of downloading .zip files just to look at the two lines of code I’m interested in).

Also, being interested in Microsoft’s Silverlight platform, implementing a source code viewer using this new technology would be a good introduction to this platform.

So here’s a first whack at the viewer implemented in Silverlight 1.0. Unfortunately, it requires Internet Explorer at this point. FireFox has an issue with the <!DOCTYPE> statement and I need to update the javascript event handling and DOM methods to support FireFox. Opera, at this point, doesn’t support Silverlight.

Of course, Silverlight 1.0 is pretty basic. It doesn’t contain any form-type controls (like a text box control) and I ended up writing a lot more Javascript than I wanted to. I’m looking forward to the feature set of Silverlight 2.0, including making use of .NET languages and having a wrapper around the DOM object model.

However, the bottom line is that Silverlight 1.0 is still pretty capable. The implementation requires downloading a single zip file from the server, which contains the HTML-formatted source code and an index file. The formatted code is generated off-line using GeSHi. A separate zip file contains the original source code.

I hope to improve this with Silverlight 2.0 by eliminating the HTML-formated zip file and perform the syntax highlighting at the client.

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

Blog at WordPress.com.