-
Dec 13th, 2017, 01:11 PM
#1
[RESOLVED] Toggle Visibility of Controls
Currently I have two RadioButtons stored in a FlowLayoutPanel to toggle between options yes and no (I wanted to use a CheckBox but I'm required to use RadioButtons). What happens is that everything after the FlowLayoutPanel is toggled based on which RadioButton is Checked; I was able to make it work using this in the Yes RadioButton's CheckedChanged event:
Code:
'Get the parent container
Dim parent As Control = DirectCast(sender, Control).Parent
Dim grandparent As Control = parent.Parent
Dim checked As Boolean = DirectCast(sender, RadioButton).Checked
'Toggle the visibility
For Each ctl As Control In grandparent.Controls.Cast(Of Control).Where(Function(c) c.Top >= parent.Bottom)
ctl.Visible = checked
Next
However, I feel like there could be a situation (though I cannot think of one) where the z-order of the controls could get messed up and I'm wanting to know if this solution that I have now will always preserve the order in which the controls appear.
Originally I got the index of the parent control in relation to the grandparent's children and attempted to iterate (both forwards and backwards) through the remaining controls, but apparently the index of the control has no bearing on what order the controls are displayed?
Basically, long story short, I want a reliable .net equivalent to jQuery's .next() method only I don't need the option for a selector.
-
Dec 13th, 2017, 01:58 PM
#2
Re: Toggle Visibility of Controls
The concept of a "next" control isn't related to where they are in the Controls collection. That collection's index order is specified by the designer and could be arbitrary. The only concept of "order" controls tend to have is the tab order. To that end, the GetNextControl() function will return the next control in tab order, and can search forwards or backwards. So set the TabIndex of each control in the order you want and it'll probably do the job!
I'm not sure exactly what determines tab order when TabIndex is the same. I'm sure it's documented somewhere but it seems like you can probably set the TabIndex more easily than satisfying that algorithm.
Personally if I had to implement this, I'd have my radio buttons bound to a Boolean property in my ViewModel, and each of the controls I want to appear/disappear would bind their Visible property to that property as well.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Dec 13th, 2017, 02:35 PM
#3
Re: Toggle Visibility of Controls
Here is why I didn't bind the Visible property, I have Labels that are toggled in all of this that are also toggled based on if a pattern is met in its respective control (basically an error Label). So if I would've bound the Visible property solely on if the RadioButton is checked, it would work for everything except for the error Labels, so since I'd need to handle these Labels anyways I figured I might as well handle everything else too.
-
Dec 13th, 2017, 02:48 PM
#4
Re: Toggle Visibility of Controls
If I knew more about the logic, that's just a slightly more complicated ViewModel pattern or maybe a BindingConverter.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Dec 13th, 2017, 03:05 PM
#5
Re: Toggle Visibility of Controls
I don't mind providing you with the logic.
These are the scenarios for toggling the error Labels:
- If a DataGridView has at least one row where the first column (CheckBoxColumn) is Checked, then its respective error Label is hidden.
- If a (different) DataGridView has at least one row, then its respective error Label is hidden.
- (I have multiple fields like this scenario) If the "Yes" RadioButton is Checked and the TextBox has Text, then its respective error Label is hidden.
Then there is the scenario for toggling the "other" controls, the controls look like this:
Code:
-GroupBox
--Label (prompt)
--FlowLayoutPanel
---RadioButton (yes)
---RadioButton (no)
--Label (value description)
--Either a TextBox or a DataGridView or a TableLayoutPanel with two Labels on top of two TextBoxes
--Error Label
Whenever the use checks the Yes RadioButton, everything on and after the value description Label is toggled except for the error Label if the condition is met from the scenarios listed above.
-
Dec 13th, 2017, 06:32 PM
#6
Re: Toggle Visibility of Controls
Originally Posted by Sitten Spynne
The concept of a "next" control isn't related to where they are in the Controls collection.
Actually, it is, after a fashion. The order of controls in a parent's Controls collection matches their z-order. Try this to prove it to yourself:
vb.net Code:
Dim controls = Me.Controls.Cast(Of Control)()
MessageBox.Show(String.Join(Environment.NewLine, controls.Select(Function(c) c.Name)))
Dim firstControl = controls.First()
Dim lastControl = controls.Last()
firstControl.SendToBack()
lastControl.BringToFront()
MessageBox.Show(String.Join(Environment.NewLine, controls.Select(Function(c) c.Name)))
That code shows that the order of the Controls collection actually changes when you change the z-order. When you add a new control in the designer, that control is added to the beginning of the Controls collection because it is added in front of all other controls in the z-order. You can change the z-order by right-clicking a control in the designer and selecting 'Bring to Front' or 'Send to Back' or you can use the Document Outline window.
Originally Posted by Sitten Spynne
I'm not sure exactly what determines tab order when TabIndex is the same. I'm sure it's documented somewhere but it seems like you can probably set the TabIndex more easily than satisfying that algorithm.
I just tested to confirm what I thought to be the case and it is indeed the z-order that breaks the deadlock. Unlike TabIndex, two controls can never have the same z-index so Tab order is always clearly defined.
Last edited by jmcilhinney; Dec 13th, 2017 at 06:38 PM.
-
Dec 13th, 2017, 06:35 PM
#7
Re: Toggle Visibility of Controls
-
Dec 14th, 2017, 09:15 AM
#8
Re: Toggle Visibility of Controls
I was actually completely unaware of the ErrorProvider class and stumbled upon it by accident. With this, I'm able to eliminate the need for my error Label that I had and then bind the description Label and other control (TextBox, DataGridView, etc.) to the Checked property of the respective "No" RadioButton.
To set the ErrorProvider for all of the controls, I'm handling the following method in their Validating event:
Code:
Private Sub TextBox_Required(sender As Object, e As System.ComponentModel.CancelEventArgs)
'Get the TextBox that is required
Dim txtBox As TextBox = DirectCast(sender, TextBox)
'Toggle the error or remove the error
ErrorProvider_PLC.SetError(txtBox, If(String.IsNullOrWhiteSpace(txtBox.Text), "This is a required field.", String.Empty))
End Sub
-
Dec 14th, 2017, 09:24 AM
#9
Re: [RESOLVED] Toggle Visibility of Controls
What's funny is I thought about suggesting an ErrorProvider, but because I know you're an expert I decided you had probably thought about it and decided to do things this way. >.<
RE: z-order that's good to know JMC, I think the problem I had now that I've looked around is I've seen several different ways to handle z-order in the frameworks I use and it's ruined my memory for how WinForms originally did it. Either way it sort of sounded like TabIndex wouldn't be a burden to maintain. It kind of bugs me that z-order and TabIndex aren't more closely related.
I still want to try the ViewModel approach I suggested but it takes a bit of work in WinForms so I'll be slow. I was going to do it yesterday but it was the kind of day where I had to go home to take care of emergency errands 3 times.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Dec 14th, 2017, 09:36 AM
#10
Re: [RESOLVED] Toggle Visibility of Controls
To be honest, I don't really care for the ErrorProvider class, but the reason why I decided to use it is because it eliminated the need to worry about the manual error Label that I had originally wanted to do.
If you do wind up working up a suggestion, I'd certainly appreciate it.
-
Dec 14th, 2017, 01:28 PM
#11
Re: [RESOLVED] Toggle Visibility of Controls
I started on it for fun, and despite my intent to make it work like MVVM, trying to implement something like MVVM in WinForms requires that you sit down and think about a data binding framework and I was hoping to finish in "2-3 sessions" instead of "a few weeks".
The project's not ready to post, I'm only up to the "visibility depends on a radio button AND a text box" stage yet. I might get everything done and still decide to swap to MVVM. But it's fun and this thread's already resolved so why not toss some interim ideas out?
I came up with this neat idea of a "Binder" to use in a form.
Code:
Public Interface IBinder
Property WhenTrue As Action
Property WhenFalse As Action
End Interface
Imports WindowsApp42
Public MustInherit Class DefaultBinder
Implements IBinder
Private _whenTrue As Action
Public Property WhenTrue As Action Implements IBinder.WhenTrue
Get
Return _whenTrue
End Get
Set(value As Action)
_whenTrue = value
Update()
End Set
End Property
Private _whenFalse As Action
Public Property WhenFalse As Action Implements IBinder.WhenFalse
Get
Return _whenFalse
End Get
Set(value As Action)
_whenFalse = value
Update()
End Set
End Property
Protected MustOverride Sub Update()
End Class
The idea is when you want to bind two things, you implement one of these. Update() has to be called when anything might want to know something exciting has changed. So we could implement a binder that does "something" when a TextBox's text equals "Do it!":
Code:
Public Class DoItBinder
Inherits DefaultBinder
Private _target As TextBox
Public Sub New(ByVal target As TextBox)
_target = target
AddHandler _target.TextChanged, AddressOf WhenTextChanges
End Sub
Private Sub WhenTextChanges(...)
Update()
End Sub
Protected Overrides Sub Update()
If _target.Text = "Do it!" Then
WhenTrue.InvokeIfSet()
Else
WhenFalse.InvokeIfSet()
End If
End Sub
End Class
I think there's some work that could make DefaultBinder have a useful default Update(), but whatever, I warned you I'm mid-implementation. This is version like, 6 in an hour.
I went down a path that gave me a few things. One is a UserControl with radio buttons for a YesNo enum. It's a thing that can be generalized to "any Enum" but that didn't feel relevant at this phase. It has events to indicate when the value changes, that's crucial to any attempt at binding. That led to a type that will call WhenTrue if the radio button is "Yes" and a TextBox's Text is not empty.
Code:
Public Class YesAndTextNotEmptyBinder
Inherits DefaultBinder
Private _yesNoSource As IYesNoSource
Private _trueValue As YesNo
Private _textSource As TextBox
Private _whenTrue As Action
Public Sub New(
yesNoSource As IYesNoSource,
trueValue As YesNo,
textSource As TextBox)
_yesNoSource = yesNoSource
_trueValue = trueValue
_textSource = textSource
AddHandler _yesNoSource.ValueChanged, AddressOf WhenYesNoChanges
AddHandler _textSource.TextChanged, AddressOf WhenTextChanges
Update()
End Sub
Private Sub WhenYesNoChanges()
Update()
End Sub
Private Sub WhenTextChanges()
Update()
End Sub
Protected Overrides Sub Update()
Dim isEmpty = Not String.IsNullOrWhiteSpace(_textSource.Text)
Dim matchesTrueValue = _yesNoSource.Value = _trueValue
If isEmpty AndAlso matchesTrueValue Then
WhenTrue.InvokeIfSet()
Else
WhenFalse.InvokeIfSet()
End If
End Sub
End Class
That is enough to finish the task for the disappearing, reappearing label, but I did decide I liked it better if I stuck a slight helper in between:
Code:
Imports WindowsApp42
Public Class VisibilityBinder
Inherits DefaultBinder
Private _target As Control
Public Sub New(
ByVal innerBinder As IBinder,
ByVal target As Control)
_target = target
innerBinder.WhenTrue = Sub()
target.Visible = True
Update()
End Sub
innerBinder.WhenFalse = Sub()
target.Visible = False
Update()
End Sub
End Sub
Protected Overrides Sub Update()
Dim toInvoke = If(_target.Visible, WhenTrue, WhenFalse)
toInvoke.InvokeIfSet()
End Sub
End Class
That's a kind of general, "I'll make any control visible when the other binding is true and invisible when the other binding is false".
So my Form ended up being this in a Load handler:
Code:
Dim innerBinder = New YesAndTextNotEmptyBinder(YesNoChoice1, YesNo.Yes, TextBox1)
Dim visibilityBinder = New VisibilityBinder(innerBinder, Label1)
It's not super hard to envision the other cases with a helper like this. I'd try something like this for the DataGridView case:
Code:
Public Class CheckBoxColumnBinder
Inherits DefaultBinder
Private _target As DataTable
Public Sub New(ByVal target As DataTable)
_target.AddHandler RowChanged, AddressOf WhenRowChanges
Update()
End Sub
Private Sub WhenRowChanges(...)
Update()
End Sub
Protected Overrides Sub Update()
Dim hasCheck = _target.Rows.Any(Function(r) CBool(r("whatever")))
If hasCheck Then
WhenTrue.InvokeIfSet()
Else
WhenFalse.InvokeIfSet()
End If
End Sub
End Class
Then:
Code:
Dim innerBinder = New CheckBoxColumnBinder(currentData.Tables("Products"))
Dim errorLabelVisibleBinder = New VisibilityBinder(innerBinder, lblProductsError)
Etc.
The trick, as always, isn't "there's no ugly code" but "the ugly code is hidden behind nice facades."
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|