The previous post outlined in an XAML UI using a ListView how to display a List of instances of a class of public properties using data binding. Layout and padding issues were examined as well as binding to a dictionary of objects. This article covers programmatically adding elements of a collection to an XAML UI.
The aim is to display the properties of a Bluetooth Low Energy (BLE) device as available with through the Device Information GATT Service that a BLE device makes available for discovery. The object of this blog is not to cover exercising the Device Information service as I have covered the Bluetooth API elsewhere:
Bluetooth Low Energy on Windows 10 Creators Edition and a RPi3 issue. I do have a UWP app in development that explores a BLE GATT device’s Services and Characteristics in a generic manner. Watch for BLEX in The Store.
The properties available from the service are listed in teh following enumeration:
public enum SensorTagProperties { SysId, DeviceName, ModelName, SerialNumber, FirmwareDate, HardwareRevision, SoftwareRevision, ManufacturerId, BTSigCertification, PNPId, BatteryLevel };
Actually the BatteryLevel is available through another BLE GATT service Battery Service. In the app this service is separately called to to get the battery level and appended to this dictionary of properties.
At issue is that whist the Device Information service may (?) be mandatory, none of its characteristics (properties) are mandatory. A device can have all, some, one or none of them. The Battery Service is not mandatory. So in querying a device for its properties one cannot anticipate which and how many properties will be returned and so a collection of variable size such as a List or Dictionary is a suitable data structure to use for the returned data In the BLEX app a Dictionary using SenorTagProperties enums as the key.
The previous post showed how to create a ListView data template for a Dictionary. In this blog we look at doing it programmatically in a loop. We start with a blank StackPanel (vertical) to the XAML code and programmatically add controls to it’s Children. The items then appear in order added under each other in the StackPanel. Actually, one element is added in the UI code so we can h\can observe the panel is Design but the StackPanel is cleared of Children just before the loop.
<TextBlock x:Name="TxtProperties" Margin="20,0,0,0" Text="DeviceProperties:" /> <StackPanel x:Name="DynamicPanel" Orientation="Vertical" HorizontalAlignment="Left"> <TextBlock TextAlignment="Left" Text="DeleteMe" /> </StackPanel>
XAML Code
In Editor Design Mode
The outline of the code behind to populate the StackPanel follows:
DynamicPanel.Children.Clear(); if (ListProperties != null) { foreach (var prop in ListProperties) { TextBock txtValue = new TextBlock(); …. Set TextBlock properties including prop key and values ….. DynamicPanel.Children.Add(txtValue); } }
Initially a single TextBlock was used with its Text property set to a string created through a string.Format() statement.
val1 = string.Format("{0} {1} {2}", prop.Key, prop.Value.Item1, prop.Value.Item2);
Each dictionary value is a three item tuple. Item3 is a byte array. Item1 is the array cast to string. Item2 is a string formatted representation of the array bytes, each as a hex value:
The string.Format is improved by setting column widths, the purpose of which is to align the key, value1 and value2 in columns as would be displayed in teh ListView:
val1 = string.Format("{0:50} {1:50} {2:100}", prop.Key, prop.Value.Item1, prop.Value.Item2);
And finally, the TextBlock font was set to a Fixed Width Font, Courier New. The columns though did not align.
It was then decided that this could be solved by using three TextBlocks in a horizontal StackPanel, each TextBlock of fixed width.
Actually a UserControl consisting of the Grid and three TextBlocks was used. See The ThreeHoriziontalTextblocks UserControl at the end of the b;log.
The loop code behind then becomes:
if (ListProperties != null) { foreach (var prop in ListProperties) { ThreeHoriziontalTextblocks txtValue = new ThreeHoriziontalTextblocks(); txtValue.Width = 810; txtValue.Height = 20; txtValue.HorizontalAlignment = HorizontalAlignment.Left; txtValue.Visibility = Visibility.Visible; val1 = string.Format("{0}", prop.Key); val2 = prop.Value.Item1; val3 = prop.Value.Item2; txtValue.SetVal(val1, val2, val3); DynamicPanel.Children.Add(txtValue); } }
Item1 and Item2 are strings. the Key is an enum so the string.Format() converts it to a right cantered string.
Finally a header is added before the loop (after to Children.Clear() statement):
DynamicPanel.Children.Clear(); ThreeHoriziontalTextblocks txtHeader = new ThreeHoriziontalTextblocks(); txtHeader.Width = 810; txtHeader.Height = 20; txtHeader.HorizontalAlignment = HorizontalAlignment.Left; txtHeader.Visibility = Visibility.Visible; string val1 = "Property"; string val2 = "Value"; string val3 = "Bytes"; txtHeader.SetVal(val1, val2, val3); txtHeader.IsHeader = true; DynamicPanel.Children.Add(txtHeader); if (ListProperties != null) { foreach (var prop in ListProperties) { ….. ….. ….. } }
And the outcome when the app runs is:
Wala!
<UserControl x:Name="ThreeHoriziontalTextblocks1" x:Class="BLExplorer.ThreeHoriziontalTextblocks" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:BLExplorer" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="30" d:DesignWidth= "810"> <Grid> <Grid.RowDefinitions> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="5"/> <ColumnDefinition Width="150"/> <ColumnDefinition Width="5"/> <ColumnDefinition Width="450"/> </Grid.ColumnDefinitions> <TextBlock x:Name="TitleTB" FontStyle="Italic" FontWeight="Bold" Foreground="Blue" Grid.Row="0" Grid.Column="0" Text="" TextAlignment="Right"/> <TextBlock x:Name="ValueTB1" Grid.Row="0" Grid.Column="2" Text="" TextAlignment="Center"/> <TextBlock x:Name="ValueTB2" Grid.Row="0" Grid.Column="4" Text="" TextAlignment="Left"/> </Grid> </UserControl>
The User\Control XAML code
The code behind is, in the main, just a method to directly set the TextBlock Text properties:
public void SetVal(string v1, string v2, string v3) { TitleTB.Text = v1; ValueTB1.Text = "\"" + v2 + "\""; ValueTB2.Text = v3; }
A solution is now presented in the Addendum that zeroes the spacing between items in a ListView
Part 1: XAML Layout for a Dictionary-Part1
Part 2: XAML Layout for a Dictionary-Part2 (This)
Part 3: XAML Layout for a Dictionary-Addendum