Results 1 to 11 of 11

Thread: [RESOLVED] Toggle Visibility of Controls

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,711

    Resolved [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.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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.

  3. #3

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,711

    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.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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.

  5. #5

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,711

    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.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  6. #6
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Toggle Visibility of Controls

    Quote Originally Posted by Sitten Spynne View Post
    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:
    1. Dim controls = Me.Controls.Cast(Of Control)()
    2.  
    3. MessageBox.Show(String.Join(Environment.NewLine, controls.Select(Function(c) c.Name)))
    4.  
    5. Dim firstControl = controls.First()
    6. Dim lastControl = controls.Last()
    7.  
    8. firstControl.SendToBack()
    9. lastControl.BringToFront()
    10.  
    11. 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.
    Quote Originally Posted by Sitten Spynne View Post
    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.

  7. #7
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: Toggle Visibility of Controls

    I wonder whether this might be useful to you:

    http://www.vbforums.com/showthread.p...on)&highlight=

  8. #8

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,711

    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
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  9. #9
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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.

  10. #10

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,711

    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.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  11. #11
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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
  •  



Click Here to Expand Forum to Full Width