Skip to content

Commit 1897e10

Browse files
authored
Icon added to Context Menu Item (#321)
Added icons for context menu items
1 parent 1d08dc1 commit 1897e10

File tree

19 files changed

+696
-62
lines changed

19 files changed

+696
-62
lines changed

src/DIPS.Xamarin.UI.Android/ContextMenu/ContextMenuButtonRenderer.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using Android.Content;
5-
using Android.Support.V7.Widget;
5+
using Android.Support.V7.View.Menu;
66
using Android.Views;
7+
using AndroidX.AppCompat.Widget;
78
using DIPS.Xamarin.UI.Android.ContextMenu;
89
using DIPS.Xamarin.UI.Controls.ContextMenu;
10+
using Java.Lang.Reflect;
911
using Xamarin.Forms;
1012
using Xamarin.Forms.Internals;
1113
using Xamarin.Forms.Platform.Android;
@@ -49,12 +51,17 @@ protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
4951

5052
private void OpenContextMenu(object sender, EventArgs e)
5153
{
52-
var popupMenu = new PopupMenu(m_context, Control, (m_contextMenuButton.ContextMenuHorizontalOptions == ContextMenuHorizontalOptions.Right)
53-
? (int)GravityFlags.Right
54-
: (int)GravityFlags.Left);
55-
m_menuItems = ContextMenuHelper.CreateMenuItems(m_contextMenuButton.ItemsSource, m_contextMenuButton, popupMenu);
54+
var gravity = (m_contextMenuButton.ContextMenuHorizontalOptions == ContextMenuHorizontalOptions.Right)
55+
? (int)GravityFlags.Right
56+
: (int)GravityFlags.Left;
57+
var popupMenu = new PopupMenu(m_context, Control);
58+
m_menuItems = ContextMenuHelper.CreateMenuItems(m_context, m_contextMenuButton.ItemsSource,
59+
m_contextMenuButton, popupMenu);
5660
popupMenu.SetOnMenuItemClickListener(this);
57-
popupMenu.Show();
61+
var menuHelper = new MenuPopupHelper(Context,(MenuBuilder) popupMenu.Menu, Control);
62+
menuHelper.Gravity = gravity;
63+
menuHelper.SetForceShowIcon(m_menuItems.Keys.Any(contextMenuItem => !string.IsNullOrEmpty(contextMenuItem.Icon) || !string.IsNullOrEmpty(contextMenuItem.AndroidOptions.IconResourceName))); //Show icons if there is any context menu items with a icon added
64+
menuHelper.Show();
5865
m_contextMenuButton.SendContextMenuOpened();
5966
}
6067

@@ -65,24 +72,26 @@ public bool OnMenuItemClick(IMenuItem theTappedNativeItem)
6572
{
6673
if (theTappedNativeItem.IsCheckable) //check the item
6774
{
68-
if (contextMenuItem.Parent is ContextMenuGroup && theTappedNativeItem.IsChecked) //You are unchecking a grouped item, which means its single mode and it should not be able to uncheck
75+
if (contextMenuItem.Parent is ContextMenuGroup &&
76+
theTappedNativeItem
77+
.IsChecked) //You are unchecking a grouped item, which means its single mode and it should not be able to uncheck
6978
{
7079
return true;
7180
}
72-
81+
7382
m_menuItems.ForEach(pair =>
7483
{
7584
if (pair.Value.GroupId == theTappedNativeItem.GroupId) //Uncheck previous items
7685
{
77-
pair.Value.SetChecked(false);
86+
pair.Value.SetChecked(false);
7887
}
7988
});
80-
89+
8190
m_contextMenuButton.ResetIsCheckedForTheRest(contextMenuItem);
8291
contextMenuItem.IsChecked = !contextMenuItem.IsChecked;
8392
theTappedNativeItem.SetChecked(contextMenuItem.IsChecked);
8493
}
85-
94+
8695
contextMenuItem.SendClicked(m_contextMenuButton);
8796
return true;
8897
}

src/DIPS.Xamarin.UI.Android/ContextMenu/ContextMenuHelper.cs

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
using System.Collections.Generic;
2-
using System.ComponentModel;
32
using System.Linq;
3+
using Android.Content;
4+
using Android.Graphics.Drawables;
45
using Android.Support.V4.View;
5-
using Android.Support.V7.View.Menu;
6-
using Android.Support.V7.Widget;
76
using Android.Views;
7+
using AndroidX.AppCompat.Widget;
88
using DIPS.Xamarin.UI.Controls.ContextMenu;
9+
using Xamarin.Forms;
910
using Xamarin.Forms.Internals;
11+
using Xamarin.Forms.Shapes;
12+
using Menu = Android.Views.Menu;
1013

1114
namespace DIPS.Xamarin.UI.Android.ContextMenu
1215
{
1316
internal static class ContextMenuHelper
1417
{
15-
internal static Dictionary<ContextMenuItem, IMenuItem> CreateMenuItems(
18+
internal static Dictionary<ContextMenuItem, IMenuItem> CreateMenuItems(Context context,
1619
IEnumerable<ContextMenuItem> contextMenuItems,
1720
ContextMenuButton contextMenuButton, PopupMenu popupMenu, int groupIndex = 0)
1821
{
@@ -26,22 +29,23 @@ internal static Dictionary<ContextMenuItem, IMenuItem> CreateMenuItems(
2629
{
2730
contextMenuGroup.Parent = contextMenuButton;
2831
groupIndex += 1;
29-
32+
3033
if (items.Count(i => i is ContextMenuGroup) >
3134
1) //If there is more than one group, add the group title and group the items
3235
{
3336
var groupMenu = popupMenu.Menu.AddSubMenu(groupIndex, index, Menu.None,
3437
contextMenuGroup.Title);
35-
38+
3639
if (groupMenu == null) continue;
3740
if (contextMenuGroup.ItemsSource == null) continue;
38-
41+
3942
foreach (var contextMenuItemInGroup in contextMenuGroup.ItemsSource)
4043
{
41-
var contextMenuItemInGroupIndex = contextMenuGroup.ItemsSource.IndexOf(contextMenuItemInGroup);
42-
var menuItem = groupMenu.Add(groupIndex, contextMenuItemInGroupIndex, Menu.None, contextMenuItemInGroup.Title);
43-
TrySetChecked(contextMenuButton, menuItem, contextMenuItemInGroup);
44-
contextMenuItemInGroup.Parent = contextMenuGroup;
44+
var contextMenuItemInGroupIndex =
45+
contextMenuGroup.ItemsSource.IndexOf(contextMenuItemInGroup);
46+
var menuItem = groupMenu.Add(groupIndex, contextMenuItemInGroupIndex, Menu.None,
47+
contextMenuItemInGroup.Title);
48+
UpdateMenuItem(context, contextMenuButton, groupIndex, contextMenuItemInGroup, menuItem);
4549
dict.Add(contextMenuItemInGroup, menuItem);
4650
}
4751

@@ -53,7 +57,8 @@ internal static Dictionary<ContextMenuItem, IMenuItem> CreateMenuItems(
5357
}
5458
else //Only one group, add this to the root of the menu so the user does not have to tap an extra time to get to the items.
5559
{
56-
var newDict = CreateMenuItems(contextMenuGroup.ItemsSource, contextMenuButton, popupMenu,
60+
var newDict = CreateMenuItems(context, contextMenuGroup.ItemsSource, contextMenuButton,
61+
popupMenu,
5762
groupIndex);
5863
newDict.ForEach(pair =>
5964
{
@@ -70,20 +75,38 @@ internal static Dictionary<ContextMenuItem, IMenuItem> CreateMenuItems(
7075
else
7176
{
7277
var menuItem = popupMenu.Menu.Add(groupIndex, index, Menu.None, contextMenuItem.Title);
73-
TrySetChecked(contextMenuButton, menuItem, contextMenuItem);
74-
if (groupIndex == 0) //Not in a group
75-
{
76-
contextMenuItem.Parent = contextMenuButton;
77-
}
78+
UpdateMenuItem(context, contextMenuButton, groupIndex, contextMenuItem, menuItem);
79+
7880
dict.Add(contextMenuItem, menuItem);
7981
}
8082
}
81-
83+
8284
MenuCompat.SetGroupDividerEnabled(popupMenu.Menu, true);
83-
85+
8486
return dict;
8587
}
8688

89+
private static void UpdateMenuItem(Context context, ContextMenuButton contextMenuButton, int groupIndex,
90+
ContextMenuItem contextMenuItem, IMenuItem menuItem)
91+
{
92+
if (!string.IsNullOrEmpty(contextMenuItem.Icon))
93+
{
94+
var id = context.Resources?.GetIdentifier(contextMenuItem.Icon, "drawable", context.PackageName);
95+
var androidResourceId = context.Resources?.GetIdentifier(contextMenuItem.AndroidOptions.IconResourceName, "drawable",context.PackageName);
96+
id = androidResourceId ?? id;
97+
if (id != null)
98+
{
99+
menuItem.SetIcon((int)id);
100+
}
101+
}
102+
103+
TrySetChecked(contextMenuButton, menuItem, contextMenuItem);
104+
if (groupIndex == 0) //Not in a group
105+
{
106+
contextMenuItem.Parent = contextMenuButton;
107+
}
108+
}
109+
87110
private static void TrySetChecked(ContextMenuButton contextMenuButton, IMenuItem menuItem,
88111
ContextMenuItem contextMenuItem)
89112
{
@@ -92,6 +115,7 @@ private static void TrySetChecked(ContextMenuButton contextMenuButton, IMenuItem
92115
{
93116
contextMenuButton.ResetIsCheckedForTheRest(contextMenuItem);
94117
}
118+
95119
menuItem?.SetChecked(contextMenuItem.IsChecked);
96120
}
97121
}

src/DIPS.Xamarin.UI.Android/DIPS.Xamarin.UI.Android.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
<Reference Include="System.Core" />
4949
<Reference Include="System.Xml.Linq" />
5050
<Reference Include="System.Xml" />
51+
<Reference Include="Xamarin.AndroidX.AppCompat">
52+
<HintPath>..\..\..\..\.nuget\packages\xamarin.androidx.appcompat\1.2.0.6\lib\monoandroid90\Xamarin.AndroidX.AppCompat.dll</HintPath>
53+
</Reference>
5154
</ItemGroup>
5255
<ItemGroup>
5356
<Compile Include="ContextMenu\ContextMenuButtonRenderer.cs" />
@@ -68,6 +71,9 @@
6871
</ItemGroup>
6972
<ItemGroup>
7073
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.3" />
74+
<PackageReference Include="Xamarin.AndroidX.AppCompat">
75+
<Version>1.2.0.6</Version>
76+
</PackageReference>
7177
<PackageReference Include="Xamarin.Essentials">
7278
<Version>1.6.1</Version>
7379
</PackageReference>

src/DIPS.Xamarin.UI.iOS/ContextMenu/ContextMenuHelper.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.ComponentModel.Design;
33
using System.Linq;
4+
using CoreFoundation;
45
using DIPS.Xamarin.UI.Controls.ContextMenu;
56
using DIPS.Xamarin.UI.Extensions;
67
using UIKit;
@@ -44,7 +45,21 @@ internal static Dictionary<ContextMenuItem, UIMenuElement> CreateMenuItems(
4445

4546
else
4647
{
47-
var uiAction = UIAction.Create(contextMenuItem.Title, null, null,
48+
UIImage image = null;
49+
50+
if (!string.IsNullOrEmpty(contextMenuItem.Icon))
51+
{
52+
image = UIImage.FromBundle(contextMenuItem.Icon.File);
53+
}
54+
55+
if(!string.IsNullOrEmpty(contextMenuItem.iOSOptions.SystemIconName)) //Override image with SF Symbols if this is what the consumer wants
56+
{
57+
58+
var systemImage = UIImage.GetSystemImage(contextMenuItem.iOSOptions.SystemIconName);
59+
image = systemImage ?? image;
60+
}
61+
62+
var uiAction = UIAction.Create(contextMenuItem.Title, image, null,
4863
uiAction => OnMenuItemClick(uiAction, contextMenuItem, contextMenuButton));
4964

5065
if (contextMenuItem.IsChecked)

src/DIPS.Xamarin.UI/Controls/ContextMenu/ContextMenuHorizontalOptions.cs

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/DIPS.Xamarin.UI/Controls/ContextMenu/ContextMenuItem.Properties.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public object CommandParameter
4848
/// <summary>
4949
/// The title of the context menu item
5050
/// </summary>
51-
public string Title { get; set; }
51+
public string? Title { get; set; }
5252

5353
/// <summary>
5454
/// Determines if the native check mark should be added to the item when its tapped
@@ -63,12 +63,52 @@ public object CommandParameter
6363
/// <summary>
6464
/// The parent of the context menu item
6565
/// </summary>
66-
public object Parent { get; internal set; }
66+
public object? Parent { get; internal set; }
6767

6868
/// <summary>
6969
/// The subtitle of the menu item
7070
/// </summary>
7171
/// <remarks>Only works on iOS</remarks>
7272
public string? Subtitle { get; set; }
73+
74+
/// <summary>
75+
/// <see cref="iOSContextMenuItemOptions"/>
76+
/// </summary>
77+
// ReSharper disable once InconsistentNaming
78+
public iOSContextMenuItemOptions iOSOptions { get; set; } = new();
79+
80+
/// <summary>
81+
/// <see cref="AndroidContextMenuItemOptions"/>
82+
/// </summary>
83+
public AndroidContextMenuItemOptions AndroidOptions { get; set; } = new();
84+
85+
/// <summary>
86+
/// The icon to be used as a image with the context menu item
87+
/// </summary>
88+
public FileImageSource? Icon { get; set; }
89+
}
90+
91+
/// <summary>
92+
/// The Android specific context menu item options
93+
/// </summary>
94+
public class AndroidContextMenuItemOptions
95+
{
96+
/// <summary>
97+
/// Set this to override the Context menu item icon with a Android Resource
98+
/// </summary>
99+
/// <remarks>This can be any resource in your Resources drawable, but you can also check out Android.Resource.Drawable.icon-name which is built in</remarks>
100+
public string IconResourceName { get; set; }
101+
}
102+
103+
/// <summary>
104+
/// The iOS specific context menu item options
105+
/// </summary>
106+
public class iOSContextMenuItemOptions
107+
{
108+
/// <summary>
109+
/// Set this to override the Context menu item icon with a SF Symbol
110+
/// </summary>
111+
/// <remarks>To see all SF Symbols go to https://developer.apple.com/sf-symbols/</remarks>
112+
public string SystemIconName { get; set; }
73113
}
74114
}

src/DIPS.Xamarin.UI/Controls/ContextMenu/IContextMenu.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,23 @@ public interface IContextMenu
3636
public event EventHandler? ItemClicked;
3737

3838
/// <summary>
39-
/// <inheritdoc cref="ContextMenuHorizontalOptions"/>
39+
/// <see cref="ContextMenuHorizontalOptions"/>
4040
/// </summary>
4141
public ContextMenuHorizontalOptions ContextMenuHorizontalOptions { get; set; }
4242
}
43+
44+
/// <summary>
45+
/// The horizontal options for the context menu
46+
/// </summary>
47+
public enum ContextMenuHorizontalOptions
48+
{
49+
/// <summary>
50+
/// Position the menu to the right of the content to attach a context menu to
51+
/// </summary>
52+
Right = 0,
53+
/// <summary>
54+
/// Position the menu to the left of the content to attach a context menu to
55+
/// </summary>
56+
Left
57+
}
4358
}

src/Samples/DIPS.Xamarin.UI.Samples.Android/DIPS.Xamarin.UI.Samples.Android.csproj

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@
7676
<None Include="Properties\AndroidManifest.xml" />
7777
</ItemGroup>
7878
<ItemGroup>
79+
<AndroidResource Include="Resources\drawable-hdpi\edit.png" />
80+
<AndroidResource Include="Resources\drawable-mdpi\edit.png" />
81+
<AndroidResource Include="Resources\drawable-xhdpi\edit.png" />
82+
<AndroidResource Include="Resources\drawable-xxhdpi\edit.png" />
83+
<AndroidResource Include="Resources\drawable-xxxhdpi\edit.png" />
84+
<AndroidResource Include="Resources\drawable\edit.png">
85+
<SubType>Designer</SubType>
86+
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
87+
</AndroidResource>
7988
<AndroidResource Include="Resources\layout\Tabbar.xml" />
8089
<AndroidResource Include="Resources\layout\Toolbar.xml" />
8190
<AndroidResource Include="Resources\values\styles.xml" />
@@ -93,13 +102,6 @@
93102
<AndroidResource Include="Resources\mipmap-xxxhdpi\icon.png" />
94103
<AndroidResource Include="Resources\mipmap-xxxhdpi\launcher_foreground.png" />
95104
</ItemGroup>
96-
<ItemGroup>
97-
<Folder Include="Resources\drawable-hdpi\" />
98-
<Folder Include="Resources\drawable-xhdpi\" />
99-
<Folder Include="Resources\drawable-xxhdpi\" />
100-
<Folder Include="Resources\drawable-xxxhdpi\" />
101-
<Folder Include="Resources\drawable\" />
102-
</ItemGroup>
103105
<ItemGroup>
104106
<ProjectReference Include="..\..\DIPS.Xamarin.UI.Android\DIPS.Xamarin.UI.Android.csproj">
105107
<Project>{c2db1eb7-34d6-4106-9bd2-d475ac3ebc19}</Project>

src/Samples/DIPS.Xamarin.UI.Samples.Android/MainActivity.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ protected override void OnCreate(Bundle savedInstanceState)
2424
ToolbarResource = Resource.Layout.Toolbar;
2525

2626
base.OnCreate(savedInstanceState);
27-
27+
2828
Essentials.Platform.Init(this, savedInstanceState); //Xamarin essentials
2929

3030
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
171 Bytes
Loading

0 commit comments

Comments
 (0)