Introduction
I was asked to create a small reminder application for an organization I belong to. Ever hour it would prompt with a quote. My "work" answer for something like this would be to create a Windows service, make it self installing, easy peazy. But I wanted to be adventurous, because this is mostly U/I and I didn't want to mess with the service user account, how could this be done without a service? This article outlines this adventure...
Background
The basic components to this project are:
- Windows (WinForms in this case, but WPF will work too),
OnClose
and Timers
- Sockets/Listeners for forcing single instance and "killing" the application
- InnoSetup Script
Using the Code
- Create a new Windows project (New Project... Windows)
- Create a timer. Drag and drop a timer object, set the parameters, create a timer handler. Your project may not be on a timer, that's ok, if an event triggers the window to show/hide, that's all you want
- Create a "Force Close" mechanism, I created a
checkbox
called "CanClose
"
- Override
FormClosing
private void TheMessage_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = !CanClose.Checked;
this.Hide();
}
- Create a separate
ForceClose()
method which sets CanClose
and calls "Close
"
public void ForceClose()
{
CanClose.Checked = true;
Close();
}
- Sockets and threading. This is where it gets interesting. So I wanted the behavior that the application should be single instance. There are several good ways to do that, the mutex here on CodeProject is great. But, I also needed it to install/uninstall cleanly, which means it needs to force itself closed. To do that, I used sockets listener/client on a dedicated port. It sounds counter intuitive, but I created the client first (is that the chicken or the egg?!)
public void Main(string[] args){
...
bool killStream = args.Length > 0 && args[0] == "-kill";
try
{
using (TcpClient clnt = new TcpClient())
{
clnt.Connect("localhost", 7231);
if (clnt.Connected)
{
clnt.GetStream().Write(new byte[]
{ (byte)(killStream ? 0 : 1) }, 0, 1); clnt.Close();
return;
}
}
}
catch (SocketException) { }
if (killStream) return;
Finally the listener...
TcpListener lstn = new TcpListener
(System.Net.IPAddress.Parse("127.0.0.1"), 7231);
lstn.Start();
while (true)
{
using (TcpClient clnt = lstn.AcceptTcpClient())
{
byte[] buff = new byte[1];
if (1 == clnt.GetStream().Read(buff, 0, 1))
{
switch (buff[0])
{
case 0: msgWindow.ForceClose();
return;
case 1: msgWindow.Show();
continue;
}
}
}
}
- And finally, you need an InnoSetup Script that will invoke this before attempting to re-install/uninstall, that looks like this...
[Files]
Source: "{Your Project}\bin\Release\AntiService.exe"; DestDir: "{app}";
Flags: ignoreversion; BeforeInstall: StopLBApp
...
[Code]
procedure StopLBApp();
var
FileName : String;
ResultCode: Integer;
begin
Log('Asking any existing processes to stop now');
FileName := ExpandConstant('{app}\AntiService.exe');
if FileExists(FileName) then
begin
if not ShellExec('', FileName, '-kill', '',
SW_SHOWNORMAL, ewNoWait, ResultCode) then
MsgBox('DeinitializeSetup:' #13#13 'Execution of ''' +
FileName + ''' failed. ' + SysErrorMessage(ResultCode) + '.',
mbError, MB_OK);
end;
end;
Summary
So what you have is an application that once it's started, will stay running, and will attempt to show a window every time it's launched or on some event. Timer in my case. I hope you have enjoyed!
History
- 9th September, 2010: Initial post