65.9K
CodeProject is changing. Read more.
Home

InfoIP a tool to find your WAN IP

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3 votes)

Sep 8, 2014

CPOL

5 min read

viewsIcon

18702

downloadIcon

525

A SW to send your WAN IP and other info via email

 Download InfoIP.zip (Source code, Setup Project, exe and setup.msi)

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: 

  1. InfoViewModel.vb
  2. Config.vb
  3. Crypt.vb
  4. Email.vb
  5. Network.vb
  6. Application.xaml.vb
  7. MainWindow.xaml.vb

I hope to soon translate them with the entire project in C#.

InfoViewModel.vb

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

 

 

Config.vb

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
    ''' <summary>
    ''' Funzione statica che restituisce il valore di una chiave del file di configurazione
    ''' </summary>
    ''' <param name="keyAppSetting">La chiave</param>
    ''' <returns>Il valore della chiave</returns>
    ''' <remarks></remarks>
    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
    ''' <summary>
    ''' Metodo statico che consente di valorizzare una chiave nel file di configurazione.
    ''' Se la chiave non è presente viene creata e valorizzata
    ''' </summary>
    ''' <param name="keyAppSetting">La chiave</param>
    ''' <param name="valore">Il valore</param>
    ''' <remarks></remarks>
    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

 

Crypt.vb

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 RijndaelManaged (made available by Microsoft).
The peculiarity of encryption is to use the MAC address - found through the Network 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
    ''' <summary>
    ''' Funzione che effettua la crittografia di una stringa in chiaro
    ''' </summary>
    ''' <param name="input">La stringa da crittografare</param>
    ''' <returns>
    ''' Restituisce una stringa a partire da input rappresentate una matrice di Unsigned Integer a 8 bit nella rappresentazione equivalente codificata con cifre base 64.
    ''' </returns>
    ''' <remarks></remarks>
    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
    ''' <summary>
    ''' Funzione che effettua la decrittografia di una stringa ottenuta da Encode
    ''' </summary>
    ''' <param name="input">Una stringa rappresentate una matrice di Unsigned Integer a 8 bit nella rappresentazione equivalente codificata con cifre base 64</param>
    ''' <returns>
    ''' Restituisce una stringa a partire da input per la quale avviene una decodifica di tutti i byte della matrice di byte
    ''' </returns>
    ''' <remarks>Crypt.Encode dovrà essere obbligatoriuamente eseguito prima</remarks>
    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

 

Email.vb

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

    ''' <summary>
    ''' Funzione che invia la Email
    ''' </summary>
    ''' <param name="oggetto">L'oggetto della Email</param>
    ''' <param name="testo">Il testo della Email</param>
    ''' <param name="mailMittente">Il mittente delle Email</param>
    ''' <param name="mailDestinatari">Il destinatario della Email</param>
    ''' <param name="smtp">Il server SMTP</param>
    ''' <param name="isSsl">Usare o meno una connessione SSL</param>
    ''' <param name="portSmtp">Porta del server SMTP</param>
    ''' <param name="password">Password per l'autenticazione</param>
    ''' <param name="showException">
    ''' Indica se segnalare o meno l'eccezione
    ''' Utile nell'invio automatico sse si verifica al primo invio in modo da fermare il BackgroundWorker che altrimenti andrebbe sempre in errore
    ''' </param>
    ''' <param name="forceSend">
    ''' Indica se continuare ad effettuare dei tentativi fino a quando la email non viene spedita.
    ''' Utile nell'invio automatica sse si verifica il cambio del WAN IP
    ''' </param>
    ''' <returns>
    ''' T, se tutto ok
    ''' F, altrimenti
    ''' </returns>
    ''' <remarks></remarks>
    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

 

Network.vb

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
    ''' <summary>
    ''' Funzione che restituisce il MAC address dell'interfaccia
    ''' </summary>
    ''' <returns>
    ''' Restituisce il MAC address Ethernet o Wireless, nel caso in cui siano entrambe presenti restituisce il valore relativo all'interfaccia Wireless
    ''' </returns>
    ''' <remarks></remarks>
    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
  • LAN IP
    ''' <summary>
    ''' Funzione che restituisce l'IP della LAN
    ''' </summary>
    ''' <returns>LAN IP</returns>
    ''' <remarks></remarks>
    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
  • Default Gateway IP
    ''' <summary>
    ''' Funzione che restituisce l'IP del default Gateway
    ''' </summary>
    ''' <returns>Router IP</returns>
    ''' <remarks></remarks>
    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:
    ''' <summary>
    ''' Funzione che restituisce l'IP della WAN
    ''' </summary>
    ''' <returns>WAN IP</returns>
    ''' <remarks>
    ''' Fa utilizzo del servizio web http://checkip.dyndns.org/
    ''' </remarks>
    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

 

Application.xaml.vb

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 Application.xaml
<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 Source="/Xceed.Wpf.AvalonDock.Themes.Metro;component/Theme.xaml"/>-->
            </ResourceDictionary.MergedDictionaries>
            <!-- Converter -->
            <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"/>
            <!-- Style -->
...
            <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

        ' CurrentUICulture: considera i regional setting del SO
        ' CurrentCulture: considera i regional setting dell'utente
        'Dim cultureName = Thread.CurrentThread.CurrentCulture.Name
        'Dim xmlLang = XmlLanguage.GetLanguage(cultureName)
        'FrameworkElement.LanguageProperty.OverrideMetadata(GetType(FrameworkElement), New FrameworkPropertyMetadata(xmlLang))
        'vedi http://stackoverflow.com/questions/4041197/how-to-set-and-change-the-culture-in-wpf applica le impostazioni scelte all'exe e a tutte le dll
        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"

''' <summary>
''' From Validators to Boolean
''' </summary>
''' <remarks></remarks>
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

 

MainWindow.xaml.vb

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.

    ''' <summary>
    ''' Metodo che provvede all'invio automatico delle informazioni allo scadere del timeout impostato da interfaccia
    ''' </summary>
    ''' <param name="verificaSeInviare">Indica se verificare il cambio delle informazioni prima di effettuare l'invio delle stesse</param>
    ''' <remarks></remarks>
    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>:

  • the properties
#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:

    ''' <summary>
    ''' Metodo che esegue SendInfo per inviare la email con le informazioni, dopo di che attende il timeout impostato prima di terminare il job
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    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

    ''' <summary>
    ''' Metodo che disabilita la visualizzazione del BusyIndicator_InvioAuto nel momento in cui il job è terminato
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    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 InfoViewModel.vb) 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 Application.xaml.vb like a MultiConverter call ValidatorConveter:

    <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

...

    ''' <summary>
    ''' Metodo che gestisce la creazione della Notify Icon per la System Tray Application
    ''' </summary>
    ''' <remarks></remarks>
    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

    ''' <summary>
    ''' Metodo che gestisce il ripristino della Window da System Tray
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    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! Smile | :)

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

    ''' <summary>
    ''' Funzione che restituisce la versione del software
    ''' </summary>
    ''' <returns>La versione corrente dell'assembly</returns>
    ''' <remarks></remarks>
    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