Skip to content

Commit b1d181c

Browse files
authored
ContextActions on all Views! (#8)
* Enhance context menu functionality across platforms Updated project files to support context menus in collection views for Android, iOS, and Windows. Introduced new handler classes and improved project structure by adding a "Controls" folder. Removed platform-specific comments in `PlatformClass1.cs` for a more generic implementation. * Add context actions to MainPage and restructure project Updated MainPage.xaml to include an Image with ContextActionBehavior, shifting focus from CollectionView. Commented out CollectionView usage in MainPage.xaml.cs and modified ClickCommand handling. Removed Controls folder reference in PJ.ContextActions.Maui.csproj. Introduced ContextActionBehavior class for managing context menu items, with platform-specific implementations for Windows. * Update context menu handling and configuration settings - Adjusted `.editorconfig` to change CA1001 severity to `none` and set CA1063 to `error`. - Modified `ContextActionBehavior.cs` to include a new `MenuItems` property and updated platform-specific directives. - Added global using directive for `PJ.ContextActions.Maui.Helpers` in `GlobalUsings.cs`. - Introduced `GetValueOrDefault` extension method in `Helpers.cs` and added `CreateMenuItems` method for iOS. - Updated `PJ.ContextActions.Maui.sln` to include additional solution items. - Created `ContextActionBehavior.ios.cs` for iOS-specific context menu behavior. - Added `InteractionDelegate.ios.cs` to manage context menu configurations. * Fix syntax in WMenyFlyoutItem initialization Added a comma after the CommandParameter assignment to ensure proper syntax and maintain consistency in the code structure. * Refactor uiInteraction type in ContextActionBehavior Changed the type of the `uiInteraction` field from `IUIInteraction` to `UIContextMenuInteraction` in the `ContextActionBehavior` class to better support context menus on iOS. * Add Android-specific ContextActionBehavior implementation Introduces a new partial class `ContextActionBehavior` for Android, extending `PlatformBehavior<View, AView>`. This implementation adds context menu management capabilities, including a `ContextMenuListenerFactory` for custom listeners. The `OnAttachedTo` and `OnDetachedFrom` methods are overridden to manage the context menu listener lifecycle. A new sealed class `AViewContextMenuListener` is created to handle context menu item setup and click events. The original `ContextActionBehavior` class is updated to remove Android-specific code. * Add OnDetachedFrom method to ContextActionBehavior Implement OnDetachedFrom to clear ContextFlyout when detached from a View, ensuring no lingering context menus remain. * Add check for empty collection * code style
1 parent cdd505e commit b1d181c

20 files changed

+258
-96
lines changed

.editorconfig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dotnet_style_readonly_field = true:suggestion
3838
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
3939
dotnet_style_predefined_type_for_member_access = true:silent
4040
dotnet_style_require_accessibility_modifiers = omit_if_default:error
41+
dotnet_diagnostic.CA1001.severity = none
4142

4243
# Code files
4344
[*.sln]
@@ -83,7 +84,7 @@ dotnet_style_require_accessibility_modifiers = omit_if_default:error
8384
dotnet_diagnostic.CA1063.severity = error
8485

8586
# CA1001: Type owns disposable field(s) but is not disposable
86-
dotnet_diagnostic.CA1001.severity = error
87+
dotnet_diagnostic.CA1001.severity = none
8788

8889
# Add braces (IDE0011)
8990
csharp_prefer_braces = false:warning
Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<ContentPage
3-
x:Class="PJ.ContextActions.Sample.MainPage"
4-
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5-
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6-
xmlns:local="clr-namespace:PJ.ContextActions.Sample"
7-
xmlns:pj="clr-namespace:PJ.ContextActions.Maui;assembly=PJ.ContextActions.Maui">
3+
x:Class="PJ.ContextActions.Sample.MainPage"
4+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6+
xmlns:local="clr-namespace:PJ.ContextActions.Sample"
7+
xmlns:pj="clr-namespace:PJ.ContextActions.Maui;assembly=PJ.ContextActions.Maui">
88

9+
<Image Source="dotnet_bot.png">
10+
<Image.Behaviors>
11+
<pj:ContextActionBehavior>
12+
<pj:ContextActionBehavior.MenuItems>
13+
<pj:MenuItem
14+
Clicked="MenuItem_Clicked"
15+
Icon="dotnet_bot.png"
16+
Text="Primeiro" />
17+
<pj:MenuItem Command="{Binding ClickCommand}" Text="Segundo" />
18+
</pj:ContextActionBehavior.MenuItems>
19+
</pj:ContextActionBehavior>
20+
</Image.Behaviors>
21+
</Image>
922

10-
<CollectionView x:Name="cv">
23+
24+
<!--<CollectionView x:Name="cv">
1125
<pj:ContextActions.ContextActions>
1226
<pj:MenuItem
1327
Clicked="MenuItem_Clicked"
@@ -21,5 +35,5 @@
2135
<Label Text="{Binding .}" />
2236
</DataTemplate>
2337
</CollectionView.ItemTemplate>
24-
</CollectionView>
38+
</CollectionView>-->
2539
</ContentPage>

samples/PJ.ContextActions.Sample/MainPage.xaml.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ public MainPage()
1414
for (var i = 0; i < 100; i++)
1515
list.Add($"Item {i}");
1616

17-
cv.ItemsSource = list;
17+
//cv.ItemsSource = list;
1818

1919
ClickCommand = new Command<object>((i) =>
2020
{
2121
Debug.Assert(i is not null);
2222

23-
Debug.Assert(i is string);
23+
//Debug.Assert(i is string);
2424

2525
System.Diagnostics.Debug.WriteLine($"Segundo item clicado: {i}");
2626
});

src/PJ.ContextActions.Maui/PJCollectionViewHandler.android.cs renamed to src/PJ.ContextActions.Maui/CollectionView/PJCollectionViewHandler.android.cs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -112,33 +112,3 @@ public void OnCreateContextMenu(IContextMenu? menu, Android.Views.View? v, ICont
112112
}
113113
}
114114
}
115-
116-
sealed class MenuItemClickListener : Java.Lang.Object, IMenuItemOnMenuItemClickListener
117-
{
118-
readonly CommandBag bag;
119-
120-
public MenuItemClickListener(CommandBag bag)
121-
{
122-
this.bag = bag;
123-
}
124-
125-
public bool OnMenuItemClick(IMenuItem item)
126-
{
127-
if (item is null)
128-
{
129-
return false;
130-
}
131-
132-
var menuItem = bag.item;
133-
var element = bag.cvItem;
134-
135-
menuItem.FireClicked(element);
136-
137-
if (menuItem.Command?.CanExecute(element) is true)
138-
{
139-
menuItem.Command.Execute(element);
140-
}
141-
142-
return true;
143-
}
144-
}

src/PJ.ContextActions.Maui/PJCollectionViewHandler.ios.cs renamed to src/PJ.ContextActions.Maui/CollectionView/PJCollectionViewHandler.ios.cs

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,34 +60,5 @@ public PJDelegator(ItemsViewLayout itemsViewLayout, ReorderableItemsViewControll
6060
var createMenu = UIMenu.Create([.. CreateMenuItems(items, cv, element)]);
6161

6262
return UIContextMenuConfiguration.Create(null, null, _ => createMenu);
63-
64-
//TODO Implement UIImage
65-
static IEnumerable<UIMenuElement> CreateMenuItems(List<MenuItem> items, BindableObject cv, object element)
66-
{
67-
foreach (var (index, item) in items.Index())
68-
{
69-
item.BindingContext = cv.BindingContext;
70-
var action = UIAction.Create(
71-
item.Text,
72-
CreateImage(item.Icon),
73-
index.ToString(),
74-
_ =>
75-
{
76-
item.FireClicked(element);
77-
var command = item.Command;
78-
if (command?.CanExecute(element) is true)
79-
{
80-
command.Execute(element);
81-
}
82-
});
83-
84-
yield return action;
85-
}
86-
87-
static UIImage? CreateImage(string? icon)
88-
{
89-
return string.IsNullOrEmpty(icon) ? null : new UIImage(icon);
90-
}
91-
}
9263
}
9364
}

src/PJ.ContextActions.Maui/PJCollectionViewHandler.windows.cs renamed to src/PJ.ContextActions.Maui/CollectionView/PJCollectionViewHandler.windows.cs

File renamed without changes.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Android.Views;
2+
using AView = Android.Views.View;
3+
using View = Microsoft.Maui.Controls.View;
4+
5+
namespace PJ.ContextActions.Maui;
6+
partial class ContextActionBehavior : PlatformBehavior<View, AView>
7+
{
8+
public Func<AView.IOnCreateContextMenuListener>? ContextMenuListenerFactory { get; set; }
9+
10+
protected override void OnAttachedTo(View bindable, AView platformView)
11+
{
12+
if (MenuItems.Count is 0)
13+
{
14+
return;
15+
}
16+
17+
var contextMenuListener = ContextMenuListenerFactory?.Invoke() ?? new AViewContextMenuListener([.. MenuItems], bindable);
18+
platformView.SetOnCreateContextMenuListener(contextMenuListener);
19+
}
20+
21+
protected override void OnDetachedFrom(View bindable, AView platformView)
22+
{
23+
platformView.SetOnCreateContextMenuListener(null);
24+
}
25+
}
26+
27+
sealed class AViewContextMenuListener : Java.Lang.Object, AView.IOnCreateContextMenuListener
28+
{
29+
readonly MenuItem[] menuItems;
30+
readonly View view;
31+
32+
public AViewContextMenuListener(MenuItem[] menuItems, View view)
33+
{
34+
this.menuItems = menuItems;
35+
this.view = view;
36+
}
37+
38+
public void OnCreateContextMenu(Android.Views.IContextMenu? menu, AView? v, Android.Views.IContextMenuContextMenuInfo? menuInfo)
39+
{
40+
if (menu is null || v is null)
41+
{
42+
return;
43+
}
44+
45+
foreach (var (index, item) in menuItems.Index())
46+
{
47+
item.BindingContext = view.BindingContext;
48+
var mItem = menu.Add(0, index + 1, index, item.Text);
49+
Assert(mItem is not null);
50+
mItem.SetOnMenuItemClickListener(new MenuItemClickListener(new(view, item)));
51+
}
52+
}
53+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace PJ.ContextActions.Maui;
2+
3+
public partial class ContextActionBehavior
4+
{
5+
public ICollection<MenuItem> MenuItems { get; set; } = [];
6+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using UIKit;
2+
3+
namespace PJ.ContextActions.Maui;
4+
partial class ContextActionBehavior : PlatformBehavior<View, UIView>
5+
{
6+
public Func<UIContextMenuInteractionDelegate>? InteractionDelegateFactory { get; set; }
7+
UIContextMenuInteraction uiInteraction = default!;
8+
protected override void OnAttachedTo(View bindable, UIView platformView)
9+
{
10+
if (MenuItems.Count is 0)
11+
{
12+
return;
13+
}
14+
15+
var menuToCreate = CreateMenuItems(MenuItems, bindable, bindable);
16+
17+
var @delegate = InteractionDelegateFactory?.Invoke() ?? new InteractionDelegate([.. menuToCreate]);
18+
19+
uiInteraction = new UIContextMenuInteraction(@delegate);
20+
21+
platformView.AddInteraction(uiInteraction);
22+
}
23+
24+
protected override void OnDetachedFrom(View bindable, UIView platformView)
25+
{
26+
platformView.RemoveInteraction(uiInteraction);
27+
uiInteraction.Dispose();
28+
}
29+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Microsoft.UI.Xaml;
2+
using WMenuFlyout = Microsoft.UI.Xaml.Controls.MenuFlyout;
3+
using WMenyFlyoutItem = Microsoft.UI.Xaml.Controls.MenuFlyoutItem;
4+
5+
namespace PJ.ContextActions.Maui;
6+
partial class ContextActionBehavior : PlatformBehavior<View, FrameworkElement>
7+
{
8+
protected override void OnAttachedTo(View bindable, FrameworkElement platformView)
9+
{
10+
if (MenuItems.Count is 0)
11+
{
12+
return;
13+
}
14+
15+
platformView.ContextFlyout = CreateMenu(bindable);
16+
}
17+
18+
protected override void OnDetachedFrom(View bindable, FrameworkElement platformView)
19+
{
20+
platformView.ContextFlyout = null;
21+
}
22+
23+
WMenuFlyout CreateMenu(View view)
24+
{
25+
var contextMenu = new WMenuFlyout();
26+
var items = contextMenu.Items;
27+
28+
var mauiCommand = new Command<CommandBag>(static bag =>
29+
{
30+
bag.item.FireClicked(bag.cvItem);
31+
var command = bag.item.Command;
32+
33+
if (command?.CanExecute(bag.cvItem) is true)
34+
{
35+
command.Execute(bag.cvItem);
36+
}
37+
});
38+
39+
foreach (var item in MenuItems)
40+
{
41+
item.BindingContext = view.BindingContext;
42+
items.Add(new WMenyFlyoutItem
43+
{
44+
Text = item.Text,
45+
Command = mauiCommand,
46+
CommandParameter = new CommandBag(view, item),
47+
Icon = item.Icon?.CreateIconElementFromIconPath()
48+
});
49+
}
50+
51+
return contextMenu;
52+
}
53+
}

0 commit comments

Comments
 (0)