Skip to content
Merged
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
24 changes: 12 additions & 12 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 50 additions & 24 deletions sample/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Title="ContextMenuContainer Demo"
BackgroundColor="#f5f5f5"
AutomationId="sample_page"
x:Name="mainPage"
>

<ContentPage.BindingContext>
Expand All @@ -27,6 +28,32 @@
<Setter Property="Margin" Value="12,8,12,0" />
<Setter Property="TextColor" Value="#3E3E3E" />
</Style>
<DataTemplate x:Key="ListItem" x:DataType="vm:Person">
<c:ContextMenuContainer AutomationId="{Binding AutomationId}">
<c:ContextMenuContainer.MenuItems>
<c:ContextMenuItem
Text="{Binding FirstName, StringFormat='Edit {0}'}"
Command="{Binding ViewModel.EditPersonCommand, Source={x:Reference mainPage}}"
CommandParameter="{Binding .}"
Icon="{Binding ViewModel.SettingsIconSource, Source={x:Reference mainPage}}"/>
<c:ContextMenuItem
Text="{Binding FirstName, StringFormat='Delete {0}'}"
IsDestructive="True"
Command="{Binding ViewModel.DeletePersonCommand, Source={x:Reference mainPage}}"
CommandParameter="{Binding .}" />
</c:ContextMenuContainer.MenuItems>
<c:ContextMenuContainer.Content>
<Frame Margin="4" Padding="12" BackgroundColor="#00897B" CornerRadius="4">
<Grid ColumnDefinitions="40, *">
<Border StrokeShape="Ellipse" WidthRequest="32" HeightRequest="32" BackgroundColor="#004D40">
<Label Text="{Binding FirstLetter}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" FontAttributes="Bold" />
</Border>
<Label Grid.Column="1" Text="{Binding FullName}" TextColor="White" VerticalOptions="Center" Margin="8,0,0,0" AutomationId="{Binding NameAutomationId}" />
</Grid>
</Frame>
</c:ContextMenuContainer.Content>
</c:ContextMenuContainer>
</DataTemplate>
</ContentPage.Resources>

<ScrollView>
Expand Down Expand Up @@ -129,32 +156,31 @@
</c:ContextMenuContainer.Content>
</c:ContextMenuContainer>

<!-- Nested controls example -->
<Label Text="Nested Controls" Style="{StaticResource HeaderStyle}" />
<c:ContextMenuContainer x:Name="container5" AutomationId="container5">
<c:ContextMenuContainer.MenuItems>
<c:ContextMenuItem Text="Add Item" Command="{Binding AddItemCommand}" Icon="{Binding SettingsIconSource}" />
<c:ContextMenuItem Text="Clear Items" Command="{Binding ClearItemsCommand}" IsDestructive="True" />
</c:ContextMenuContainer.MenuItems>
<c:ContextMenuContainer.Content>
<Frame Style="{StaticResource CardStyle}" BackgroundColor="#9575CD">
<StackLayout>
<Label Text="Collection Example" FontSize="16" FontAttributes="Bold" TextColor="White" />
<CollectionView ItemsSource="{Binding ListItems}" EmptyView="No items in the collection" HeightRequest="120">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="4" Padding="8" BackgroundColor="#7E57C2" CornerRadius="4">
<Label Text="{Binding .}" TextColor="White" />
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</Frame>
</c:ContextMenuContainer.Content>
</c:ContextMenuContainer>

<!-- CollectionView with Context Menu on Each Item -->
<Frame Style="{StaticResource CardStyle}" BackgroundColor="#26A69A">
<StackLayout>
<c:ContextMenuContainer x:Name="container5" AutomationId="container5">
<c:ContextMenuContainer.MenuItems>
<c:ContextMenuItem Text="Add Person" Command="{Binding AddItemCommand}" Icon="{Binding SettingsIconSource}" />
<c:ContextMenuItem Text="Clear List" Command="{Binding ClearItemsCommand}" IsDestructive="True" />
</c:ContextMenuContainer.MenuItems>
<c:ContextMenuContainer.Content>
<StackLayout>
<Label Text="CollectionView Per-Item ContextMenu" FontSize="16" FontAttributes="Bold" TextColor="White" />
<Label Text="Each item has its own context menu - long press or right-click on any person or on this header to access global actions" TextColor="White" />
<Label AutomationId="collection_result_label" Text="{Binding CollectionViewResultText}" TextColor="White" FontAttributes="Italic" />
</StackLayout>
</c:ContextMenuContainer.Content>
</c:ContextMenuContainer>
<CollectionView ItemsSource="{Binding ListItems}" ItemTemplate="{StaticResource ListItem}" HeightRequest="200" AutomationId="people_collection" EmptyView="No items in the collection" />
</StackLayout>
</Frame>


<BoxView HeightRequest="20" />


</StackLayout>
</ScrollView>
</ContentPage>
4 changes: 3 additions & 1 deletion sample/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Microsoft.Maui.Controls;

using APES.MAUI.Sample.ViewModels;
namespace APES.MAUI.Sample;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
public IMainViewModel ViewModel => BindingContext as IMainViewModel;

}

158 changes: 152 additions & 6 deletions sample/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

namespace APES.MAUI.Sample.ViewModels
{
public class MainViewModel : INotifyPropertyChanged


public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;

Check warning on line 15 in sample/ViewModels/MainViewModel.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
protected void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
Expand All @@ -20,7 +22,62 @@
NotifyPropertyChanged(propertyName);
return true;
}
}

/// <summary>
/// Represents a person for the CollectionView example.
/// </summary>
public class Person : ViewModelBase
{
private string _firstName = string.Empty;
public string FirstName
{
get => _firstName;
set
{
SetField(ref _firstName, value);
NotifyPropertyChanged(nameof(FullName));
NotifyPropertyChanged(nameof(FirstLetter));
NotifyPropertyChanged(nameof(AutomationId));
NotifyPropertyChanged(nameof(NameAutomationId));
}
}

private string _lastName = string.Empty;
public string LastName
{
get => _lastName;
set
{
SetField(ref _lastName, value);
NotifyPropertyChanged(nameof(FullName));
NotifyPropertyChanged(nameof(AutomationId));
NotifyPropertyChanged(nameof(NameAutomationId));
}
}

public string FullName => $"{FirstName} {LastName}";
public string FirstLetter => string.IsNullOrEmpty(FirstName) ? "#" : FirstName[0].ToString().ToUpper();
public string AutomationId => $"person_{FirstName?.ToLower()}_{LastName?.ToLower()}";
public string NameAutomationId => $"person_name_{FirstName?.ToLower()}_{LastName?.ToLower()}";
}

public interface IMainViewModel {
public ICommand FirstCommand { get; }
public ICommand SecondCommand { get; }
public ICommand DynamicSectionCommand { get; }
public ICommand DestructiveCommand { get; }
public ICommand ConstructiveCommand { get; }
public ICommand NeverEndingCommand { get; }
public ICommand ToggleConditionalCommand { get; }
public ICommand ConditionalCommand { get; }
public ICommand AddItemCommand { get; }
public ICommand ClearItemsCommand { get; }
public ICommand EditPersonCommand { get; }
public ICommand DeletePersonCommand { get; }
}
public class MainViewModel : ViewModelBase, IMainViewModel
{
#region Properties
private string _text;
public string Text
Expand Down Expand Up @@ -48,6 +105,8 @@
public ICommand ConditionalCommand { get; }
public ICommand AddItemCommand { get; }
public ICommand ClearItemsCommand { get; }
public ICommand EditPersonCommand { get; }
public ICommand DeletePersonCommand { get; }
#endregion

#region Context Menu Properties
Expand All @@ -72,8 +131,16 @@
set => SetField(ref _conditionalActionStatus, value);
}

private ObservableCollection<string> _listItems = new();
public ObservableCollection<string> ListItems

private string _collectionViewResultText = "Right-click on a person to see actions";
public string CollectionViewResultText
{
get => _collectionViewResultText;
set => SetField(ref _collectionViewResultText, value);
}

private ObservableCollection<Person> _listItems = new();
public ObservableCollection<Person> ListItems
{
get => _listItems;
set => SetField(ref _listItems, value);
Expand All @@ -95,6 +162,8 @@
ConditionalCommand = new Command(ConditionalAction);
AddItemCommand = new Command(AddItem);
ClearItemsCommand = new Command(ClearItems);
EditPersonCommand = new Command<Person>(EditPerson);
DeletePersonCommand = new Command<Person>(DeletePerson);
_deleteIconSource = "outline_delete_black_24.png";
SettingsIconSource = "outline_settings_black_24.png";

Expand Down Expand Up @@ -174,11 +243,49 @@
private void InitializeListItems()
{
// Add some initial items
ListItems.Add("Example Item 1");
ListItems.Add("Example Item 2");
// ListItems.Add(new Person { FirstName = "Thiago", LastName = "Ferreira" });
// ListItems.Add(new Person { FirstName = "Noor", LastName = "Abdallah" });
// ListItems.Add(new Person { FirstName = "Mateo", LastName = "Garcia" });
// ListItems.Add(new Person { FirstName = "Yuki", LastName = "Tanaka" });
AddItem();
AddItem();

}

private void AddItem() => ListItems.Add($"New Item {ListItems.Count + 1}");
private readonly List<(string First, string Last)> _namePool = new() {
("Dmitri", "Ivanov"),
("João", "Silva"),
("Priya", "Sharma"),
("Ahmed", "Al-Sayed"),
("Anastasia", "Volkova"),
("Pedro", "Oliveira"),
("Rahul", "Verma"),
("Fatima", "Hassan"),
("Ivan", "Sokolov"),
("Ana", "Costa"),
("Ananya", "Singh"),
("Omar", "Khalid"),
("Olga", "Smirnova"),
("Maria", "Santos"),
("Rohan", "Gupta"),
("Layla", "Ibrahim"),
("Vladimir", "Popov"),
("Lucas", "Pereira"),
("Vikram", "Patel"),
("Youssef", "Mahmoud")
};

private void AddItem() {
var c = ListItems.Count;
(string First, string Last) p;
if(c < _namePool.Count) {
p = _namePool[ListItems.Count];
} else
{
p = ("New person", $"#{c}");
}
ListItems.Add(new Person() { FirstName = p.First, LastName = p.Last });
}

private void ClearItems() => ListItems.Clear();

Expand All @@ -198,5 +305,44 @@

public FileImageSource SettingsIconSource { get; private set; }
private readonly FileImageSource _deleteIconSource;

private void EditPerson(Person person)
{
if (person == null){
CollectionViewResultText = "Person was null!";
Logger.Error("ContextMenuContainer EditPerson person was null!");
return;
}
CollectionViewResultText = $"Editing: {person. FullName} at {DateTime.Now:HH: mm:ss}";
}

private void DeletePerson(Person person)
{


try
{
if (person == null){
CollectionViewResultText = "Person was null!";
Logger.Error("ContextMenuContainer DeletePerson person was null!");
return;
}
var index = ListItems.IndexOf(person);
if (index < 0 )
{
CollectionViewResultText = "Person was not found!";
Logger.Error("ContextMenuContainer DeletePerson person return index less than 0!");
}
ListItems.RemoveAt(index);
CollectionViewResultText = $"Deleted: {person.FullName} at {DateTime.Now:HH:mm:ss}";
}
catch(Exception ex)
{
CollectionViewResultText = ex.Message;

}
}


}
}
Loading
Loading