Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions 10.0/UserInterface/PickerDemo/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PickerDemo"
x:Class="PickerDemo.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml"/>
<ResourceDictionary Source="Resources/Styles/Styles.xaml"/>
<ResourceDictionary Source="Resources/Styles/AppStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
14 changes: 14 additions & 0 deletions 10.0/UserInterface/PickerDemo/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace PickerDemo;

public partial class App : Application
{
public App()
{
InitializeComponent();
}

protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
14 changes: 14 additions & 0 deletions 10.0/UserInterface/PickerDemo/AppShell.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="PickerDemo.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PickerDemo"
Title="PickerDemo">

<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage"/>

</Shell>
9 changes: 9 additions & 0 deletions 10.0/UserInterface/PickerDemo/AppShell.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace PickerDemo;

public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
33 changes: 33 additions & 0 deletions 10.0/UserInterface/PickerDemo/Control/CustomPicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Maui.Platform;

namespace PickerDemo.Control;

public class CustomPicker : Picker, ICustomPicker
{
public static readonly BindableProperty DialogBackgroundColorProperty =
BindableProperty.Create(nameof(DialogBackgroundColor), typeof(Color), typeof(CustomPicker), null);

public static readonly BindableProperty DialogTextColorProperty =
BindableProperty.Create(nameof(DialogTextColor), typeof(Color), typeof(CustomPicker), null);

public static readonly BindableProperty SelectedTextColorProperty =
BindableProperty.Create(nameof(SelectedTextColor), typeof(Color), typeof(CustomPicker), null);

public Color SelectedTextColor
{
get => (Color)GetValue(SelectedTextColorProperty);
set => SetValue(SelectedTextColorProperty, value);
}

public Color DialogBackgroundColor
{
get => (Color)GetValue(DialogBackgroundColorProperty);
set => SetValue(DialogBackgroundColorProperty, value);
}

public Color DialogTextColor
{
get => (Color)GetValue(DialogTextColorProperty);
set => SetValue(DialogTextColorProperty, value);
}
}
8 changes: 8 additions & 0 deletions 10.0/UserInterface/PickerDemo/Control/ICustomPicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace PickerDemo.Control;

public interface ICustomPicker
{
Color DialogBackgroundColor { get; }
Color DialogTextColor { get; }
Color SelectedTextColor { get; }
}
3 changes: 3 additions & 0 deletions 10.0/UserInterface/PickerDemo/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
global using System.Collections.ObjectModel;
global using System.ComponentModel;
global using Microsoft.Extensions.Logging;
130 changes: 130 additions & 0 deletions 10.0/UserInterface/PickerDemo/Handlers/CustomPickerHandler.Android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Reflection;
using Microsoft.Maui.Handlers;
using Android.Widget;
using AppCompatAlertDialog = AndroidX.AppCompat.App.AlertDialog;
using MauiPicker = Microsoft.Maui.Platform.MauiPicker;
using PickerDemo.Control;
using Microsoft.Maui.Platform;
using AndroidView = Android.Views.View;
using AndroidViewGroup = Android.Views.ViewGroup;

namespace PickerDemo.Handlers;

public partial class CustomPickerHandler : PickerHandler
{
// TODO: To avoid using reflection code, the _dialog variable should be exposed as protected in the PickerHandler.Android.
private FieldInfo? _dialogFieldInfo;

protected override void ConnectHandler(MauiPicker platformView)
{
base.ConnectHandler(platformView);

// Cache the FieldInfo (only done once)
_dialogFieldInfo ??= GetDialogField();

platformView.Click += OnCustomizeDialog;
}

// TODO: To avoid using reflection code, the _dialog variable should be exposed as protected in the PickerHandler.Android.
static FieldInfo? GetDialogField()
{
return typeof(PickerHandler).GetField("_dialog",
BindingFlags.NonPublic |
BindingFlags.Instance);
}

protected override void DisconnectHandler(MauiPicker platformView)
{
platformView.Click -= OnCustomizeDialog;
base.DisconnectHandler(platformView);
}

void OnCustomizeDialog(object? sender, EventArgs e)
{
if (VirtualView is not CustomPicker customPicker)
{
return;
}

// Get dialog via reflection (unavoidable without framework changes)
var dialog = _dialogFieldInfo?.GetValue(this) as AppCompatAlertDialog;

// Apply background color to the dialog window
if (dialog?.Window is not null && customPicker.DialogBackgroundColor is not null)
{
dialog.Window.SetBackgroundDrawable(new Android.Graphics.Drawables.ColorDrawable(customPicker.DialogBackgroundColor.ToPlatform()));
}

var dialogListView = dialog?.ListView;
if (dialogListView?.Adapter is not null)
{
// Wrap the original adapter with custom adapter
var customAdapter = new ColoredPickerAdapter(
dialogListView.Adapter,
customPicker.DialogTextColor,
customPicker.SelectedTextColor,
VirtualView?.SelectedIndex ?? -1);

// Set the custom adapter - this ensures GetView() is called for each item
dialogListView.Adapter = customAdapter;
if (VirtualView?.SelectedIndex >= 0)
{
dialogListView.SetItemChecked(VirtualView.SelectedIndex, true);
}
}
}
}

/// <summary>
/// Custom adapter that wraps the original picker adapter and applies custom text colors.
/// This ensures colors are applied during view creation.
/// </summary>
internal class ColoredPickerAdapter : BaseAdapter
{
private readonly IListAdapter _baseAdapter;
private readonly Color? _textColor;
private readonly Color? _selectedTextColor;
private readonly int _selectedPosition;

public ColoredPickerAdapter(
IListAdapter baseAdapter,
Color? textColor,
Color? selectedTextColor,
int selectedPosition)
{
_baseAdapter = baseAdapter ?? throw new ArgumentNullException(nameof(baseAdapter));
_textColor = textColor;
_selectedTextColor = selectedTextColor;
_selectedPosition = selectedPosition;
}

// Return the total number of items from the original adapter
public override int Count => _baseAdapter.Count;

// Return the data item at the specified position
public override Java.Lang.Object? GetItem(int position) => _baseAdapter.GetItem(position);

// Return the unique identifier for the item at the specified position
public override long GetItemId(int position) => _baseAdapter.GetItemId(position);

public override AndroidView? GetView(int position, AndroidView? convertView, AndroidViewGroup? parent)
{
// Get the view from the original adapter.
var view = _baseAdapter.GetView(position, convertView, parent);

// Customize the view if it's a CheckedTextView
if (view is CheckedTextView textView)
{
var isSelected = position == _selectedPosition;

// Apply the appropriate text color based on selection state
var color = isSelected ? _selectedTextColor : _textColor;
if (color is not null)
{
textView.SetTextColor(color.ToPlatform());
}
}

return view;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Microsoft.Maui.Handlers;
using Microsoft.UI.Xaml.Controls;
using WinColor = Windows.UI.Color;
using PickerDemo.Control;
using MauiColor = Microsoft.Maui.Graphics.Color;
using SolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush;

namespace PickerDemo.Handlers;

public partial class CustomPickerHandler : PickerHandler
{
protected override ComboBox CreatePlatformView()
{
var comboBox = base.CreatePlatformView();
if (VirtualView is CustomPicker customPicker)
{
UpdateComboBoxStyle(comboBox, customPicker);
}
return comboBox;
}

public static void MapDialogBackgroundColor(CustomPickerHandler handler, CustomPicker picker)
{
if (handler.PlatformView is ComboBox comboBox)
{
UpdateComboBoxStyle(comboBox, picker);
}
}

public static void MapDialogTextColor(CustomPickerHandler handler, CustomPicker picker)
{
if (handler.PlatformView is ComboBox comboBox)
{
UpdateComboBoxStyle(comboBox, picker);
}
}

public static void MapSelectedTextColor(CustomPickerHandler handler, CustomPicker picker)
{
if (handler.PlatformView is ComboBox comboBox)
{
UpdateComboBoxStyle(comboBox, picker);
}
}

static void UpdateComboBoxStyle(ComboBox comboBox, CustomPicker picker)
{
// Apply same colors to dropdown items
var itemStyle = new Microsoft.UI.Xaml.Style(typeof(ComboBoxItem));

// Apply dialog background color if provided
if (picker.DialogBackgroundColor is not null)
{
var dialogBackgroundColor = ConvertToWinColor(picker.DialogBackgroundColor);
itemStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(ComboBoxItem.BackgroundProperty, new SolidColorBrush(dialogBackgroundColor)));
}

// Apply dialog text color if provided
if (picker.DialogTextColor is not null)
{
var textColor = ConvertToWinColor(picker.DialogTextColor);
itemStyle.Setters.Add(new Microsoft.UI.Xaml.Setter(ComboBoxItem.ForegroundProperty, new SolidColorBrush(textColor)));
}

// Apply selected text color if provided
if (picker.SelectedTextColor is not null)
{
var selectedTextColor = ConvertToWinColor(picker.SelectedTextColor);
var selectedForegroundBrush = new SolidColorBrush(selectedTextColor);
// Add custom resources that will be used for selected state
comboBox.Resources["ComboBoxItemForegroundSelected"] = selectedForegroundBrush;
}

comboBox.Resources[typeof(ComboBoxItem)] = itemStyle;
}

static WinColor ConvertToWinColor(MauiColor color)
{
return WinColor.FromArgb(
(byte)(color.Alpha * 255),
(byte)(color.Red * 255),
(byte)(color.Green * 255),
(byte)(color.Blue * 255)
);
}
}
27 changes: 27 additions & 0 deletions 10.0/UserInterface/PickerDemo/Handlers/CustomPickerHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.Maui.Handlers;
using PickerDemo.Control;
using Microsoft.Maui;

namespace PickerDemo.Handlers;

public partial class CustomPickerHandler : PickerHandler, IElementHandler
{
public static IPropertyMapper<CustomPicker, CustomPickerHandler> CustomMapper { get; } =
new PropertyMapper<CustomPicker, CustomPickerHandler>(PickerHandler.Mapper)
{
#if IOS || WINDOWS
[nameof(CustomPicker.DialogBackgroundColor)] = MapDialogBackgroundColor,
[nameof(CustomPicker.DialogTextColor)] = MapDialogTextColor,
[nameof(CustomPicker.SelectedTextColor)] = MapSelectedTextColor
#endif
};

public CustomPickerHandler() : base(CustomMapper)
{
}

public CustomPickerHandler(IPropertyMapper? mapper = null)
: base(mapper ?? CustomMapper)
{
}
}
Loading
Loading