How can I build a single instance application using Click Once?
Asked Answered
F

4

15

I need to have a single instance application (as per this answer), but it needs to be deployed via click once.

The problem is that I require that click once doesn't automatically detect an update an attempt to load a newer version while the application is running. If it is running, then I need the other instance to be made active. Usually, when selecting a Click Once link, the very first thing it does is attempt to find an update. I want to intercept this and check for an already running instance prior to launching the normal update process.

Does anyone know how this is possible within a Click Once deployment scenario?

Funambulist answered 29/10, 2008 at 22:56 Comment(1)
"bool ok" is always set to true when I open the application more than once.Goodwin
U
22

To tackle the problem, we built a prototype application which has the following two functionalities.

  1. Multiple instances on one pc are disabled. A single instance application is deployed via clickonce. When a user tries to start a second instance of the app, a message will pop up indicating that "Another instance is already running".

  2. Checks for an update asynchronously, and installs the update if one exists. A message: "An update is available" will pop up if there is an update available when a user runs a new instance.

The process to build the demo application is as follows:

Step 1: Detect an active instance application using Mutex class.

namespace ClickOnceDemo
{
    static class Program
    {
        /// summary>
        /// The main entry point for the application.
        /// /summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault( false );
            bool ok;
            var m = new System.Threading.Mutex( true, "Application", out ok );
            if ( !ok )
            {
                MessageBox.Show( "Another instance is already running.", ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString() );
                return;
            }
           Application.Run( new UpdateProgress() );
        }
    }
}

Step 2: Handle update programmatically

Before we do that, we should disable the automatic ClickOnce update checking (in the Publish -- Updates... dialog).

Then we create two forms: UpdateProgress and mainForm, where UpdateProgress indicates download progress and mainForm represents the main application.

When a user runs the application, updateProgress will be launched firstly to check for updates. When updating completes, mainForm will start and updateProgress will be hidden.

namespace ClickOnceDemo
{
public partial class UpdateProgress : Form
 {
  public UpdateProgress()
        {
            InitializeComponent();
            Text = "Checking for updates...";

            ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
            ad.CheckForUpdateCompleted += OnCheckForUpdateCompleted;
            ad.CheckForUpdateProgressChanged += OnCheckForUpdateProgressChanged;

            ad.CheckForUpdateAsync();
       }

        private void OnCheckForUpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e)
        {
            lblStatus.Text = String.Format( "Downloading: {0}. {1:D}K of {2:D}K downloaded.", GetProgressString( e.State ), e.BytesCompleted / 1024, e.BytesTotal / 1024 );
            progressBar1.Value = e.ProgressPercentage;
        }

        string GetProgressString( DeploymentProgressState state )
        {
            if ( state == DeploymentProgressState.DownloadingApplicationFiles )
            {
                return "application files";
            }
            if ( state == DeploymentProgressState.DownloadingApplicationInformation )
            {
                return "application manifest";
            }
            return "deployment manifest";
        }

        private void OnCheckForUpdateCompleted(object sender, CheckForUpdateCompletedEventArgs e)
        {
            if ( e.Error != null )
            {
                MessageBox.Show( "ERROR: Could not retrieve new version of the application. Reason: \n" + e.Error.Message + "\nPlease report this error to the system administrator." );
                return;
            }
            if ( e.Cancelled )
            {
                MessageBox.Show( "The update was cancelled." );
            }

            // Ask the user if they would like to update the application now.
            if ( e.UpdateAvailable )
            {
                if ( !e.IsUpdateRequired )
                {
                    long updateSize = e.UpdateSizeBytes;
                    DialogResult dr = MessageBox.Show( string.Format("An update ({0}K) is available. Would you like to update the application now?", updateSize/1024), "Update Available", MessageBoxButtons.OKCancel );
                    if ( DialogResult.OK == dr )
                    {
                        BeginUpdate();
                    }
                }
                else
                {
                    MessageBox.Show( "A mandatory update is available for your application. We will install the update now, after which we will save all of your in-progress data and restart your application." );
                    BeginUpdate();
                }
            }
            else
            {
                ShowMainForm();
            }
        }

        // Show the main application form
        private void ShowMainForm()
        {
            MainForm mainForm = new MainForm ();
            mainForm.Show();
            Hide();
        }

        private void BeginUpdate()
        {
            Text = "Downloading update...";
            ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
            ad.UpdateCompleted += ad_UpdateCompleted;
            ad.UpdateProgressChanged += ad_UpdateProgressChanged;

            ad.UpdateAsync();
        }

        void ad_UpdateProgressChanged( object sender, DeploymentProgressChangedEventArgs e )
        {
            String progressText = String.Format( "{0:D}K out of {1:D}K downloaded - {2:D}% complete", e.BytesCompleted / 1024, e.BytesTotal / 1024, e.ProgressPercentage );
            progressBar1.Value = e.ProgressPercentage;
            lblStatus.Text = progressText;
        }

        void ad_UpdateCompleted( object sender, AsyncCompletedEventArgs e )
        {
            if ( e.Cancelled )
            {
                MessageBox.Show( "The update of the application's latest version was cancelled." );
                return;
            }
            if ( e.Error != null )
            {
                MessageBox.Show( "ERROR: Could not install the latest version of the application. Reason: \n" + e.Error.Message + "\nPlease report this error to the system administrator." );
                return;
            }

            DialogResult dr = MessageBox.Show( "The application has been updated. Restart? (If you do not restart now, the new version will not take effect until after you quit and launch the application again.)", "Restart Application", MessageBoxButtons.OKCancel );
            if ( DialogResult.OK == dr )
            {
                Application.Restart();
            }
            else
            {
                ShowMainForm();
            }
        }
    }
}

The application works well and we hope it is a good solution for the problem.
Special thanks to Timothy Walters for providing the source code

Undershot answered 27/11, 2008 at 5:16 Comment(0)
R
5

Sure - you can disable the automatic the ClickOnce update checking (in the Publish -> Updates.. dialog), then use the objects and commands in the System.Deployment.Application namespace to pragmatically check for updates.

Check out:

If there is an update, you can do your single instance application checks before actually updating, by calling:

Rugose answered 29/10, 2008 at 23:53 Comment(0)
I
0

I don't think you'll be able to do it quite like this as the check before run is outside of your code.

However you can change the clickonce deployment options to check for updates during code execution.

If you need more control then you can use the ApplicationDeployment Update or CheckForUpdate methods to have absolute over the update process.

Incursion answered 29/10, 2008 at 23:4 Comment(0)
S
0

I used http://wpfsingleinstance.codeplex.com/ in my WPF ClickOnce application with great success. I did not have to change anything.

Selfregulated answered 3/9, 2012 at 17:37 Comment(1)
Where do I add this line and what how do I declare a "dummy enum"?Portfire

© 2022 - 2024 — McMap. All rights reserved.