Introduction
Nowadays some ISPs provide new customers with their own routers which seem to have locked configuration settings. This tiny software is designed to overcome this difficulty sending via email essential information such as LAN IP, WAN IP and Default Gateway IP necessary for the remote desktop connection to your PC.
Background
I have worked on this project because a colleague of mine asked me if there was a software that would allow to know the WAN IP, because the software used, after the upgrade the router's firmware (router personal and not provided by your ISP), has stopped to work because there is no possible to implement an opportune configuration.
That’s why I have started work on this tool whose aim is find and send via email the WAN IP.
Using the code & Point of Interest
In this article I upload the source code (the solution also includes the project for the setup, for VS 2010) that exe.
The most interesting classes are:
- InfoViewModel.vb
- Config.vb
- Crypt.vb
- Email.vb
- Network.vb
- Application.xaml.vb
- MainWindow.xaml.vb
I hope to soon translate them with the entire project in C#.
In this class has been implemented logic MVVM, based on the class InfoData
that contains the informations shown in UI. The peculiarity of InfoViewModel
is that implements IDataErrorInfo
and INotifyPropertyChanged
, so this allows you respectively to validate the individual information and to update and change the information in the UI.
#Region "IDataErrorInfo"
Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
Return "Errors in the validation of the informations inserted"
End Get
End Property
Default Public ReadOnly Property Item([property] As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
Select Case [property]
Case "EmailFrom"
Return Me.ValidateEmailFrom()
Case "Server"
Return Me.ValidateServer()
Case "Port"
Return Me.ValidatePort()
Case "EmailTo"
Return Me.ValidateEmailTo()
Case "Timeout"
Return Me.ValidateTimeout()
End Select
Return Nothing
End Get
End Property
#End Region
#Region "INotifyPropertyChanged"
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
Here is shown how to validate the email address through the appropriate regular expression:
Private Function ValidateEmailFrom() As String
If Me.mIsChangedEmailFrom Then
If String.IsNullOrEmpty(Me.EmailFrom.ToString()) Then
Return "email in SMTP is missing"
End If
If Not ((New Regex("^(\s*,?\s*[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})+\s*$")).IsMatch(Me.EmailFrom.ToString())) Then
Return "email in SMTP is wrong"
End If
End If
Return Nothing
End Function
Here instead is shown how to automatically update the UI through the event PropertyChanged
on two properties eg:
Public Property DefaultGatewayIP As IPAddress
Get
Return mInfoData.DefaultGatewayIP
End Get
Set(value As IPAddress)
If mInfoData.DefaultGatewayIP IsNot Nothing Then
If Not mInfoData.DefaultGatewayIP.Equals(value) Then
mInfoData.DefaultGatewayIP = value
Me.OnPropertyChanged("DefaultGatewayIP")
End If
Else
mInfoData.DefaultGatewayIP = value
Me.OnPropertyChanged("DefaultGatewayIP")
End If
End Set
End Property
Public Property EmailFrom As String
Get
Return mInfoData.EmailFrom
End Get
Set(value As String)
If String.Compare(mInfoData.EmailFrom, value) <> 0 Then
mIsChangedEmailFrom = True
mInfoData.EmailFrom = value
Me.OnPropertyChanged("EmailFrom")
End If
End Set
End Property
This class used together the
Crypt
class, from the MainWindow
class, is responsible to get/set the informations from/to app.config and exposes:
- a
Shared
function named GetValueAppSetting
Public Shared Function GetValueAppSetting(keyAppSetting As String) As String
Try
Return ConfigurationManager.AppSettings(keyAppSetting)
Catch generatedExceptionName As Exception
Return ""
End Try
End Function
- a
Shared
sub named SetValueAppSetting
Public Shared Sub SetValueAppSetting(keyAppSetting As String, valore As String)
Dim myConfiguration As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
If myConfiguration.AppSettings.Settings(keyAppSetting) IsNot Nothing Then
myConfiguration.AppSettings.Settings(keyAppSetting).Value = valore
Else
myConfiguration.AppSettings.Settings.Add(keyAppSetting, valore)
End If
myConfiguration.Save(ConfigurationSaveMode.Modified)
End Sub
This class is responsible to encode and decode the password that will be saved in the app.config, after have been encrypted through use of the algorithm <a href="http://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged%28v=vs.100%29.aspx">RijndaelManaged</a>
(made available by Microsoft).
The peculiarity of encryption is to use the MAC address - found through the <a href="#Network">Network</a>
class - together at the string set in VariabiliGlobali.BaseStringForRijndael
, to create the secret key and the initialization vector, both of 256 bit, so to ensure as far as possible the information security.
Public Sub New()
Dim network As New Network
mMacAddress = network.MacAddress
If String.IsNullOrEmpty(mMacAddress) Then
mMacAddress = DefaultMacAddress
End If
mKey = StrReverse(mKey) + StrReverse(mMacAddress)
mIV = mIV + mMacAddress
mRijndael.KeySize = 256
mRijndael.BlockSize = 256
mRijndael.Key = ASCIIEncoding.ASCII.GetBytes(mKey)
mRijndael.IV = ASCIIEncoding.ASCII.GetBytes(mIV)
End Sub
Public Function Encode(input As String) As String
Dim toEncrypt() As Byte = Encoding.UTF8.GetBytes(input)
Dim output() As Byte = mRijndael.CreateEncryptor().TransformFinalBlock(toEncrypt, 0, toEncrypt.Length)
Return Convert.ToBase64String(output)
End Function
Public Function Decode(input As String) As String
Dim toDecrypt As [Byte]() = Convert.FromBase64String(input)
Dim output As [Byte]() = mRijndael.CreateDecryptor().TransformFinalBlock(toDecrypt, 0, toDecrypt.Length)
Return Encoding.UTF8.GetString(output)
End Function
This class is obviously responsible for sending you a mail with the info shown in UI. The peculiarity is the function Invia
that on the basis of the caller (mail on demand or automatic mail) that will pass particular values for the last two parameters.
But other than that the main thing is the instance creation of the SmtpClient
class with the set of .DeliveryMethod = SmtpDeliveryMethod.Network
and .UseDefaultCredential = False
necessary to allow the correct authentication
Public Function Invia(oggetto As String, testo As String, mailMittente As String, mailDestinatari As Dictionary(Of String, String),
smtp As String, isSsl As Boolean, portSmtp As String, password As String,
Optional showException As Boolean = True, Optional forceSend As Boolean = False) As Boolean
Dim result As Boolean = False
Dim mSmtpClient As SmtpClient = Nothing
Try
invia:
If isSsl Then
mSmtpClient = New SmtpClient(smtp, Convert.ToInt32(portSmtp).ToString()) With { _
.EnableSsl = isSsl, _
.DeliveryMethod = SmtpDeliveryMethod.Network, _
.UseDefaultCredentials = False _
}
mSmtpClient.Credentials = New NetworkCredential(mailMittente, password)
Else
mSmtpClient = New SmtpClient(smtp)
mSmtpClient.DeliveryMethod = SmtpDeliveryMethod.Network
End If
Me.Messaggio.Subject = oggetto + " " + DateTime.Now.ToString()
Dim mittente As New MailAddress(mailMittente)
Me.Messaggio.From = mittente
Dim destinatario As New MailAddress(mailDestinatari("To"))
Dim mCc As String = String.Empty
Dim mBcc As String = String.Empty
If mailDestinatari.ContainsKey("Cc") Then
mCc = mailDestinatari("Cc")
End If
If mailDestinatari.ContainsKey("Bcc") Then
mBcc = mailDestinatari("Bcc")
End If
Me.Messaggio.[To].Add(destinatario)
If mCc <> "" Then
Me.Messaggio.CC.Add(mCc)
End If
If mBcc <> "" Then
Me.Messaggio.Bcc.Add(mBcc)
End If
Me.Messaggio.IsBodyHtml = True
Me.Messaggio.BodyEncoding = UTF8Encoding.UTF8
Me.Messaggio.Body = testo.ToString()
mSmtpClient.Send(Me.Messaggio)
result = True
Catch ex As Exception
If showException Then
segnalaUgualmenteErrore:
Dim messaggioSpecifico As String = String.Empty
If Not showException Then
messaggioSpecifico = vbNewLine + vbNewLine + "NOTE:" + vbNewLine +
"Ten attempts have already been made WITHOUT SUCCESS, so the automatic procedure WILL BE INTERRUPTED!!"
End If
MessageBox.Show(ex.Message + messaggioSpecifico,
"Email",
MessageBoxButton.OK, MessageBoxImage.Exclamation)
Return result
End If
If Not forceSend Then
GoTo segnalaUgualmenteErrore
Else
Thread.Sleep(1000)
mSmtpClient.Dispose()
GoTo invia
End If
End Try
Return result
End Function
This class is responsible to find the follow information:
- MAC address that, in the case where Ethernet and Wireless are present, returns the value for the Wireless interface like is visible here
Private Function GetMacAddress() As String
Dim result As String = String.Empty
Dim theNetworkInterfaces() As System.Net.NetworkInformation.NetworkInterface = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
For Each currentInterface As System.Net.NetworkInformation.NetworkInterface In theNetworkInterfaces
If currentInterface.NetworkInterfaceType = Net.NetworkInformation.NetworkInterfaceType.Ethernet Then
result = currentInterface.GetPhysicalAddress().ToString()
End If
If currentInterface.NetworkInterfaceType = Net.NetworkInformation.NetworkInterfaceType.Wireless80211 Then
result = currentInterface.GetPhysicalAddress().ToString()
End If
Next
Return result
End Function
Private Function GetLanIP() As String
Dim result As String = String.Empty
Try
result = (From ip In Dns.GetHostEntry(Dns.GetHostName()).AddressList Where ip.AddressFamily = AddressFamily.InterNetwork Select ip)(0).ToString()
Catch ex As Exception
MessageBox.Show(ex.Message + vbNewLine + vbNewLine + "Program execution will continue!", "GetLanIP", MessageBoxButton.OK, MessageBoxImage.Exclamation)
End Try
Return result
End Function
Private Function GetDefaultGatewayIP() As IPAddress
Dim result As Object = Nothing
Try
Dim card = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault()
If card IsNot Nothing Then
Dim address = card.GetIPProperties().GatewayAddresses.FirstOrDefault()
If address IsNot Nothing Then
result = address.Address
End If
End If
Catch ex As Exception
MessageBox.Show(ex.Message + vbNewLine + vbNewLine + "Program execution will continue!", "GetDefaultGateway", MessageBoxButton.OK, MessageBoxImage.Exclamation)
End Try
Return result
End Function
- WAN IP, to retrieve this information has been necessary use the service available on the site http://checkip.dyndns.org/ using the class
WebClient
:
Private Function GetWanIP() As String
Dim result As String = String.Empty
Try
result = (New Regex("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")) _
.Matches((New WebClient()).DownloadString(CheckIpDynDns))(0).ToString()
If String.IsNullOrEmpty(result) Then
Throw New Exception(CheckIpDynDns + " result not available. Program execution will continue!")
End If
Catch ex As Exception
MessageBox.Show(ex.Message + vbNewLine + vbNewLine + "Program execution will continue!", "GetExternalIp", MessageBoxButton.OK, MessageBoxImage.Exclamation)
End Try
Return result
End Function
This is the class that manages the application in the widest sense of the term. In fact, here you can specify:
- what is the window that you will see at startup (see
StartupUri
) - its <a id="Application.xaml" name="Application.xaml">Application.xaml</a>
<Application x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:InfoIP"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
StartupUri="MainWindow.xaml"
DispatcherUnhandledException="Application_DispatcherUnhandledException"
ShutdownMode="OnLastWindowClose">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<me:BooleanToVisibilityConverter x:Key="CollapsedIfFalse" TriggerValue="False" IsHidden="False"/>
<me:BooleanToEnableConverter x:Key="EnabledIfFalse"/>
<me:BooleansToBooleanConverter x:Key="MultiBooleanToBoolean"/>
<me:ValidatorConverter x:Key="MultiValidatorToBoolean"/>
<me:BooleanToVisibilityConverter x:Key="CollapsedIfTrue" TriggerValue="True" IsHidden="False"/>
...
<SolidColorBrush x:Key="BackgroundReadonly">#FFEBEBEB</SolidColorBrush>
</ResourceDictionary>
</Application.Resources>
</Application>
- some events like,
Application_Startup
(eg to verify that there are not multiple instances of application running) and Application_DispatcherUnhandledException
(to catch the unhandled exception)
Private Sub Application_Startup(ByVal sender As Object, ByVal e As System.Windows.StartupEventArgs) Handles Me.Startup
...
Dim mutexName = Me.Info.AssemblyName
If Not MutexExists(mutexName) Then
mInstanceMutex = New Mutex(True, mutexName)
Else
MessageBox.Show(Me.Info.AssemblyName.ToString() + " is already running",
Me.Info.AssemblyName.ToString(),
MessageBoxButton.OK, MessageBoxImage.Warning)
Me.Shutdown()
Exit Sub
End If
FrameworkElement.LanguageProperty.OverrideMetadata(GetType(FrameworkElement), New FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)))
End Sub
Private Sub Application_DispatcherUnhandledException(sender As System.Object, e As System.Windows.Threading.DispatcherUnhandledExceptionEventArgs)
Try
Application.Current.MainWindow.Cursor = Cursors.Arrow
Catch ex As Exception
End Try
Dim msg As String = "ATTENTION!" + vbNewLine + "There was an error!" + vbNewLine + "The application will terminate" + vbNewLine + vbNewLine + "Error Message: " + e.Exception.Message
If e.Exception.InnerException IsNot Nothing Then
msg += vbNewLine + e.Exception.InnerException.Message
End If
MessageBox.Show(msg, System.Reflection.Assembly.GetEntryAssembly.GetName.Name, MessageBoxButton.OK, MessageBoxImage.Error)
Process.GetCurrentProcess.Kill()
e.Handled = True
End Sub
#Region "ValidatorConverter"
Public Class ValidatorConverter
Implements IMultiValueConverter
Public Function Convert(values() As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
Dim isValid As Boolean
For Each v In values
If v.Equals(DependencyProperty.UnsetValue) Then
isValid = True
Else
isValid = False
Exit For
End If
Next
Return isValid
End Function
Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
#End Region
This is the class for the main window, it is characterized by the utilization of BackgroundWorker
, to enable the use of the class BusyIndicator
of the Extended WPF Toolkit while occurs the email despatch.
Private Sub SendInfoAuto(Optional ByVal verificaSeInviare As Boolean = False)
If Not BusyIndicator_InvioAuto.IsBusy Then
Me.BusyProgressValue = 0.0
BusyIndicator_InvioAuto.BusyContent = "Seconds to check: "
BusyIndicator_InvioAuto.IsBusy = True
mBackgroundWorker = New BackgroundWorker()
mBackgroundWorker.WorkerSupportsCancellation = True
AddHandler mBackgroundWorker.DoWork, AddressOf Me.DoWorkAuto
AddHandler mBackgroundWorker.RunWorkerCompleted, AddressOf Me.OnWorkCompletedAuto
mBackgroundWorker.RunWorkerAsync(New Tuple(Of Dispatcher, Dispatcher, Integer, Boolean) _
(Me.Dispatcher,
BusyIndicator_InvioAuto.Dispatcher,
Convert.ToInt32(IIf(String.IsNullOrEmpty(Me.TextBox_Timeout.Text.ToString()), 60, Me.TextBox_Timeout.Text)),
verificaSeInviare))
Else
If mBackgroundWorker IsNot Nothing AndAlso mBackgroundWorker.IsBusy Then
mBackgroundWorker.CancelAsync()
End If
BusyIndicator_InvioAuto.IsBusy = False
End If
End Sub
As shown above for the automatic email despatch feature has been used a specifc busy indicatator BusyIndicator_InvioAuto
, this to allow the update of value while expires the timeout (see second figure) - note that the value of timeout desider is passed like parameter in mBackgroundWorker.RunWorkerAsync
.
The management of the timeout uses two properties of this class, then exposed in XAML through the tag <Window.Resources>
:
#Region "Proprietà"
#Region "Pubbliche"
Public Property BusyProgressValue() As Double
Get
Return CType(GetValue(BusyProgressValueProperty), Double)
End Get
Set(ByVal value As Double)
SetValue(BusyProgressValueProperty, value)
End Set
End Property
Public Property BusyProgressMaximum() As Double
Get
Return CType(GetValue(BusyProgressMaximumProperty), Double)
End Get
Set(ByVal value As Double)
SetValue(BusyProgressMaximumProperty, value)
End Set
End Property
#Region "Condivise"
Public Shared ReadOnly BusyProgressValueProperty As DependencyProperty = _
DependencyProperty.Register("BusyProgressValue", GetType(Double), GetType(MainWindow), New PropertyMetadata(0.0))
Public Shared ReadOnly BusyProgressMaximumProperty As DependencyProperty = _
DependencyProperty.Register("BusyProgressMaximum", GetType(Double), GetType(MainWindow), New PropertyMetadata(0.0))
#End Region
#End Region
#Region "Private"
#End Region
#End Region
- the XAML
<Window.Resources>
<Window.Resources>
<Style x:Key="busyProgressBarStyle" TargetType="ProgressBar">
<Setter Property="IsIndeterminate" Value="False"/>
<Setter Property="Minimum" Value="0"/>
<Setter Property="Maximum" Value="{Binding ElementName=mainWindow, Path=BusyProgressMaximum}"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Value" Value="{Binding ElementName=mainWindow, Path=BusyProgressValue}"/>
<Setter Property="Background" Value="{StaticResource BackgroundReadonly}"/>
</Style>
</Window.Resources>
- the XAML
BusyIndicator_InvioAuto
<xctk:BusyIndicator x:Name="BusyIndicator_InvioAuto" IsBusy="False" ProgressBarStyle="{StaticResource busyProgressBarStyle}"
Width="154" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="5"
Background="{StaticResource BackgroundReadonly}"/>
Furthermore as shown above in first code snippet, the mBackgroundWorker
variable does its work by setting two handlers for the events DoWorker
and RunWorkerCompleted
:
Private Sub DoWorkAuto(sender As Object, e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim arg As Tuple(Of Dispatcher, Dispatcher, Integer, Boolean) = CType(e.Argument, Tuple(Of Dispatcher, Dispatcher, Integer, Boolean))
Dim mainWindowDispatcher As Dispatcher = arg.Item1
Dim busyIndicatorDispatcher As Dispatcher = arg.Item2
Dim timeout As Integer = arg.Item3
Dim verificaSeInviare As Boolean = arg.Item4
Me.Dispatcher.BeginInvoke(DispatcherPriority.Send, DirectCast(Sub()
If Not SendInfo("-= AUTO =-", verificaSeInviare, True) Then
mStopMailAuto = True
worker.CancelAsync()
End If
End Sub, SendOrPostCallback), Nothing)
For i As Integer = 1 To timeout
If worker.CancellationPending Then
e.Cancel = True
Exit Sub
End If
mainWindowDispatcher.Invoke(New Action(Of Integer)(AddressOf Me.UpdateProgressBarValue), i)
busyIndicatorDispatcher.Invoke(New Action(Of Integer)(AddressOf Me.UpdateCaption), timeout - i)
Thread.Sleep(1000)
Next
End Sub
Private Sub OnWorkCompletedAuto(sender As Object, e As RunWorkerCompletedEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
If Object.ReferenceEquals(mBackgroundWorker, worker) Then
mBackgroundWorker.Dispose()
mBackgroundWorker = Nothing
BusyIndicator_InvioAuto.IsBusy = False
Thread.Sleep(1000)
If Not mStopMailAuto Then
SendInfoAuto(True)
End If
End If
End Sub
Another feature of the class is the use of validation (see <a href="#InfoViewModel">InfoViewModel.vb</a>
) for the information present in UI, to ensure the proper functioning of the buttons for sending the email. The validation is specifically carried out by using in the XAML of MainWindow.xaml
by the MultiValidatorToBoolean
that is defined in the class <a href="#Application">Application.xaml.vb</a>
like a MultiConverter
call <a href="#ValidatorConverter">ValidatorConveter</a>
:
<Button Name="Button_MailAuto" VerticalAlignment="Top" HorizontalAlignment="Right" Width="40" Height="40" Grid.Row="1" Grid.Column="3"
ToolTip="Start the check to send automatic email with the informations" Margin="5"
Visibility="{Binding ElementName=BusyIndicator_InvioAuto, Path=IsBusy, Converter={StaticResource CollapsedIfTrue}}">
<Image Source="/InfoIP;component/Images/mail_start.ico" Stretch="None"/>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="BorderBrush" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource MultiValidatorToBoolean}">
<Binding ElementName="TextBox_EmailFrom" Path="(Validation.Errors)[0].ErrorContent"/>
<Binding ElementName="TextBox_Server" Path="(Validation.Errors)[0].ErrorContent"/>
<Binding ElementName="TextBox_Port" Path="(Validation.Errors)[0].ErrorContent"/>
<Binding ElementName="TextBox_EmailTo" Path="(Validation.Errors)[0].ErrorContent"/>
<Binding ElementName="TextBox_Timeout" Path="(Validation.Errors)[0].ErrorContent"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
The latest application's feature is the opportunity to put it in the system tray through the notify icon.
To realize ir there is the chance to use the project WPF NotifyIcon (much more complete), but having had little time to devote myself to it, I decided to adopt the solution provided by WinForms adding the System.Windows.Forms
and System.Drawing
in assembly references.
So through this code the goal has been reached:
Class MainWindow
Inherits System.Windows.Window
...
Private Sub SetNotifyIconInSystemTrayApplication()
mNotifyIcon = New System.Windows.Forms.NotifyIcon()
mNotifyIcon.Icon = New System.Drawing.Icon(FileIco)
mNotifyIcon.Text = Assembly.GetExecutingAssembly.GetName.Name.ToString()
mNotifyIcon.BalloonTipText = Me.Title
mNotifyIcon.Visible = True
AddHandler mNotifyIcon.DoubleClick, AddressOf RipristinaDaSystemTray
End Sub
Private Sub RipristinaDaSystemTray(sender As Object, e As EventArgs)
Me.Show()
Me.WindowState = WindowState.Normal
End Sub
...
Protected Overrides Sub OnStateChanged(e As EventArgs)
If Me.WindowState = WindowState.Minimized Then
Me.Hide()
Me.ShowInTaskbar = False
If mNotifyIcon IsNot Nothing Then
mNotifyIcon.ShowBalloonTip(400)
mNotifyIcon.Visible = True
End If
Else
mNotifyIcon.Visible = False
Me.ShowInTaskbar = True
End If
MyBase.OnStateChanged(e)
End Sub
Protected Overrides Sub OnClosed(e As EventArgs)
mNotifyIcon.Dispose()
mNotifyIcon = Nothing
MyBase.OnClosed(e)
End Sub
Conclusion
I hope this article with the attachments will help you, if you find bugs or improvements please contact me. Happy coding!
History
The changelog:
+ version 1.0.0 (03/07/2014):
-
+ beta version (20/06/2014):
- After the beta test release that without much smartness sent you an email at the end of timeout (in short a spam software :P), I decided to improve this tool and this article is the result. I hope that like you ;P
... that in the code behind is returned by... :P
Private Function GetVersion() As String
Return Assembly.GetExecutingAssembly.GetName.Name.ToString() + " - v. " +
Assembly.GetExecutingAssembly.GetName.Version.Major.ToString() _
& "." & Assembly.GetExecutingAssembly.GetName.Version.Minor.ToString() _
& "." & Assembly.GetExecutingAssembly.GetName.Version.Build.ToString()
End Function