Picker with custom template

mrw 201 Reputation points
2024-06-17T19:12:50.6466667+00:00

This is quite simple task but I am running short on ideas how to implement it. I have added FontAwesome to my app and would like to create a picker control that would display two different labels with different fonts side by side, but it looks like Picker control applies font on control level and it is not possible to display two different fonts for each item side by side. I need this so I could display icon from FontsAwesome and text with my default font. Are there any solutions to this?

Here is what I have tried so far:

 public class DualLabelPicker : Picker

 {

   public static readonly BindableProperty ItemsSourceProperty =

       BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(DualLabelPicker), null);



   public IEnumerable ItemsSource

   {

     get { return (IEnumerable)GetValue(ItemsSourceProperty); }

     set { SetValue(ItemsSourceProperty, value); }

   }



   public static readonly BindableProperty IconFontFamilyProperty =

       BindableProperty.Create(nameof(IconFontFamily), typeof(string), typeof(DualLabelPicker), default(string));



   public string IconFontFamily

   {

     get { return (string)GetValue(IconFontFamilyProperty); }

     set { SetValue(IconFontFamilyProperty, value); }

   }



   public static readonly BindableProperty TextFontFamilyProperty =

       BindableProperty.Create(nameof(TextFontFamily), typeof(string), typeof(DualLabelPicker), default(string));



   public string TextFontFamily

   {

     get { return (string)GetValue(TextFontFamilyProperty); }

     set { SetValue(TextFontFamilyProperty, value); }

   }



   protected override void OnPropertyChanged(string propertyName = null)

   {

     base.OnPropertyChanged(propertyName);



     if (propertyName == ItemsSourceProperty.PropertyName)

     {

       RefreshPickerItems();

     }

   }



   private void RefreshPickerItems()

   {

     Items.Clear();

     if (ItemsSource != null)

     {

       foreach (IconTextPickerItem item in ItemsSource)

       {

         FormattedString formattedString = new FormattedString();



         Span iconSpan = new Span { Text = item.Icon, FontFamily = IconFontFamily, FontAttributes = FontAttributes.Italic };

         formattedString.Spans.Add(iconSpan);



         Span textSpan = new Span { Text = " " + item.Text, FontFamily = TextFontFamily };

         formattedString.Spans.Add(textSpan);



         // Convert the FormattedString to a string and add it to the Items collection

         Items.Add(FormattedStringToString(formattedString));

       }

     }

   }



   private string FormattedStringToString(FormattedString formattedString)

   {

     StringBuilder stringBuilder = new StringBuilder();



     foreach (Span span in formattedString.Spans)

     {

       stringBuilder.Append(span.Text);

     }



     return stringBuilder.ToString();

   }

 }

Here is model

  public class IconTextPickerItem

  {

    public string Icon { get; set; }

    public string Text { get; set; }

  }

EDIT 1:

So I have created now this class to Platforms > Windows

using Microsoft.Maui.Handlers;

using Microsoft.UI.Xaml.Controls;

using Microsoft.UI.Xaml.Markup;

using UWPDataTemplate = Microsoft.UI.Xaml.DataTemplate;



namespace Application.Windows

{

  public partial class PickerRowExHandler : PickerHandler

  {

    protected override ComboBox CreatePlatformView()

    {

      return new PickerComboBox();

    }



    protected override void ConnectHandler(ComboBox platformView)

    {

      base.ConnectHandler(platformView);

    }



    protected override void DisconnectHandler(ComboBox platformView)

    {

      base.DisconnectHandler(platformView);

    }

  }



  public class PickerComboBox : ComboBox

  {

    public PickerComboBox()

    {

      // Define the XAML markup for the DataTemplate

      string xaml = @"

                <DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'

                              xmlns:local='using:CalendarApplication.Models'>

                    <StackPanel Orientation='Horizontal'>

                        <TextBlock Text='{Binding Icon}' FontFamily='FontAwesomeSolid' FontSize='20' VerticalAlignment='Center' Margin='5'/>

                        <TextBlock Text='{Binding Text}' VerticalAlignment='Center' Margin='5'/>

                    </StackPanel>

                </DataTemplate>";



      // Load the XAML markup as the ItemTemplate

      ItemTemplate = (UWPDataTemplate)XamlReader.Load(xaml);

    }

  }

}

Then I have added this to generic Models folder

  public class PickerItem

  {

    public string Text { get; set; }

    public string Icon { get; set; } // Path to the icon or a resource identifier

  }

Then this to ViewModel

public MainViewModel()

{

  Items = new ObservableCollection<PickerItem>

    {

        new PickerItem { Text = "User", Icon = "\uf007" },

        new PickerItem { Text = "Cog", Icon = "\uf013" },

        new PickerItem { Text = "Home", Icon = "\uf015" },

    };

}

public ObservableCollection<PickerItem> Items { get; set; }

Then this to xaml

    <controls:PickerRowEx x:Name="MyPicker" Padding="0,0,0,0" 

                          BackgroundColor="Transparent" BorderColor="DarkBlue" HorizontalOptions="CenterAndExpand" 

                          HorizontalTextAlignment="Center" MinimumWidthRequest="200"

                          SelectedIndexChanged="MyPicker_SelectedIndexChanged" TextColor="Blue"

                          ItemsSource="{Binding Items}">

      <controls:PickerRowEx.ItemTemplate>

        <DataTemplate x:DataType="models:PickerItem">

          <StackLayout Orientation="Horizontal">

            <Label Text="{Binding Icon}" FontFamily="FontAwesomeSolid" FontSize="20" VerticalOptions="Center" Margin="5"/>

            <Label Text="{Binding Text}" VerticalOptions="Center" Margin="5"/>

          </StackLayout>

        </DataTemplate>

      </controls:PickerRowEx.ItemTemplate>

    </controls:PickerRowEx>

Currently problem is that I see there is correct amount of items in my combobox in UI, but I do not see any text. So I assume the problem is with bindings and that below can not assign correct x:DataType="models:PickerItem", but I do not know how to set it properly. Tried several options without success

  public class PickerComboBox : ComboBox

  {

    public PickerComboBox()

    {

      // Define the XAML markup for the DataTemplate

      string xaml = @"

                <DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'

                              xmlns:local='using:CalendarApplication.Models'>

                    <StackPanel Orientation='Horizontal'>

                        <TextBlock Text='{Binding Icon}' FontFamily='FontAwesomeSolid' FontSize='20' VerticalAlignment='Center' Margin='5'/>

                        <TextBlock Text='{Binding Text}' VerticalAlignment='Center' Margin='5'/>

                    </StackPanel>

                </DataTemplate>";



      // Load the XAML markup as the ItemTemplate

      ItemTemplate = (UWPDataTemplate)XamlReader.Load(xaml);

    }

  }
.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
3,135 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,561 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Leon Lu (Shanghai Wicresoft Co,.Ltd.) 71,441 Reputation points Microsoft Vendor
    2024-06-18T06:31:13.5433333+00:00

    Hello,

    This is quite simple task but I am running short on ideas how to implement it. I have added FontAwesome to my app and would like to create a picker control that would display two different labels with different fonts side by side

    You can do this by Entry and a popup layout.

    No need to use handler and custom picker's popup window for every platforms.

    Here are steps.

    When the Entry get focus, it will popup a window like the Picker.

    <Entry Focused="Entry_Focused"/>
    

    I set entry.IsReadOnly = true;, it will not pop up keyboard for android/ios platforms. Then popup a window. And popup will return listview selected item.

    Here is Entry's background code.

    private async void Entry_Focused(object sender, FocusEventArgs e)
    {
         var popup = new PopUpPicker();
         if (e.IsFocused)
         {
             Entry entry = sender as Entry;
             entry.IsReadOnly = true;
             entry.Unfocus();
             var result = await this.ShowPopupAsync(popup, CancellationToken.None);
     
             if (result is PickerItem stringResult)
             {
     
                 entry.Text = stringResult.Text;
     
             }
             entry.IsReadOnly = false;
         }
    }
    

    3.Here is my popup layout, you can custom it by your need.

    <?xml version="1.0" encoding="utf-8" ?>
    <toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
                 x:Class="MauiApp9.PopUpPicker"
    >
    <VerticalStackLayout>
                 <ListView ItemsSource="{Binding Items}" SelectionMode="Single"          ItemSelected="ListView_ItemSelected">
                     <ListView.ItemTemplate>
                       <DataTemplate>
                        <ViewCell>
                          <HorizontalStackLayout>
                                <Label  Text="{Binding Icon}" FontFamily="FontAwesomeSolid" FontSize="20" VerticalTextAlignment="Center"  Margin="5"/>
     
                                <Label  Text="{Binding Text}" VerticalTextAlignment="Center" Margin="5"/>
                          </HorizontalStackLayout>
                        </ViewCell>
                       </DataTemplate>
                     </ListView.ItemTemplate>
                  </ListView>
           </VerticalStackLayout>
    </toolkit:Popup>
    

    4.After you selected an item in the listview, this popup window will be closed.

    public partial class PopUpPicker : Popup
    {
        public PopUpPicker()
        {
            InitializeComponent();
        }
        private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            CloseAsync(e.SelectedItem);   
        }
    }
    

    I can reproduce the issue, when I open the picker, I get following error. BindingContext cannot be found in the loaded xaml.

    Error: BindingExpression path error: 'Icon' property not found on 'Windows.Foundation.IReference`1<String>'. BindingExpression: Path='Icon' DataItem='Windows.Foundation.IReference`1<String>'; target element is 'Microsoft.UI.Xaml.Controls.TextBlock' (Name='null'); target property is 'Text' (type 'String')
    

    Best Regards,

    Leon Lu


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    **Note: Please follow the steps in our [documenta

    1 person found this answer helpful.