Tuesday, March 26, 2019

#PowerApps: Numeric Up/Down control with persisted button press event using components

I just recently returned from the Microsoft MVP Global Summit 2019 where I had a chance to meet some of the top minds in the Microsoft PowerApps and Flow space. This was a truly exciting moment as I have been learning from the very same MVPs I met - yes, we do learn from each other!

In one of my hallway discussions, I ran into my buddy Mehdi Slaoui Adaloussi, Principal Program Manager at Microsoft, who I first met at Microsoft Build 2018. I had read Mehdi's recent article on reusable components and, in particular, that I had been playing with his version of the Numeric Up/Down Control.

See 10 Reusable Components: tab control, calendar, dialog box, map control and more.

I must start by saying that the components Mehdi put in place expose some very clever implementation techniques, so I highly recommend you download the msapp files and load them up in your environment and study them.

The Numeric Up Down control in particular, caught my attention as it required multiple and repeated individual clicks to advance the value up or down, which could take away from the user experience, so I decided to build from where Mehdi left off, by changing a few things.

NOTE: my implementation does not account for the control stylistic settings added by Mehdi, but this is sure an easy feat to accomplish.

Getting Started

NOTE: You will need to enable the Components experimental feature, before you can follow these steps.

1. Create a new component

Click on New Component under the Components tab to create your component. Rename the default name to NumericUpDn.

Components tab

2. Add the controls needed to create this component.

For this control, we will need the following 6 controls:

  • 2 button controls (from the toolbar)
  • The Up icon (from the Icon gallery)
  • The Down icon (from the Icons gallery)
  • A Timer control (from the Controls gallery)
  • A Text Input control (from the Text gallery)
I always recommend you worry about the layout and aesthetics at the very end of the implementation. Nonetheless, I keep the controls close for ease of organization at the end of the implementation. 

Controls
The most important thing right now is to get the needed controls. I will also explain the use of each control as we go along. 


3. Add Component custom properties

For the Numeric Up Down control, we will need 5 custom properties as follow:

Custom Properties

Default
: Number / Input. This will serve to seed our numeric initial text input value when the control is first loaded within an app.

Min: Number / Input. This will be the lower limit for our numeric up/down control. When clicking the down button, we will check to ensure the control value itself never gets below the minimum value.

Max: Number / Input. This will be the upper limit for our numeric up/down control. When clicking the up button, we will check to ensure the control value itself never exceeds the maximum value.

Sensitivity: Number / Input. this will control how fast or slow the button press behaves to increase or decrease the numeric value in the text input field.

Value: Number / Output. This will be the value returned by the control to the calling app. 

4. Rename the Controls

Now that we have all the controls and custom properties in place, we will begin by renaming the controls for readability sake and ease of following - it's also a good practice.

Button1, rename to BtnUp
Button2, rename to BtnDn
Icon1, rename to IcnUp
Icon2, rename to IcnDn
TextInput1, rename to NumValue

Rename component controls

NOTE: renaming the Timer control seems to break the timer itself - this is a bug I have reported to the PowerApps team.

5. Add some logic

NumValue control: for this control, change the Format to Number. We will want to ensure the text input control Default property is set to the incoming Default custom property value if the initial value is blank, as follows:



Timer1 control: this is perhaps the most important control on the component, since it will basically control the overall behavior of the timer. First, let's set some properties:

  • Start property. We will want to start the timer when either the BtnUp or BtnDn pressed events are fired. Since the Start property is a true/false control (boolean) we can set the property to BtnUp.Pressed || BtnDn.Pressed

  • Duration property. We will set this property to NumericUpDn.Sensitivity. Basically, we are setting a delay between each increment or decrement of the NumValue control.

  • Repeat property. Set to true. Since we want to persist the button press event, we want the timer to restart each time after the Duration cycle is completed.

  • Reset property. We need the Timer to reset each time either button is released from a Pressed state. Hence, we can use the same true/false state as the Start property, BtnUp.Pressed || BtnDn.Pressed.

Timer control Data settings

Phew! We are done with the basic settings for the timer control settings.

Next, the timer must perform a couple actions: 1) on start, it will evaluate which button was pressed, and based on the button, increase or decrease the value in the text control. 2) on end, it will evaluate whether we've reached the lower or upper limits established by the Min and Max custom properties, respectively.

For the OnTimerStart event,

OnTimerStart

For the OnTimerEnd event,

OnTimerEnd event

BtnUp and BtnDn controls: we also want a user to retain the ability to click the buttons without persisting the pressed event, effectively advancing the NumValue control one by one until the upper or lower limits are reached. Hence we must also add some validation to the OnSelect event of each button.

For the BtnUp OnSelect event,

BtnUp OnSelect event

For the BtnDn OnSelect event,

BtnDn OnSelect event

6. Now some aesthetics

We have completed our low code implementation. Now, we are off to setting organize the controls and set some properties that will make this a useful control.

a) Select the BtnUp control and set the size to 62 width and 24 height; set the x and y positions to 257 and 2, respectively. Set the Border Radius property to 5. Set the Fill property to RGBA(56, 96, 178, 0). Set the BorderColor, HoverColor, and HoverFill properties to BtnUp.Fill. Clear the Text property (blank).

b) Select the BtnDn control and set the size to 62 width and 24 height; set the x and y positions to 257 and 26, respectively. Set the Border Radius property to 5. Set the Fill property to RGBA(56, 96, 178, 0). Set the BorderColor, HoverColor, and HoverFill properties to BtnDn.Fill. Clear the Text property (blank).

c) Select the IcnUp control and set the size to 64 width and 25 height; set the x and y positions to 256 and 1, respectively. Set the Fill property to RGBA(0, 18, 107, 1); set the Color property to RGBA(255, 255, 255, 1).

d) Select the IcnDn cotrol and set the size to 64 width and 26 height, set the x and y positions to 256 and 26. Set the Fill property to RGBA(0, 18, 107, 1); set the Color property to RGBA(255, 255, 255, 1).

NOTE: By setting these properties, the buttons and the icons are now overlaid on each other. To further access these control properties, use the control navigation pane on the left of the Design Studio.

e) Select the NumValue control and set the size to 256 width and 51 height, set the x and y positions to 0 and 1

f) Finally, set the NumericUpDn component size to 322 width and 57 height.

You should now have something that look like this:

NumericUpDn component (shown at 150%)

7. Testing the Component

To test the component, I have added the control from the Components gallery, a slider for the timer sensitivity, and a couple Text Input boxes, along with a label to track the output from the componentized control. You can quickly guess what goes to what.

NOTE: please ensure the Text Input boxes are of numeric type.

Test Screen 

The end result can be appreciated in this video.


You can download the component control from the PowerApps Community Apps Gallery, here.

Until next post,

MG.-
Mariano Gomez, MVP

Thursday, March 7, 2019

#MSDYNGP: Named Printers and Redirected Printers in RDP environments

A lot of the guiding principles for deploying Named Printers in a Terminal Server or Citrix environment comes from two of my favorite articles, written by my good friend and fellow Microsoft Business Applications MVP, David Musgrave (twitter: @winthropdc). David happens to be the creator of Named Printers and probably understands the product better than anyone I know. You can read his articles here:

Using Named Printers with Terminal Server
Using Named Printers with Terminal Server Revisited

These articles continue to be very relevant if you are in an environment where a Print (or Printer) server is the norm and published printers are standard. Print servers are used to interface printers with devices in a network, but mostly to standardize administrative policies, and balance the document load that printers can manage. Part of the standardization is to ensure printers are uniquely identified across the networks, regardless of whether you are accessing the network remotely or physically connected to it. Print servers also ensure that print drivers are consistent across the network, which in turn reduces the possibility of driver clashes or unsupported drivers.

If you are familiar with Named Printers, one of the things it likes is standard drivers and standard printer names. The minute the binary information - stored at the OS level - about a printer driver or name no longer matches the binary information stored by Named Printers - at the database level - about the same printer, chances are Named Printers will cease to work properly. However, in a print server environment with published printers, this is easily fixed by reconfiguring the printer properties in Named Printers.

But, why am I telling you this? In the era of BYOD and remote offices, system administrators no longer have the time or the willingness to be dealing with such mundane tasks as worrying about printers and drivers. Heck, most of us work from our home now or a roaming between different offices. Yet, as users, we still need the ability to perform the simple, mundane task of printing documents and generate reports from our ERP system. Enters printer redirection.

Printer redirection was first implemented in Windows 2000 Server.  Printer redirection enables the users to print to their locally installed printer from a terminal services session.  The Terminal Server client enumerates the local print queues to detect the locally installed printers.  This list is presented to the server and server creates the print queue in the session. The TS client provides the driver string name for the locally installed printers and if the server has matching drivers installed then the printers will be redirected.  When we look at Printers on the Terminal Server, a redirected printer will have a name similar to what is shown below:


Note the printer name is presented with a Printer_Name (redirected sessionId) label. The session Id changes each and every time the user logs in and logs out of the terminal services session. Given what we know about Named Printers, it's safe to say this would wreak havoc causing errors, like the following, to show up during printing:


Document_Printer "Printer_Name (Redirected SessionId)" or PaperSource "sourceInfo" is not valid

You can go back into Named Printers and recapture the printer properties if need be, but the same will need to be done each and every time a user logs in and logs out of the terminal services session. If you have more than one user directing documents to the same physical printer via Named Printers, then this solution (recapturing the printer properties) is simple unusable.

So, what can be done?

Thinking about the problem, I realized this could not be just a Microsoft Dynamics GP/Named Printers issue. There are a multitude of applications designed to capture and store printer properties they rely on to consistently create a print experience. I started wondering how others are dealing with this issue. So onto Google I went to request search terms like "rename printers", "rename redirected printer", etc., I finally ended up with a very interesting hit on a company called Babbage Technologies, located in Minnesota. Babbage have a small product called RenPrinters which in essence applies a regular expression to the redirected printer name and allowing you to specify a static name with a combination of the printer name, user name, and machine name. You can pick and choose which combination to use. This is done at the server operating system level, which then allows you to map that static named printer to Named Printers.

The following shows the main application control panel:


There are a number of predefined regular expressions along with a number of predefined printer name formats. You could configure named printers to use a template user name scenario or create a template per machine depending on your specific needs. Another important feature is the ability to exclude printers using specific drivers from being renamed, giving you greater control over how the application behaves.

A server reboot and now the printer appears as defined by the Printer Name Format expression:


This is super useful now as Named Printers is once again happy: standard printer name, standard properties!



Now, to be fair, there are other solutions in the market. There's an open source solution called Printerceptor currently available on GitHub. Printerceptor uses PowerShell to rename redirected printers and uses the same concept of regular expressions and name formatting to do the job. Of course, open source means you are subjected to the developer's availability to fix a problem, if one is found.

Hope you found this informative and helpful.

Until next post,

MG.-
Mariano Gomez, MVP

Monday, March 4, 2019

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

In Part 1 of this series, you saw how my first version of the digital clock went. Although it got the job done, it was plagued with repetitive code, repetitive controls, and over saturation of variables, which in turn rendered the application hard to follow, and worse yet, affected performance.

In this article, I will show how to use PowerApps Components to promote reusability and decrease the code footprint. Components is currently a preview feature, hence word of caution when using them as you may need to retest your app once it becomes generally available.



The previous experience showed us that we can save time and code by creating a component to be used for the digits of the clock. This digit component could then be enhanced by allowing the developer to pass in the digit to be displayed and the foreground and background colors the segments - all set up as custom properties to the component - as shown here:


We have also added code for each of the segments that will bring them to the foreground or place them in the background, based on the DigitValue custom property. Here's a code snippet for Fill property for the top segment of our digit:


Note that here we need to reference the name of the component within the scope of the variable. All the code to implement the additional segments can be found in the previous article or by downloading a copy of the msapp file for this project.

Once we have the component in place, we can then move to app surface, where we add the 3 digits as components, the separating dots, and 4 timers as in our previous app. Since the code to activate the segments is in the component itself (as shown above), there's no need to add 3 buttons to encapsulate that code anymore.

Hence our first timer control, Timer1, will simply do 2 things:


  • On start, it will evaluate the night mode toggle and set the proper background and foreground depending on the setup parameters (on the Setup screen)
  • On end, it will advance the digit counter. 


NOTE: Each timer is set to 1000 milliseconds with the Repeat property set to true.

The end result is a super streamlined application, with a reusable component and little code to go along, keeping up with the Low Code spirit of PowerApps.



For the full implementation of this project can be found on the PowerApps community website, here.

Until next post,

MG.-
Mariano Gomez, MVP

#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

Thursday, February 14, 2019

#PowerApps: Simple Object Proximity and Collision Detection

As of late, I have been very interested in all things "Motion" as it relates to developing PowerApps applications - see #PowerApps: Motion Patterns with Parametric Equations. Although, most of the apps you will see tend to be around solving business problems, you cannot really dismiss the capabilities of PowerApps as a gaming platform.


The Basics

In this article, I explore a simple object proximity and collision detection approach, based on some simple logic. The world of gaming uses more sophisticated algorithms based on the laws of physics and what's not, but keep in mind that PowerApps is designed to be a low code/no code environment, hence access to user driven programmatic methods is extremely limited.

The following is a representation of the actual canvas apps I created for this example. It consists of 4 directional arrows to provide motion to smiley, a simple character added from the icons gallery. In addition, we have a rectangular obstacle added as well using the icons gallery. Finally, a slider which controls the steps of the smiley character in any direction. 

Canvas app

There are also two hidden button controls, which we will use to do branch logic. I have found that having code in hidden buttons act as a way to encapsulate logic that you can execute as if it were a method, as you can use PowerApps' Select function to run the On Select code behind the button.

For ease of setting control properties, I have grouped both the directional arrows in one group called Arrows, and the hidden buttons in another group called BusinessLogic, as shown on the left pane of design surface.


Adding Code

Screen On Visible

For the On Visible event, we are simply initializing the position of our smiley character. Smiley will be placed at roughly the center of the screen. The (X,Y) coordinates are stored in two context variables called xPos and yPos, which are then assigned to the X and Y property of the Smiley character.


You can set the obstacle icon away from Smiley, anywhere you want to.

Note: you could add a couple controls to input the position and width and height of the obstacle, but for simplicity sake, I have chosen to do this manually.


Directional Arrows Group

The directional arrows will control Smiley's motion up, down, left, or right, according to the arrow pressed. The up arrow will simple reduce the position on the Y axis, the down arrow will increase it. Conversely, the left arrow will decrease Smiley's position on the X axis, and the right arrow will increase it. All these movements are done in incremental steps dictated by the slide control value, setting the pace of the movement, if you will.

Note that I have chosen to define a directional variable, SmileyDirection, which tracks whether the motion is decreasing (up, left) or increasing (down, right).

Up Arrow



Left Arrow


Down Arrow


Right Arrow



Business Logic Group

Each time Smiley's motion is affected by the directional arrows, we need to evaluate whether its next step in that direction will put it in proximity with the obstacle, hence leaving it in danger of colliding on the next move.

The first logic branch says:

If Smiley's next step in the current direction (top, bottom) is within the horizontal boundaries of the obstacle - dictated by the value pair Rc1.X and Rc1.X + Rc1.Width - it will only be in the clear if it's outside of the vertical boundaries (the obstacle's height). Otherwise, Smiley would be in imminent danger of colliding on the next move in the current direction of motion - and this is where it gets interesting!

We also need to factor Smiley's own height when the motion is in the direction of the obstacle from the top as the value pair (Smiley.X, Smiley.Y) only represents the upper left corner of Smiley's position. In complex gaming design, this is done from the center of gravity of the object, but remember, we are not that fancy here.

EvalLogic1


If Smiley is not within the horizontal boundaries of the obstacle, then we need to similarly evaluate if Smiley it's within the vertical boundaries of the obstacle, while traveling left or right - dictated by the value pair Rc1.Y and Rc1.Y + Rc1.Height. Then, it stands to reason, that Smiley will only be in the clear, if it's outside the horizontal boundaries in the direction of travel.

Again, we want to factor in Smiley's width to ensure that if we are traveling towards the object from the right, we detect its proximity more accurately.

EvalLogic2



The Warning Label

Each EvalLogic button updates the CollisionWarning context variable, which is then assigned to the Text property of the label (place within the directional arrows pane).


The Final Results

When all the pieces are in place, the following is the final results:


Until next post,

MG.-
Mariano Gomez, MVP

Monday, February 11, 2019

#PowerApps: Motion Patterns with Parametric Equations

As of late, I have been experimenting with motion in Microsoft PowerApps. This has actually helped me to revisit different mathematical models describing the motion of objects throw a two-dimensional space. As a result, I have created this video explaining how to create the following motion patterns:





You can download the msapp file from the PowerApps Community Apps Gallery here.

Until next post,

MG.-
Mariano Gomez, MVP

Sunday, November 4, 2018

Analyzing expense receipts with Azure Cognitive Services and Microsoft Flow

Recently, Business Applications MVP Steve Endow and I delivered a session at the User Group Summits in Phoenix, and in particular, to the GPUG Summit titled, "Microsoft Dynamics GP and Azure Services". In this course we detailed a number of Azure Services (among the hundreds) that could potentially be used with Microsoft Dynamics GP.

Being that I have also been working my way through completing a Microsoft sanctioned PowerApps, Flow, and CDS self-paced, online training class offered by edX (click here for more info on the course) and presented by Business Applications MVP Shane Young, I asked myself, what could I do with Microsoft Flow and Azure Services that could benefit Microsoft Dynamics GP users?

Playing with some Azure Services, I came across Azure Cognitive Services which offers the capability of reading images and analyzing them for text via its Computer Vision service. As it turns out, this service offers an optical character recognition (OCR) feature, which is capable of returning the full text or a JSON formatted document with the text. The idea here would be to use Microsoft Flow to read a newly created receipt in a OneDrive folder and transfer the file to Cognitive Services' Computer Vision for analysis, then get back the parsed text from the OCR service. 

Let's see how it's done!

Provision the Computer Vision service

The first thing is to ensure Computer Vision has been enabled as a service on your Azure tenant. For this visit the Azure Portal, click on Create a Resource, then select Computer Vision from the AI + Machine Learning category within the Azure Marketplace. 

Computer Vision

Fill in some basic information like the resource name, location, pricing tier (there's a F0 free service!), and a resource group. Click the Create button when done. This will provision your Computer Vision resource.
 
Copy the service endpoint address and access keys

Once the service is provisioned, click on All Resources, select the Computer Vision resource you just created, then click on Overview.

Grab the service endpoint address and the access keys. You can obtain the access keys by clicking on Show access keys.. (two access keys are provided). 

Computer Vision service endpoint info

This is, by far, one of the easiest services to provision and requires no extra configuration, beyond establishing some access control to limit who can use the service, but that's not a topic for this discussion.

Setup a new Microsoft Flow flow

Over in Microsoft Flow, I started by setting up a blank flow and selected OneDrive's "When a file is created trigger" as this would setup the simple trigger point for when an expense receipt file is added to a Receipts folder I had previously created. You will then be prompted to setup the connection information for OneDrive.

Blank flow with "When a file is created" trigger

NOTE: I selected my personal OneDrive for this, but this can also be done with a folder on your OneDrive for Business environment. In this case, you will want to authenticate with your Office 365 credentials.
Receipts folder


Submit file to Computer Vision service

As it also turns out, Microsoft Flow has a connector to the Azure Computer Vision API, which exposes two actions: OCR to JSON and OCR to Text. Add a New Step and type Computer Vision in the search bar. Select Computer Vision API, then choose OCR to Text action.

Computer Vision API connector - OCR to Text action

Once again, you will be prompted for the connection information to your Computer Vision service on Azure. Enter the user account, the access key and service endpoint as gathered in step two, above.

Computer Vision API - credentials entry
Once credentials are entered, you can decide what to submit to Computer Vision. In this case, we what to send the File Content, which we can select from Dynamic content fields.

File Content from Dynamics content fields

Configure Email step with Results

Upon completion, we want to send the resulting OCR from the analyzed image via email, so we will add another step to the flow. This time, we will a connector to Office 365 Outlook and will choose the Send an Email action for our next step.
Office 365 Outlook connector - Send Email action

We can then setup the properties for the Send an Email step. I have chosen to send the email to myself, and compose a subject line using the File name from the OneDrive directory. As body, I am including the Detected Text, which can be selected from the OCR to Text category under Dynamic content. I've included both the original file and content as part of the attachments.



Finally, I have given this flow a name and saved it.

Testing the Flow

I have dropped two receipt files within my OneDrive Receipts folder. These two receipts present various degrees of quality and text that can be recognized by the service. I was particularly interested in the second receipt (right) as this one was very folded and cracked so I was curious to see how it would be analyzed.

Receipts
For the second receipt, the OCR service returned the JSON payload and a status 200, indicating it was successful in processing and delivering a response.

JSON payload for second receipt

The actual email I received look like this and contained the following text:

Receipt analysis

Now, remember that my goal isn't to judge the accuracy of the OCR result delivered by Computer Vision, but rather to show how easy it is to build these kinds of solutions with Microsoft Flow and existing Azure services. Something like this would take an inordinate amount of time to build using traditional development tools and services.

Conceivably, I could create a simple PowerApps application that uses the Camera control to take the picture of the receipt and save it to my OneDrive folder. At this point, the receipt would be picked up by the Flow and analyzed by Computer Vision as we have established here. Why would this be important? Perhaps if you want to parse the JSON payload and rather submit to Microsoft Dynamics GP or Dynamics 365 as an account payables voucher, this would be useful.

Until next post,

MG.-
Mariano Gomez, MVP