REP (Talk | contribs) (→⚠️ Context Switching And Templates: seperated examples for Xamarin) |
KGR (Talk | contribs) (→Improving performance on complex List item templates) |
||
(28 intermediate revisions by 2 users not shown) | |||
Line 88: | Line 88: | ||
* 🏆 '''Deploy a new Default folder''' every time you update your client version, to ensure that the latest UI version is the foundation for your customizing. | * 🏆 '''Deploy a new Default folder''' every time you update your client version, to ensure that the latest UI version is the foundation for your customizing. | ||
* 🏆 '''Only include xamls that are needed for your customizing.''' | * 🏆 '''Only include xamls that are needed for your customizing.''' | ||
− | ** Remove files for child Areas and Items that are not included in | + | ** Remove files for child Areas and Items that are not included in the project this customizing will be used for. This is especially relevant when copying customizings from other projects. |
− | + | ||
=== How to select the right Control === | === How to select the right Control === | ||
Line 101: | Line 101: | ||
** For example, there is no need to use a Filter + ListView to show a single UBIK object or property. Instead, you can bind the specific context of that object or property to a xaml control, and everything nested inside will inherit that binding context. This will be explained more in the section on [[XAML_Best_practices#Reusable_Templates|Reusable Templates]]. | ** For example, there is no need to use a Filter + ListView to show a single UBIK object or property. Instead, you can bind the specific context of that object or property to a xaml control, and everything nested inside will inherit that binding context. This will be explained more in the section on [[XAML_Best_practices#Reusable_Templates|Reusable Templates]]. | ||
** Similarly, simple EvalExpressions can in many cases be replaced with [[XAML_Best_practices#Converters|Converters]]. | ** Similarly, simple EvalExpressions can in many cases be replaced with [[XAML_Best_practices#Converters|Converters]]. | ||
− | ** The Visibility attribute can be added to any control. There is no need to wrap any control in a Grid simply to apply a Visibility. The same goes for layouting attributes such as Grid.Row/Column. | + | ** The Visibility attribute can be added to any control. '''There is no need to wrap any control in a Grid simply to apply a Visibility.''' The same goes for layouting attributes such as Grid.Row/Column. |
* 🏆 When you need to show a collection of items such as ListView, '''always use our custom versions'''. These are UBIK-optimized for speed and performance. | * 🏆 When you need to show a collection of items such as ListView, '''always use our custom versions'''. These are UBIK-optimized for speed and performance. | ||
** These are [[XAML_Tips#Version_3.7_.26_later|SelectionBoundListView]] on UWP and [[Xamarin_XAML#SfListViewExt|SfListViewExt]] on Xamarin. | ** These are [[XAML_Tips#Version_3.7_.26_later|SelectionBoundListView]] on UWP and [[Xamarin_XAML#SfListViewExt|SfListViewExt]] on Xamarin. | ||
Line 117: | Line 117: | ||
** This is especially an issue in Xamarin, both for performance, but also because sfListViewExt tends to cause layouting issues by taking up more space than needed, unlike in UWP where a ListViewExt is capable of calculating its size based on the size of its items. Therefore, when using nested ListViews in Xamarin, constrain the size of the inner ListView with a fixed HeightRequest attribute (or WidthRequest in the case of a horizontal list). | ** This is especially an issue in Xamarin, both for performance, but also because sfListViewExt tends to cause layouting issues by taking up more space than needed, unlike in UWP where a ListViewExt is capable of calculating its size based on the size of its items. Therefore, when using nested ListViews in Xamarin, constrain the size of the inner ListView with a fixed HeightRequest attribute (or WidthRequest in the case of a horizontal list). | ||
** 🏆 '''Alternatives to the optimized ListViews should be used with extreme caution.''' Controls such as CollectionView or BindableLayout are not optimized for good performance in UBIK. | ** 🏆 '''Alternatives to the optimized ListViews should be used with extreme caution.''' Controls such as CollectionView or BindableLayout are not optimized for good performance in UBIK. | ||
− | + | ||
+ | |||
=== Using Styles and Templates === | === Using Styles and Templates === | ||
Line 136: | Line 137: | ||
** If the repeated customizing is used in only one xaml, create a ResourceDictionary on the parent container and defining your style there. | ** If the repeated customizing is used in only one xaml, create a ResourceDictionary on the parent container and defining your style there. | ||
** Otherwise, place your style in UBIKThemes so it can be accessed across the entire project. | ** Otherwise, place your style in UBIKThemes so it can be accessed across the entire project. | ||
− | ** For more information, read the section on [[XAML_Best_practices#Implicit_and_Explicit_Styling|Implicit and Explicit Styling]] below. | + | ** '''For more information, read the section on [[XAML_Best_practices#Implicit_and_Explicit_Styling|Implicit and Explicit Styling]] below.''' |
* 🏆 If you repeatedly define the same combinations of controls, '''use Control/DataTemplates'''. | * 🏆 If you repeatedly define the same combinations of controls, '''use Control/DataTemplates'''. | ||
** DataTemplates and ControlTemplates are reusable XAML elements that can be defined once and applied several times throughout the customizing. | ** DataTemplates and ControlTemplates are reusable XAML elements that can be defined once and applied several times throughout the customizing. | ||
** You may have noticed that a DataTemplate is the basis for an item template in UBIKThemes. However, these can be applied anywhere in the xaml, and do not require a ListView to be rendered. | ** You may have noticed that a DataTemplate is the basis for an item template in UBIKThemes. However, these can be applied anywhere in the xaml, and do not require a ListView to be rendered. | ||
− | ** For more information, read the section on [[XAML_Best_practices#Templating|Content Templating]] below. | + | ** '''For more information, read the section on [[XAML_Best_practices#Templating|Content Templating]] below.''' |
* 🏆 '''Reduce the number of elements, especially containers''', wherever possible. | * 🏆 '''Reduce the number of elements, especially containers''', wherever possible. | ||
** Layouts are often defined in multiple nested containers, usually Grids and StackPanels. This has an impact on performance if it leads to a very deep hierarchy of rendered elements. On mobile devices with less computational power, the difference can be felt. | ** Layouts are often defined in multiple nested containers, usually Grids and StackPanels. This has an impact on performance if it leads to a very deep hierarchy of rendered elements. On mobile devices with less computational power, the difference can be felt. | ||
Line 148: | Line 149: | ||
** Sometimes it’s unavoidable that multiple containers are needed. In Xamarin, applying the [[Xamarin_XAML#Layout_compression_examples|HeadlessLayout]] style can reduce their impact on the visual tree. | ** Sometimes it’s unavoidable that multiple containers are needed. In Xamarin, applying the [[Xamarin_XAML#Layout_compression_examples|HeadlessLayout]] style can reduce their impact on the visual tree. | ||
** Some ideas for reducing the number of nested controls can be seen [[XAML_Best_practices#Alternatives_to_using_Multiple_Controls|later in this article]]. | ** Some ideas for reducing the number of nested controls can be seen [[XAML_Best_practices#Alternatives_to_using_Multiple_Controls|later in this article]]. | ||
− | + | ||
==== Implicit and Explicit Styling ==== | ==== Implicit and Explicit Styling ==== | ||
Line 164: | Line 165: | ||
* The localized Style definitions in this StackPanel will be passed down to all controls contained within it, no matter how deep the nesting goes. | * The localized Style definitions in this StackPanel will be passed down to all controls contained within it, no matter how deep the nesting goes. | ||
* The BasedOn attribute can be used to create a sub-style based on another existing style. However, the TargetType must always match. | * The BasedOn attribute can be used to create a sub-style based on another existing style. However, the TargetType must always match. | ||
− | < | + | |
+ | |||
+ | |||
+ | === Simplifying Layouts === | ||
+ | In UWP / on desktop UBIK, the processor is generally powerful enough to handle more complex UIs. However, mobile devices running Xamarin versions of UBIK will noticeably struggle to render sophisticated UIs made of deeply nested levels of containers. There are two ways this situation can be improved; the first is to reduce the number of containers used, and the second is to apply [[Xamarin_XAML#Layout_compression_examples|Layout Compression]] wherever possible. Finally, some ideas will be presented for other ways to reduce the number of controls needed to render the same UI. | ||
+ | |||
+ | |||
+ | ==== Reducing Containers ==== | ||
+ | Containers refers to controls whose purpose is to display other controls. The most basic examples are the Grid and StackLayout. These containers are frequently nested in order to finetune a UI, however, the downside is that each one adds a new branch in what is called a "visual tree', even if the control itself is not visible. When using many containers, it is useful to ask yourself if each container is really necessary, or if the visual tree can be simplified. | ||
+ | |||
+ | The examples below show three different ways for showing the same content, each utilizing less controls, leading to a simpler visual tree. | ||
+ | |||
+ | <tabs> | ||
+ | <tab name="2 StackLayouts, 3 Labels"> | ||
+ | <source lang = "xml"> | ||
+ | <StackLayout> | ||
+ | <StackLayout Orientation="Horizontal"> | ||
+ | <Label Text="{x:Static resources:UBIKIcons.IconName}" FontFamily="{StaticResource UBIKSymbols}"/> | ||
+ | <Label Text="Name" /> | ||
+ | </StackLayout> | ||
+ | |||
+ | <Label Text="Description" /> | ||
+ | </StackLayout> | ||
+ | </source> | ||
+ | </tab> | ||
+ | |||
+ | <tab name="1 Grid, 3 Labels"> | ||
+ | <source lang = "xml"> | ||
+ | |||
+ | <Grid Grid.ColumnDefinitions="24,*" Grid.RowDefinitions="Auto,Auto"> | ||
+ | <Label Text="{x:Static resources:UBIKIcons.IconName}" FontFamily="{StaticResource UBIKSymbols}"/> | ||
+ | <Label Grid.Column="1" Text="Name" /> | ||
+ | <Label | ||
+ | Grid.Row="1" | ||
+ | Grid.ColumnSpan="2" | ||
+ | Text="Description lorem ipsum..." /> | ||
+ | </Grid> | ||
+ | </source> | ||
+ | </tab> | ||
+ | |||
+ | <tab name="1 Label"> | ||
+ | <source lang = "xml"> | ||
+ | <Label> | ||
+ | <Label.FormattedString> | ||
+ | <FormattedText> | ||
+ | <Span FontFamily="{StaticResource UBIKSymbols}" Text="{x:Static resources:UBIKIcons.IconName}" /> | ||
+ | <Span Text="Name & #10;" /> | ||
+ | <Span Text="Description lorem ipsum..." /> | ||
+ | </FormattedText> | ||
+ | </Label.FormattedString> | ||
+ | </Label> | ||
+ | </source> | ||
+ | </tab> | ||
+ | |||
+ | Note: & #10; (without the blank space) is the Xamarin glyph code for a newline. The equivalent in UWP is & #x0a;. | ||
+ | </tabs> | ||
+ | |||
+ | Now imagine the UI element should have a colored background. You may be tempted to wrap everything in a Frame, however, you can just as easily assign color using the Background attribute on the StackLayout, Grid, or even the Label itself. | ||
+ | |||
+ | Granted, this example is very simple, however, the point illustrated here is that you should fid creative ways to reduce the number of controls nested in your UI. | ||
+ | |||
+ | Another thing to consider is how many times this particular UI definition will be rendered on screen. You have more freedom to be uneconomical in your use of controls when when designing items that will appear once on a page, such as the page header, rather than with item templates or control templates that are rendered several, or even dozens, of times in one view. | ||
+ | |||
+ | |||
+ | ==== Layout Compression ==== | ||
+ | |||
+ | |||
+ | ==== Alternatives to using Multiple Controls ==== | ||
+ | |||
+ | |||
Line 175: | Line 245: | ||
=== Template Selectors === | === Template Selectors === | ||
− | Various parts of the UBIK UI are dynamically swapped in based on specific conditions. One example is the UBIKPropertyDirectItemContainer.xaml, which serves as the base for an item in the property list, and which renders a different type-appropriate editing UI for each kind of property input (such as | + | Various parts of the UBIK UI are dynamically swapped in based on specific conditions. One example is the UBIKPropertyDirectItemContainer.xaml, which serves as the base for an item in the property list, and which renders a different type-appropriate editing UI for each kind of property input (such as TextBox for manual input String, Double, or Integer; selection for link properties, or list-based String, Double, or Integer; clock and calendar pickers for DateTime; Lat., Lon., and Alt. input for geo coordinates, etc). |
The logical tree used by these selectors to render different UIs is documented in our article [[UBIK_Templates]]. | The logical tree used by these selectors to render different UIs is documented in our article [[UBIK_Templates]]. | ||
− | + | ||
=== DataTemplate vs. ControlTemplate in Xamarin === | === DataTemplate vs. ControlTemplate in Xamarin === | ||
Line 187: | Line 257: | ||
* Example 2: Consider a Property ItemTemplate, where we can view or edit a metaproperty. In this case, the '''context''' of the ItemTemplate would be a single property, and the bindings available would reflect that; we can bind to its Description, Value, DisplayString, Unit, or even its SortedValueRecords, if property change history is activated. | * Example 2: Consider a Property ItemTemplate, where we can view or edit a metaproperty. In this case, the '''context''' of the ItemTemplate would be a single property, and the bindings available would reflect that; we can bind to its Description, Value, DisplayString, Unit, or even its SortedValueRecords, if property change history is activated. | ||
* Use a '''ContentControl''' or our custom '''controls:ContentControl''' (with the attribute ContentTemplate="{DynamicResource ...}" and TemplateContext="{Binding ...}") to display it. | * Use a '''ContentControl''' or our custom '''controls:ContentControl''' (with the attribute ContentTemplate="{DynamicResource ...}" and TemplateContext="{Binding ...}") to display it. | ||
− | + | ||
+ | |||
==== ControlTemplate ==== | ==== ControlTemplate ==== | ||
ControlTemplate is technically used for showing how a specific control can be used; for example, if you want to customize a new template for how a Button should appear. However, it can be used similar to the DataTemplate, with one added bonus; its ContentPresenter sub-control allows for dynamically adding content at every instance, as opposed to a single universal layout that is shared by all. | ControlTemplate is technically used for showing how a specific control can be used; for example, if you want to customize a new template for how a Button should appear. However, it can be used similar to the DataTemplate, with one added bonus; its ContentPresenter sub-control allows for dynamically adding content at every instance, as opposed to a single universal layout that is shared by all. | ||
Line 196: | Line 267: | ||
* Note that implicit styles can also be added to the ControlTemplate, and will be inherited by any content nested in the ContentView. | * Note that implicit styles can also be added to the ControlTemplate, and will be inherited by any content nested in the ContentView. | ||
* Use a '''ContentView''' (with the attribute ControlTemplate="{DynamicResource ...}") to display it. | * Use a '''ContentView''' (with the attribute ControlTemplate="{DynamicResource ...}") to display it. | ||
− | + | ||
+ | |||
{{Attention|Since the ControlTemplate is not technically designed to work directly with data bindings, it does inherit the context from its ContentView by default. However this can be easily fixed by adding the <nowiki>BindingContext="{TemplateBinding BindingContext}"</nowiki> attribute to the root grid of the template.}} | {{Attention|Since the ControlTemplate is not technically designed to work directly with data bindings, it does inherit the context from its ContentView by default. However this can be easily fixed by adding the <nowiki>BindingContext="{TemplateBinding BindingContext}"</nowiki> attribute to the root grid of the template.}} | ||
− | + | ||
+ | |||
{{Hint|An understanding of '''Contexts''' is fundamental to using templates, as this informs what data bindings are directly available. Some more information can be found at this article on [[Object_hierarchy_in_XAML:_NextLevel,_ParentLevel,_LinkedLevel#The_ContentViewModel.28s.29|the ContentViewModel]] }} | {{Hint|An understanding of '''Contexts''' is fundamental to using templates, as this informs what data bindings are directly available. Some more information can be found at this article on [[Object_hierarchy_in_XAML:_NextLevel,_ParentLevel,_LinkedLevel#The_ContentViewModel.28s.29|the ContentViewModel]] }} | ||
− | + | ||
=== ControlTemplate in UWP === | === ControlTemplate in UWP === | ||
− | The situation is much simpler in UWP, which only makes use of the ControlTemplate to define custom templates. The ContentControl is used to render an instance of a template. | + | The situation is much simpler in UWP, which only makes use of the ControlTemplate to define custom templates. The ControlTemplate in UWP inherits the context from its content control, so no equivalent to Xamarin's <nowiki>BindingContext="{TemplateBinding BindingContext}"</nowiki> is required. |
− | + | ||
− | + | The ContentControl is used to render an instance of a template. | |
+ | |||
=== Contexts and Context Switching === | === Contexts and Context Switching === | ||
− | In xaml, every element in the UI is informed by a '''context'''. This is the data that is delivered by UBIK immediately when binding. Knowing the context that will be used by a content control is vital when using templates, as this directly affects what bindings you can prepare in your template. Furthermore, all content views that use the same template must have | + | In xaml, every element in the UI is informed by a '''context'''. This is the data that is delivered by UBIK immediately when binding. Knowing the context that will be used by a content control is vital when using templates, as this directly affects what bindings you can prepare in your template. Furthermore, all content views that use the same template must have similar contexts, to a degree. |
For example: | For example: | ||
− | Two of the more frequently customized elements are Objects and their Metaproperties. A template used for | + | Two of the more frequently customized elements are Objects and their Metaproperties. A template used for a UBIK object will have bindings such as Title, Values[PROPERTY], etc, whereas one designed for a Metaproperty will have DisplayValue, Description, Unit, and so on. Using a template designed for a UBIK object will have no benefit if the context applied to it is that of a property, and vice versa. |
==== Where does the context come from? ==== | ==== Where does the context come from? ==== | ||
− | Context is inherited. When you begin customizing a xaml template, it generally has a known use, such as a child area, document viewer, or item in a property list, to name just a few of many. In these cases, the context is quite clear. However, when you begin creating templates that can be used anywhere | + | Context is inherited. When you begin customizing a xaml template, it generally has a known use, such as a child area, document viewer, or item in a property list, to name just a few of many. In these cases, the context is quite clear. However, when you begin creating general templates that can be used anywhere in the client, there will likely be the need to specify what context should be applied each time the template is used. |
+ | |||
+ | |||
+ | [[Image:XBP_ContentTemplates.png]] | ||
+ | |||
+ | In the above example, we see a variety of property editing fields. To cut down on repetitive XAMLing, a template is created for inputting a single property, consisting in this case of Description, DisplayValue, a placeholder label, and some indicators such as whether the property is required or filled. In each case, the context needs to be set to a specific metaproperty defined on the 'controls:ContentControl' that hosts each instance of the template, through the TemplateContext attribute. | ||
+ | |||
+ | |||
+ | <source lang = "xml"> | ||
+ | <controls:ContentControl | ||
+ | ContentTemplate="{DynamicResource PropertyField_Required}" | ||
+ | TemplateContext="{Binding Properties.VisibleItems[MP_DETECTION]}" > | ||
+ | <controls:ContentControl.GestureRecognizers> | ||
+ | <TapGestureRecognizer Command="{Binding Properties.VisibleItems[MP_DETECTION].PropertyClickedCommand}"/> | ||
+ | </controls:ContentControl.GestureRecognizers> | ||
+ | </controls:ContentControl> | ||
+ | </source> | ||
+ | |||
+ | {{Hint|Adding the behavior to the ContentControl, rather than defining it in the template itself, allows flexibility in the customizing as different property-editing commands can be paired with the same UI based on the required functionality.}} | ||
+ | |||
==== Context Switching ==== | ==== Context Switching ==== | ||
− | To | + | To repeat, context is almost always inherited. If you forget to assign a specific context to a content control (as in the above example), the context of the XAML element that hosts that content control will be used. When you think about it, any number of nested Grids still inherit the context of the area or item template they are hosted in. |
− | It is also usually very simple to do: | + | Context-switching is simply the name given to the act of assigning a different context to a particular control, when needed. It is also usually very simple to do: |
'''UWP''' | '''UWP''' | ||
Line 226: | Line 319: | ||
DataContext="{Binding ...}" | DataContext="{Binding ...}" | ||
</source> | </source> | ||
− | + | <br> | |
'''Xamarin''' | '''Xamarin''' | ||
<source lang = "xml"> | <source lang = "xml"> | ||
BindingContext="{Binding ...}" | BindingContext="{Binding ...}" | ||
</source> | </source> | ||
− | + | <br> | |
The above attributes can be added to any control to change it's binding context. | The above attributes can be added to any control to change it's binding context. | ||
+ | |||
Try this experiment: | Try this experiment: | ||
Line 240: | Line 334: | ||
Text="{Binding Title}" /> | Text="{Binding Title}" /> | ||
</source> | </source> | ||
− | |||
− | |||
− | |||
− | |||
− | ==== | + | By using this code snippet, we expect to see the Title or the current object. However, because we have a context switch applied, we would instead see the Title of the parent object. For Xamarin use '''Label''' instead of TextBlock and '''BindingContext''' instead of DataContext. |
+ | |||
+ | |||
+ | {{Attention|Using this technique to switch the context of a control does it for ''all bindings on that control''. Since the entire control is now 'focused' directly on a different context, existing bindings will need to be adapted. An easy example is the Visibility of the TextBlock, which we might still want to bind to certain conditions on the child object, while displaying the data of the parent. | ||
+ | |||
+ | One exception is the controls:ContentControl, which through its TemplateContext attribute accepts the possibility to have a different context for its content, therefore decoupling the context of the template from the actual control, [[XAML_Best_practices#Context_Switching_And_Templates|as described below]].}} | ||
+ | |||
+ | Furthermore, since context continues to be inherited, any controls contained within the context-switched one (imagine it's a Grid containing more elements, rather than a simple TextBlock) will all also have the context of ParentLevel. | ||
+ | |||
+ | |||
+ | ==== Context Switching And Templates ==== | ||
<tabs> | <tabs> | ||
<tab name="DataTemplate (Xamarin only)"> | <tab name="DataTemplate (Xamarin only)"> | ||
Line 279: | Line 379: | ||
{{Attention|Remember that Styling in Xaml is type-based, so you cannot use the same template with controls:ContentControl (ContentTemplate attribute) and ContentView (ControlTemplate attribute), as DataTemplate and ControlTemplate are technically different types.}} | {{Attention|Remember that Styling in Xaml is type-based, so you cannot use the same template with controls:ContentControl (ContentTemplate attribute) and ContentView (ControlTemplate attribute), as DataTemplate and ControlTemplate are technically different types.}} | ||
− | + | ||
− | + | ||
− | + | ||
{{UnderConstructionEnd}} | {{UnderConstructionEnd}} | ||
− | + | ||
− | + | ||
== Performance == | == Performance == | ||
Line 306: | Line 404: | ||
== Testing of UI == | == Testing of UI == | ||
XAML can be sometimes very weird, there are dependencies or default values that you don’t see immediately. So, a good way to prevent millions of fixes for the customer, because the environment, different device or even a different windows version destroys your UI, is to write a test plan where you test your implementation. It’s important to see exactly your controls in action on their own and acting with each other, so you can prevent doing lots of ‘easy’ fixes. | XAML can be sometimes very weird, there are dependencies or default values that you don’t see immediately. So, a good way to prevent millions of fixes for the customer, because the environment, different device or even a different windows version destroys your UI, is to write a test plan where you test your implementation. It’s important to see exactly your controls in action on their own and acting with each other, so you can prevent doing lots of ‘easy’ fixes. | ||
+ | |||
+ | {{UnderConstructionStart}} | ||
+ | |||
+ | == Improving performance on complex List item templates == | ||
+ | |||
+ | === Overview === | ||
+ | Performance issues in XAML-based UI development often stem from unnecessary UI rendering and binding errors. Hidden elements remain in the UI tree, impacting performance, while premature bindings cause background errors. To optimize complex list item templates, UI content can be loaded only when needed, and binding errors can be reduced by applying view models at the control level. These improvements enhance efficiency, reduce log clutter, and improve maintainability. | ||
+ | |||
+ | === Technique 1: Loading UI content on demand === | ||
+ | It was discovered that UI controls hidden with '''IsVisible=False''' remain in the UI tree. To improve performance on highly complex item templates, like UBIKTaskItem, rendered repeatedly such as in ListViews, a workaround can be applied to only load the UI content when the '''visibility''' binding condition is met. | ||
+ | |||
+ | ==== Previous Approach ==== | ||
+ | Previously, we used '''IsVisible''' directly with a binding condition. The approach looked like this: | ||
+ | |||
+ | <source lang = "xml"> | ||
+ | <Label IsVisible="{Binding MROViewModel.PreviousValue, Converter={StaticResource NullToNotBool}}"> | ||
+ | <Label.FormattedText> | ||
+ | <FormattedString> | ||
+ | <Span Text="{Binding MROViewModel.PreviousValue}" /> | ||
+ | <Span Text=" " /> | ||
+ | <Span Text="{Binding MROViewModel.Unit}" /> | ||
+ | </FormattedString> | ||
+ | </Label.FormattedText> | ||
+ | </Label> | ||
+ | </source> | ||
+ | <br> | ||
+ | |||
+ | ==== Improved Approach ==== | ||
+ | To prevent unnecessary loading of UI elements when their '''visibility''' is False, it is possible to replace the UI content with a content hosting control, such as a '''ContentView''' or '''ContentControl'''. The original content should instead be defined as a template in '''UBIKThemes''', as shown below. | ||
+ | |||
+ | <source lang = "xml"> | ||
+ | <ContentView> | ||
+ | <ContentView.Triggers> | ||
+ | <DataTrigger Binding="{Binding MROViewModel.PreviousValue, Converter={StaticResource NullToNotBool}, TargetNullValue=false, FallbackValue=false}" | ||
+ | Value="True" TargetType="ContentView"> | ||
+ | <Setter Property="ControlTemplate" Value="{StaticResource PreviousValueTemplate}" /> | ||
+ | </DataTrigger> | ||
+ | </ContentView.Triggers> | ||
+ | </ContentView> | ||
+ | </source> | ||
+ | <br> | ||
+ | |||
+ | The important part of the snippet above is the '''DataTrigger'''. Through this technique, the '''ControlTemplate''' of the content hosting control is only set once the visibility condition is met, preventing the UI content from being loaded until it is explicitly required. | ||
+ | And the associated '''ControlTemplate''', located in UBIKThemes: | ||
+ | <br> | ||
+ | <source lang = "xml"> | ||
+ | <ControlTemplate x:Key="PreviousValueTemplate"> | ||
+ | <Label> | ||
+ | <Label.FormattedText> | ||
+ | <FormattedString> | ||
+ | <Span Text="{TemplateBinding BindingContext.MROViewModel.PreviousValue}" /> | ||
+ | <Span Text=" " /> | ||
+ | <Span Text="{TemplateBinding BindingContext.MROViewModel.Unit}" /> | ||
+ | </FormattedString> | ||
+ | </Label.FormattedText> | ||
+ | </Label> | ||
+ | </ControlTemplate> | ||
+ | </source> | ||
+ | <br> | ||
+ | |||
+ | '''Why This Change?''' | ||
+ | * '''More Modular''': The separation into discrete templates makes individual UI elements reusable and maintainable. | ||
+ | * '''Better Performance''': Eliminates unnecessary loading of UI elements in the background, that are not visible to the user. | ||
+ | |||
+ | <br> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | === Technique 2: Reducing background Binding errors === | ||
+ | |||
+ | Complex item templates such as UBIKTaskItem were found to produce large numbers of '''errors''' while rendering, likely as bindings are being resolved before the relevant '''viewmodels''' can be loaded by the client. While these '''errors''' are temporary and not visible to the user, the cumulative effect of numerous binding errors raised while rendering items can be felt when navigating to pages with large numbers of items. | ||
+ | <br> | ||
+ | {{Attention|Unfortunately, these bindings errors are not normally logged in the UBIKDebug.log. Therefore, the recommendation is to use the following technique whenever using a ContentControl or controls:ContentControl.}} | ||
+ | |||
+ | ==== Previous Approach ==== | ||
+ | Initially, we used a '''ContentControl''' with a direct '''ContentTemplate''' and '''TemplateContext''', as shown below: | ||
+ | |||
+ | <tabs> | ||
+ | <tab name="Xamarin"> | ||
+ | <source lang = "xml"> | ||
+ | xmlns:ctrls="clr-namespace:UBIK.CPL.Controls;assembly=UBIK.CPL" | ||
+ | </source> | ||
+ | </tab> | ||
+ | <tab name="MAUI"> | ||
+ | <source lang = "xml"> | ||
+ | xmlns:ctrls="clr-namespace:UBIK.MAUI.Controls;assembly=UBIK.MAUI" | ||
+ | </source> | ||
+ | </tab> | ||
+ | </tabs> | ||
+ | |||
+ | <source lang = "xml"> | ||
+ | <ctrls:ContentControl | ||
+ | ContentTemplate="{StaticResource TaskPropertyTemplateSelector}" | ||
+ | TemplateContext="{Binding MROViewModel}" /> | ||
+ | </source> | ||
+ | <br> | ||
+ | |||
+ | [[File:Binding_logs.png]] | ||
+ | |||
+ | |||
+ | ==== Improved Approach ==== | ||
+ | One workaround that reduced the number of '''binding errors''' encountered was to refer to '''viewmodels''' indirectly in the bindings. | ||
+ | |||
+ | <source lang = "xml"> | ||
+ | <ctrls:ContentControl x:Name="TaskPropertyTempSelCtrl" | ||
+ | BindingContext="{Binding MROViewModel}" | ||
+ | TemplateContext="{Binding BindingContext, Source={x:Reference TaskPropertyTempSelCtrl}}"> | ||
+ | </ctrls:ContentControl> | ||
+ | </source> | ||
+ | <br> | ||
+ | |||
+ | The previous example used an '''MROViewModel''' binding directly in the '''TemplateContext''' property of the '''<ctrls:ContentControl>.''' | ||
+ | However, a workaround that seems to avoid triggering background binding errors is to apply the '''MROViewModel''' to the '''<ctrls:ContentControl>''' itself via the '''BindingContext''' property, and then request that '''BindingContext''' using the x:Reference syntax seen in the '''TemplateContext''' property. | ||
+ | |||
+ | '''Why This Change?''' | ||
+ | * '''Avoids Additional Binding Errors''': Using this workaround seems to prevent binding errors. | ||
+ | |||
+ | |||
+ | {{UnderConstructionEnd}} | ||
[[Category:Pages with broken file links|XAML Best practices]] | [[Category:Pages with broken file links|XAML Best practices]] | ||
[[Category:XAML|XAML Best practices]] | [[Category:XAML|XAML Best practices]] |
Latest revision as of 09:08, 24 March 2025
This article recommends approaches to customizing and troubleshooting your UBIK custom UIs.
To learn about the fundamentals of XAML, check out our article XAML Basics.
The most important specific Best Practices are marked with a 🏆
Conventions
Conventions are ‘rules’ that make our xamls collectively readable and shareable. By aiming to construct our xaml files in a similar style, we reduce the effort required by others to read and understand our work. This section includes guidelines on how to name controls, and how to internally organize your xaml files.
Naming Conventions
<Button x:Name="SubmitFormButton" ... />
- The x:Name is used to give a unique name for an object within a xaml file. It’s not valid in a ResourceDirectory (such as a style, template, etc, either defined within a xaml file, or in UBIKThemes), which requires using x:Key instead.
- Likewise there is a difference when referencing x:Name and x:Key elements.
x:Name - UWP: ElementName=SubmitFormButton | Xamarin: Source={x:Reference SubmitFormButton} x:Key - Both: {StaticResource SubmitFormButtonStyle} or {DynamicResource SubmitFormButtonStyle}
- Use explicit language when giving a name or key, for example: “PropStkpnl” is not easily readable, but “PropertiesStackPanel” is.
- Furthermore, “FailureCodeExpression” does not describe the purpose of the expression, whereas “FailureCode_HasUnfilledFields” does.
- Naming it according to its purpose also makes it easier to use in xaml, such as when combining the result with a converter for an intended outcome (eg. An warning symbol visible when FailureCode_HasUnfilledFields is true).
- You do not have to name every object in your code, only if it is referred to by another element. However, sometimes adding names to controls helps a reader understand their intended function.
Formatting
Formatting xamls properly is critical to others being able to quickly read and understand a customizing.
![]() | If you struggle to maintain nice formatting while writing your xaml code, you can find formatters that automatically such as Notepad++’s “Pretty Print” or VS Code’s “XAML Styler” plugins. |
Best Practices
- x: Name or x: Key should come first.
- A control with up to 2 Attributes can be added in one line. If it has more than 2, they should be stacked over another for readability.
- Avoid using a formatter where the indentation matches the name of the control
- Add controls in a Top-to-Bottom order in the xaml file. Additionally, try to add them in a Left-to-Right order.
- Controls in Grid.Row/Column 0 should be higher up in your xaml file than controls placed in other rows and columns.
- Respect indentation of nested controls.
- Opening and closing tags should always begin in the same column.
- Controls nested inside them should begin exactly one column to the right.
- When pasting code snippets into your work, be sure to correct the differences in indentations.
- Use Linebreaks mindfully.
- It is difficult to describe rules for how linebreaks should be used, but the general idea is to use them to group elements in your xaml file, and to show which controls are nested (no linebreaks) and which controls are 'siblings' (divided by linebreaks).
- ✔️Put linebreaks in between large sections of a UI definition (eg. between Grids or major elements of the page, or between content set on different Rows/Columns).
- ❌ Avoid linebreaks in the middle of smaller elements (eg. a stackpanel with 2 texts inside).
- 🏆 Add comments.
- This is one of the most critical features of clear code, saving the reader from wasting time struggling to read the code, or even having to search and ask around.
- Use comments to describe the position, purpose, and function of every control.
- When maintain xamls according to a ticket, put the ticket number in a comment so a reader has access to background information on the change.
- Remove unnecessary code. Ensure everything included in your xaml has a purpose. This includes large sections of commented out code.
- This both makes the xaml clearer, but also improves performance by avoiding unnecessary loading and rendering effort.
- This is especially true when copying from other’s work.
- If you’re not sure if something is necessary, comment it out and see if your xaml still fulfils its function. If it works, remove it.
- Read more in the next section on Performance.
- 🏆 Empty out your UBIKThemes for every new customizing.
- When working in Xamarin, it is recommended to empty out your UBIKThemes file (ie. delete everything between the <ResourceDictionary> tags) and only add what you intend to customize.
- Unfortunately, this approach is currently not 100% possible in UWP xamls, as we noticed that custom styles are not utilized on controls unless the controls are also in UBIKThemes, shown instead using the standard grey/orange. However, diligent customizers can find out how to do so below: An issue with dynamically refreshing style resources causes emptying of UBIKThemes to be much more complicated on UWP than in Xamarin.
This is because StaticResources are only updated once they are overwritten again in Themes. The most common symptom of this issue is that for example, if UBIKAccentColor is changed to blue, this customization will not be reflected by styles for any control defined in UBIKThemes, unless the styles are 'written' again in UBIKThemes. So in this example, the regular button hover state will remain orange, unless UBIKButtonStyle is included in the UBIKThemes of that project.
Therefore, the only approach we can recommend is to remove everything from UBIKThemes, and add styles back once they fail to reflect the customized color resources. Since this approach is impractical, we do not encourage emptying UBIKThemes for UPW as strongly as we do for Xamarin.
- Organize UBIKThemes in sections.
- For example, standard UBIKThemes defines resources grouped into; Color resource / Size resources / Text styles / Button styles / etc
- In Xamarin, keep Item Templates at the end of UBIKThemes.
- Either way, it is recommended to keep your UBIKThemes file as lean as possible (read the next point), as well as maintaining a grouping strategy. Use the tags <!--#region Example--> and <!--#endregion--> to define groups of content, for example
<!--#region Text Styles-->, <!--#region DataTemplates-->, or <!--#region Item Templates-->.