VB Form

 

This is a simple form sample that attempts to demonstrate many of the operations you may use in developing an interactive client application.

 

Imports System

Imports System.Timers

Imports System.Text

Imports System.Drawing

Imports System.Collections

Imports System.Windows.Forms

Imports System.Data

Imports System.IO

Imports System.Runtime.Serialization

Imports System.Runtime.Serialization.Formatters.Binary

Imports IDN.IO

  

Public Class Form1

    Inherits System.Windows.Forms.Form

  

    ' --------------------------------------------------------------------

    Private m_tagServer As TagServer  = Nothing      ' The Tag Server.

    Private m_schedule  As IOSchedule = Nothing      ' Current schedule.

    Private m_tag       As IOTag      = Nothing      ' Current tag.

    Private m_fileName  As String     = "default.qd" ' Default filename.

  

    ' Used for displaying information in the textboxes.

    Private m_tagEventLines      As New ArrayList(100)

    Private m_scheduleEventLines As New ArrayList(100)

    Private m_outputLines        As New ArrayList(100)

  

    Private WithEvents m_getTagButton As System.Windows.Forms.Button

    Private m_tagEventTextBox As System.Windows.Forms.TextBox

    Private WithEvents m_outputTextBox As System.Windows.Forms.TextBox

    Private WithEvents m_getScheduleButton As System.Windows.Forms.Button

    Private m_scheduleEventTextBox As System.Windows.Forms.TextBox

    Private m_openFileDialog As System.Windows.Forms.OpenFileDialog

    Private m_saveFileDialog As System.Windows.Forms.SaveFileDialog

    Private WithEvents m_tagPropertiesButton As System.Windows.Forms.Button

    Private WithEvents m_schedulePropertiesButton As System.Windows.Forms.Button

    Private WithEvents m_tagSubscribeButton As System.Windows.Forms.Button

    Private WithEvents m_tagUnsubscribeButton As System.Windows.Forms.Button

    Private WithEvents m_scheduleSubscribeButton As System.Windows.Forms.Button

    Private WithEvents m_scheduleUnsubscribeButton As System.Windows.Forms.Button

    Private WithEvents m_tagWriteButton As System.Windows.Forms.Button

    Private m_mainMenu As System.Windows.Forms.MainMenu

    Private menuItem1 As System.Windows.Forms.MenuItem

    Private menuItem2 As System.Windows.Forms.MenuItem

    Private WithEvents m_newMenuItem As System.Windows.Forms.MenuItem

    Private WithEvents m_openMenuItem As System.Windows.Forms.MenuItem

    Private WithEvents m_saveMenuItem As System.Windows.Forms.MenuItem

    Private WithEvents m_saveAsMenuItem As System.Windows.Forms.MenuItem

    Private WithEvents m_exitMenuItem As System.Windows.Forms.MenuItem

    Private WithEvents m_tagServerMenuItem As System.Windows.Forms.MenuItem

    Private m_scheduleGroupBox As System.Windows.Forms.GroupBox

  

    ' --------------------------------------------------------------------

    Public Sub New()

        InitializeComponent()

    End Sub

  

    ' --------------------------------------------------------------------

    #Region "Windows Form Designer generated code"

    ' removed...

    #End Region

  

    ' --------------------------------------------------------------------

    <STAThread()> _

    Shared Sub Main()

        Application.Run(New Form1)

  

    End Sub

  

    ' --------------------------------------------------------------------

    '

    Private Sub OnFormLoad(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load

  

        ' Create a Tag Server.

        m_tagServer = New TagServer

  

        ' Clean now.  

        m_tagServer.Dirty = False

  

        ' Sign up for any error events from the IO.

        AddHandler m_tagServer.Errored, AddressOf OnIOErroredEventHandler

    End Sub

  

    ' --------------------------------------------------------------------

    ' Let the user select servers, create or edit schedules and tags to

    ' be monitored.

    Private Sub OnTagServerMenuItemClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_tagServerMenuItem.Click

        If Not (m_tagServer Is Nothing) Then

            m_tagServer.ShowDialog()

        End If

  

        ' We can do this because the server objects ignore duplicte

        ' event handlers.

        SubscribeToAllPropertyChangedEvents(m_tagServer)

    End Sub

  

    ' ---------------------------------------------------------------------

    ' Demonstrate how to select a tag from the Tag Server and subscribe and

    ' and unsubscribe from its Updated event.

    Private Sub OnGetTagClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_getTagButton.Click

        If Not (m_tagServer Is Nothing) Then

  

            ' Let the user browse the configured IOTag's and select one.

            Dim tag As IOTag = CType(m_tagServer.SelectItem(GetType(IOTag)), IOTag)

  

            ' Was a new tag was selected by the user? 

            If Not (tag Is Nothing) Then

  

                ' If we already had one running in our example UI

                ' then unsubscribe from the old tag's events.

                If Not (m_tag Is Nothing) Then

                    RemoveHandler m_tag.Updated, New UpdatedDelegate(AddressOf OnTagUpdatedEventHandler)

                End If

  

                ' This is now our new selected tag.

                m_tag = tag

  

                ' Subscribe to the tag's Updated event.

                AddHandler m_tag.Updated, AddressOf OnTagUpdatedEventHandler

  

                ' Change the tag GroupBox text to the tag name.

                m_tagGroupBox.Text = tag.Name

            End If

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' This event comes from the tag so 'sender' can be cast to an IOTag.

    ' Check the type before casting in a real application.

    Public Sub OnTagUpdatedEventHandler(ByVal sender As Object, ByVal e As UpdateEventArgs)

        Try

            ' Use a StringBuilder for better speed.

            Dim sb As New StringBuilder

  

            ' Is the tag an array?

            If e.Sample.Value.GetType().IsArray Then

  

                ' Display the array values on a line.

                Dim val As System.Array = CType(e.Sample.Value, System.Array)

                Dim i As Integer

                For i = 0 To val.Length -1

                    sb.AppendFormat("{0}, ", val.GetValue(i))

                Next i

  

                ' Get rid of that last comma separator so it looks good and add a LF.

                If sb.Length >= 2 Then

                    sb.Length = sb.Length - 2

                End If

                sb.Append(vbCr + vbLf)

                WriteTagTextBox(sb.ToString())

  

                ' Display the timestamp and quality.

                sb.Length = 0

                sb.AppendFormat("{0:MM/dd/yyyy, hh:mm:ss}:" + vbTab + "{1}", e.Sample.TimeStamp, e.Sample.Quality)

                WriteTagTextBox(sb.ToString())

            Else

                ' Not an array so it's much easier.

                sb.AppendFormat("{0}:" + vbTab + "{1:MM/dd/yyyy, hh:mm:ss}:" + vbTab + "{2}", e.Sample.Value, e.Sample.TimeStamp, e.Sample.Quality)

                WriteTagTextBox(sb.ToString())

            End If

        Catch ex As Exception

            WriteOutputTextBox("Error updating tag: " + ex.Message)

        End Try

    End Sub

  

    ' --------------------------------------------------------------------

    ' Demonstrate using an object's ShowDialog() method for editing properties.

    Private Sub OnTagPropertiesClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_tagPropertiesButton.Click

        If Not (m_tag Is Nothing) Then

            m_tag.ShowDialog()

        Else

            WriteOutputTextBox("No current tag exist. Try selecting a tag first.")

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Demonstrate using a tag's Write() method, using dialogs.

    Private Sub OnTagWriteClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_tagWriteButton.Click

        If Not (m_tag Is Nothing) Then

            WriteOutputTextBox(m_tag.Write().ToString())

        Else

            WriteOutputTextBox("No current tag exists. Try selecting a tag first.")

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Start listening to UpdatedEvents. Duplicate assignments are ignored.

    Private Sub OnTagSubscribeClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_tagSubscribeButton.Click

        If Not (m_tag Is Nothing) Then

            AddHandler m_tag.Updated, AddressOf OnTagUpdatedEventHandler

        Else

            WriteOutputTextBox("No current tag exists. Try Configuring servers then Selecting a tag first.")

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Stop listening to UpdatedEvents. Duplicates are ignored.

    Private Sub OnTagUnsubscribeClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_tagUnsubscribeButton.Click

        If Not (m_tag Is Nothing) Then

            RemoveHandler m_tag.Updated, New UpdatedDelegate(AddressOf OnTagUpdatedEventHandler)

        Else

            WriteOutputTextBox("No current tag exists. Try Configuring servers then Selecting a tag first.")

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Let the user select a schedule to monitor.

    Private Sub OnGetScheduleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_getScheduleButton.Click

        If Not (m_tagServer Is Nothing) Then

  

            ' Use the provided UI to pick a schedule.

            Dim schedule As IOSchedule = CType(m_tagServer.SelectItem(GetType(IOSchedule)), IOSchedule)

  

            ' Was a new schedule was selected by the user?

            If Not (schedule Is Nothing) Then

  

                ' If we already had one running

                If Not (m_schedule Is Nothing) Then

                    RemoveHandler m_schedule.Updated, New UpdatedDelegate(AddressOf OnScheduleUpdatedEventHandler)

                End If

  

                ' This is now our new current schedule.

                m_schedule = schedule

  

                ' Subscribe to the events.

                AddHandler m_schedule.Updated, AddressOf OnScheduleUpdatedEventHandler

  

                ' Change the schedule's GroupBox text to the schedule name?

                m_scheduleGroupBox.Text = m_schedule.Name

            End If

        End If

    End Sub

  

    ' ------------------------------------------------------------------------

    ' Handle schedule Updated events. The schedule has an UpdatedTags list.

    ' Get the tag values directly from the IOTag objects.

    Private Sub OnScheduleUpdatedEventHandler(ByVal sender As Object, ByVal e As UpdateEventArgs)

        Try

            ' Use a StringBuilder for better speed.

            Dim sb As New StringBuilder

  

            ' Scan and display the list of tags that changed.

            WriteScheduleTextBox("")

            Dim i As Integer

            For i = 0 To (CType(sender, IOSchedule).UpdatedTags.Count) -1

  

                ' Array values are not processed here for simplicity.

                ' Look at the tag Updated handler for an array example.

                Dim tag As IOTag = CType(CType(sender, IOSchedule).UpdatedTags.GetByIndex(i), IOTag)

                sb.Length = 0

                sb.AppendFormat("{0}:" + vbTab + "{1}:" + vbTab + "{2:MM/dd/yyyy, hh:mm:ss}:" + vbTab + "{3}", tag.Name, tag.Value, tag.TimeStamp, tag.Quality)

                WriteScheduleTextBox(sb.ToString())

            Next i

        Catch ex As Exception

            WriteOutputTextBox("Error updating schedule: " + ex.Message)

        End Try

    End Sub

  

    ' --------------------------------------------------------------------

    ' Demonstrate using an object's ShowDialog() method for editing properties.

    Private Sub OnSchedulePropertiesClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_schedulePropertiesButton.Click

        If Not (m_schedule Is Nothing) Then

            m_schedule.ShowDialog()

        Else

            WriteOutputTextBox("No current schedule exist. Try selecting a schedule first.")

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Susbscribe to the schedule Updated event.

    Private Sub OnScheduleSubscribeClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_scheduleSubscribeButton.Click

        If Not (m_schedule Is Nothing) Then

            AddHandler m_schedule.Updated, AddressOf OnScheduleUpdatedEventHandler

        Else

            WriteOutputTextBox("No current schedule exists. Try Configuring servers then Selecting a schedule first.")

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Unsubscribe from the schedule Updated event.

    Private Sub OnScheduleUnsubscribeClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_scheduleUnsubscribeButton.Click

        If Not (m_schedule Is Nothing) Then

            RemoveHandler m_schedule.Updated, New UpdatedDelegate(AddressOf OnScheduleUpdatedEventHandler)

        Else

            WriteOutputTextBox("No current schedule exists. Try Configuring servers then Selecting a schedule first.")

        End If

    End Sub

  

    ' ------------------------------------------------------------------------

    ' Handle PropertyChanged events for all of the Tag Server's objects.

    Public Sub OnPropertyChangedEventHandler(ByVal sender As Object, ByVal e As IDN.IO.PropertyChangedEventArgs)

        Try

            ' Display the sender's name.

            WriteOutputTextBox(vbCr + vbLf + "Property changed event from: " + CType(sender, IDNBrowsable).Name)

  

            ' Loop through and display any changed property's name and value.

            Dim ke As IEnumerator = e.Properties.Keys.GetEnumerator()

            Dim ve As IEnumerator = e.Properties.Values.GetEnumerator()

            ke.MoveNext()

            ve.MoveNext()

            Dim i As Integer

            For i = 0 To e.Properties.Count -1

                WriteOutputTextBox(ke.Current.ToString() + ": " + ve.Current.ToString())

                ke.MoveNext()

                ve.MoveNext()

            Next i

  

            ' This is how to specifically detect watchdog failure.

            If sender.GetType() Is GetType(IOServer) Then

                If e.Properties.Contains("Mode") Then

                    If CType(e.Properties("Mode"), ServerMode) = ServerMode.StatusCheckFailure Then

                        WriteOutputTextBox("Watchdog status check failure.")

                    End If

                End If

            End If

  

        Catch ex As Exception

            WriteOutputTextBox("Error on PropertyChanged event: " + ex.Message)

        End Try

    End Sub

  

    ' --------------------------------------------------------------------

    ' Handle error message events.

    Public Sub OnIOErroredEventHandler(ByVal sender As Object, ByVal e As IDN.IO.ErroredEventArgs)

        If Not (e.Sender Is Nothing) AndAlso e.Sender.GetType().IsSubclassOf(GetType(IDNBrowsable)) Then

            WriteOutputTextBox(e.Severity.ToString() + ": " + e.Timestamp.ToString() + ": " + CType(e.Sender, IDNBrowsable).Name+" " + e.Message)

        Else

            WriteOutputTextBox(e.Severity.ToString() + ": " + e.Timestamp.ToString()+" " + e.Message)

        End If

    End Sub

   

    ' --------------------------------------------------------------------

    ' Recursive method to scan the entire Tag Server data tree and to direct

    ' ALL PropertyChanged events to our one handler. "Node" in this case

    ' refers to a data tree node.

    Private Sub SubscribeToAllPropertyChangedEvents(ByVal inNode As IDNBrowsable)

        AddHandler inNode.PropertyChanged, AddressOf OnPropertyChangedEventHandler

        If inNode.Children.Count > 0 Then

            Dim i As Integer

            For i = 0 To inNode.Children.Count -1

                SubscribeToAllPropertyChangedEvents(CType(inNode.Children.GetByIndex(i), IDNBrowsable))

            Next i

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Save the Tag Server configuration to the current file.

    Private Sub OnSaveMenuItemClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_saveMenuItem.Click

        If Not (m_tagServer Is Nothing) Then

  

            ' Ever been saved yet?

            If m_fileName = "default.qd" Then

                OnSaveAsMenuItemClick(sender, e)

            Else

                Dim stream As Stream = File.Open(m_fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)

                If Not (stream Is Nothing) Then

                    Dim formatter As New BinaryFormatter

  

                    ' This is all that is needed to save the contents of the

                    ' IO configuration onto the stream.

                    formatter.Serialize(stream, m_tagServer)

                    stream.Close()

                End If

  

                ' This clears the dirty flag for ALL items in the Tag Server.

                m_tagServer.Dirty = False

            End If

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Save the Tag Server configuration to a user selected file.

    Private Sub OnSaveAsMenuItemClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_saveAsMenuItem.Click

        If Not (m_tagServer Is Nothing) Then

            m_saveFileDialog.FileName = m_fileName

            If m_saveFileDialog.ShowDialog(Me) = DialogResult.OK Then

                Dim stream As Stream = File.Open(m_fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)

                If Not (stream Is Nothing) Then

                    m_fileName = m_saveFileDialog.FileName

                    Dim formatter As New BinaryFormatter

  

                    ' Serialize the Tag Server configuration onto the stream

                    formatter.Serialize(stream, m_tagServer)

                    stream.Close()

                End If

  

                ' Clean.

                m_tagServer.Dirty = False

            End If

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Open a Tag Server configuration.

    Private Sub OnOpenMenuItemClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_openMenuItem.Click

  

        ' If we already have an active node object...

        If Not (m_tagServer Is Nothing) Then

  

            ' Check for changes so we don't overwrite it with the new selection.

            If m_tagServer.Dirty Then

  

                ' Prompt the user to save the changes before overwriting with the new configuration.

                If MessageBox.Show("Save Changes to: " + m_fileName + "?", "Save Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes Then

  

                    ' Reuse this for saving.

                    OnSaveMenuItemClick(sender, e)

                End If

            End If

        End If

  

        ' Now let the user get the file to open.

        If m_openFileDialog.ShowDialog(Me) = DialogResult.OK Then

            Dim stream As Stream = Nothing

            stream = m_openFileDialog.OpenFile()

            If Not (stream Is Nothing) Then

                m_fileName = m_openFileDialog.FileName

                Dim formatter As New BinaryFormatter

  

                ' Release any existing Tag Server.

                m_tagServer.Release()

  

                ' Create a new Tag Server and its configuration from the file

                m_tagServer = CType(formatter.Deserialize(stream), TagServer)

                stream.Close()

            End If

  

            If Not (m_tagServer Is Nothing) Then

  

                ' Sign up for any error message events from the IO. 

                AddHandler m_tagServer.Errored, AddressOf OnIOErroredEventHandler

  

                ' Consider this a fresh start.

                m_tagServer.Dirty = False

  

                ' Connect any servers that were loaded.

                m_tagServer.Connect()

  

                ' Watch ALL of the property changed events.

                SubscribeToAllPropertyChangedEvents(m_tagServer)

            End If

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Create a new Tag Server configuration.

    Private Sub OnNewMenuItemClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_newMenuItem.Click

  

        ' Check for changes in any of the Tag Server objects

        If Not (m_tagServer Is Nothing) Then

  

            ' Has anything changed?

            If m_tagServer.Dirty Then

  

                ' Does the user want to save those changes?

                If MessageBox.Show("Save Changes to: " + m_fileName + "?", "Save Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes Then

                    OnSaveMenuItemClick(sender, e)

                End If

            End If

  

            ' Release the old Tag Server.

            m_tagServer.Release()

  

            ' Create a new 'blank' one.

            m_tagServer = New TagServer

  

            ' We're fresh and clean.

            m_tagServer.Dirty = False

  

            ' Set the default filename (this is NOT the way to maintain the filename).

            m_fileName = "default.qd"

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    ' Exit.

    Private Sub OnExitMenuItemClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_exitMenuItem.Click

  

        ' Check for Tag Server configuration changes.

        OnClosing(Nothing, Nothing)

        Close()

    End Sub

  

    ' --------------------------------------------------------------------

    ' Called directly when the "X" is clicked.

    Private Overloads Sub OnClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing

  

        ' Check for changes in Tag Server items.

        If Not (m_tagServer Is Nothing) Then

  

            ' Any changes?

            If m_tagServer.Dirty Then

  

                ' Prompt to save changes.

                If MessageBox.Show("Save Changes to: " + m_fileName + "?", "Save Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes Then

  

                    ' Call the save method.

                    OnSaveMenuItemClick(sender, e)

                End If

            End If

        End If

  

        ' Releases everything.

        m_tagServer.Release()

    End Sub

  

    ' --------------------------------------------------------------------

    ' The UI handlers below are needed because generally you can only call

    ' a UI control from the thread that created it. Access to these methods

    ' in our example are from a mixture of UI calls and event handlers.  The

    ' event handlers' threads are not going to be the same as the UI thread.

    Private Sub WriteScheduleTextBox(ByVal inLine As String)

        If m_scheduleEventLines.Count = 100 Then

            m_scheduleEventLines.RemoveAt(99)

        End If

        m_scheduleEventLines.Insert(0, inLine)

        If m_scheduleEventTextBox.InvokeRequired Then

            m_scheduleEventTextBox.BeginInvoke(New OnScheduleUIDelegate(AddressOf OnScheduleUIEventHandler), Nothing)

        Else

            m_scheduleEventTextBox.Lines = CType(m_scheduleEventLines.ToArray(GetType(String)), String())

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    Protected Delegate Sub OnScheduleUIDelegate()

    Protected Sub OnScheduleUIEventHandler()

        m_scheduleEventTextBox.Lines = CType(m_scheduleEventLines.ToArray(GetType(String)), String())

    End Sub

  

    ' --------------------------------------------------------------------

    Private Sub WriteTagTextBox(ByVal inLine As String)

        If m_tagEventLines.Count = 100 Then

            m_tagEventLines.RemoveAt(99)

        End If

        m_tagEventLines.Insert(0, inLine)

        If m_tagEventTextBox.InvokeRequired Then

            m_tagEventTextBox.BeginInvoke(New OnTagUIDelegate(AddressOf OnTagUIEventHandler), Nothing)

        Else

            m_tagEventTextBox.Lines = CType(m_tagEventLines.ToArray(GetType(String)), String())

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    Protected Delegate Sub OnTagUIDelegate()

    Protected Sub OnTagUIEventHandler()

        m_tagEventTextBox.Lines = CType(m_tagEventLines.ToArray(GetType(String)), String())

    End Sub

  

    ' --------------------------------------------------------------------

    ' This one scrolls backwards.

    Private Sub WriteOutputTextBox(ByVal inLine As String)

        If m_outputLines.Count = 500 Then

            m_outputLines.RemoveAt(0)

        End If

        m_outputLines.Add(inLine)

        If m_outputTextBox.InvokeRequired Then

            m_outputTextBox.BeginInvoke(New OnOutputUIDelegate(AddressOf OnOutputUIEventHandler), Nothing)

        Else

            m_outputTextBox.Lines = CType(m_outputLines.ToArray(GetType(String)), String())

        End If

    End Sub

  

    ' --------------------------------------------------------------------

    Protected Delegate Sub OnOutputUIDelegate()

    Protected Sub OnOutputUIEventHandler()

        m_outputTextBox.Lines = CType(m_outputLines.ToArray(GetType(String)), String())

    End Sub

  

    ' --------------------------------------------------------------------

    ' We are using this event handler to scroll to the bottom when we

    ' change the text allowing it to go backwards.

    Private Sub OnOutputTextBoxTextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_outputTextBox.TextChanged

        m_outputTextBox.SelectionStart = m_outputTextBox.Text.Length

        m_outputTextBox.ScrollToCaret()

    End Sub

End Class



Copyright © 2004-2017 Industrial DOT NET, Inc.