Reputation: 443
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:
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.
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
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
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