User Interface Design Archives | DMC, Inc. https://www.dmcinfo.com/blog/category/application-development/user-interface-design/ Tue, 30 Dec 2025 18:19:32 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 https://cdn.dmcinfo.com/wp-content/uploads/2025/04/17193803/site-icon-150x150.png User Interface Design Archives | DMC, Inc. https://www.dmcinfo.com/blog/category/application-development/user-interface-design/ 32 32 Avalonia UI: Introduction and Initial Impression https://www.dmcinfo.com/blog/15658/avalonia-ui-introduction-and-initial-impression/ Thu, 20 Feb 2025 12:01:59 +0000 https://www.dmcinfo.com/blog/15658/avalonia-ui-introduction-and-initial-impression/ Avalonia UI is an open-source UI framework for cross-platform, .NET applications. It is free to use under the MIT license, and it supports Windows, macOS, Linux, iOS, Android, and WebAssembly. The framework is owned by the commercial entity, AvaloniaUI OÜ, and it is maintained by a community of developers and a core team of around […]

The post Avalonia UI: Introduction and Initial Impression appeared first on DMC, Inc..

]]>
Avalonia UI is an open-source UI framework for cross-platform, .NET applications. It is free to use under the MIT license, and it supports Windows, macOS, Linux, iOS, Android, and WebAssembly. The framework is owned by the commercial entity, AvaloniaUI OÜ, and it is maintained by a community of developers and a core team of around twenty that work full-time for the company. 

Avalonia is a “spiritual successor” to WPF which gives developers a familiar experience. You can code in C#, F#, or XAML for Avalonia’s UI, and you can use different IDEs such as Visual Studio, Visual Studio Code, and JetBrains Rider, but JetBrains Rider is the IDE Avalonia recommends for development. 

A Brief History of Avalonia UI 

Avalonia had its first commit in December of 2013 when it was still called Perspex. Although Avalonia has been around for over a decade, it started gaining more popularity in 2020 when it joined the .NET Foundation. The partnership with .NET Foundation was short-lived though, and Avalonia left it in February of 2024

Avalonia XPF 

Along with the open-source, free to use, UI framework, the AvaloniaUI OÜ company also sells licenses to Avalonia XPF

Avalonia XPF is a cross-platform fork of WPF, and it allows developers to “instantly” make a cross-platform version of their WPF project. It does this by leaving presentation core and presentation framework untouched so the WPF application “just works”. If the project has other Windows dependencies outside of WPF though, it may need additional massaging to migrate those features. For example, if your project only targets .NET Framework, which is Windows specific, updates will be needed to allow it to work cross-platform. 

Initial Impression 

Overall, Avalonia feels like a modern version of WPF, and it cleans up some of WPF’s quirks. 

If  you are familiar with WPF, the ramp up time to Avalonia is fairly quick. The largest differences are getting used to Avalonia’s styling and learning the different UI controls and their properties. 

For developers that are new to both WPF and Avalonia, it’s likely that it would take about the same amount of time to learn either framework. The documentation for WPF is much more extensive since it’s been around longer, but a lot of it can also apply to Avalonia. Conversely, Avalonia has fewer resources, but the resources that are available are better organized and modernized. The documentation to get started with Avalonia can be found here

Support 

Avalonia releases updates consistently which can give developers peace of mind that it is maintained well. It supports .NET Framework 4.6.2+, .NET Core 2.0+, and .NET 5+. 

When using the Model-View-ViewModel (MVVM) pattern, Avalonia works well with the ReactiveUI and MVVM Community ToolKit libraries. This is great news since WPF also supports these libraries. 

IDEs 

As a WPF developer that already has a Visual Studio license, VS is my go-to IDE, and there is an Avalonia Extension for Visual Studio 2022. Although this extension exists, it is a bit lackluster. It does not support the code completion nor the rich syntax highlighting for AXAML (Avalonia’s flavor of XAML) files that you would typically expect for XAML files. These deficiencies can cause a slower or less enjoyable developer experience. 

Visual Studio Syntax Highlighting
Visual Studio Syntax Highlighting 

The IDE Avalonia recommends is JetBrains Rider. Rider is free only for non-commercial use. This IDE makes programming in Avalonia much smoother as it does support code completion and syntax highlighting for AXAML. It also supports more obvious highlighting for file types, and its IntelliSense code “usages” feature finds references in both C# and AXAML files.

JetBrains Rider Syntax Highlighting
JetBrains Rider Syntax Highlighting 

Unfortunately, Avalonia does not support hot reload at all, but it does have an AXAML design previewer which is similar to the XAML Live Preview. 

AXAML Design Preview
AXAML Design Preview 

Cross-Platform Demo 

To demonstrate the look and feel of Avalonia on different platforms, I followed the Avalonia Music Store App tutorial and deployed it to a Windows and Linux operating system respectively. 

Avalonia on Windows
Windows 

Avalonia on Linux
Linux

As you can see, the application looks consistent across different platforms thanks to Avalonia’s independent rendering. Avalonia does not rely on the native UI controls of the operating system. Instead, it draws the entire UI itself which allows for more flexibility and customization. 

Takeaways 

Avalonia is a great option to have for desktop development, especially for cross-platform use cases. Avalonia’s styling features and components streamline development while also providing a similar feel to WPF. While Avalonia seems like WPF 2.0, WPF is still a strong choice for Windows platforms. WPF has a long history of development behind it and many resources available that have led it to be DMC’s standby for a long time. 

At DMC, we are always looking towards the future and learning new technologies to better support the wide variety of needs our customers have. We are excited to continue exploring Avalonia UI to provide expert solutions for cross-platform, desktop development. 

Ready to take your Application Development project to the next level? Contact us today to learn more about our solutions and how we can help you achieve your goals. 

The post Avalonia UI: Introduction and Initial Impression appeared first on DMC, Inc..

]]>
Custom Image Provider Implementation in PySide https://www.dmcinfo.com/blog/17084/custom-image-provider-implementation-in-pyside/ Thu, 02 Nov 2023 14:34:05 +0000 https://www.dmcinfo.com/blog/17084/custom-image-provider-implementation-in-pyside/ Introduction In application development, projects require various depths of involvement. Some projects may need you to interconnect a bunch of trendy frameworks and open-source libraries, while other projects will require full-scale development in a lesser-known framework not even designed for the task at hand. A framework that is particularly interesting to work with that occasionally […]

The post Custom Image Provider Implementation in PySide appeared first on DMC, Inc..

]]>
Introduction

In application development, projects require various depths of involvement. Some projects may need you to interconnect a bunch of trendy frameworks and open-source libraries, while other projects will require full-scale development in a lesser-known framework not even designed for the task at hand. A framework that is particularly interesting to work with that occasionally lacks proper documentation is PySide.

I’ve had an opportunity to explore some image rendering capabilities of the framework and would like to share some tips and best practice standards for your custom application.

Image Provider

Before we dive deeper into the topic, I want to note that I am using PySide6, and the image provider lives under PySide6.QtQuick.

Here is a decent, thorough documentation page on the QQuickImageProvider that you may wish to examine to gain a better understanding of the concept. 

The primary purpose of the image provider is to allow the application to render images from sources other than the standard files. Good examples of such sources are the in-memory data and dynamically generated images.

If you simply need to render a pre-existing image in a common format, then I highly recommend looking into the native QML Image component capabilities.

Setup

Disclosure: code casing might be inconsistent with what you are used to in Python, but I tried incorporating both QML and Python standards where applicable.

  • camelCase – function names (QML/C++ – inherited function override + consistency)
  • PascalCase – objects (QML/C++ – inherited base class + consistency)
  • snake_case – variables (Python – standard)

To begin custom image provider implementation, you have to instantiate your image provider class that inherits PySide6.QtQuick.QQuickImageProvider.

Custom Image Provider Definition

Image 1. custom image provider definition

class CustomImageProvider(QQuickImageProvider):
    def __init__(self, image_provider_id: str):
        super().__init__(QQuickImageProvider.ImageType.Image)
        """Image provider metadata."""
        self.provider_id = image_provider_id

        """Image provider data."""
        self._images: dict[str, np.ndarray] = dict()

        """Suggested utility objects."""
        # self.SharedConstants = SharedConstants()
        # self._imageConstructor = ImageConstructor()
        # self._idConstructor = IdConstructor()

Notice that I created a public property provider_id. You do not technically need it, but I highly recommend introducing one, especially if you are anticipating multiple image provider instances. For example, I worked on an app that required multiple tabs to be open in parallel, each with access to an image provider. Since each tab needed its own library of custom images, with image IDs not necessarily globally unique, I had to instantiate a unique image provider per tab to avoid data conflicts. The provider_id really helps identify which image provider to use and, more importantly, which image provider can be cleared out for garbage collection purposes as the tab closes.

You also need to create a data structure to hold your images. A dictionary has the convenience of id-data mapping. QML will always request an image using a string id and mapping hashable string ids to image data sounds like a perfect opportunity to use a dictionary. To avoid any unexpected behavior, I recommend instantiating the data structure as an internal property and, thus, I called it simply _images.

In the comments I am also suggesting the usage of the following objects:

  • SharedConstants – implement to store and use constants that are shared across the application. Remember, if anything is used in both QML and Python and is ultimately hardcoded, then you should instantiate it as a shared constant, and that constant could be used by both QML and Python. Otherwise, any change to a hardcoded value will become a living nightmare of chasing down all the instances of that value in the code. Remember, you cannot easily, if at all, debug QML.
  • ImageConstructor – use this class to define functions that could be used to construct pixel data that would be stored in the _images data structure.
  • IdConstructor – use this class to define functions that could be used to construct ids that would be used to store data in the _images data structure.

Method Override

QQuickImageProvider has requestPixmap, requestImage and requestTexture that you can override to implement your custom functionality. Each method’s signature is similar to the others, so I will focus on the requestImage method as I have worked with it the most.

Requestimage Override

Image 2. requestImage override

    def requestImage(self, image_id: str, size: QSize, requested_size: QSize) -> QImage:
        if (image_id in self._images.keys()) and (self._images[image_id] is not None):
            """Retrieve the image data from the image library."""
            _pixels = self._images[image_id]

            """According to the documentation: 
            In all cases, size must be set to the original size of the image. 
            This is used to set the width and height of the relevant Image if 
            these values have not been set explicitly."""
            image_size = QSize(_pixels.shape[1], _pixels.shape[0])
            if size:
                size = image_size

            """Construct the size of the returned image."""
            width = (
                requested_size.width()
                if requested_size.width() > 0
                else image_size.width()
            )
            height = (
                requested_size.height()
                if requested_size.height() > 0
                else image_size.height()
            )

            """Construct the image."""
            img = QImage(
                _pixels.data,
                height,
                width,
                QImage.Format_RGBA8888,
            )

            return img
        else:
            raise ValueError(
                self.provider_id
                + " image provider was unable to find image "
                + image_id
            )

Each of the three methods requires a signature that includes image_id, size and requested_size. It is okay to rename those input variables, but the typing must remain the same. Nevertheless, I do not recommend changing the names.

  • image_id – the string ID of the image by which you will be looking up the pixel data in the defined _images data structure.
  • size – is not technically used for anything in the Python implementation of the image provider. According to the official documentation: “In all cases, size must be set to the original size of the image. This is used to set the width and height of the relevant Image if these values have not been set explicitly.” This is a remnant of the C++ framework. This variable is passed into this method by reference and must be updated. Every other input is passed in by value.
  • requested_size – the size of the Image component in QML that requested the image from the custom image provider.

The return value of the overridden method must be a properly constructed QImage that QML will be able to display in the application. Notice that the first input for the QImage constructor is a buffer object pointing to the start of the array’s data. This is also a C++ memory management quirk that Python has to deal with. The format also plays a big role in image rendering. The current QImage.Format_RGBA8888 decodes the given data array as though there are four 8-bit unsigned integers for red, green, blue, and alpha channels per pixel. This is crucial, since, if you provide the wrong array type, the displayed image will either be incorrect or won’t show up at all. QML will not throw an error, so you might spend a lot of time trying to figure out why your image is not rendering. A friend of mine told me this, I am certainly not speaking from experience…

The sizing of the image is another topic for discussion. Depending on your implementation, you might want to keep the original size of the image in any rendering case, or, on the contrary, you might always want to resize the image to the window size. Sometimes, you might need to implement the sizing so that it is dynamic and dependent on the window state/size. Basically, what I am saying is that the sizing implementation in the code above is subject to change depending on your application needs; however, you must assign the current image original size to the passed by reference size variable.

Data Management

Now that we’ve implemented the basic image provider functionality, we have to develop data management capabilities. Image data must be somehow stored in the image provider, and, since the data structure _images was defined internally, we have to define functions that will allow insertion of the new data into the dictionary as well as its removal.

It is also important to implement data validation code that could be broken down into helper functions. This is where you make sure that the data provided for addition could be displayed using the defined QImage format.

You can also include some of your data manipulation and ID construction code. Basically, this is the perfect opportunity to get the best use out of your predefined ImageConstructor and IdConstructor objects. You still have to make sure that the data on the output is suitable for the defined QImage format.

Main Image Provider Management Function

Image 3. main image provider management function

The following implementation is an example of how you can develop the data removal functionality.

Supplemental Management Function

Image 4. supplemental management function


    def addOrUpdateLayer(self, layer_id: str, pixel_data: np.ndarray) -> dict:
        """Data validation and manipulations to prep for the render-ready format"""
        # Insert your data validation code
        # Insert your data manipulation code

        """Map the new data to the desired id and save to the library."""
        layer = {layer_id: pixel_data}
        self._images.update(layer)

        return layer

    def removeLayer(self, layer_id: str) -> None:
        """Apply key-value pair deletion logic"""
        del self._images[layer_id]

Helper Functionality

I highly recommend keeping your custom image provider as lightweight as possible for scaling and maintainability purposes; however, you may still want to implement some basic helper functions like:

  • isIDTaken – to check whether a given ID already exists in the image provider to avoid overriding stored data.
  • isImageLoaded – to check whether the data is present in the data structure before querying it.
  • clearImageProviderInstance – to manage the memory that the image provider occupies.

Example implementation for each is presented below:

Supplemental Image Provider Functionality

Image 5. supplemental image provider functionality

    def isIDTaken(self, image_id: str) -> bool:
        return image_id in self._images.keys()

    def isImageLoaded(self, image_id: str) -> bool:
        return self._images[image_id] is not None

    def clearImageProviderInstance(self):
        self._qml_engine.removeImageProvider(self._id)

In Action

The following code snippets do not require much explanation but are a good starting point in learning how to use the custom image provider in the scope of any application. Note, we must add an image provider to the application engine.

Since we defined the image provider class with a unique ID property, you must provide one to each image provider you insert into the application. Keep in mind that QML engine requires you to provide a unique ID in the first place, so you should just store that ID in the image provider itself.

Image Provider Usage in an Example

Image 6. image provider usage in an example

if __name__ == "__main__":
    """Set up the application."""
    app = QApplication([])
    engine = QQmlApplicationEngine()

    """Instantiate an image provider."""
    unique_id = "unique_image_provider_id"
    image_provider = CustomImageProvider(unique_id)
    engine.addImageProvider(unique_id, image_provider)

    """Add an image to the image provider."""
    image_provider.addOrUpdateLayer(
        "unique_image_id",
        np.array(
            [
                [
                    [255, 0, 0, 255],
                    [255, 0, 0, 255],
                    [255, 0, 0, 255],
                ],
                [
                    [0, 255, 0, 255],
                    [0, 255, 0, 255],
                    [0, 255, 0, 255],
                ],
                [
                    [0, 0, 255, 255],
                    [0, 0, 255, 255],
                    [0, 0, 255, 255],
                ],
            ],
            dtype=np.uint8,
        ),
    )

    """Load and tun the app."""
    engine.load("main.qml")
    app.exec()

According to the code above, the example image we are defining has a 3-pixel height and a 3-pixel width. It’s a square of three colored stripes: red, green, blue with full opacity (4th alpha channel).

Example QML Code

Image 7. example QML code

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 600
    height: 600
    title: "Custom Image App"

    Image {
        anchors.fill: parent
        // Set the source to the custom image provider.
        // Include the image id if you would like to show a particular image.
        source: "image://unique_image_provider_id/unique_image_id"
    }
}

I am choosing to keep the QML code fairly simple and straightforward. This code snippet does not necessarily follow any coding standards, but rather serves as a quick and dirty playground to show off some custom image provider capabilities.

Produced Result

Image 8. produced result

The produced result is just as we expected, a stretched out 3×3 pixel image! Notice that the Image component anchors onto its parent, so the requestedSize will be inherited from the ApplicationWindow component size.

We could also set the requestedSize manually in QML. This way, the size of the constructed image will not change dynamically with the ApplicationWindow.

Example of Setting the Image Size Manually

Image 9. example of setting the image size manually

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 600
    height: 600
    title: "Custom Image App"

    Image {
        width: 450
        height: 300
        // Set the source to the custom image provider.
        // Include the image id if you would like to show a particular image.
        source: "image://unique_image_provider_id/unique_image_id"
    }
}

Notice the difference in the newly rendered result:

Result of Setting the Image Size Manually

Image 10. result of setting the image size manually

Alternative Setup

A less recommended, but valid nonetheless, implementation is to let the custom image provider insert itself into the application upon instantiation.

Alternative Custom Image Provider Definition

Image 11. alternative custom image provider definition

class CustomImageProvider(QQuickImageProvider):
    def __init__(
        self, image_provider_id: str, qml_application_engine: QQmlApplicationEngine
    ):
        super().__init__(QQuickImageProvider.ImageType.Image)
        """Image provider metadata."""
        self.provider_id = image_provider_id

        """Handle image provider self insertion into the application."""
        self._qml_application_engine = qml_application_engine
        self._qml_application_engine.addImageProvider(self.provider_id, self)

        """Image provider data."""
        self._images: dict[str, np.ndarray] = dict()

        """Suggested utility objects."""
        # self.SharedConstants = SharedConstants()
        # self._imageConstructor = ImageConstructor()
        # self._idConstructor = IdConstructor()

The following is an example of such an image provider in action.

Example Usage of the Alternative Custom Image Provider

Image 12. example usage of the alternative custom image provider

if __name__ == "__main__":
    """Set up the application."""
    app = QApplication([])
    engine = QQmlApplicationEngine()

    """Instantiate an image provider."""
    unique_id = "unique_image_provider_id"
    image_provider = CustomImageProvider(unique_id, engine)

    """Add an image to the image provider."""
    image_provider.addOrUpdateLayer(
        "unique_image_id",
        np.array(
            [
                [
                    [255, 0, 0, 255],
                    [255, 0, 0, 255],
                    [255, 0, 0, 255],
                ],
                [
                    [0, 255, 0, 255],
                    [0, 255, 0, 255],
                    [0, 255, 0, 255],
                ],
                [
                    [0, 0, 255, 255],
                    [0, 0, 255, 255],
                    [0, 0, 255, 255],
                ],
            ],
            dtype=np.uint8,
        ),
    )

    """Load and tun the app."""
    engine.load("main.qml")
    app.exec()

Notes and Tips

  1. Keep your image provider lightweight. Put all the functionality you think is relevant to it somewhere else, because chances are, it is not. The image provider should really be treated as a data structure that has functionality only to store and remove images.
  2. Make your image provider usable in every place of your application. Avoid putting select-component/window-only functionality in here.
  3. This implementation will also likely work in PySide2 since that is where I originally developed it.

 Learn more about Resizing UIs with QML Layouts and contact us today for your next project.

The post Custom Image Provider Implementation in PySide appeared first on DMC, Inc..

]]>
3 Common Pitfalls of Theme Customization with Material UI  https://www.dmcinfo.com/blog/17372/3-common-pitfalls-of-theme-customization-with-material-ui/ Wed, 19 Jul 2023 13:14:15 +0000 https://www.dmcinfo.com/blog/17372/3-common-pitfalls-of-theme-customization-with-material-ui/ Material UI is a great tool for speeding up the development process of your react app. The library offers out-of-the-box React UI components with built-in properties that greatly simplify the implementation of the styling and interaction requirements of your product.  Out-of-the-box UI rarely matches the predetermined design of an application alone, however. In these situations, […]

The post 3 Common Pitfalls of Theme Customization with Material UI  appeared first on DMC, Inc..

]]>

Material UI is a great tool for speeding up the development process of your react app. The library offers out-of-the-box React UI components with built-in properties that greatly simplify the implementation of the styling and interaction requirements of your product. 

Out-of-the-box UI rarely matches the predetermined design of an application alone, however. In these situations, we are required to open these components and tinker with their CSS styles directly.

Material UI offers extensive documentation for component styling and customization, but the actual process of reworking these styles can be confusing and frustrating. In this blog post, I hope to simplify this process by covering 3 common pitfalls when overriding material UI styles. 

This post will exemplify these pitfalls by overriding styles on a MUI TextField component. The implementation of these examples can be found in this Code Sandbox. The source code we will start off with is a single TextField component rendered to the page, with style overrides controlled with MUI’s built-in createTheme() function: 

const theme = createTheme({
 components: {
   MuiInputBase: {
     styleOverrides: {
       root: {}
     }
   }
 }
});
export default function App() {
 return (
   
     
       
         }}
       />
     
   
 );
}

With no overrides in place to begin, the rendered page looks like this: 

There are multiple approaches to specifying style overrides within your project, and you may require something different than what is provided in this demo. For more information on the different approaches for locating style overrides, check out the MUI documentation.  

Part 1: MaterialUI Slot Selectors 

Style overrides are most commonly located in a “theme” object. This object designates custom styles to components and the “slots” that compose a given component. Each slot maps to a single HTML element rendered as part of the larger component. 

To add or override a style, we specify both the component and slot that we are targeting. The styles we specify are applied in a CSS class specific to that element. In the example below, we target the input element within an MUI TextField, which is accessed by the MUI component MuiInputBase and slot root:  

const theme = createTheme({
  components: {
    MuiInputBase: {
      styleOverrides: {
        root: {
          color: "green"
        }
      }
    }
  }
});
Screenshot of code on a computer

Pitfall: Choosing the Wrong Slot 

When we specify styles for a component, they may appear in the rendered CSS but not induce a visible change on the page. In this case, it is possible that the slot you have specified is not the correct one to target. A good practice to prevent this issue is to determine where these styles you want to override are being initially applied, and ensure you specify those slots in your theme. 

For example, say that I am trying to both increase the width of the TextField border and remove the preset borderRadius. If I apply both updates to the MuiInputBase-root slot, I get the following result: 

const theme = createTheme({
  components: {
    MuiOutlinedInput: {
      styleOverrides: {
        root: {
          borderWidth: 3,
          borderRadius: 0
        }
      }
    }
  }
});

 

As you can see, the border has been removed but the width of the box stays the same. If I investigate the styling specified for each slot, I see that there is a default value of borderRadius specified for MuiOutlinedInput-root, but the default borderWidth set on MuiOutlinedInput-notchedOutline is still being applied:

If I move this override to the correct slot, we can see the desired output: 

const theme = createTheme({
  components: {
    MuiOutlinedInput: {
      styleOverrides: {
        root: {
          borderRadius: 0
        },
        notchedOutline:{
          borderWidth: 3
        }
      }
    }
  }
});

 

Part 2: Correct MUI Class Reference Syntax 

Beyond the theme specification exemplified above, MUI theme overrides allow for string keys specifying selectors for higher levels of control over the styles applied.  

There are many features of this string notation, but two features are used most frequently: 

  • Specifying the component-slot names of a descendant MUI component (akin to CSS descendant combinators) 

  • Specifying a state selector which applies styles to the component only if it is in a certain state (akin to CSS pseudo-classes) 

An example of both can be seen below: 

const theme = createTheme({
 components: {
   MuiOutlinedInput: {
     styleOverrides: {
       root: {
         borderRadius: 0,
         "&. MuiSvgIcon-root": {
           color: "rgb(100,100,100)"
         },
         "&.Mui-focused": {
           backgroundColor: "rgb(220,240,250)",
         }
       },
       notchedOutline: {
         borderWidth: 3,
         borderColor: "black"
       }
     }
   }
 }
})

 

Here, we have added an adornment icon to the TextField which has a default color of black (“rgb(0,0,0)”), but we can specify a selector for this MUI component and update its color to a dark gray (“rgb(220,240,250)”). Additionally, we can set the background color of the field, whenever the field is in focus, by specifying a background color in the state selector “.Mui-focused”.

Pitfall: String selector syntax 

These string-notation selectors are fantastic for adding a higher level of specificity to your overrides, but they also fall outside of the typing for MUI themes – every key with this notation is just a string. This means that applying styles to incorrectly formatted selectors is an error which will fail silently. With the large feature set of these string selectors, referring to the MUI documentation on formatting is best for more complex cases. For simpler and more common cases like the one above, here is a quick tip that may save you some time:

  • Descendant selectors follow the notation "& .[Component]-[Slot]". Note the space after the ampersand.  

  • State selectors follow the notation "&.[state]" with no space after the ampersand. 

This small difference is due to how these strings are parsed into plain CSS selectors. Descendant components are implemented with descendant CSS combinators as mentioned above, which require a space. State selectors are simply appended to the existing component class name, so the space is not included. Failing to follow this format for either type of selector results in styles not being applied. Below, we can see that neither the background highlight nor the color modification of the adornment icon is applied when the format is incorrect. 

const theme = createTheme({
 components: {
   MuiOutlinedInput: {
     styleOverrides: {
       root: {
         borderRadius: 0,
         "&. MuiSvgIcon-root": {
           color: "rgb(100,100,100)"
         },
         "&.Mui-focused": {
           backgroundColor: "rgb(220,240,250)",
         }
       },
       notchedOutline: {
         borderWidth: 3,
         borderColor: "black"
       }
     }
   }
 }
});

 

Part 3: Selector Specificity is Correct 

The nested selector structure shown in the last two examples follows the same rule as applying classes to HTML elements: if two classes are applied to an element and one class has a more specific selector than the other, then the more specific class’s styles will be displayed with higher priority than the other.

In MUI theme overrides, specificity is determined by matching slot selectors and state selectors, as well as any base CSS selector. 

Pitfall: Override's selector is still not a higher specificity than the out-of-the-box styling 

There are already many selectors in the default styles of a MUI component, which are very specific to certain elements within it. If your style override does not show up and the default style is still applied, check the specificity of the style and try to match it with your overriding selector. 

I’ll demonstrate this by extending the example from the last two sections. If you noticed in the image from the last section, our custom black border is not present when the text box is selected. The border becomes thinner, and the color changes to a darker blue.

If we dig around the DOM a bit when the TextField component is in focus, we can see a default TextField style that is making this change with the help of the higher specificity of the “.Mui-focused” state selector: 

If we want to override the border color of the text field when it is in focus, we need to match the level specificity from the selector of the default style: 

const theme = createTheme({
 components: {
   MuiOutlinedInput: {
     styleOverrides: {
       root: {
         borderRadius: 0,
         "&. MuiSvgIcon-root": {
           color: "rgb(100,100,100)"
         },
         "&.Mui-focused": {
           backgroundColor: "rgb(220,240,250)",
           "& .MuiOutlinedInput-notchedOutline":{
             borderColor: "black"
           }
         }
       },
       notchedOutline: {
         borderWidth: 3,
         borderColor: "black"
       }
     }
   }
 }
});

Now, if we look at the DOM and the resultant page, we see that the more specific default style has been successfully overridden: 

Another important consideration here is that the selectors must be nested in the right order to be successfully applied; if the .MUI-focused state selector is nested in MuiOutlinedInput-notchedOutline class selector, then the style would not apply. 

Conclusion 

MUI’s default styles and style overrides provide a very high level of configurability to web developers, but opening their out-of-the-box solutions can be tricky. The pitfalls exemplified are not the only possible issues, but, from my experience, keeping these common problems in mind will reduce the time needed for debugging your styles a considerable amount. 

Learn more about DMC’s Web Application Development services and contact us for your next project. 

The post 3 Common Pitfalls of Theme Customization with Material UI  appeared first on DMC, Inc..

]]>
LVGL for International GUI Design https://www.dmcinfo.com/blog/17764/lvgl-for-international-gui-design/ Wed, 01 Mar 2023 10:12:27 +0000 https://www.dmcinfo.com/blog/17764/lvgl-for-international-gui-design/ Creating a high-quality user interface that can run on embedded systems is a challenging task, and that’s doubly (or perhaps triply) true if it’s intended for an international user base. As with any difficult job, however, it becomes much more manageable with the right tools. Read on to learn the basics of one such tool, […]

The post LVGL for International GUI Design appeared first on DMC, Inc..

]]>

Creating a high-quality user interface that can run on embedded systems is a challenging task, and that’s doubly (or perhaps triply) true if it’s intended for an international user base.

As with any difficult job, however, it becomes much more manageable with the right tools. Read on to learn the basics of one such tool, LVGL, and its associated utilities.

Getting Started with LVGL

In order to develop with LVGL, it will need to be ported to your platform of choice.

First, pull the latest release of LVGL into a project configured for your target and create “lv_conf.h” from the included template. This file contains a variety of parameters for tweaking the library’s behavior, but they can be left at their default values for now. Add a call to lv_init to your program, ensuring it’s in a location where it will occur before any other LVGL functions are called.

Next, you will need to define drivers for the display showing your UI and any input devices used to interact with it. The specifics of these drivers will vary greatly from project to project, and LVGL already has excellent documentation on porting, so only the general structure will be covered here.

In simple cases, you will only need to allocate some memory to act as a draw buffer, define the resolution of the display, and implement a function to send the information LVGL places into the draw buffer to your screen. The example below shows a basic setup, but additional configuration options exist to implement things like double-buffering or screen rotation.

C
static lv_color_t draw_buffer[SCREEN_HOR_RES * SCREEN_VER_RES / 10];
static lv_disp_drv_t disp_drv;
static lv_disp_draw_buf_t disp_buf;

void flush_buffer_callback(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p);

lv_disp_t* lvgl_display_port_init()
{
    //configure any platform specific peripherals here

    //set up draw buffer
    lv_disp_draw_buf_init(&disp_buf, draw_buffer, NULL, SCREEN_HOR_RES * SCREEN_VER_RES / 10);

    //initialize display driver
    lv_disp_drv_init(&disp_drv);

    //configure display driver
    disp_drv.draw_buf = &disp_buf;
    disp_drv.hor_res = SCREEN_HOR_RES;
    disp_drv.ver_res = SCREEN_VER_RES;
    disp_drv.flush_cb = flush_buffer_callback;

    //set optional fields to customize display driver here
   
    //finalize driver setup
    return lv_disp_drv_register(&disp_drv);
}

void flush_buffer_callback(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p)
{
    /* copy pixels from 'color_p' to the area of the screen described by 'area' */
}

Configuring an input device follows a similar process. Use lv_indev_drv_init to set up the driver, define its type, attach a function for reading its value, and register it using lv_indev_drv_register.

C
static lv_indev_drv_t indev_drv;
void input_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);

void lvgl_indev_port_init(void);
{
    //configure any necessary platform specific peripherals here

    //set up input device driver
    lv_indev_drv_init(&indev_drv);

    //configure input device driver
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = input_read;
    
    //finalize driver setup
    lv_indev_t* touch_indev = lv_indev_drv_register(&indev_drv);
}

void input_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    /* Read location from input device (i.e. last touch location on a touchscreen) */

    /* Read state from input device (i.e. is a touchscreen currently being touched) */

    /* Store current location (x, y) and state (pressed, released) in 'data' */
}

 

With the display and input device drivers implemented, the final step is to create an environment for LVGL to run in. In simple applications, this might be an infinite loop at the end of main function, while more complex applications may utilize an RTOS (real-time operating system) and give LVGL its own thread. Many approaches will work as long as lv_tick_inc and lv_timer_handler are called periodically, with lv_tick_inc occurring at least as frequently as lv_timer_handler.

Note that LVGL functions are not inherently thread-safe or interrupt-safe; if using interrupts or an RTOS, utilize synchronization tools like mutexes or take care to limit calls to LVGL such that they cannot overlap.

With all of the above in place, LVGL should be ready to use.

Internationalization Tools

In addition to the base library, LVGL has several supplementary tools to aid in development. When creating a UI for an international user base, two such tools stand out as essential: LVGL’s internationalization library, which provides simple tools for text substitution, and LVGL's font converter, which allows a developer to parse glyphs from several fonts into a single file for use with LVGL.

For both of these tools, you will need node.js (at least version 14) and the package manager it comes bundled with (called npm). You can download them here. Sticking with the default installation options will work for the tools covered here.

After installing, ensure that both node.js and npm are added to your system PATH. Open command prompt, and install the font converter with npm i lv_font_conv -g and the internationalization library with npm i lv_i18n -g. Both tools should now be usable in your project.

lv_i18n

The process described below was adapted from the setup instructions provided with lv_i18n with notes from personal experience. For the original instructions, see the lv_i18n github.

Start off with a simple example, and wrap any text which will be translated in _( ). For strings containing plurals, _p( ) can be used instead to support languages with different pluralization rules from your base language. Include “lv_i18n.h” (this file doesn’t exist yet as it will be generated by the tool later) and, after initializing LVGL, call lv_i18n_init and lv_i18n_set_locale. The locale can be set to any language that you plan to support, and it can be changed any time that it’s safe to call an LVGL API function.

Next, create a folder to store translations, add a .yml file for each language you plan to support (including the language you are developing in), and title the files with language codes. Write the language code, followed by a colon, into the first line of each file (ex: Portuguese would have a file called “pt.yml”, and it’s first line would be “pt:”). Once your example is done, from a command prompt in your project, run the following (without angle brackets):

lv_i18n extract -s ‘<path to your source files>/*.+(c|cpp|h|hpp)’ -t ‘<path to your yml translations>/*.yml’

This will populate the translation files with a list of every string from your project wrapped in _( ). Next to each, add its translation in the language corresponding to the yml file it’s in. To implement these translations, run the following:

lv_i18n compile -t ‘<path to your yml translations>/*.yml’ -o ‘<path where you want lv_i18n.h to go>’

The output location of this command will receive lv_i18n.h and lv_i18n.c, which will contain all of your translations in a form that the tool can reference. It will need to be accessible by any file that calls _( ), so set up its location accordingly.

Now, your example should be ready to run. Any time that code containing a string wrapped in _( ) executes, the current locale will be checked and the string will be substituted for its translation in that locale. This is a simple, literal substitution, so anything referencing the string will be affected, not just LVGL functions. Additionally, if new text is added, you will need to run lv_i18n extract and lv_i18n compile again.

lv_font_conv

If one of your languages uses a script other than the Latin alphabet, you may notice missing characters when its translation is displayed. This is where the font converter comes in. Most fonts don’t describe a glyph for every Unicode character, so you will need to either find one that has all the characters you need or create one by splicing together a few fonts.

First, prepare a list of characters needed, or ranges of characters. Even if you have a font which contains all the characters needed, it’s likely that it also contains many that you don’t, and fonts take up a lot of space (especially physically larger ones; by default, LVGL stores them as byte arrays, and more pixels means more bytes).

Next, parse this list into a font converter call. Broadly, calls to the font converter are structured as a sequential list of font files, which characters to take from them, and then a few parameters to customize the output. Supply a path to the font file with --font, specify which characters to include from it with --range or --symbols, and repeat for each font to be included.

When adding characters, --symbols accepts a list of characters. Duplicates are ignored, so you can freely copy and paste everything from one language’s translation into this argument, but be careful to remove any spaces as they may be treated as the end of the argument. --range accepts a single Unicode value or a contiguous range of Unicode values. See Unicode ranges for reference. You can add as many --range and --symbols arguments as needed. Next, add the font size to generate with --size, the format with --format, the level of detail with --bpp, and a file path to output to with -o. The font converter github page has more details on each of these parameters.

 Altogether, a call might look like the following:

Bash
 lv_font_conv --font ARIALUNI.TTF

  --range 0x0000-0x017F

  --range 0x0400-0x04FF

  --symbols КАСБОЙ

 --font NotoSansArabic-Regulat.ttf

  --range 0x0600-0x06FF

  --range 0xFE70-0xFEFF

 --size 10 --format lvgl --bpp 3 -o examplefont.c
 

Note that newlines and indentation are included for clarity, the only separator should be spaces since this is invoked from a command line. To that end, it’s helpful to store arguments in a text file, then parse them into calls in a script with something like bash’s mapfile command.

Once a font has been generated, it can be included in the project, declared with LV_FONT_DECLARE, and used the same as LVGL’s default fonts.

Common Challenges

What follows is a collection of some common challenges one might encounter when creating internationalized UIs, and some tips as to how they can be handled with the tools discussed here.

Base Direction

Languages vary in their base directions; English, for example, is a left-to-right (LTR) language, while languages using the Arabic script are right-to-left languages (RTL). LVGL has some built-in handling for different base directions, namely the base_dir style property.

When creating an object, it can be given a base direction with lv_obj_set_style_base_dir, or attaching a style with the property already set (by lv_style_set_base_dir). This will work in most cases, but, with strings containing both LTR and RTL languages, you may need to specify text direction manually. For this, one can use Unicode directional indicators. Including the following in your project will allow you to change direction within a string.

#define LRI "\xE2\x81\xA6"

#define RLI "\xE2\x81\xA7"

#define PDI "\xE2\x81\xA9"

LRI indicates left-to-right, RLI the opposite, and PDI (pop directional indicator) undoes the most recent indicator. Wrapping chunks of text in LRI/RLI … PDI can be thought of as splitting the overall string into substrings, with the base direction of the overall string governing the order these substrings will appear. As an example, if the following string were to be placed in a label with RTL base direction:

“<LRI>123<PDI> <RLI>456<PDI> <LRI>789<PDI>”

(note: angle brackets included for clarity), it would appear on screen as

“789 654 123”.

Note also that LVGL may output warnings for missing glyph descriptions when rendering strings containing these characters. They are zero-width characters and function correctly regardless of whether they are included in the font, so these warnings can be safely ignored or suppressed in lv_conf.h (though missing glyph warnings are very helpful when working with custom fonts).

Information Density

Languages vary in information density; three Chinese characters might convey the same information as twenty Latin ones in English or thirty in Spanish. As such, it’s helpful to leave extra space in screen layouts for languages less dense than the one being used for development.

When this isn't possible, however, one can also use LVGL’s long text handling. Calling lv_label_set_long_mode will allow the developer to specify whether a label with text that does not fit in its width scrolls, wraps, or is cut off (optionally ending the displayed text with “…”) . Make sure to set the width of the label after calling this function.

In cases where built-in long modes are not desirable, another option is to decrease font size. Using lv_txt_get_size to check whether a string will fit in a space with a given font, it’s possible to implement automatic font scaling (though the exact process will vary greatly from UI to UI depending on the desired behavior).

Inserting Variables into Translated Text

One common use of text in a UI is to provide context for numerical information, which is complicated by the fact that translations need to be compiled beforehand. Thankfully, there’s a simple solution. As mentioned before, lv_i18n’s translation function _( ) is a simple, direct replacement. This means that the translated string can be used with basic C text functions, namely snprintf. Including “%s” (or other format codes) in both the initial string and its translations will allow values to be inserted into the string by code after translation. When using this method, take care to allocate enough space in the buffer passed to snprintf for your longest translation.

When a string contains multiple variable values, they can appear in different orders from language to language. This can be solved with either clever translation to keep fields in the same order, or by tracking which language is active and supplying arguments to sprintf in the corresponding order.

Duplicate words

Languages often have words that mean several different things. As an example, an English UI might include the string “set” in multiple places, some using the word as a verb, others as a noun. These words don’t necessarily line up between languages, so the string “set” would likely need two translations in other languages. lv_i18n can only attach one translation to each original string per language, so this may initially seem like a significant issue, but the tool is perfectly capable of handling these situations.

lv_i18n creates a lookup table for each language, mapping strings to other strings. The initial, untranslated strings scraped from your file are really just IDs to search translations by, and you can supply corresponding “translations” for them in your base locale (development language) just like any other language.

Returning to the previous example with the word “set”, you can change the strings in your code to “set (v)” and “set (n)” and re-run lv_i18n extract. In the yml files belonging to other languages, fill in the translation for "set" as a noun next to “set (n)” and "set" as a verb next to “set (v)”. In “en.yml”, just place the word “set” next to both.

Translation files implementing duplicate words

Re-run lv_i18n compile. Running the program in English, you should still see “set” everywhere, but changing the language will now show different translations for different uses of “set”.

Learn More about DMC's Embedded User Interface Design services and contact us today for your next project.

The post LVGL for International GUI Design appeared first on DMC, Inc..

]]>
Advantages of .NET and Python for Test & Measurement Applications https://www.dmcinfo.com/blog/18030/advantages-of-net-and-python-for-test-measurement-applications/ Wed, 07 Dec 2022 16:57:14 +0000 https://www.dmcinfo.com/blog/18030/advantages-of-net-and-python-for-test-measurement-applications/ Prologue In May 2019, at what would become the last NI Week ever, I led a session called “Learning to Love Text Again With Measurement Studio.” It was scheduled for 8:30 AM on the last day of the conference, so I was surprised that so many travel-weary engineers stumbled in, red-eyed, with clumps of taco […]

The post Advantages of .NET and Python for Test & Measurement Applications appeared first on DMC, Inc..

]]>
Prologue

In May 2019, at what would become the last NI Week ever, I led a session called “Learning to Love Text Again With Measurement Studio.” It was scheduled for 8:30 AM on the last day of the conference, so I was surprised that so many travel-weary engineers stumbled in, red-eyed, with clumps of taco still stuck in their hair, ready to listen to me talk about what I thought was a fairly niche topic. I was wrong! The room was filled with enthusiasm, although some of it was already about where to get lunch.

It is now December 2022. NI Week is gone, but the Test & Measurement community is still hungry for Austin’s spectacular tacos and alternative software development platforms. A mere three-and-a-half years later, I have finally found time to convert that presentation into a series of blog posts for those of you who weren’t there (or who slept through it). The intent is to motivate the use of Python or Microsoft’s .NET platform as programming environments for Test & Measurement Automation, and to provide you with some guidance toward getting started.

Introduction

The purpose of this post is to describe the advantages of Python and .NET software development for Test & Measurement applications. The de facto standard is generally National Instruments LabVIEW, due to the shallow learning curve and the quality and feature set of NI hardware; however, NI is also very good about offering hardware APIs for other languages — which gives you the flexibility to choose another option if it’s the right tool for the job. This post will describe why, in certain circumstances, the right tool may be Python or .NET.

In a separate post, I also offer a brief overview of NI’s Measurement Studio — which is a very useful set of tools that makes it easier for LabVIEW developers to get started with .NET.

Measurement Studio offers:

  • .NET classes and functions analogous to LabVIEW’s data analysis VIs
  • .NET data types analogous to those used by LabVIEW (e.g., analog and digital waveform types)
  • A .NET API for creating and managing TDMS files
  • Controls, indicators, and graph elements that can be used in .NET UIs

If I make a compelling case here and you’d like to try building your next Test & Measurement application in .NET, I recommend getting started with the free trial of Measurement Studio. The familiar tools that it provides will help you leverage your existing LabVIEW knowledge to work efficiently in a new environment.

Strengths of LabVIEW Development

From the beginning, NI’s mission for LabVIEW was to make it easy for scientists and engineers to build Test & Measurement applications. Since the mid-80s, the guiding principles of NI’s LabVIEW investment were to:

  • Enable fast, easy software development
  • Make hardware integration as simple as possible
  • Provide a standard library with a focus on engineering functionality
  • Lower the barrier to producing graphical user interfaces

For these reasons, there are some use cases for which LabVIEW is an obvious win. If you need to get an application up and running quickly, if it needs to acquire, process, and visualize data, and if it will neither be deployed widely nor maintained for a very long time, then you will likely benefit from the productivity of developing in LabVIEW.

Where Can We Do Better?

Larger projects require larger teams in order to meet delivery deadlines or maintain the software throughout its lifecycle. However, larger teams need to be able to work in parallel without stepping on each other’s toes. Beyond the initial delivery, maintaining the software can become a challenge as it ages and evolves and as developers drift in and out of the project. Here, we will discuss some advantages to Python and C# that reduce the complexity of maintaining a large software project throughout its lifecycle.

Enabling Technology

Oddly enough, the graphical nature of LabVIEW, which makes it one of the most beginner-friendly programming languages, also makes it extremely difficult to compare two pieces of code. It is easy to navigate and visually parse LabVIEW code, but very difficult to graphically represent the difference between two pieces of code.

Consider the case in which you wrote a VI and shared it with a colleague. If that colleague edited it and gave you a new version, how would you find all of the differences? You can hunt them down on your own, but you’ll need to search every case structure and check the default value of every control. Even if it were a simple VI that could be visually compared easily, you’d have to identify each change as either functional or cosmetic (i.e., an additional wire bend is a difference, but an inconsequential one). Some automated tools exist, but they tend to be hard to configure and use. Furthermore, once all of the differences are identified, how do you visualize them concisely?

Text source code is much more limited in terms of layout. It’s very easy for a computer to parse two chunks of text, compare them, and produce a simple visualization of the differences (often referred to as a “diff”). Simple as it may seem, this enables a substantial number of tools and techniques for managing source code that are not available for complex binary files like VIs. This is critical for code reviews as a quality assurance technique. Senior developers can easily review only the diffs, which are both concise and automatically generated.

Multi-developer Workflows

Being able to see only the differences in two chunks of code is important for quality assurance, but it gets even better: a diff can also be used to easily merge changes into source code as well. This is a major enabler for multi-developer scenarios because it means that two or more developers can make changes to different parts of the same source file, and those changes can be blended together easily (i.e., they won’t collide unless there are different variations of the same lines of text).

DMC’s platform of choice is GitLab, which is a web-based software development management tool.

GitLab provides:

  • A revision-control system (git)
  • Source code navigation, viewing, and comparing
  • Organized methods for users to track issues and resolutions
  • Automations for testing and building applications
  • Different user roles that enable better collaboration with both internal and external teams
  • Many other features

Of particular importance is the issue resolution workflow, which can be separated into individual workflows for different user roles, such as:

In this diagram, we have several people working in parallel (from top to bottom): a technical lead, a few developers, and anyone else with access to the source. Anyone with access to the source code repository can test the code and report issues (bottom). Developers (center) can select a reported issue, create a branch of code dedicated to its resolution, resolve the issue, and submit a “merge request.” The technical lead of the project (top) can then review those merge requests, ensure the code meets quality standards (by viewing the diff of that branch with the main line of development), and merge the updated code into the main line of development independently and in parallel with the developers.

We have found that the cadence of code reviews is easier to maintain with this workflow. GitLab serves as a portal to view only the parts of the code that have changed and allows you to discuss those changes with the developer (asynchronously, via the web interface) before merging them. Instead of sitting down in a conference room and having the developer  walk the technical lead through all the changes in a branch, the technical lead can simply view a diff those changes and enter comments or suggestions that the developer can then address later (as shown below). Once all comments have been addressed and the technical lead is satisfied with the changes, they can be merged.

The GitLab comparison tool shows the source code diff and allows a code reviewer to leave comments & questions for the code's author.

This substantially improves the code review workflow, allowing the tech lead and developers to work asynchronously, communicate effectively, and collaborate productively.

Separation of Concerns

One objective of good software design is “separation of concerns.” One must break down the problem into smaller and smaller problems, and then those into even smaller problems, continuing until there is a tree of convenient fun-size problems that can each be solved easily. Implicit in this methodology is the idea that each problem should be independent of the others. The programmer should establish clear interfaces between problems to keep them logically separate.

If you haven’t heard of SOLID design principles, consider reading through a blog post by my colleague and LabVIEW aficionado Steven Dusing. The “S” in SOLID stands for “single responsibility,” meaning that, as you separate your concerns into smaller, more manageable problems, you should end up with chunks of code that do one thing and one thing only.

If different pieces of code are truly separated, you can even delegate them to people with different skill sets. Wouldn’t it be nice to have a UI designer handle the graphical layout of your application while engineers develop the business logic? A LabVIEW VI has a user interface (front panel) that is inextricably tied to the business logic (block diagram). This makes it dirt-simple to produce a GUI application, but it also makes it very hard to have a complex UI that is separate from the business logic and can be developed by a different person.

As an example, consider WPF, one of the graphical frameworks available on the .NET platform. The UI layout is specified with an XML-style language completely separately from the run-time logic. Since the graphical layout is specified as a text language, diffs are supported for easy review, and, since it exists in its own file, your UI/UX designer can work in parallel with the engineering team. We have effectively separated our concerns: UI things get handled in a UI file by a UI expert, and business logic things get handled in C# code by C# experts.

Outside of the .NET framework, various graphical toolkits are available but my personal preference is Qt — which is available under the terms of the LGPL (mostly). Qt provides QML, which is conceptually similar to XAML (i.e., like XAML, QML is a declarative UI description language), and gets used in the same way to separate your UI design from business logic. Business logic can be implemented in Python, C++, or some other languages with third-party bindings to Qt.

Best Practices for Software Development

In LabVIEW 8.2, object-oriented design principles were introduced to LabVIEW, which was a difficult balancing act. NI’s team aimed to make Object-Oriented Programming (OOP) accessible to scientists and engineers who didn’t necessarily have a computer scientist’s background, without compromising the fundamentals on which LabVIEW was built.

This was done out of a recognition that:

Object-oriented programming has demonstrated its superiority over procedural programming as an architecture choice in several programming languages. It encourages clear divisions between sections of the code, it is easier to debug, and it scales better for large programming teams. LabVIEW R&D wanted this power to be accessible by our customers. We wanted the language to be able to enforce some of these software best-practices.

There are a lot of compelling reasons to use an object orientation (OO) approach, and NI needed to balance that with the need to maintain the concept of dataflow, and a recognition that this might not be the right approach for every person, every project, and every situation. Consequently, the implementation of OO in LabVIEW is very useful and very easy to use, but it is substantially different from traditional OO. For larger projects with larger teams, leveraging the scalability and modularity of a truly object-oriented language can lead to a better product and a more efficient development experience.

Deployment

The three languages we’ve been discussing most (LabVIEW, C#, and Python) all need to run in the context of some set of libraries on the target machine. That may be the LabVIEW Runtime Engine, the .NET Framework, or the Python interpreter, respectively.

Applications that will be widely distributed will generally benefit from the fact that all Windows installations either have the .NET runtime installed already, or they make it easy to do so automatically. Python even has the advantage of creating virtual environments which allow multiple Python interpreters to co-exist on a system, and for each project to have its own set of dependencies installed without any of them colliding with the others. Virtual environments are important for development, but they can also be used to ensure that applications are installed consistently across various systems.

For large development projects, the deployment procedure should be considered early. In many cases, the .NET framework is already installed and available on Windows machines, and if the target will be Linux, Python is widely available and often distributed by default with an OS installation.

Community Support and Engagement

The largest and most active development communities generally produce some of the most useful third-party tools, often under open-source license terms. Based on the relatively informal TIOBE index, both C# and Python land in the top 5 most searched programming languages. Using that as a proxy for the activity of their respective communities, it is not surprising that the package managers for these platforms runneth over with useful tools and libraries that can be readily used in your projects.

The .NET framework provides a package management system called NuGet, which currently hosts over 330,000 unique packages. For Python, the pip module is used to connect to pypi (over 420,000 unique packages) or other package repositories. While LabVIEW does have both JKI’s VI Package Manager (with over 1000 packages) and NI’s own package manager, neither offers the same level of community engagement.

As projects get larger, the availability and quality of reusable tools and libraries can help drive down the amount of code you need to maintain yourself.

Summary

In recent years, NI has made substantial investments in support for .NET and Python. While LabVIEW continues to be a good option for some Test & Measurement applications, many (particularly large projects) could benefit from different tools.

For some engineers, the learning curve of training up on a language other than LabVIEW may be prohibitive. In these cases, I recommend considering NI Measurement Studio as a stepping stone. Click here to read my overview of how Measurement Studio can fill the gaps between LabVIEW and C#.

Learn more about DMC's Test & Measurement Automation solutions, and contact us today for your next project.

The post Advantages of .NET and Python for Test & Measurement Applications appeared first on DMC, Inc..

]]>
5 Advanced UI Design Hacks for Beginners https://www.dmcinfo.com/blog/20580/5-advanced-ui-design-hacks-for-beginners/ Fri, 08 Nov 2019 15:19:15 +0000 https://www.dmcinfo.com/blog/20580/5-advanced-ui-design-hacks-for-beginners/ As a UX/UI Designer, there are so many tips and tricks I wish I knew from the start. Several quick little “quality of life things” that I know now would have sped up my workflow and improved my designs. These things often come from experience and having good mentors around you. If you need some […]

The post 5 Advanced UI Design Hacks for Beginners appeared first on DMC, Inc..

]]>
As a UX/UI Designer, there are so many tips and tricks I wish I knew from the start. Several quick little “quality of life things” that I know now would have sped up my workflow and improved my designs. These things often come from experience and having good mentors around you.

If you need some tips and tricks ASAP, then you came to the right blog. Here are five advanced design hacks for beginners written by an intermediate.

Tip #1

Don’t waste time creating multiple shades of black. Use an opacity slider instead.

Whether creating a poster, user interface, or a simple presentation, using different shades of black for text will be necessary. Instead of consistently using your color picker to select new colors, why not try different shades of black over a white background.

Using pure shades of black (#000000) causes eye strain for readers over long periods of time. It is best to use a slightly “off-black” color as your absolute black. Instead of color picking three or more hex color values, use black with different opacity as a solution.

In the example below, I used my “off-black” as my primary color and decreased its opacity depending to where it will be applied (i.e., primary content, secondary content, tertiary content)

Tip #2

Use color, font weight, and spacing to create hierarchy within your design.

Color and Weight

A common mistake when styling text for a UI or any piece of design is relying too much on font size to control your hierarchy.

Instead of leaving all the heavy lifting to font size alone, a good trick is to use color or font weight to do the same job. If this bit of text is so important, it’s better to make it bolder, not bigger. This technique increases the text’s visual prominence and contrast without drastically increasing its overall footprint on the page.

Two font weights are usually enough for UI work: A normal (Regular) font-weight for most body copy type text, and a heavier (Bold) font-weight for the text you want to emphasize (headlines).

You may want to stay away from any font labeled “Light” or “Thin.” I’ve made this mistake several times and had to correct it. If you want to de-emphasize some text use a lighter color or smaller font size instead of a thinner weight.

As for color, try and to stick to two or three colors in your UI design:

  • A dark color (i.e., for primary content, like the headline of an article)
  • A grey for secondary content (i.e., body paragraph copy or descriptive text below the headline)
  • A lighter grey for a tertiary copy (i.e., the date an article was published on or a location tag).

These two tips will help create a more clearly defined hierarchy within your designs without falling into the common pitfalls when trying to do so.

Now that you have your content styled correctly, it’s time to space everything out and solidify the user experience. 

Spacing

There are many actions you can take to group UI elements. Using a generous space between objects is a fast, simple, and easy to implement solution. One of the fundamentals rules of UX Design, the Law of Proximity, states that objects that are closer together spatially tend to be grouped together.

In this example, we can see that by moving and grouping elements together with white space, we can more easily create groups and eliminate the need for boxes or rule elements. This approach creates a cleaner design and one that is easier for your developers to implement.

Tip #3

Use color to separate lists, dropdowns, and table components instead of rule elements.

Creating rows and columns in any setting, for any component, can be a tedious and boring task at times. From a user’s perspective, however, a well-designed list, table, or drop menu can make an experience easier to use and more rewarding.

Common issues with row-based components include difficulty reading, losing your place on a particular line, or getting information confused and mismatched.

Instead of using lines and strokes to divide rows, try a colored background instead. This approach makes each line unique compared to the ones on top and below it. Making this cosmetic decision gets two birds with one stone as it eliminates users’ pain points and makes the component more visually pleasing.

rule elements 2

 

Tip #4

Avoid huge blocks of color. Instead, use small pops of color to spice up and accent a bland design. 

People often turn to photography, iconography, or a complex digital illustration to bring visual flair to their designs (if you’ve explored Dribble recently, you’ll know what I’m talking about). Instead, a simple trick many other designers and I use is to add a colorful accent to part of your interface that would otherwise feel a bit bland. Rather than adding huge blocks of color and going visually overboard, small accents of color can go a long way and exemplify restraint.

color accents
accent color 2

Have a hard time picking colors? Try using coolors.co. The color picker is flexible and easy to use. Their UI makes it easy to pick one color and build the rest of the palette around it, which is a feature I really like. 

Tip #5

Not every button needs a background color and a rounded corner.

What would a user interface be without buttons? There are many different styles you can add to your button. When I first started designing user interfaces, I approached buttons with a very semantic-heavy thought process. 

For example, if I needed to design a “start process” button, I would make it green. If I needed a “stop” button, I would make it red. 

Avoid this trope by dividing buttons styles into a more critical factor: hierarchy of importance (i.e., primary, secondary, and tertiary actions).

Primary actions should be distinct. Solid, high contrast background colors. Secondary actions should be clearly defined, but not the most prominent. Outline styles or lower contrast background colors are great options. Tertiary actions should be discoverable but unobtrusive. Styling these actions as links is good approach.

button types

Destructive does not always = Red

If a destructive action isn’t the primary action on the page, it might be better to give to make it a more secondary or tertiary action look. A destructive button that is the primary action on the page or dialog is worthy of the big, red, bold button. See the examples below.

I hope these “5 Advanced UI Design Hacks for Beginners” were helpful and get you thinking about new ways to approach the decisions you have to make every day as a UX/UI designer. I’m sure you would have discovered these on your own in time, but I’m glad you decided to do your research and streamline the process.

Learn more about DMC’s UX/UI Design Services.

The post 5 Advanced UI Design Hacks for Beginners appeared first on DMC, Inc..

]]>
Android 10 Logo & Identity Review https://www.dmcinfo.com/blog/20895/android-10-logo-identity-review/ Wed, 02 Oct 2019 11:47:28 +0000 https://www.dmcinfo.com/blog/20895/android-10-logo-identity-review/ History Android Inc. was founded in Palo Alto, California in October 2003 by Andy Rubin, Rich Miner, Nick Sears, and Chris White. Soon after its creation, it was acquired by Google in 2005, and first released to the public in 2008. Android is a mobile operating system for smartphones, tablets, and other connected devices. It is based on […]

The post Android 10 Logo & Identity Review appeared first on DMC, Inc..

]]>
History

Android Inc. was founded in Palo Alto, California in October 2003 by Andy Rubin, Rich Miner, Nick Sears, and Chris White. Soon after its creation, it was acquired by Google in 2005, and first released to the public in 2008. Android is a mobile operating system for smartphones, tablets, and other connected devices. It is based on a modified version of the Linux and other open source software. The platform is free to download, distribute, and modify.

There are thousands of different phones and tablets from companies such as Samsung, Huawei, One Plus, and LG that use some version of Android as their operating system. Roughly 3 billion people around the world use android. It is, by far the most used mobile device software.

Android 10 is the tenth major iteration of the software and also marks the move away from the unique naming convention Android previously used. Along with the name change comes a major shift in the logo and identity of Android. This is bound to turn some heads and upset some hardcore fans. Let’s take a look at the new identity created by Brooklyn born design studio, Huge.

New Identity Intro Video
Video credit to: Android

About the Identity

In a recent blog post, Sameer Samat, VP of Product Management at Android, was quoted saying, “This year, we’re introducing a more modern, accessible look. The design of the logo draws inspiration from the most recognizable non-human member of the community, the Android robot. The robot belongs to everyone in the community and has been a symbol of the fun and curiosity at the heart of Android. Now, it has a special place in our logo.”

Logo on White
Image credit to: Huge Inc.

Logo on Dark
Image credit to: Huge Inc.

Alternate Logo Lockups
Image credit to: Huge Inc.

The full-bodied robot has become so well-known and symbolic of Android that it is hard to see them ever fully moving away from it. However, the logo did have its flaws. The color, the size, and wordmark all had their own quirks, which made the identity feel dated and less mature than this new refreshed version. The new robot head works amazingly well as an icon. We’re so familiar with the full-bodied version that reducing it down to just the head makes perfect sense. Users still understand the context, the logo is more compact, reduces to a smaller size more easily, and is far more charming. The facial expressions of the robot itself are more playful and better executed than previous robot expressions.

Icon Expressions
Image credit to: Huge Inc.

The old wordmark was my least favorite part of the entire Android brand. The combination of all upper-case letters and the wide-open “D’s” was painful for designers all around. The new logo font is much better. A basic geometric San Serif set in either black or white, it is interesting and fits the aesthetic of the brand well.

Lastly, the new green is a considerable improvement, with a more vibrant hue that is unique and energizing. Once again, Sameer Samat, “We also changed the logo (wordmark to be more specific) from green to black. It’s a small change, but we found the green was hard to read, especially for people with visual impairments.”

Color Scheme
Image credit to: Huge Inc.

The new logo may upset some Android fans as it makes drastic changes to the robot and the wordmark. I think it is undeniable that this new logo is so much better and pleasing for a designer.

Typography
Image credit to: Huge Inc.

I like the new typeface as it reminds me of other popular fonts such as Helvetica, Gotham, and Proxima Nova.

Digital Advertising Collateral
Image credit to: Huge Inc.

Print and digital collateral looks fresh and bright. Huge went with these pill, button-like shapes in both stroked and filled styles. While fairly basic, these match the full visual language of Android and the UI associated with the operating system itself.

UI Examples
Image credit to: Huge Inc.

Final Thoughts

Overall, I find the update lively, exciting, and maturation of the Android brand. The old identity fit the first few iterations of Android perfectly. In the early days, not only was Android new, the technology, smartphone, and connected device landscape was nowhere near as advanced as it is today. The logo always gave me the impression Android was for developers, programmers, and the tech-savvy. Digital collateral from that time feels almost nostalgic now.

Old Android Developer Artwork 
Image credit to: Android Guys

Android Oreo Artwork
Image credit to: GSM Arena

Old Android Developer Logo

Image credit to: XDA Developers

Old Android Robot Costumes
Image credit to: The New York Times

Android had got to a point where it outgrew the old branding. Now that both the technology and industry is well established, the branding can focus less on the developer community and more on the consumers themselves. This new logo is a logical step forward for a company that keeps innovating year after year. Huge did a fabulous job with this redesign and has set up Android for even more future success.

Learn more about DMC’s UX/UI Design Services Here.

The post Android 10 Logo & Identity Review appeared first on DMC, Inc..

]]>
User Interface Design Tips: Checkboxes vs Toggle Switches https://www.dmcinfo.com/blog/22434/user-interface-design-tips-checkboxes-vs-toggle-switches/ Tue, 27 Nov 2018 15:50:10 +0000 https://www.dmcinfo.com/blog/22434/user-interface-design-tips-checkboxes-vs-toggle-switches/ Interactivity, or input from a user, is expected in almost every type of mobile or desktop application, website, or interface. Users enter personal information, change application settings, and navigate various menus while using an interface. As designers, it is our job to provide users with the correct controls to make these inputs easier and quicker. Two […]

The post User Interface Design Tips: Checkboxes vs Toggle Switches appeared first on DMC, Inc..

]]>
Interactivity, or input from a user, is expected in almost every type of mobile or desktop application, website, or interface. Users enter personal information, change application settings, and navigate various menus while using an interface. As designers, it is our job to provide users with the correct controls to make these inputs easier and quicker.

Two of the most common controls, toggle switches and checkboxes, appear to accomplish the same task and often get their use cases confused as a result. However, there are very specific instances where you should use one or the other. This blog will explain the differences between checkboxes and toggle switches and identify when to use each in your user interfaces. 

Distinguishing Between Checkboxes and Toggle Switches

checkbox control has two states: unselected and selected. Checkboxes should be utilized when a user can choose any number of listed options. Checkboxes will usually require buttons such as “Submit, OK, Next, Apply” after the boxes have been checked.

toggle switch represents a physical switch and is an “either/or” control that allows users to turn things on or off, like a light switch. Similar to flipping a light switch, the effects of that switch are felt immediately. This same characteristic applies to UI toggle switches.

Switches can provide an added benefit to mobile users. Switches have a larger touchpoint to connect with and can provide more haptic feedback compared to checkboxes.

Tapping a toggle switch is a two-step action: selection and execution, whereas a checkbox is just selection of an option and the execution/saving action is usually required later on or is in a separate location.

Below are few use-cases to help you decide which control is right for your user interface.

When to Use a Toggle Switch

  • An instant response is required without review or confirmation. 
  • A setting requires an on/off or show/hide function.

 When to use a toggle switch in your UI

When to Use Checkboxes

  • Options need to be confirmed or reviewed by the user before they are submitted.
  • The user has to perform additional steps for changes to become active.

When to use checkboxes in design When to use checkboxes in your UI

  • Multiple options are available, and the user has to select one or more options from them.

When to use a checkbox When to use a checkbox

  • A single yes/no choice is provided, or only one option can be selected, and its meaning is obvious.

Using a checkbox in design Using a checkbox in design

  • The user is toggling independent features or behaviors, and you want to offer two options for an on/off type of decision.

Best practices for checkboxes in design Best practices for UI checkboxes

Conclusion

When deciding between a switch or checkbox, focus on context, not function. Ask yourself whether a setting should take immediate effect or not. Ask yourself whether users need to check their settings before they apply them.

Learn more about DMC's User Interface Design expertise.

The post User Interface Design Tips: Checkboxes vs Toggle Switches appeared first on DMC, Inc..

]]>
DMC Attends Inaugural TwinCAT HMI Training https://www.dmcinfo.com/blog/22685/dmc-attends-inaugural-twincat-hmi-training/ Mon, 17 Sep 2018 10:33:52 +0000 https://www.dmcinfo.com/blog/22685/dmc-attends-inaugural-twincat-hmi-training/ Earlier this year, DMC attended a training course on the new TwinCAT HMI at Beckhoff’s U.S. headquarters in Savage, MN. We were impressed by the flexibility and convenience offered by the platform and have already enjoyed using it on projects. Due to its unique developer-friendly design, DMC has been able to reduce development time and […]

The post DMC Attends Inaugural TwinCAT HMI Training appeared first on DMC, Inc..

]]>
Earlier this year, DMC attended a training course on the new TwinCAT HMI at Beckhoff’s U.S. headquarters in Savage, MN. We were impressed by the flexibility and convenience offered by the platform and have already enjoyed using it on projects. Due to its unique developer-friendly design, DMC has been able to reduce development time and save money for our clients. Here’s a couple of the features of Beckhoff’s new HMI that set it apart:

Web-based Control and Monitoring

The most appealing aspect of Beckhoff’s platform is that it leverages the internet to allow end users to control and monitor their PLCs from anywhere. Interfacing with industrial controllers via the internet is a common request for DMC, and the TwinCAT HMI makes this easier than ever before with integrated server- and client-side development connecting easily to the underlying PLC access. The key benefit is that a web-based HMI can be accessed from any number of different clients – a traditional monitor screen nearby the device being controlled, a tablet carried around the area by a supervisor, or even a mobile phone halfway around the world. The HMI includes a responsive grid designed to facilitate development for multiple device sizes.

Multiple Web Clients for TwinCAT HMI Server

WYSIWYG Web Page Design

As any web developer can tell you, managing the location and arrangement of content on the page can be one of the most time-consuming aspects to do manually. Development for TwinCAT gets around these issues using a What You See Is What You Get style editor, paired with other features to make life easy for developers. Selecting and arranging components is as simple as dragging them in from a menu. The HMI features dozens of prebuilt components, including commonly used displays and controls, as well as advanced charts. We walked through a demo project using several different charts, which update live when linked with the PLC data, making it very easy to display live system data of all sorts to your end users.

TwinCAT HMI Development in Visual Studio

Development in Visual Studio

This is a good point to bring up the ease of development afforded by the IDE. Development for the HMI is integrated directly into Visual Studio, which is very familiar to DMC from our extensive .NET development. The Visual Studio pane and menu system’s familiarity makes navigation quick, and Beckhoff’s custom screens are also very easy to use. Integrating development into a widely used and well-regarded editor allows engineers to develop a simple web-based HMIs for their PLCs without requiring extensive knowledge of web technologies. However, developers familiar with web technologies (like DMC) still have access to the generated results, allowing us to tailor a solution to the exact needs of our clients.

Easy Extension and Customization

As mentioned, Beckhoff’s existing framework controls allow for rapid development, especially for simple applications. However, the HMI is also very extensible, allowing for flexible and very custom solutions. DMC took an additional course in designing extensions for custom applications. The extensibility allows for custom Javascript interactive controls, in the case that the provided components don’t quite fit the bill. Further, the HMI allows for custom server controls in C#, which allows us to develop connect to any system, use any library, or employ any custom code from our .NET expertise. The flexibility of these extensions, combined with the ease of creating a simple solution, makes us very excited to work on projects with this new HMI.

Thanks to Zachary Daulton and co. at Beckhoff for hosting us and giving us a preview of the software!

The post DMC Attends Inaugural TwinCAT HMI Training appeared first on DMC, Inc..

]]>
Flattening WinCC’s Skeuomorphism: The Toggle Switch https://www.dmcinfo.com/blog/23412/flattening-winccs-skeuomorphism-the-toggle-switch/ Thu, 15 Feb 2018 12:52:39 +0000 https://www.dmcinfo.com/blog/23412/flattening-winccs-skeuomorphism-the-toggle-switch/ I recently developed an HMI using WinCC 7.4 for an automation project. Building it from the ground up afforded me the opportunity to deviate from WinCC’s default skeuomorphic controls and instead experiment with more modern, flatter UI elements found on consumer-facing products like smartphones and web interfaces (think Facebook, Google, and Apple). I relied on WinCC’s […]

The post Flattening WinCC’s Skeuomorphism: The Toggle Switch appeared first on DMC, Inc..

]]>
I recently developed an HMI using WinCC 7.4 for an automation project. Building it from the ground up afforded me the opportunity to deviate from WinCC’s default skeuomorphic controls and instead experiment with more modern, flatter UI elements found on consumer-facing products like smartphones and web interfaces (think Facebook, Google, and Apple).

I relied on WinCC’s faceplate types to create generic controls that could be dragged and dropped as needed without additional overhead. I wanted this interface to feel comfortable for operators, so I opted for a design that mirrored interfaces with which users would feel more familiar.

 

In this post, I’ll discuss my custom toggle switch control.

 

When to Use a Toggle Switch

Toggle switches, like light switches, should be used to control binary operations that go into effect immediately (as opposed to a checkbox that is usually coupled with an Apply, Submit, or Next button). 

Functionally, toggle switches follow a standard across platforms; right and colored indicates a “powered” or “enabled” state, and left and grey means “off” or “disabled.” However, each company designs its control a little differently to give a unique feel.

Here are designs that some high-profile companies use:

Img 1: Microsoft Windows 10


Img 2: Google Android


iPhone Notifications Hangouts Menu.

Img 3: Apple iOS

 

How to use the ToggleSwitch in WinCC 7

I made two variations of my toggle switch. The regular fptToggleSwitch faceplate has properties for displaying text within the switch. Deprecated due to its ambiguity, this style does not follow best practices for some designers

The text could arguably indicate either state or action (if the switch says on, does that mean it is currently on? Or will pressing it cause it to turn on?). An alternative standard is to simply remove text, and let the fact that ‘right’ and ‘colored’ indicate ‘on’, while ‘left’ and ‘non-colored’ indicate ‘off’. To further reduce ambiguity, the fptToggleSwitch_OuterText faceplate might be more appropriate, which displays text on either side of the switch. This method solidifies the intended functionality.

I’ve attached the files for both types of switches. Feel free to use as-is or customize it to tailor it more to your application.

Files

ToggleSwitch Faceplates

 

  • IsToggled: Tie this property to the tag corresponding to the status of the device or bit that the switch is toggling.
     
  • OnText: For the regular faceplate, this is the text that is displayed when the switch is toggled on. For the outer text faceplate, this is the text that is shown on the right side of the switch.
     
  • OffText: For the regular faceplate, this is the text that is displayed when the switch is toggled off. For the outer text faceplate, this is the text that is shown on the left side of the switch.
     
  • OnColor – Color helps indicate the state of the switch to the operator. It is standard practice for the presence/absence of color to represent on/off, enabled/disabled, true/false, etc. The OnColor property defines the color of the switch when it is on. Choose a color that matches the color scheme of your project.

Don’t forget to set an event for when the control is pressed!

 

See the Switch in Action

 

Conclusion

Intuitive and user-friendly interfaces aren’t just for picky consumers; they can greatly improve the usefulness and effectiveness of a machine. Users who can better navigate and operate their machine are less likely to make mistakes that can cause unplanned downtime. 

Check out our HMI and SCADA programming and User Interface Design pages to see what DMC can do to improve your machine interface!

The post Flattening WinCC’s Skeuomorphism: The Toggle Switch appeared first on DMC, Inc..

]]>