In the first parts of this ongoing series on exploring .NET MAUI (https://codemag.com/Article/2408041/Exploring-.NET-MAUI-Getting-Started and https://codemag.com/Article/2409041/Exploring-.NET-MAUI-Styles-Navigation-and-Reusable-UI), you created your first .NET MAUI application and ran that application on both a Windows computer and an Android emulator. You created data-entry pages, partial pages to reuse on those pages, and you navigated among those pages. In this article, you'll continue to use more data entry controls and learn to perform data binding between controls. You're going to create a class with properties that you can bind to controls on a page as well. When you change the values of properties in a class, you need to raise a PropertyChanged event so the UI can update those controls that are bound to the properties. You're going to create a base class that helps you raise that event any time the property values change.
Use a Switch Control for Yes/No Input
Simple entry controls allow a user to enter any data they want. For some input you need a simple yes or no answer from the user. For example, if you have a Boolean property such as IsEmployed on a business object, use a Switch control to represent the two states for this property. On a Windows computer, the Switch control appears as a toggle coupled with a label next to it with the words On and Off, as shown in Figure 1.
Open the Views\UserDetailView.xaml file and add an Auto to the RowDefinitions attribute of the Grid control. Locate the last
Run the application and click on Users > Navigate to Detail to see the switch control. Click on the Switch control a couple of times to see the labels change. The labels On and Off may not make sense for what you're trying to represent with the Switch control. In the example above, it makes more sense for the labels to be Yes and No. You may also not want the labels to appear at all when running on Windows. There's no exposed property to set these two labels, but you may write a little C# code to set these labels. In the first part of this article series (https://codemag.com/Article/2408041/Exploring-.NET-MAUI-Getting-Started), you added a SetWindowsOptions() method to the MauiProgram.cs file. Around this method, you added a conditional compile statement #if WINDOWS. Instead of using these conditional compile statements around each method that you want to run on only a specific platform, let's create a class in the Platforms\Windows folder instead. When a class is created within any of the sub-folders under the Platforms folder, that class is only compiled for running on that specific OS, thus you don't need to use conditional compilation statements. Right mouse-click on the Platforms\Windows folder and add a new class named WindowsHelpers. Into this class, you're going to place the SetWindowOptions() method you created in the first article, and a new method named SetSwitchText(). This new method allows you to set the OnContent and OffContent properties after a Switch control has been rendered on a .NET MAUI page. Replace the entire contents of the WindowsHelpers.cs file with the code shown in Listing 1. Open the MauiProgram.cs file and in the CreateMauiApp() method where you called the SetWindowOptions() method, modify it to use **WindowsHelpers.**SetWindowOptions(builder) instead. Also, call the SetSwitchText() method on the same class as shown below: Remove the SetWindowOptions() method in the MauiProgram.cs file and the using statements wrapped in the #WINDOWS conditional compile statement at the top of the file. Run the application and click on Users > Navigate to Detail to see the Switch control with the labels Yes and No, as shown in Figure 2. If you don't pass any parameters to the SetSwitchText(), no labels are displayed next to the Switch at all. If you have a small set of items that the user can select a single value from, the RadioButton control is one of a few controls you can use. The RadioButton controls (Figure 3) are a mutually exclusive set of inputs. When the user clicks on one RadioButton, the previously selected button is unchecked and the one newly clicked becomes checked. You may set the initial button to be checked by setting the IsChecked property to a true value. Each set of RadioButton controls should have the same GroupName property set to a unique value. For example, GroupName=“EmployeeType” is used to group those RadioButton controls shown in Figure 3. If you had a set of RadioButton controls to group a user's ethnicity, you set the GroupName property of those controls to GroupName=“Ethnicity”. Open the Views\UserDetailView.xaml file and add an “Auto” to the RowDefinitions attribute of the Grid control. Locate the last
Run the application and click on Users > Navigate to Detail to see the radio buttons, as shown in Figure 3. Click back and forth between the two RadioButton controls to see the other one become unchecked. It's always better to let users select from a list rather than typing in something by themselves. The DatePicker control displays a calendar from which the user may select a date. The Date property gets the date selected by the user or sets the date to start with when the calendar is first displayed. By default, the Date property is set to today's date. The Format property is a string value set to a standard or custom .NET format string you pass to the ToString() method, such as dateValue.ToString(“D”). The default format string is set to the long date pattern “D”. There are two properties, MinimumDate and MaximumDate, that allow you to set the range of dates the user is allowed to select from. Let's add a Birth Date field to the user detail page and use the DatePicker control to allow the user to select the date from the calendar. Open the Views\UserDetailView.xaml file and add an “Auto” to the RowDefinitions attribute of the Grid control. Locate the last
Run the application and click on Users > Navigate to Detail to click on the Birth Date entry field. A calendar control is displayed, as shown in Figure 4. The TimePicker control displays a list to the user from which they may select an hour and minute (see Figure 5). The Time property gets the time selected by the user or sets the time to start with when the list of hours and minutes is first displayed. The Time property is a TimeSpan data type. By default, the Time property is set to zero (0) or midnight (12:00:00 AM). The Format property is a string value set to a standard or custom .NET format string you pass to the ToString() method, such as timeValue.ToString(“t”). The default format string is set to the short time pattern “t”. Be aware that on a macOS, the Format property has no effect on the control. Let's add a Start Time field to the user detail page and use the TimePicker control to allow the user to select the time from an hour and minute list, as shown in Figure 5. Open the Views\UserDetailView.xaml file and add an “Auto” to the RowDefinitions attribute of the Grid control. Locate the last
Run the application and click on Users > Navigate to Detail. Then click on the start time entry field to have an hour and minute list from which you can select, as shown in Figure 5. If you have a small set of values the user can select from but not a lot of real estate on your page for radio button controls, a Picker control can be an appropriate choice. The Picker control has an ItemsSource property that can be set to any IEnumerable collection. The SelectedIndex property is an integer value representing the index number within the collection that's selected. The SelectedItem property represents the actual item selected. Most often the ItemsSource property is set at runtime via data binding, but for this sample, you're going to use a hard-coded array of strings. Let's add a Label and Entry field to enter a Phone number. Next to the Phone entry field, display a list of phone types the user can select from, as shown in ** REF _Ref164167504 \h * MERGEFORMAT Figure 6**. Open the Views\UserDetailView.xaml file and add an “Auto” to the RowDefinitions attribute of the Grid control. Locate the last
Within the
Run the application and click on Users > Navigate to Detail and click on the down arrow on the right of the Picker to see the list of phone types appear. Select one of the options to watch the list close and the selected item appear next to the phone number entry. The Save and Cancel buttons you added to the page use text to display what each button does. If you wish to use an image instead of text, change the Button control to an ImageButton control. Use the Source property to reference an image contained in the Resources\Images folder. When I'm working with just an image, I like to add a Tooltip for when a user hovers over the button on a Windows computer or when they do a long press on a mobile device. This is accomplished by setting the ToolTipProperties.Text property to the appropriate text. Modify the Save and Cancel buttons in the last
The ImageButton control doesn't have any default styling, so open the CommonStyles.xaml file in the Resources\Styles folder in the Common.Library.MAUI project and add a new global style as shown in the following XAML: When dealing with images in a .NET MAUI application, make sure the name of the image is all lowercase, as mixed case image names don't work on all mobile devices. Underlines are acceptable but try not to use any other special characters as they may also not work on all operating systems. Run the application and click on Users > Navigate to Detail to see the new ImageButton controls with the save and cancel images displayed, as shown in Figure 7. What if you wish to have both an image and text (Figure 8) within a button? The Button control supports an ImageSource property that allows you to set the name of an image located within the Resources/Images folder. There's also a ContentLayout property to help you position where within the button you want the image placed. The valid values for this property are Left, Top, Right, Bottom. If you wish to add some spacing between the image and the text, place a comma and then the number of device-independent pixels you want. For example, set ContentLayout=“Left,40” to position the image on the left, with 40 pixels between the image and the text. Run the application and click on Users > Navigate to Detail to see the buttons with both an image and text, as shown in **Figure **8. To display a picture on a page where there's typically no interaction with that picture, use the Image control. The Image control has a Source property to which you set the name of a file located in the Resources\Images folder. The Source property may also access a URI or a stream to display the picture. As previously mentioned, make sure your picture file names are all lowercase and don't contain any special characters other than maybe an underscore. The Aspect property controls how the picture is scaled when displaying within the container. Let's add a Label control to display “Product Picture”. Then use the Image control to display a picture, as shown in Figure 9. Open the Views\ProductDetailView.xaml file and add an “Auto” to the RowDefinitions attribute of the Grid control. Locate the last
Run the application and click on Products to see the new Image control, as shown in Figure 9. To allow the user to enter a large amount of text, use the Editor control. Use the AutoSize property to tell the editor to automatically change the size of the control to accommodate the user input. By default, this property is set to false. Useful properties for this control are IsSpellCheckEnabled, IsTextPredictionEnabled, MaxLength, and Placeholder. Let's add a Product Notes entry area to the product detail page and use the Editor control to allow a user to add multiple lines of text. Open the Views\ProductDetailView.xaml file and add an “Auto” to the RowDefinitions attribute of the Grid control. Locate the last
Run the application and click on **Products **to see the new Editor control, as shown in Figure 10. A Slider control displays a long horizontal rule along which you can drag a “thumb” to change the value (see Figure 11). Using the Minimum and Maximum property, you can control the range that's put into the Value property as the user drags the thumb. The Value property is a data type of double. If you wish to have the value change in whole number increments, you need to write a single line of C# code. Let's replace the Weight Entry control on the product detail page to use a Slider control instead. Open the Views\ProductDetailView.xaml file and locate the Label and Entry control for Weight. Leave the Label control but replace the Entry control with the following XAML: Use data binding to see the value from the Slider appear in the Label as the thumb is dragged back and forth. In this code, you set the Minimum property to a value of one (1) and Maximum property to a value of 100. Set the initial value in the Value property to a one (1). Always set a starting value to avoid having to handle nulls in the code behind. In the Label control, set the BindingContext property to reference the “weight” Slider control and bind the Text property of the Label to the Value property in the Slider. As the thumb is dragged back and forth on the slider, the label displays the current number from the Value property through the magic of data binding. After adding the XAML, position your cursor on the “Weight_Changed” in the ValueChanged attribute and press F12. This creates the event procedure to be called each time the user slides the thumb on the slider. Write the following code in this event procedure: The argument, e, received in this event procedure, contains both the OldValue and NewValue properties each time it's called. As mentioned, the Value property is of type double, so if you wish to use integer increments, simply apply the Math.Round() method to the e.NewValue property, passing zero (0) as the second parameter for the number of digits to use when rounding. Run the application and click on Products to see the new Slider control, as shown in Figure 11. Move the thumb back and forth to watch the new value appear in the label immediately below the slider. The Stepper control allows you to increment or decrement a number, as shown in Figure 12. By binding a Stepper to an Entry control, you allow a user to either type in a number or click on the plus or minus sign on the Stepper. The Value property on a Stepper either gets or sets the current number. The Minimum and Maximum properties allow you to specify the range of values the user can move among. The Increment property lets you set how much to increase or decrease the Value property. The Value property is a double data type, so the Increment property can be set to almost any value you wish. Let's bind two Stepper controls to both the Cost and Price Entry controls on the product detail page. Open the Views\ProductDetailView.xaml file and locate the <Label Grid.Row="6" Text="Still Employed?" />
<Switch Grid.Row="6" Grid.Column="1" />
Try It Out
Override the Switch Labels
Listing 1: Create a WindowsHelpers class to change Windows-only settings
using Microsoft.Maui.Handlers;
using Microsoft.Maui.LifecycleEvents;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using WinRT.Interop;
namespace AdventureWorks.MAUI;
public class WindowsHelpers
{
#region SetWindowOptions Method
public static void SetWindowOptions(MauiAppBuilder builder)
{
builder.ConfigureLifecycleEvents(events =>
{
events.AddWindows(wndBuilder =>
{
wndBuilder.OnWindowCreated(window =>
{
IntPtr hndl = WindowNative.GetWindowHandle(window);
WindowId winId = Win32Interop.GetWindowIdFromWindow(hndl);
AppWindow appWnd = AppWindow.GetFromWindowId(winId);
if (appWnd.Presenter is OverlappedPresenter p)
{
p.Maximize();
//p.HasBorder = false;
//p.HasTitleBar = false;
//p.IsAlwaysOnTop = false;
//p.IsMaximizable = false;
//p.IsMinimizable = false;
//p.IsModal = false;
//p.IsResizable = false;
}
});
});
});
}
#endregion
#region SetSwitchText Method
public static void SetSwitchText(string onContent = "", string offContent = "")
{
SwitchHandler.Mapper.AppendToMapping("SwitchText", (h, v) =>
{
// Get rid of On/Off label beside switch
h.PlatformView.OnContent = onContent;
h.PlatformView.OffContent = offContent;
h.PlatformView.MinWidth = 0;
});
}
#endregion
}
#if WINDOWS
WindowsHelpers.SetWindowOptions(builder);
// Set labels on all <Switch> elements
WindowsHelpers.SetSwitchText("Yes", "No");
//WindowsHelpers.SetSwitchText();
#endif
Try It Out
RadioButton Controls Help Users Select a Single Item from a List
<Label Text="Employee Type" Grid.Row="7" />
<FlexLayout Grid.Row="7" Grid.Column="1" Wrap="Wrap" Direction="Row">
<HorizontalStackLayout>
<Label Text="Full-Time" />
<RadioButton IsChecked="True" GroupName="EmployeeType" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Part-Time" />
<RadioButton GroupName="EmployeeType" />
</HorizontalStackLayout>
</FlexLayout>
Try It Out
The DatePicker Control Avoids Typing in Date Values
<Label Text="Birth Date" Grid.Row="8" />
<DatePicker Grid.Row="8" Grid.Column="1" />
Try It Out
Select an Hour and Minute Using the TimePicker Control
<Label Text="Start Time"
Grid.Row="9" />
<TimePicker Grid.Row="9"
Grid.Column="1"
Time="08:00:00" />
Try It Out
Choose an Item from a List with the Picker Control
Listing 2: Enter a phone number and select the type of phone from a drop-down list
<Label Text="Phone"
Grid.Row="10" />
<FlexLayout Grid.Row="10"
Grid.Column="1"
Wrap="Wrap"
Direction="Row">
<HorizontalStackLayout>
<Entry MinimumWidthRequest="150"
Text="" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Picker VerticalTextAlignment="Center">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Home</x:String>
<x:String>Mobile</x:String>
<x:String>Work</x:String>
<x:String>Other</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
</HorizontalStackLayout>
</FlexLayout>
Try It Out
Display Images on a Button
<HorizontalStackLayout Grid.Row="11"
Grid.Column="1">
<ImageButton Source="save.png"
ToolTipProperties.Text="Save Data"
Clicked="SaveButton_Clicked" />
<ImageButton Source="cancel.png"
ToolTipProperties.Text="Cancel Changes" />
</HorizontalStackLayout>
<Style TargetType="ImageButton">
<Setter Property="BackgroundColor" Value="Blue" />
</Style>
Try It Out
Image and Text in a Single Button
<HorizontalStackLayout Grid.Row="11" Grid.Column="1">
<Button Text="Save" ImageSource="save.png" ToolTipProperties.Text="Save Data" ContentLayout="Left" Clicked="SaveButton_Clicked" />
<Button Text="Cancel" ImageSource="cancel.png" ContentLayout="Left" ToolTipProperties.Text="Cancel Changes" />
</HorizontalStackLayout>
Try It Out
Image Control
<Label Text="Product Picture" Grid.Row="13" />
<Image Grid.Row="13" Grid.Column="1" HorizontalOptions="Start" Aspect="Center" Source="bikeframe.jpg" />
Try It Out
Editor Control
<Label Text="Product Notes" Grid.Row="14" />
<Editor Grid.Row="14" Grid.Column="1" HeightRequest="100" />
Try It Out
Binding a Slider to a Label Control
<VerticalStackLayout Grid.Row="7" Grid.Column="1">
<Slider x:Name="weight" Value="1" Minimum="1" Maximum="100" ValueChanged="Weight_Changed" />
<Label BindingContext="{x:Reference weight}" Text="{Binding Value}" />
</VerticalStackLayout>
private void Weight_Changed(object sender, ValueChangedEventArgs e)
{
weight.Value = Math.Round(e.NewValue, 0);
}
Try It Out
Bind a Stepper to an Entry Control