AdvancedHMI Software

General Category => Open Discussion => Topic started by: Phrog30 on April 10, 2017, 09:12:30 AM

Title: User control help
Post by: Phrog30 on April 10, 2017, 09:12:30 AM
I have a user control which is just a button that I reuse several times throughout the program.  The image changes depending on if there are active alarms and I show how many alarms are present (btn.Text).  The only way I can get this to work is by a timer.  While this does work it's not very elegant and I would assume not best practice.  So, I tried to create an event based on alarm count.  When the value changes it raises the event.  This works, but it will not work on the button image or text.  Any ideas why?  When I'm using the event I use the same code, but instead on inside of timer tick I just create a sub and call it after the event.  The event works, but the image or text for the button doesn't.

Code: [Select]
Public Class Nav_Active_Alarm

    Dim picAnimate As Boolean = False

    Private mActive_Alarm_Count As Integer
    Public Event Active_Alarm_Count_Changed(ByVal mvalue As Integer)
    Public Property Count() As Integer
        Get
            Count = mActive_Alarm_Count
        End Get
        Set(ByVal value As Integer)
            mActive_Alarm_Count = value
            If Not Me.DesignMode Then
                'RaiseEvent Active_Alarm_Count_Changed(mActive_Alarm_Count)
            End If

        End Set
    End Property

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        If Not Me.DesignMode Then
            If Main.dt_Active_Alarms IsNot Nothing Then
                If Main.dt_Active_Alarms.Rows.Count = 0 Then
                    btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Static
                    picAnimate = False
                Else
                    If Not picAnimate Then
                        btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Animation
                        picAnimate = True
                    End If
                End If

                If Main.dt_Active_Alarms.Rows.Count > 0 Then
                    btn_Goto_Active_Alarm.Text = Main.dt_Active_Alarms.Rows.Count
                Else
                    btn_Goto_Active_Alarm.Text = Nothing
                End If

            End If
        End If

    End Sub

End Class
Title: Re: User control help
Post by: Archie on April 10, 2017, 10:25:53 AM
Is this along the lines of what you want to do:
Code: [Select]
Public Class AlarmButton
    Inherits Button

    Public Event AlarmCountChanged As EventHandler(Of AlarmCountChangedEventArgs)

#Region "Properties"
    Private m_AlarmCount As Integer
    Public Property AlarmCount As Integer
        Get
            Return m_AlarmCount
        End Get
        Set(value As Integer)
            If m_AlarmCount <> value Then
                m_AlarmCount = value
                OnAlarmCountChanged(New AlarmCountChangedEventArgs(m_AlarmCount))

                If m_AlarmCount > 0 Then
                    BackgroundImage = m_AlarmImage
                Else
                    BackgroundImage = m_NoAlarmImage
                End If

                Text = m_AlarmCount
            End If
        End Set
    End Property

    Private m_NoAlarmImage As Image
    Public Property NoAlarmImage As Image
        Get
            Return m_NoAlarmImage
        End Get
        Set(value As Image)
            m_NoAlarmImage = value
        End Set
    End Property

    Private m_AlarmImage As Image
    Public Property AlarmImage As Image
        Get
            Return m_AlarmImage
        End Get
        Set(value As Image)
            m_AlarmImage = value
        End Set
    End Property
#End Region

#Region "Events"
    Protected Overridable Sub OnAlarmCountChanged(ByVal e As AlarmCountChangedEventArgs)
        RaiseEvent AlarmCountChanged(Me, e)
    End Sub
#End Region
End Class

'*************************************************************************************************
'* Event args class to pass arguments to event handler
'*************************************************************************************************
Public Class AlarmCountChangedEventArgs
    Inherits EventArgs

#Region "Constructor"
    Public Sub New()
        MyBase.New
    End Sub

    Public Sub New(ByVal AlarmCount As Integer)
        Me.New
        m_AlarmCount = AlarmCount
    End Sub
#End Region

    Private m_AlarmCount As Integer
    Public Property AlarmCount As Integer
        Get
            Return m_AlarmCount
        End Get
        Set(value As Integer)
            m_AlarmCount = value
        End Set
    End Property
End Class

- Add a new class to your project named AlarmButton and add the above code.
- Build the project
- From the Toolbox, add an AlarmButton
- Set AlarmImage and NoAlarmImage
- As you change the AlarmCount your image and text should changed
Title: Re: User control help
Post by: Phrog30 on April 10, 2017, 01:16:11 PM
That doesn't work either.  I can see the event.  But for whatever reason the images don't change.
Title: Re: User control help
Post by: Archie on April 10, 2017, 03:22:04 PM
I posted a demo project (AlarmButtonDemo) showing the images change with the count:

https://sourceforge.net/projects/advancedhmi/files/advancedhmi/3.5/SampleProjects/
Title: Re: User control help
Post by: Phrog30 on April 11, 2017, 09:01:05 AM
Thanks Archie, however, that's not quite what I'm after.  Unless I'm missing something, I would still have to add code for each button.  If I'm correct on that, then what's the point?  What I'm after is a reusable object that you just drag and go.  What I want, I was able to get working but only using a timer, which I guess will be good enough.

James
Title: Re: User control help
Post by: Archie on April 11, 2017, 09:03:11 AM
What do you want to control the count? Is it linked to a value in the PLC?
Title: Re: User control help
Post by: Phrog30 on April 11, 2017, 09:39:30 AM
The PLC does know the count, but I'm using a variable in .NET code.  I was actually being lazy and just used the row count in a grid: Main.dt_Active_Alarms.Rows.Count
Title: Re: User control help
Post by: Archie on April 11, 2017, 01:04:30 PM
I updated the project with the button alarm count linked to a DataGridView row count

You can also control it through a PLC value by inheriting it into a control based off one of the existing control classes that will give it the ComComponent and the PLCAddress* properties.
Title: Re: User control help
Post by: Phrog30 on April 11, 2017, 01:20:38 PM
Thank you Archie, I will look at that when I get a chance later today or tomorrow.  Your support and help is stellar!!

James
Title: Re: User control help
Post by: Archie on April 11, 2017, 01:30:07 PM
I updated the project again to add an AlarmButtonToDGV that links to the DataGridView through a DatGridViewLink property, therefore encapsulating the code in the control.
Title: Re: User control help
Post by: Phrog30 on April 20, 2017, 12:07:53 PM
Archie, your code worked well.  Thank you.  However, can you explain to me why this code doesn't work?
Code: [Select]
Public Class Nav_Active_Alarm

Private mActive_Alarm_Count As Integer
Public Event Active_Alarm_Count_Changed(ByVal mvalue As Integer)
Public Property Count() As Integer
    Get
        Count = mActive_Alarm_Count
    End Get
    Set(ByVal value As Integer)
        mActive_Alarm_Count = value
        If Not Me.DesignMode Then
            RaiseEvent Active_Alarm_Count_Changed(mActive_Alarm_Count)
            test()
        End If

    End Set
End Property

Private Sub test()

    If Not Me.DesignMode Then

        If mActive_Alarm_Count = 0 Then
            Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Static
            'console or msgbox will work but updating the image will not
        Else
            Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Animation
                'console or msgbox will work but updating the image will not
            End If

    End If

End Sub

End Class

I can see the event working by either a console write or message box, but images, text, etc. will not update.  However, if I call this code from a timer, it will.  I'm at a loss for why this wouldn't work.  So, for future reference can you explain how I could make this work?  A colleague mentioned I needed to refresh or update for this to work, but I tried and it made no difference.

Thanks in advance...
Title: Re: User control help
Post by: Archie on April 20, 2017, 02:26:07 PM
However, can you explain to me why this code doesn't work?
Code: [Select]
Get
        Count = mActive_Alarm_Count
    End Get
This piece of code does not return any value, so when something tries to get the Count property, it will return nothing. I would have thought the compiler would have flagged it as an error.
   
Code: [Select]
Private Sub test()
    If Not Me.DesignMode Then
        If mActive_Alarm_Count = 0 Then
            Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Static
        Else
            Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Animation
         End If
    End If
In your test subroutine there is a reference to an undefined object. Based on the Me qualifier, that would tell me it is referring to an object within the class, but there is no btn-Goto_Active_Alarm object in the Nav_Active_Alarm class.
Title: Re: User control help
Post by: Phrog30 on April 20, 2017, 03:58:20 PM
Archie-
I actually am getting a valid value on "Count".  I can add this to the console and see it return the correct value.  Like I said, I can add a timer and on tick it will run the same identical code that you see and it works.  That's the part I'm having trouble to understand.  It's no big deal, just something that is bugging me why I can't figure it out.

I don't see any error or warnings.  By the way, this is a user control and there is a button named "btn_Goto_Active_Alarm".

James
Title: Re: User control help
Post by: Archie on April 20, 2017, 04:48:11 PM
By the way, this is a user control and there is a button named "btn_Goto_Active_Alarm".
After I posted that it occurred to me that you said it was a UserControl which means Visual Studio generated a partial class that has the definition of any items you add using the designer.

As for the Getter, try it like this:

Get
   Return mActive_Alarm_Count
End Get
Title: Re: User control help
Post by: Phrog30 on April 21, 2017, 11:43:04 AM
I tried but that didn't make any difference.  I'm still seeing the event, but I can't get text or images, etc, to change.  I added a text changed event:

Code: [Select]
Private Sub txtChanged(sender As Object, e As EventArgs) Handles MyBase.TextChanged

        Console.WriteLine("im changed")

       
End Sub

and I'm seeing this event, but the actual text isn't changing. 
Title: Re: User control help
Post by: Archie on April 21, 2017, 02:23:33 PM
I posted another project that uses a UserControl and the same code that you posted (only changed to match .NET patterns and practices):

https://sourceforge.net/projects/advancedhmi/files/advancedhmi/3.5/SampleProjects
Title: Re: User control help
Post by: Phrog30 on April 21, 2017, 03:18:04 PM
Thanks for posting Archie.  The issue with your example is each instance needs code for the count, which is what I'm trying to eliminate.  The goal is to mimic the PV+ and global objects.  So, I just started with a simple button that I could drop on a form and it would work.  The only way I could get it to work was to use a timer to "poll" the value, instead of the value getting "pushed" to each instance.  I just didn't like using a timer but I guess I will have to live with it.

Thanks again,
James
Title: Re: User control help
Post by: Phrog30 on April 21, 2017, 03:49:05 PM
Here is a very crude example of what I am trying to achieve.  The 3 buttons on the form have no code.  I just dropped them and they work.  The issue, the user control uses a timer to get the values, which I think is clunky.  But, it works.  I know experienced guys would laugh at this, but with my experience that's the best I could come up with.
Title: Re: User control help
Post by: Archie on April 22, 2017, 03:21:55 PM
Now I understand what you are trying to do. This is referred to as Binding. Unfortunately WinForms lacks a graceful way to bind properties between 2 object without writing code(WPF does this nicely). There are a couple ways to go about this. One would be to use an Application Setting to hold the Count value. After you add an object to the form, in the Properties Window at the top is a section named "(Application Settings)". You can drill into this to create a new application setting and link it to the property of choice. So let's say you create a setting named AlarmCount. In your code, you would manipulate the value as such:

My.Settings.AlarmCount = 0

If you binded this to a property, when the value changes it will automatically push the new value into the property also. This is a similar technique where I demonstrated how to mimic tags as done in other HMI software.

Another method for binding that is a bit more complicated is to add properties to your object to specify the control to bind to and the property. Attached is a modified version of your project showing this technique. In design view, if you select the NavActiveAlarm and look at the Properties, you will see where I added DataBindingSource and DataBindingProperty. In the example, I set those to TextBox1 and Text. I also added ImageNoAlarm and ImageAlarm properties. These added properties create a re-usable control in which different instances can be bound to different values.
Title: Re: User control help
Post by: Phrog30 on April 22, 2017, 05:08:06 PM
Thanks, I will look at what you did later this evening.  I'm sorry I didn't explain myself well.  I tend to do a crap job explaining things.

James