#PowerApps: Using Components to create a Digital Clock - Part 1

Building a Digital Clock the traditional way

When I first set out to build a digital clock with night mode, I figured I was just going to start from what I know. Each digit of the clock is composed for 7 segments and each segment would behaves in a binary way based on the number that it needs to display.

To create each segment, I would use a Rectangle from the Icons gallery. Since I initially set out to add 3 digits to the clock - two to display seconds, and 1 to display minutes - this would require a whopping 21 rectangles, plus 2 for the blinking dots to bring the total rectangles to 23.

Controlling each digit required the use of 3 timers. Each timer would have to evaluate a value to determine which segments to display. For the right most digit, the following code is used for the timer, Timer1:

Timer1 OnTimerStart()
Select(Button1)

Timer1 OnTimerEnd()
UpdateContext({MyCounter: MyCounter + 1});
If (
    MyCounter > 9,
    UpdateContext({MyCounter: 0}),
    false
)

The code to determine which segment to display is encapsulated in a button, Button1, which allows for some isolation.

Button1 OnSelect()
// Segment 1
If(
    Or(
        MyCounter = 2,
        MyCounter = 3,
        MyCounter = 5,
        MyCounter = 6,
        MyCounter = 7,
        MyCounter = 8,
        MyCounter = 9,
        MyCounter = 0
    ),
    UpdateContext({FillColorS1: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS1: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS1: SegmentUnselected.Fill})
    )
);
// Segment 2
If(
    Or(
        MyCounter = 1,
        MyCounter = 2,
        MyCounter = 3,
        MyCounter = 4,
        MyCounter = 7,
        MyCounter = 8,
        MyCounter = 9,
        MyCounter = 0
    ),
    UpdateContext({FillColorS2: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS2: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS2: SegmentUnselected.Fill})
    )
);
// Segment 3
If(
    Or(
        MyCounter = 1,
        MyCounter = 3,
        MyCounter = 4,
        MyCounter = 5,
        MyCounter = 6,
        MyCounter = 7,
        MyCounter = 8,
        MyCounter = 9,
        MyCounter = 0
    ),
    UpdateContext({FillColorS3: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS3: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS3: SegmentUnselected.Fill})
    )
);
// Segment 4
If(
    Or(
        MyCounter = 2,
        MyCounter = 3,
        MyCounter = 5,
        MyCounter = 6,
        MyCounter = 8,
        MyCounter = 9,
        MyCounter = 0
    ),
    UpdateContext({FillColorS4: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS4: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS4: SegmentUnselected.Fill})
    )
);
// Segment 5
If(
    Or(
        MyCounter = 2,
        MyCounter = 6,
        MyCounter = 8,
        MyCounter = 0
    ),
    UpdateContext({FillColorS5: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS5: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS5: SegmentUnselected.Fill})
    )
);
// Segment 6
If(
    Or(
        MyCounter = 4,
        MyCounter = 5,
        MyCounter = 6,
        MyCounter = 8,
        MyCounter = 9,
        MyCounter = 0
    ),
    UpdateContext({FillColorS6: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS6: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS6: SegmentUnselected.Fill})
    )
);
// Segment 7
If(
    Or(
        MyCounter = 2,
        MyCounter = 3,
        MyCounter = 4,
        MyCounter = 5,
        MyCounter = 6,
        MyCounter = 8,
        MyCounter = 9
    ),
    UpdateContext({FillColorS7: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorS7: NightmodeBackgr.Fill}),
        UpdateContext({FillColorS7: SegmentUnselected.Fill})
    )
)

But keep in mind that this is only for the first digit! For the second digit (from right to left), a second timer evaluates whether the MyCounter variable is also greater than 9 to advance yet a second counter for the second digit.

Timer2 OnTimerStart()
Select(Button2)

In turn, we need to evaluate this second counter to ensure it does not exceed a value of 5.

Timer2 OnTimerEnd()
If(
    MyCounter = 0,
    UpdateContext({NyCounter: NyCounter + 1}),
    false
);
If(
    NyCounter > 5,
    UpdateContext({NyCounter: 0}),
    false
)

The Button2 code now looks awfully similar to the code we saw for Button1, but controls the display of segments for the second digit. Except for the name of certain variables, in fact, the code is the same!

Button2 OnSelect()
// Segment 1
If(
    Or(
        NyCounter = 2,
        NyCounter = 3,
        NyCounter = 5,
        NyCounter = 6,
        NyCounter = 7,
        NyCounter = 8,
        NyCounter = 9,
        NyCounter = 0
    ),
    UpdateContext({FillColorT1: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT1: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT1: SegmentUnselected.Fill})
    )
);
// Segment 2
If(
    Or(
        NyCounter = 1,
        NyCounter = 2,
        NyCounter = 3,
        NyCounter = 4,
        NyCounter = 7,
        NyCounter = 8,
        NyCounter = 9,
        NyCounter = 0
    ),
    UpdateContext({FillColorT2: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT2: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT2: SegmentUnselected.Fill})
    )
);
// Segment 3
If(
    Or(
        NyCounter = 1,
        NyCounter = 3,
        NyCounter = 4,
        NyCounter = 5,
        NyCounter = 6,
        NyCounter = 7,
        NyCounter = 8,
        NyCounter = 9,
        NyCounter = 0
    ),
    UpdateContext({FillColorT3: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT3: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT3: SegmentUnselected.Fill})
    )
);
// Segment 4
If(
    Or(
        NyCounter = 2,
        NyCounter = 3,
        NyCounter = 5,
        NyCounter = 6,
        NyCounter = 8,
        NyCounter = 9,
        NyCounter = 0
    ),
    UpdateContext({FillColorT4: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT4: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT4: SegmentUnselected.Fill})
    )
);
// Segment 5
If(
    Or(
        NyCounter = 2,
        NyCounter = 6,
        NyCounter = 8,
        NyCounter = 0
    ),
    UpdateContext({FillColorT5: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT5: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT5: SegmentUnselected.Fill})
    )
);
// Segment 6
If(
    Or(
        NyCounter = 4,
        NyCounter = 5,
        NyCounter = 6,
        NyCounter = 8,
        NyCounter = 9,
        NyCounter = 0
    ),
    UpdateContext({FillColorT6: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT6: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT6: SegmentUnselected.Fill})
    )
);
// Segment 7
If(
    Or(
        NyCounter = 2,
        NyCounter = 3,
        NyCounter = 4,
        NyCounter = 5,
        NyCounter = 6,
        NyCounter = 8,
        NyCounter = 9
    ),
    UpdateContext({FillColorT7: SegmentDayMode.Fill}),
    If(
        ToggleNightMode.Value,
        UpdateContext({FillColorT7: NightmodeBackgr.Fill}),
        UpdateContext({FillColorT7: SegmentUnselected.Fill})
    )
)

Now, we need to repeat the above for the left most digit all over again. Once again, we introduce a new timer with events that will evaluate the counter status to advance a third variable, and a button to encapsulate the code that will evaluate the segments to be activated for the third digit.

Timer3 OnTimerStart()
Select(Button3)

In turn, we need to evaluate the first and second counters for when they reset to zero, to advance the minute counter.

Timer3 OnTimerEnd()
If(
    NyCounter = 0 && MyCounter = 0,
    UpdateContext({OyCounter: OyCounter + 1}),
    false
);
If(
    OyCounter > 9,
    UpdateContext({OyCounter: 0}),
    false
)

The Button3 code is in effect an exact copy of the code in buttons Button1 and Button2, but controls the display of segments for the left most digit.

Button3 OnSelect()
// Segment 1
If(
    Or(
        OyCounter= 2,
        OyCounter= 3,
        OyCounter= 5,
        OyCounter= 6,
        OyCounter= 7,
        OyCounter= 8,
        OyCounter= 9,
        OyCounter= 0
    ),
    UpdateContext({FillColorM1: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM1: NightmodeBackgr.Fill}), UpdateContext({FillColorM1: SegmentUnselected.Fill}))
);
// Segment 2
If(
    Or(
        OyCounter= 1,
        OyCounter= 2,
        OyCounter= 3,
        OyCounter= 4,
        OyCounter= 7,
        OyCounter= 8,
        OyCounter= 9,
        OyCounter= 0
    ),
    UpdateContext({FillColorM2: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM2: NightmodeBackgr.Fill}), UpdateContext({FillColorM2: SegmentUnselected.Fill}))
);
// Segment 3
If(
    Or(
        OyCounter= 1,
        OyCounter= 3,
        OyCounter= 4,
        OyCounter= 5,
        OyCounter= 6,
        OyCounter= 7,
        OyCounter= 8,
        OyCounter= 9,
        OyCounter= 0
    ),
    UpdateContext({FillColorM3: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM3: NightmodeBackgr.Fill}), UpdateContext({FillColorM3: SegmentUnselected.Fill}))
);
// Segment 4
If(
    Or(
        OyCounter= 2,
        OyCounter= 3,
        OyCounter= 5,
        OyCounter= 6,
        OyCounter= 8,
        OyCounter= 9,
        OyCounter= 0
    ),
    UpdateContext({FillColorM4: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM4: NightmodeBackgr.Fill}), UpdateContext({FillColorM4: SegmentUnselected.Fill}))
);
// Segment 5
If(
    Or(
        OyCounter= 2,
        OyCounter= 6,
        OyCounter= 8,
        OyCounter= 0
    ),
    UpdateContext({FillColorM5: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM5: NightmodeBackgr.Fill}), UpdateContext({FillColorM5: SegmentUnselected.Fill}))
);
// Segment 6
If(
    Or(
        OyCounter= 4,
        OyCounter= 5,
        OyCounter= 6,
        OyCounter= 8,
        OyCounter= 9,
        OyCounter= 0
    ),
    UpdateContext({FillColorM6: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM6: NightmodeBackgr.Fill}), UpdateContext({FillColorM6: SegmentUnselected.Fill}))
);
// Segment 7
If(
    Or(
        OyCounter= 2,
        OyCounter= 3,
        OyCounter= 4,
        OyCounter= 5,
        OyCounter= 6,
        OyCounter= 8,
        OyCounter= 9
    ),
    UpdateContext({FillColorM7: SegmentDayMode.Fill}),
    If(ToggleNightMode.Value, UpdateContext({FillColorM7: NightmodeBackgr.Fill}), UpdateContext({FillColorM7: SegmentUnselected.Fill}))
)

As can be concluded from this exercise, the amount of (repeated) code, variable declarations, and controls increase progressively per 7-segment digit added. Another thing I noticed is, timers are not as responsive and the application is slow, overall - not the type of experience you would like and end-user to have.


NOTE: I am not sure if components is right for every application case, but one thing that I will insist on is to group objects and controls wherever possible to facilitate application readability and control navigation as an app builder.

Here's the final result:


On the positive side, building this application, using the pain staking process above thought me how I could leverage PowerApps components in order to promote code reusability, improve user experience, and increase application responsiveness. That will be the subject of my next article, Part 2 of this series.

This version of the app can be downloaded here (OneDrive).

Until next post,

MG.-
Mariano Gomez, MVP

Comments

Popular posts from this blog

Power Apps - Application Monitoring with Azure Application Insights

DBMS: 12 Microsoft Dynamics GP: 0 error when updating to Microsoft Dynamics GP 2013 R2

eConnect Integration Service for Microsoft Dynamics GP 2010