Analytics


Google
Showing posts with label Windows App. Show all posts
Showing posts with label Windows App. Show all posts

Friday, February 17, 2012

A Step by Step method to place the .Net application in the system tray

This is a snippet.  The original article is found here.  Another solution is found here.


  1. To get started, open an existing C# Windows form (or create a new one). 
  2. Open the Visual Studio Toolbox. 
  3. Drag a NotifyIcon control onto the form. The control will named notifyIcon1 by default and placed below the form because it has no visual representation on the form itself. 
  4. Set the NotifyIcon control's Text property to the name you want to appear when the user pauses the mouse over the application's icon. For example, this value could be "KillerApp 1.0". 
  5. Set the control's Icon property to the icon that you want to appear in the System Tray. 
  6. Add an event handler for the form's Resize event that will hide the application when it's minimized. That way, it won't appear on the task bar. 
private void Form1_Resize(object sender, System.EventArgs e)
{
   if (FormWindowState.Minimized == WindowState)
      Hide();
}

Add an event handler for the NotifyIcon.DoubleClick event and code it as follows so that the application will be restored when the icon is double-clicked.

private void notifyIcon1_DoubleClick(object sender,
                                     System.EventArgs e)
{
    Show();
    WindowState = FormWindowState.Normal;
}



Adding a Context Menu
  1. From the Visual Studio Toolbox, drag a ContextMenu control onto the form.
  2. Right-click the ContextMenu control and select the Edit Menu.option.
  3. Type in the options that you want to appear in your context menu. For example, you can add options such as Restore and Close Application.  As with any menu, double-click the menu item to create and code each item's handler. As an example, you could copy the code from the form's DoubleClick handler into the context menu's Restore handler and for the Close Application menu item; simply call the form's Close method.  
  4. Finally, set the NotifyIcon control's ContextMenu property to the new context menu you just created by selecting the menu from the drop-down list. Figure 2 shows a simple Tray context menu.




Additional notes from the comments in the article:


-->
And if you do not want show the icon when the application run but show it when it be hidden, you can set the

notifyicon.visible = false.

and when the form Minimized

set notifyicon.visble = true

and when in the double click event handler, set the visible to false again.


-->

to capture the X button, use the _FormClosing event.

private void _FormClosing(object sender, FormClosingEventArgs args)

Make sure you check the reason matches the user hitting the X:

if (args.CloseReason == CloseReason.UserClosing)


-->

The default setting for this check box is cleared, allowing multiple instances of the application to be run.  You can do this by following the steps below:



  1. Right-Click on MyProject inside SolutionExplorer --> Open
  2. Select Application tab
  3. Set the Make single instance application checkbox Checked/Unchecked as per the requirement.




Wednesday, November 3, 2010

Screen capture utility

Windows 7 comes with a built in screen capture utility unless you are using the started edition.  For those using earlier version of Windows you have to find your own.

The following is a good freeware to perform screen capture:

http://getgreenshot.org/

Previously, I was using Printkey but this one looks more stable.

Friday, April 17, 2009

Installing Netmeeting on Windows Vista

Netmeeting no longer comes with Vista. However, a lot of us still need to communicate with our counterparts using older windows.

The following site provides the instruction and links on how to install it on Vista:

http://support.galileo.com/SGC/GSC/Remote+Access/Netmeeting+for+Windows+Vista.htm

Here is the link to the actual Microsoft Website for the patch:

http://support.microsoft.com/kb/927853

Here is another article indicating they were able to install without the patch using compatible mode:

http://www.vistax64.com/vista-general/29349-compatibility-netmeeting.html

Tuesday, July 1, 2008

Migrating scheduled Task

In one of my previous articles, I discussed Application Scheduling using Windows Scheduler. When it comes time to migrate to a new server or to upgrade to another version of the OS, it can be a pain to recreate all the scheduled task.

The quickest way to do that is to go to the command prompt and then change folder to
c:\windows\tasks


(assuming you installed windows to c:\windows) and then copy all the .job files out to another folder. You can copy this to any folder or even your pen drive. You can then go to the new server and copy over all these files to the c:\windows\tasks folder of the new server. Assuming the new server has the same folder structure, username and password, and you have copied/installed all the required files/programs over to the new server, you just need to right click and then enable all those jobs.

Be careful with the .job files though since it contains the username and password setting in it. I am not sure if you can get the password from it.

Monday, May 19, 2008

Application Scheduling

Sometimes it is necessary to perform activities on regular intervals such as:
  1. Sending daily email alert.
  2. Compiling wip @ a specific time.
  3. Compiling yield or transactions either shiftly or daily.
This can be easily done by writing a console (non GUI) application and then scheduling the application to run at regular intervals. With Windows 2000, Windows XP, and Windows 2003, you can use the Windows scheduler.

The Windows scheduler was first introduced in Windows NT but it did not provide a nice interface for adding to and viewing the schedule (using AT command - similar to the command available in Unix). This has since been improved.

Nice thing about the Windows Scheduler is you can set it to run as different users. Typically, most will run under Admin account. However, note that when it runs, the screen will only display in the console.

One of the benefit, Windows Scheduler has over the Unix (cronjob) is that it has a built-in mechanism that will ensure only a copy of the schedule item will run at any time. While Unix does not do that. For example, you may schedule extractData and schedule it to run every 3 minutes. In Unix, if the extract takes more than 3 minutes, you may end up with multiple copies running at the same time. This will not happen if scheduled in Window scheduler.

There are also some failsafe such as automatically abort if the job takes more than a specified amount of time.

Finally when we schedule a job we typically expect it to run automatically. There are some additional precaution we should take:
  • have a log file output so that we can trace when there are problems. Easiest way is to redirect the output of the program (for example extractData >> c:\log\extractData.log)
  • Have the application generate a heartbeat file each time it completes successfully and then have another program monitor the heartbeat file modified time. If the heartbeat update time exceeds a threshold, just have the application send an alert.

Wednesday, May 7, 2008

.Net IDE

When I started work with .Net several years back, I started out with Web Matrix. This only supports Framework 1.1 but it was sufficient to get me working, however, it lacks the intellisense. Then I found other free IDEs that help with my work such as SharpDeveloper. This was better and it provide a limited intellisense capability. SharpDeveloper had its own project files so was in a way similar to Visual Studio. However, the problem I had with it was if I was to add reference to an assembly it memorizes it. So if I was to refer to my custom assembly and then decide to recompile that assembly, it was necessary to go and remove it from the reference.

Snippet Compiler was also a very nice IDE which again provide some limited intellisense. However, when Framework 2.0 was released, majority of the IDEs broke.

For a time, I could not find any good IDE but finally Microsoft came out with the express edition for Visual Studio 2005. Currently there is also a Express edition for Visual Studio 2008. The express edition of Visual Studio provided most of the functionalities of Visual Studio with some limitations.

Main limitations are:
  • Instead of everything in one, you have different packages
    • Web design
    • Windows/Console app (1 for VB and 1 for C# and 1 for C++)
  • WorkFlow capability is removed from VS 2008 express.
However, if you are just looking for a good IDE to help with writing the code then I think it is more then sufficient. I love using the ability to open a folder as a website and then just writing the code. This is useful when I want to do some quick programming without bothering with projects and solutions. You can then use either command prompt to compile or use a tool called NANT to compile your codes.

Wednesday, April 23, 2008

User Controls - ASP and Windows Application

User Controls is a wonderful way to reduce repetitive code. Take for example if you have an application that requires multiple approvers. What do you need for each approver section?

Typically, you will need:
  1. Approver Name
  2. Approver ID
  3. Approve Button
  4. Reject Button
  5. Reason for Rejecting
  6. Status (when in display mode)
You could add six controls into your application (either web or windows application) or declare one user control and then add an instance of the user control for each approver.

As for the approve and reject button, the user control can then raiseEvent to the parent page to process the event.

To declare an event in the user control, we need to declare the event and then raise the event when the button is clicked. E.g.

Public Event Approve

Private Sub appSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles appSubmit.Click
RaiseEvent Approve()
End Sub


Another potential use is a lucky draw display panel, where you may want to display multiple info such as:
  1. Employee ID
  2. Employee Name
  3. Dept
Other uses may in cases where multiple screen share the same information such as page header, login account information (I realize this can also be achieved through MasterPage which is introduced in Framework 2.0 and is only available for Web application).

So in the UserControl, we group multiple controls together. We then expose these controls through property so that we can set the values. In some cases, you can add logic such as
  • in the case of approval, do not allow the reject to be clicked unless the reject reason is filled.
  • hide the textbox for login and password and only display the label displaying the username after the person has already logged in. Also add the login logic within the user control so that we do not have to place logic in multiple pages or applications. (to do that we need to compile the code behind as a separate assembly).

Monday, April 21, 2008

Saving the Windows Form to image file programmatically

There is an article found here which shows how to programmatically save the Windows Form directly to an image file. The original code is as follows:

Const SRCCOPY As Integer = &HCC0020

Public Declare Function BitBlt Lib "gdi32" ( _
ByVal hDestDC As IntPtr, _
ByVal x As Integer, _
ByVal y As Integer, _
ByVal nWidth As Integer, _
ByVal nHeight As Integer, _
ByVal hSrcDC As IntPtr, _
ByVal xSrc As Integer, _
ByVal ySrc As Integer, _
ByVal dwRop As Integer _
) As Integer

'================================================= ==================
Public Function CaptureControl(ByVal c As Control) As Bitmap

Dim bmp As Bitmap
Dim gDest, gSource As Graphics
Dim hdcSource, hdcDest As IntPtr

bmp = New Bitmap(c.Width, c.Height)

gSource = c.CreateGraphics
Try
gDest = Graphics.FromImage(bmp)
Try
hdcSource = gSource.GetHdc
Try
hdcDest = gDest.GetHdc
Try
BitBlt( _
hdcDest, 0, 0, _
c.Width, c.Height, _
hdcSource, 0, 0, SRCCOPY _
)
Finally
gDest.ReleaseHdc(hdcDest)
End Try
Finally
gSource.ReleaseHdc(hdcSource)
End Try
Finally
gDest.Dispose()
End Try
Finally
gSource.Dispose()
End Try

Return bmp
End Function



However, I had to place an offset in order to get the image to capture correctly.

My revised code is as follows:


Const SRCCOPY As Integer = &HCC0020

Public Declare Function BitBlt Lib "gdi32" ( _
ByVal hDestDC As IntPtr, _
ByVal x As Integer, _
ByVal y As Integer, _
ByVal nWidth As Integer, _
ByVal nHeight As Integer, _
ByVal hSrcDC As IntPtr, _
ByVal xSrc As Integer, _
ByVal ySrc As Integer, _
ByVal dwRop As Integer _
) As Integer

'================================================= ==================
Public Function CaptureControl(ByVal c As Control) As Bitmap

Dim bmp As Bitmap
Dim gDest, gSource As Graphics
Dim hdcSource, hdcDest As IntPtr

bmp = New Bitmap(c.Width, c.Height)

gSource = c.CreateGraphics
Try
gDest = Graphics.FromImage(bmp)
Try
hdcSource = gSource.GetHdc
Try
hdcDest = gDest.GetHdc
Try
BitBlt( _
hdcDest, 0, 0, _
c.Width, c.Height, _
hdcSource, -3, -27, SRCCOPY _
)
Finally
gDest.ReleaseHdc(hdcDest)
End Try
Finally
gSource.ReleaseHdc(hdcSource)
End Try
Finally
gDest.Dispose()
End Try
Finally
gSource.Dispose()
End Try

Return bmp
End Function




To use the code:


Private Sub btnSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSave.Click

Dim x As Bitmap
Me.Activate()
x = CaptureControl(Me)
x.Save("c:\t2.bmp", Imaging.ImageFormat.Bmp)

End Sub


Thursday, April 10, 2008

Multi Lingual Windows Application

A very good article on how to perform Localization of Windows application can be found here. I have not tried to see how well it works.

Wednesday, April 9, 2008

Off line scalable computing

For our annual dinner, we need to create a windows application for registering employees as they enter the hall to be used to perform lucky draw. Since there is a large number of employees, we need to be able scale to multiple computers for scanning their badges.

We also want to simplify the requirement for the hardware so we do not want to install databases etc on the computer. We just want to have .Net Framework and our application on the computers. So I thought I will share an approach with you.

We assume that we will have a computers together and then use shared folders.

The following is a snapshot of the demo program:



If you key in input into one screen, it will be captured into the second screen. Each screen represents a different computer. In this example I used two folders.

This is the config file for the first computer:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ConName" value="DT01" />
<add key="monDir" value="c:\mon\drv1\" />
<add key="dirCnt" value="2" />
<add key="dir1" value="c:\mon\drv2\" />
<add key="dir2" value="c:\mon\drv3\" />
</appSettings>
</configuration>

This is the config file for the second computer:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ConName" value="DT02" />
<add key="monDir" value="c:\mon\drv2\" />
<add key="dirCnt" value="2" />
<add key="dir1" value="c:\mon\drv1\" />
<add key="dir2" value="c:\mon\drv3\" />
</appSettings>
</configuration>


In this case, each time we scan a badge in each computer, it will publish a text file to the folders in for the other computers (number of computers identified by the dirCnt. The application will also monitor any new files published by other computers to it - identified by monDir. ConName is the console name which identifies the computer name and is also used to create distinct file name when publishing.

For the sake of demo, I create two listbox. In order to support threadsafe updating of Windows Control I created a delegate (This was discussed in an earlier blog).

Delegate Sub SetTextCallback(ByVal [text] As String)

I have created two subroutine one for each listbox:


' Used to allow multiple threads to update the textbox.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.lbMsg.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.lbMsg.Items.Add([text])
chkLBSize()
End If
End Sub

' Used to allow multiple threads to update the textbox.
Private Sub SetText2(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.lbMsg2.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText2)
Me.Invoke(d, New Object() {[text]})
Else
Me.lbMsg2.Items.Add([text])
chkLBSize()
End If
End Sub


' To maintain the entries to lex then maxCnt
Private Sub chkLBSize()
Dim loopCnt, indx As Integer

If lbMsg.Items.Count > maxLen Then
indx = 0
For loopCnt = 1 To delNum
lbMsg.Items.RemoveAt(indx)
If lbMsg.Items.Count <= 0 Then
Exit For
End If
Next
End If

If lbMsg2.Items.Count > maxLen Then
indx = 0
For loopCnt = 1 To delNum
lbMsg2.Items.RemoveAt(indx)
If lbMsg2.Items.Count <= 0 Then
Exit For
End If
Next
End If
End Sub

ChkLBSize controls the number of rows maintained in each ListBox.

Next we have a global variable to facilitate stopping of the folder monitoring:

Dim StopMon As Boolean = False

We can have also some constants to determine how many rows to maintain in the listbox and how many rows to remove once the threshold is exceeded:

Const maxLen As Integer = 20
Const delNum As Integer = 5

We then have a button to start and a button to stop the folder monitoring:


Private Sub btnWatch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWatch.Click
Dim createThrd As Boolean = True

If IsNothing(monThrd) = False Then
If monThrd.IsAlive Then
createThrd = False
End If
End If
If createThrd Then

Monitor.Enter(StopMon)
StopMon = False
Monitor.Exit(StopMon)

Dim thrd As New Thread(AddressOf monFile)
thrd.Start()
monThrd = thrd
Else
SetText(Now & " - Monitoring already started")
End If
End Sub


Private Sub btnStopWatch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStopWatch.Click
Monitor.Enter(StopMon)
StopMon = True
Monitor.Exit(StopMon)
End Sub


The monitoring process is as follows:
    Private Sub monFile()
Dim monDir As String = AppSettings("monDir")

SetText(String.Format("{0} - Monitor started", Now))

While (True)
If StopMon Then
Exit While
End If
processDir(monDir)
Thread.Sleep(600)
End While
SetText(String.Format("{0} - Monitor Stopped", Now))

End Sub

Sub processDir(ByVal pDir As String)
Dim fName, fName2 As String
Dim fObj As File

If (Directory.Exists(pDir) = False) Then
SetText(String.Format("{0} - {1} directory does not exist", Now, pDir))
Else
For Each fName In Directory.GetFiles(pDir)
fName2 = Path.GetFileName(fName)
SetText(String.Format("{0} - process file {1} - name is {2}", Now, fName, fName2))
processFile(fName, fName2, pDir)
Next
End If

End Sub

Sub processFile(ByVal pFullFname As String, ByVal pFname As String, ByVal monDir As String)
Dim sr As StreamReader = New StreamReader(pFullFname)
Dim rline, tFolder, tFile As String

rline = sr.ReadLine
SetText2(rline)
sr.Close()

tFolder = monDir & "done\"
tFile = tFolder & pFname
File.Move(pFullFname, tFile)
SetText(String.Format("{0} - processed completed. Moved {1} to {2}", Now, pFullFname, tFile))

End Sub


Notice that the process is broken into three parts - can be combined if you want to. I prefer to do it this way. Go through a infinite loop and exit only when the StopMon is set to True. IThe processDir will then look up all the files, extract out the parts of the filename and pass the processing to the processFile. In processFile, I will read the file and then perform all the necessary process. In this example, it just display the output to the lbMsg2 by call SetText2.

As for the publishing, there is a button to submit the input from the TextBox:

Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSubmit.Click

' Locks vEntry so that other threads cannot access
Monitor.Enter(vEntry)
vEntry = txtEntry.Text
' Unlock vEntry after use
Monitor.Exit(vEntry)
spawnThrd()

SetText2(txtEntry.Text)
End Sub


In this case, I use a global variable vEntry. Monitor is used to lock the string object when updating the value. This value is then used to write to the files. The following generates the files:
    Sub spawnThrd()
Dim thrd As New Thread(AddressOf creFile)
thrd.Start()
End Sub

Sub creFile()
Dim fCnt, vFCnt, loopCnt As Integer
Dim vMsg, fName, fWriteDir, fMoveDir, dirKey As String

Monitor.Enter(vEntry)
vMsg = vEntry
Monitor.Exit(vEntry)

Monitor.Enter(vFileCnt)
vFCnt = vFileCnt
vFileCnt += 1
Monitor.Exit(vFileCnt)

fCnt = AppSettings("dirCnt")

For loopCnt = 1 To fCnt
dirKey = "dir" & loopCnt
SetText("dirKey is " & dirKey)
fMoveDir = AppSettings(dirKey)
fWriteDir = fMoveDir & "temp\"
fName = AppSettings("conName") & "_" & Format(Now, "yyyyMMddHH24mm") & "_" & vFCnt & ".txt"
SetText(String.Format("{0} - move dir is {1} write dir is {2} and filename is {3}", Now, fMoveDir, fWriteDir, fName))
writeFile(fMoveDir, fWriteDir, fName, vEntry)
Next

End Sub

Sub writeFile(ByVal pMoveDir As String, ByVal pWriteDir As String, ByVal pFname As String, ByVal pMsg As String)
Dim fName, tFname As String

fName = pWriteDir & pFname
tFname = pMoveDir & pFname

Dim sw As StreamWriter = New StreamWriter(fName)
sw.Write(pMsg)
sw.Close()
File.Move(fName, tFname)

SetText(String.Format("{0} - Wrote {1} and moved to {2}", Now, fName, tFname))

End Sub

SpawnThrd will create and start a thread for each of entries. creFile will loop through each of the folders identified by the dirX (as in dir1, dir2 etc) in the config file. The dirCnt facilitates this. The unique file name is achieve using a combination of Console Name (conName in config file), date and time and also a running number. Also to reduce the possibility of read/write contention, the files are written in a temp sub-folder before being moved to the main folder.