BigBobby
BigBobby

Reputation: 443

Synchronizing Multiple Controls with Multiple Events in VB.net

I have several different controls in a WinForms GUI that must all be synchronized to each other: I have 2 textboxes which display the same quantity in different units and 2 custom controls that show the quantity graphically as old-style analog gauges. Changing the value in either text box or with the custom controls results in a new value being written to a piece of hardware via USB.

What I don't want is to get more than one USB message sent to the hardware when one control is changed. When the event handler for one control updates the other three, however, then their event handlers also run. These are the two ways I've solved the problem so far, but I'm hoping there's something better:

  1. At first I just made a global flag for each set of controls. When the event handler runs it checks the flag and if it's set it immediately exists. If the flag is not set the event handler sets it and then updates the 3 other controls who's event handlers should do nothing when they execute because the global flag is now set.

  2. Now I use the AddHandler and RemoveHandler statements to turn off/on the event handlers for the other 3 controls when I update them in the first event handler. This stops the other event handlers from even firing in the first place.

My first solution is simple but feels like a massive hack. The second solution means all of my event handlers need 6 extra lines added for the AddHandler/RemoveHandler statements.

I'm hoping there's something better that I would have never dreamed off (maybe some inherent way to tell vb.net that controls are linked?) but googling has yet to help me. I appreciate whatever help I can get from you SO!

Upvotes: 0

Views: 221

Answers (2)

Fabio
Fabio

Reputation: 32455

You definitely need only one place where updating will happened.
You not mentioned what kind of UI you have (Winforms, WPF - assumed you talking about desktop application).

I suggest use MVVM style - it will work for both Winforms and WPF.

Public Class USBViewModel
    Implements INotifyPropertyChanged

    Private _Quantity As Integer
    Public Property Quantity As Integer
        Get
            Return _Quantity
        End Get
        Set(value As Integer)
            If value = _Quantity Then Exit Property
            ' Here you can update value to the USB
            _Quantity = value
            RaisePropertyChanged()
        End Set
    End Property

    #Region "INotifyPropertyChanged"

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Private Sub RaisePropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    #End region

End Class

Then you can bind one(same) instance of USBViewModel to your controls.
You can create Format and Parse eventhandlers for conversion original unit to the unit you want to show and back.
For example textboxes in Winforms can be bounded like this:

Public Class YourForm
    Private ReadOnly _ViewModel As USBViewModel
    Public Sub New()
        InitializeComponents()

        _ViewModel = new USBViewModel()

        var unitOneBunding = new Binding("Text", _ViewModel, "Quantity", True);
        unitOneBunding.Format += bindUnitOne_Format;
        unitOneBunding.Parse += bindUnitOne_Parse; 

        TextBoxUnitOneQuantity.DataBindings.Add(unitOneBunding);
    End Sub  

    private void bindUnitOne_Format(object sender, ConvertEventArgs e)
    {
        int originalUnitValue = (int)e.Value;
        int unitOneValue = ConvertToUnitOne(originalUnitValue);
        e.Value = unitOneValue;
    }

    private void bindUnitOne_Parse(object sender, ConvertEventArgs e)
    {
        int unitOneValue = (int)e.Value;
        int originalUnitValue = ConvertToOriginalUnit(unitOneValue);
        e.Value = originalUnitValue;
    }
End Class

For custom controls you can pass this same instance to the custom controls constructor and bind it there to the same property.

This solution based on the INotifyPropertyChanged and data binding.
When you use databinding controls will "listen" for INotifyPropertyChanged event and will update itself when value changed.
In case when user update control, control will call setter of bounded property.

Upvotes: 1

SSS
SSS

Reputation: 5403

It's very easy to tie yourself in knots with cascading event handlers. I'd suggest having a "Save changes" button and the USB message only gets sent when that button is clicked.

For your textboxes, you could use a different event from TextChanged. For example, if you use Validating, you can check the entered value before committing, and updating the other textbox won't trigger an endless loop:

  Private Sub TextBox1_Validating(sender As Object, e As CancelEventArgs) Handles TextBox1.Validating
    If Val(TextBox1.Text) < 0 Then
      e.Cancel = True 'don't allow negative numbers
    Else
      TextBox2.Text = (Val(TextBox1.Text) / 10).ToString
    End If
  End Sub

  Private Sub TextBox2_Validating(sender As Object, e As CancelEventArgs) Handles TextBox2.Validating
    If Val(TextBox2.Text) < 0 Then
      e.Cancel = True 'don't allow negative numbers
    Else
      TextBox1.Text = (Val(TextBox2.Text) * 10).ToString
    End If
  End Sub

If you really want realtime unit conversion, then another alternative is to check ActiveControl:

  Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
    If ActiveControl Is TextBox1 Then
      TextBox2.Text = (Val(TextBox1.Text) / 10).ToString
    End If
  End Sub

  Private Sub TextBox2_TextChanged(sender As Object, e As EventArgs) Handles TextBox2.TextChanged
    If ActiveControl Is TextBox2 Then
      TextBox1.Text = (Val(TextBox2.Text) * 10).ToString
    End If
  End Sub

Upvotes: 1

Related Questions