├── icon.png
├── SampleApp
├── Resources
│ ├── Fonts
│ │ ├── OpenSans-Regular.ttf
│ │ └── OpenSans-SemiBold.ttf
│ ├── appicon.svg
│ ├── Raw
│ │ └── AboutAssets.txt
│ ├── appiconfg.svg
│ ├── Colors.xaml
│ ├── Images
│ │ └── dotnet_bot.svg
│ └── Styles.xaml
├── Imports.cs
├── Properties
│ └── launchSettings.json
├── Platforms
│ ├── Android
│ │ ├── Resources
│ │ │ └── values
│ │ │ │ └── colors.xml
│ │ ├── AndroidManifest.xml
│ │ ├── MainApplication.cs
│ │ └── MainActivity.cs
│ ├── MacCatalyst
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ ├── Entitlements.plist
│ │ └── Info.plist
│ ├── iOS
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ └── Info.plist
│ └── Windows
│ │ ├── App.xaml
│ │ ├── app.manifest
│ │ ├── App.xaml.cs
│ │ └── Package.appxmanifest
├── Views
│ ├── MainPage.xaml.cs
│ ├── TestDelayedInitPage.xaml.cs
│ ├── DynamicItemsPage.xaml.cs
│ ├── TestDelayedInitPage.xaml
│ ├── MainPage.xaml
│ └── DynamicItemsPage.xaml
├── App.xaml.cs
├── ViewModels
│ ├── MainPageViewModel.cs
│ ├── TestDelayedInitViewModel.cs
│ └── DynamicItemsPageViewModel.cs
├── Controls
│ ├── DateTimePicker.xaml
│ └── DateTimePicker.xaml.cs
├── App.xaml
├── MauiProgram.cs
└── SampleApp.csproj
├── Vapolia.SegmentedViews
├── WidthDefinitionCollection.cs
├── ISegmentedView.cs
├── SegmentExtensions.cs
├── Segment.cs
├── FilenameBasedMultiTargeting.props
├── WidthDefinitionCollectionTypeConverter.cs
├── MauiAppBuilderExtensions.cs
├── Vapolia.SegmentedViews.csproj
├── WeakEventManager.cs
├── SegmentedViewHandler.windows.cs
├── SegmentedViewHandler.macios.cs
├── SegmentedViewHandler.android.cs
└── SegmentedView.cs
├── .run
├── Windows.run.xml
├── iOS.run.xml
└── Android.run.xml
├── .github
└── workflows
│ └── main.yaml
├── Vapolia.SegmentedViews.sln
├── README.md
├── .gitignore
└── LICENSE.md
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vapolia/SegmentedViews/HEAD/icon.png
--------------------------------------------------------------------------------
/SampleApp/Resources/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vapolia/SegmentedViews/HEAD/SampleApp/Resources/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/SampleApp/Resources/Fonts/OpenSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vapolia/SegmentedViews/HEAD/SampleApp/Resources/Fonts/OpenSans-SemiBold.ttf
--------------------------------------------------------------------------------
/SampleApp/Imports.cs:
--------------------------------------------------------------------------------
1 | global using SampleApp;
2 | global using SampleApp.Controls;
3 |
4 | // Static
5 | global using static Microsoft.Maui.Graphics.Colors;
6 |
--------------------------------------------------------------------------------
/SampleApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "Windows Machine": {
5 | "commandName": "MsixPackage",
6 | "nativeDebugging": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SampleApp/Resources/appicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #512BD4
4 | #2B0B98
5 | #2B0B98
6 |
--------------------------------------------------------------------------------
/SampleApp/Views/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using SampleApp.ViewModels;
2 |
3 | namespace SampleApp.Views;
4 |
5 | public partial class MainPage : ContentPage
6 | {
7 | public MainPage()
8 | {
9 | InitializeComponent();
10 | BindingContext = new MainPageViewModel(Navigation);
11 | }
12 | }
--------------------------------------------------------------------------------
/SampleApp/Platforms/MacCatalyst/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace SampleApp
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace SampleApp
4 | {
5 | [Register(nameof(AppDelegate))]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SampleApp/Views/TestDelayedInitPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using SampleApp.ViewModels;
2 |
3 | namespace SampleApp.Views;
4 |
5 | public partial class TestDelayedInitPage
6 | {
7 | public TestDelayedInitPage()
8 | {
9 | InitializeComponent();
10 | BindingContext = new TestDelayedInitViewModel();
11 | }
12 | }
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/WidthDefinitionCollection.cs:
--------------------------------------------------------------------------------
1 | namespace Vapolia.SegmentedViews;
2 |
3 | public class WidthDefinitionCollection : List
4 | {
5 | public WidthDefinitionCollection() {}
6 |
7 | public WidthDefinitionCollection(IEnumerable definitions) : base(definitions)
8 | {
9 | }
10 | }
--------------------------------------------------------------------------------
/SampleApp/Views/DynamicItemsPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using SampleApp.ViewModels;
2 |
3 | namespace SampleApp.Views;
4 |
5 | public partial class DynamicItemsPage : ContentPage
6 | {
7 | public DynamicItemsPage()
8 | {
9 | BindingContext = new DynamicItemsPageViewModel(Navigation);
10 | InitializeComponent();
11 | }
12 | }
--------------------------------------------------------------------------------
/SampleApp/Platforms/Windows/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.run/Windows.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/Android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SampleApp/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using SampleApp.Views;
2 |
3 | namespace SampleApp;
4 |
5 | public partial class App : Application
6 | {
7 | public App()
8 | {
9 | InitializeComponent();
10 | UserAppTheme = PlatformAppTheme;
11 | }
12 |
13 | protected override Window CreateWindow(IActivationState? activationState)
14 | {
15 | return new Window(new NavigationPage(new MainPage()));
16 | }
17 | }
--------------------------------------------------------------------------------
/SampleApp/Platforms/Android/MainApplication.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Runtime;
3 |
4 | namespace SampleApp
5 | {
6 | [Application]
7 | public class MainApplication : MauiApplication
8 | {
9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership)
10 | : base(handle, ownership)
11 | {
12 | }
13 |
14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace SampleApp
6 | {
7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
8 | public class MainActivity : MauiAppCompatActivity
9 | {
10 |
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/iOS/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 | using UIKit;
3 |
4 | namespace SampleApp
5 | {
6 | public class Program
7 | {
8 | // This is the main entry point of the application.
9 | static void Main(string[] args)
10 | {
11 | // if you want to use a different Application Delegate class from "AppDelegate"
12 | // you can specify it here.
13 | UIApplication.Main(args, null, typeof(AppDelegate));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/MacCatalyst/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 | using UIKit;
3 |
4 | namespace SampleApp
5 | {
6 | public class Program
7 | {
8 | // This is the main entry point of the application.
9 | static void Main(string[] args)
10 | {
11 | // if you want to use a different Application Delegate class from "AppDelegate"
12 | // you can specify it here.
13 | UIApplication.Main(args, null, typeof(AppDelegate));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.run/iOS.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.run/Android.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SampleApp/Resources/Raw/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories) and given a Build Action of "MauiAsset":
3 |
4 |
5 |
6 | These files will be deployed with you package and will be accessible using Essentials:
7 |
8 | async Task LoadMauiAsset()
9 | {
10 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
11 | using var reader = new StreamReader(stream);
12 |
13 | var contents = reader.ReadToEnd();
14 | }
15 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/MacCatalyst/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | com.apple.security.app-sandbox
8 |
9 |
10 | com.apple.security.network.client
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/SampleApp/ViewModels/MainPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Input;
2 | using SampleApp.Views;
3 |
4 | namespace SampleApp.ViewModels;
5 |
6 | public class MainPageViewModel
7 | {
8 | public int SegmentSelectedIndex { get; set; }
9 | public ICommand SegmentSelectionChangedCommand { get; }
10 | public ICommand GoAdvancedDemoPageCommand { get; }
11 |
12 | public MainPageViewModel(INavigation navigation)
13 | {
14 | SegmentSelectionChangedCommand = new Command(() =>
15 | {
16 | var selectedItem = SegmentSelectedIndex;
17 | //...
18 | });
19 |
20 | GoAdvancedDemoPageCommand = new Command(() =>
21 | {
22 | navigation.PushAsync(new DynamicItemsPage());
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SampleApp/Controls/DateTimePicker.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
13 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/SampleApp/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/SampleApp/MauiProgram.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Vapolia.SegmentedViews;
3 |
4 | namespace SampleApp
5 | {
6 | public static partial class MauiProgram
7 | {
8 | public static MauiApp CreateMauiApp()
9 | {
10 | var builder = MauiApp.CreateBuilder();
11 | builder.UseMauiApp()
12 | .ConfigureFonts(fonts =>
13 | {
14 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
15 | fonts.AddFont("OpenSans-SemiBold.ttf", "OpenSansSemiBold");
16 | })
17 | .UseSegmentedView();
18 |
19 | #if DEBUG
20 | builder.Logging.AddDebug();
21 | #endif
22 |
23 | return builder.Build();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/Windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | true/PM
12 | PerMonitorV2, PerMonitor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/ISegmentedView.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace Vapolia.SegmentedViews;
4 |
5 | public interface ISegmentedView : IView, ITextStyle
6 | {
7 | public Color SelectedBackgroundColor { get; }
8 | public Color SelectedTextColor { get; }
9 | public Color DisabledColor { get; }
10 | public Color BackgroundColor { get; }
11 | public Color BorderColor { get; }
12 | // public double BorderWidth { get; }
13 | public Thickness ItemPadding { get; set; }
14 |
15 | public int SelectedIndex { get; }
16 | internal void SetSelectedIndex(int i);
17 | public bool IsSelectionRequired { get; }
18 |
19 | //internal string? TextPropertyName { get; }
20 | internal IValueConverter? TextConverter { get; }
21 | internal ObservableCollection Children { get; }
22 | internal WidthDefinitionCollection? WidthDefinitions { get; }
23 | internal GridLength ItemsDefaultWidth { get; }
24 | }
25 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/Windows/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 |
3 | // To learn more about WinUI, the WinUI project structure,
4 | // and more about our project templates, see: http://aka.ms/winui-project-info.
5 |
6 | namespace SampleApp.WinUI;
7 |
8 | ///
9 | /// Provides application-specific behavior to supplement the default Application class.
10 | ///
11 | public partial class App : MauiWinUIApplication
12 | {
13 | ///
14 | /// Initializes the singleton application object. This is the first line of authored code
15 | /// executed, and as such is the logical equivalent of main() or WinMain().
16 | ///
17 | public App()
18 | {
19 | UnhandledException += (sender, e) =>
20 | {
21 | if (global::System.Diagnostics.Debugger.IsAttached)
22 | global::System.Diagnostics.Debugger.Break();
23 | };
24 |
25 | this.InitializeComponent();
26 | }
27 |
28 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
29 | }
--------------------------------------------------------------------------------
/SampleApp/Platforms/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSRequiresIPhoneOS
6 |
7 | UIDeviceFamily
8 |
9 | 1
10 | 2
11 |
12 | UIRequiredDeviceCapabilities
13 |
14 | arm64
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | XSAppIconAssets
30 | Assets.xcassets/appicon.appiconset
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/SegmentExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.Globalization;
3 |
4 | namespace Vapolia.SegmentedViews;
5 |
6 | internal static class SegmentExtensions
7 | {
8 | public static string GetText(this Segment segment, ISegmentedView segmentedControl)
9 | {
10 | if (segment.Item == null)
11 | return string.Empty;
12 |
13 | //var obj = segmentedControl.TextPropertyName != null ? segment.Item.GetType().GetProperty(segmentedControl.TextPropertyName)?.GetValue(segment.Item) : segment.Item;
14 | var obj = segment.Item;
15 |
16 | if (segmentedControl.TextConverter != null)
17 | obj = segmentedControl.TextConverter.Convert(obj, typeof(string), null, CultureInfo.CurrentCulture);
18 |
19 | return obj?.ToString() ?? string.Empty;
20 | }
21 |
22 | public static List GetWidths(this ISegmentedView segmentedView)
23 | {
24 | return segmentedView.Children.Select((segment,i) =>
25 | {
26 | if (segment.Width != null)
27 | return segment.Width.Value;
28 | if(segmentedView.WidthDefinitions?.Count > i)
29 | return segmentedView.WidthDefinitions[i];
30 | return segmentedView.ItemsDefaultWidth;
31 | }).ToList();
32 | }
33 | }
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/Segment.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace Vapolia.SegmentedViews;
4 |
5 | public class Segment : BindableObject
6 | {
7 | public static readonly BindableProperty ItemProperty = BindableProperty.Create(nameof (Item), typeof (object), typeof (Segment), propertyChanged: (bindable, value, newValue) => ((Segment)bindable).OnItemChanged(value, newValue));
8 | public static readonly BindableProperty WidthProperty = BindableProperty.Create(nameof (Width), typeof (GridLength?), typeof (Segment));
9 |
10 | public object? Item
11 | {
12 | get => GetValue(ItemProperty);
13 | set => SetValue(ItemProperty, value);
14 | }
15 |
16 | [TypeConverter(typeof(GridLengthTypeConverter))]
17 | public GridLength? Width
18 | {
19 | get => (GridLength?)GetValue(WidthProperty);
20 | set => SetValue(WidthProperty, value);
21 | }
22 |
23 | private void OnItemChanged(object value, object newValue)
24 | {
25 | if (value is INotifyPropertyChanged notifyPropertyChanged1)
26 | WeakEventManager.Unsubscribe(notifyPropertyChanged1, this, OnItemPropertyChanged);
27 |
28 | if (newValue is INotifyPropertyChanged notifyPropertyChanged2)
29 | WeakEventManager.Subscribe(notifyPropertyChanged2, this, OnItemPropertyChanged);
30 | }
31 |
32 | //Simulate the change of the whole item when an item's property has changed
33 | private void OnItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
34 | {
35 | if(e.PropertyName != nameof(Item))
36 | OnPropertyChanged(nameof(Item));
37 | }
38 |
39 | ///
40 | /// Finalizer to ensure weak event cleanup
41 | ///
42 | ~Segment()
43 | {
44 | WeakEventManager.UnsubscribeAll(this);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/MacCatalyst/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | UIDeviceFamily
15 |
16 | 2
17 |
18 | UIRequiredDeviceCapabilities
19 |
20 | arm64
21 |
22 | UISupportedInterfaceOrientations
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationLandscapeLeft
26 | UIInterfaceOrientationLandscapeRight
27 |
28 | UISupportedInterfaceOrientations~ipad
29 |
30 | UIInterfaceOrientationPortrait
31 | UIInterfaceOrientationPortraitUpsideDown
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | XSAppIconAssets
36 | Assets.xcassets/appicon.appiconset
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/FilenameBasedMultiTargeting.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/WidthDefinitionCollectionTypeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Globalization;
3 |
4 | namespace Vapolia.SegmentedViews;
5 |
6 | public class WidthDefinitionCollectionTypeConverter : TypeConverter
7 | {
8 | public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
9 | => sourceType == typeof(string);
10 | public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
11 | => destinationType == typeof(string);
12 |
13 | public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value)
14 | {
15 | var strValue = value?.ToString();
16 |
17 | if (strValue == null)
18 | throw new InvalidOperationException($"Cannot convert \"{strValue}\" into {typeof(WidthDefinitionCollection)}");
19 |
20 | var converter = new GridLengthTypeConverter();
21 | var definitions = strValue.Split(',').Select(length => (GridLength?)converter.ConvertFromInvariantString(length)).ToList();
22 | if(definitions.Any(d => d == null))
23 | throw new InvalidOperationException($"Cannot convert \"{strValue}\" into {typeof(WidthDefinitionCollection)}");
24 |
25 | return new WidthDefinitionCollection(definitions.Cast());
26 | }
27 |
28 |
29 | public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
30 | {
31 | if (value is not WidthDefinitionCollection cdc)
32 | throw new NotSupportedException();
33 | var converter = new GridLengthTypeConverter();
34 | return string.Join(", ", cdc.Select(cd => converter.ConvertToInvariantString(cd)));
35 | }
36 | }
--------------------------------------------------------------------------------
/SampleApp/Resources/appiconfg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/MauiAppBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.Runtime.Versioning;
3 |
4 | [assembly: XmlnsDefinition("https://vapolia.eu/Vapolia.SegmentedViews", "Vapolia.SegmentedViews", AssemblyName = "Vapolia.SegmentedViews")]
5 | [assembly: Microsoft.Maui.Controls.XmlnsPrefix("https://vapolia.eu/Vapolia.SegmentedViews", "segmented")]
6 | [assembly: Microsoft.Maui.Controls.XmlnsPrefix("clr-namespace:Vapolia.SegmentedViews;assembly=Vapolia.SegmentedViews", "segmented")]
7 |
8 | namespace Vapolia.SegmentedViews;
9 |
10 | [SuppressMessage("Usage", "CA2255: ’ModuleInitializer’ warning")]
11 | [SupportedOSPlatform("iOS15.0")]
12 | [SupportedOSPlatform("MacCatalyst14.0")]
13 | [SupportedOSPlatform("Android27.0")]
14 | [SupportedOSPlatform("Windows10.0.19041")]
15 | //[SupportedOSPlatform("Tizen6.5")]
16 | public static class MauiAppBuilderExtensions
17 | {
18 | ///
19 | /// Try to fix ns not found for Segment (but not SegmentedView) ?!
20 | /// When using https://vapolia.eu/Vapolia.SegmentedViews instead of clr-namespace:Vapolia.SegmentedViews;assembly=Vapolia.SegmentedViews
21 | ///
22 | public static Vapolia.SegmentedViews.Segment InternalSegment;
23 |
24 | ///
25 | /// Add Maui handlers for this control
26 | ///
27 | public static MauiAppBuilder UseSegmentedView(this MauiAppBuilder builder)
28 | {
29 | //?! Try to fix ns not found for Segment (but not SegmentedView)
30 | InternalSegment = new ();
31 |
32 | builder.ConfigureMauiHandlers(handlers =>
33 | {
34 | #if ANDROID
35 | handlers.TryAddHandler();
36 | #elif IOS || MACCATALYST
37 | handlers.TryAddHandler();
38 | #elif WINDOWS
39 | handlers.TryAddHandler();
40 | #endif
41 | });
42 |
43 | return builder;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: Publish To Nuget
2 |
3 | on:
4 | # Allows you to run this workflow manually from the Actions tab
5 | workflow_dispatch:
6 | # pull_request:
7 | # push:
8 | release:
9 | types: [published]
10 |
11 | jobs:
12 | build:
13 | name: Build
14 | #Needs windows to build the windows version
15 | runs-on: windows-latest
16 | env:
17 | NUPKG_MAJOR: 1.0.9
18 | DOTNET_CLI_TELEMETRY_OPTOUT: true
19 | DOTNET_NOLOGO: true
20 | PROJECT: Vapolia.SegmentedViews/Vapolia.SegmentedViews.csproj
21 | # CODESIGN_PFX: ${{ secrets.CODESIGN_PFX }}
22 | steps:
23 | - uses: actions/checkout@v4
24 |
25 | #Saves a nuget config
26 | - name: Setup .NET
27 | uses: actions/setup-dotnet@v4
28 | with:
29 | dotnet-version: 9.0.x
30 |
31 | # - name: Install MAUI workload
32 | # run: dotnet workload install maui
33 |
34 | - name: Build
35 | shell: pwsh
36 | run: dotnet build -c Release $env:PROJECT
37 |
38 | - name: Package & Publish NuGets
39 | shell: pwsh
40 | env:
41 | #required so if it contains special characters they are not interpreted by powershell
42 | NUGET_AUTH_TOKEN: ${{secrets.NUGETAPIKEY}}
43 | NUGET_TARGET: https://api.nuget.org/v3/index.json
44 | run: |
45 | $VERSION="$env:NUPKG_MAJOR-ci$env:GITHUB_RUN_ID"
46 | if ($env:GITHUB_EVENT_NAME -eq "release") {
47 | $VERSION = $env:GITHUB_REF.Substring($env:GITHUB_REF.LastIndexOf('/') + 1)
48 | }
49 | echo "PACKAGE VERSION: $VERSION"
50 | New-Item -ItemType Directory -Force -Path ./artifacts
51 |
52 | dotnet pack --no-build --output ./artifacts -c Release -p:PackageVersion=$VERSION $env:PROJECT
53 | # needs to CD because nuget push can't find nuget packages with a linux style path
54 | cd ./artifacts
55 | dotnet nuget push *.nupkg --skip-duplicate -k $env:NUGET_AUTH_TOKEN -s $env:NUGET_TARGET
56 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34511.84
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vapolia.SegmentedViews", "Vapolia.SegmentedViews\Vapolia.SegmentedViews.csproj", "{64CA4E01-223F-4DB7-B244-77563DF7CA34}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "SampleApp\SampleApp.csproj", "{8980EE83-C5DF-4D73-AE28-2752BB54F259}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Publish", "Publish", "{2D8D9461-9B7F-4CE1-A744-9640C6B06053}"
11 | ProjectSection(SolutionItems) = preProject
12 | .github\workflows\main.yaml = .github\workflows\main.yaml
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {64CA4E01-223F-4DB7-B244-77563DF7CA34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {64CA4E01-223F-4DB7-B244-77563DF7CA34}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {64CA4E01-223F-4DB7-B244-77563DF7CA34}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {64CA4E01-223F-4DB7-B244-77563DF7CA34}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {8980EE83-C5DF-4D73-AE28-2752BB54F259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {8980EE83-C5DF-4D73-AE28-2752BB54F259}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {8980EE83-C5DF-4D73-AE28-2752BB54F259}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
28 | {8980EE83-C5DF-4D73-AE28-2752BB54F259}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {8980EE83-C5DF-4D73-AE28-2752BB54F259}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {EE100EBC-3C29-4555-B5A5-B925FFA078CE}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/SampleApp/Views/TestDelayedInitPage.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/SampleApp/Controls/DateTimePicker.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace SampleApp.Controls
4 | {
5 | public partial class DateTimePicker : ContentView
6 | {
7 | public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(DateTimePicker), default(string));
8 |
9 | public static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(DateTimePicker), default(DateTime));
10 |
11 | public static readonly BindableProperty TimeProperty = BindableProperty.Create(nameof(Time), typeof(TimeSpan), typeof(DateTimePicker), default(TimeSpan));
12 |
13 | public static readonly BindableProperty MinimumDateProperty = BindableProperty.Create(nameof(MinimumDate), typeof(DateTime), typeof(DateTimePicker), default(DateTime));
14 |
15 | public DateTimePicker()
16 | {
17 | InitializeComponent();
18 | }
19 |
20 | public TimeSpan Time
21 | {
22 | get => (TimeSpan)GetValue(TimeProperty);
23 | set => SetValue(TimeProperty, value);
24 | }
25 |
26 | public DateTime Date
27 | {
28 | get => (DateTime)GetValue(DateProperty);
29 | set => SetValue(DateProperty, value);
30 | }
31 |
32 | public string Title
33 | {
34 | get => (string)GetValue(TitleProperty);
35 | set => SetValue(TitleProperty, value);
36 | }
37 |
38 | public DateTime MinimumDate
39 | {
40 | get => (DateTime)GetValue(MinimumDateProperty);
41 | set => SetValue(MinimumDateProperty, value);
42 | }
43 |
44 | protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
45 | {
46 | base.OnPropertyChanged(propertyName);
47 |
48 | if (propertyName == TitleProperty.PropertyName)
49 | {
50 | lblTitle.Text = Title;
51 | }
52 | else if (propertyName == DateProperty.PropertyName)
53 | {
54 | occursOn.Date = Date;
55 | }
56 | else if (propertyName == TimeProperty.PropertyName)
57 | {
58 | occursAt.Time = Time;
59 | }
60 | else if (propertyName == MinimumDateProperty.PropertyName)
61 | {
62 | occursOn.MinimumDate = MinimumDate;
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/SampleApp/Platforms/Windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | $placeholder$
17 | User Name
18 | $placeholder$.png
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/SampleApp/ViewModels/TestDelayedInitViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 | using System.Windows.Input;
4 |
5 | namespace SampleApp.ViewModels;
6 |
7 | public class TestDelayedInitViewModel : INotifyPropertyChanged
8 | {
9 | private string infoText = string.Empty;
10 | private string infoText2 = string.Empty;
11 | private object? segmentSelectedItem;
12 | private int nextInt = 42;
13 | private List persons;
14 |
15 | public object? SegmentSelectedItem
16 | {
17 | get => segmentSelectedItem;
18 | set { segmentSelectedItem = value; OnPropertyChanged(); }
19 | }
20 |
21 | public ICommand SegmentSelectionChangedCommand { get; }
22 |
23 | public string InfoText
24 | {
25 | get => infoText;
26 | set { infoText = value; OnPropertyChanged(); }
27 | }
28 | public string InfoText2
29 | {
30 | get => infoText2;
31 | set { infoText2 = value; OnPropertyChanged(); }
32 | }
33 |
34 | public ICommand AddItemCommand { get; }
35 | public ICommand RemoveItemCommand { get; }
36 | public ICommand ClearCommand { get; }
37 |
38 | public List Persons
39 | {
40 | get => persons;
41 | private set
42 | {
43 | persons = value;
44 | OnPropertyChanged();
45 | }
46 | }
47 |
48 | public TestDelayedInitViewModel()
49 | {
50 | //Testing:
51 | //set selected item before the items are set
52 |
53 | var thePersons = new List
54 | {
55 | new (1, "Johnny", "Halliday"),
56 | new (2, "Vanessa", "Paradis"),
57 | new (3, "Jose", "Garcia"),
58 | };
59 |
60 | SegmentSelectedItem = thePersons[1];
61 |
62 | MainThread.BeginInvokeOnMainThread(async () =>
63 | {
64 | await Task.Delay(TimeSpan.FromSeconds(1));
65 | Persons = thePersons;
66 |
67 | if ((Person?)SegmentSelectedItem != thePersons[1])
68 | {
69 | Console.WriteLine("Issue with SegmentedView 🤔");
70 | }
71 | });
72 |
73 | SegmentSelectionChangedCommand = new Command(() =>
74 | {
75 | InfoText = $"Selected item: {SegmentSelectedItem ?? "-"}";
76 | });
77 |
78 | AddItemCommand = new Command(() =>
79 | {
80 | Persons.Add(new(999, "Any", $"One {nextInt++}"));
81 | });
82 |
83 | RemoveItemCommand = new Command(() =>
84 | {
85 | if(Persons.Any())
86 | Persons.RemoveAt(Persons.Count-1);
87 | });
88 |
89 | ClearCommand = new Command(() =>
90 | {
91 | Persons.Clear();
92 | });
93 | }
94 |
95 | public event PropertyChangedEventHandler? PropertyChanged;
96 |
97 | private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
98 | => PropertyChanged?.Invoke(this, new(propertyName));
99 | }
--------------------------------------------------------------------------------
/SampleApp/Resources/Colors.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 | #512BD4
12 | #DFD8F7
13 | #2B0B98
14 | White
15 | Black
16 | #E1E1E1
17 | #C8C8C8
18 | #ACACAC
19 | #919191
20 | #6E6E6E
21 | #404040
22 | #212121
23 | #141414
24 |
25 | #DFD8F7
26 | #2B0B98
27 | #E5E5E1
28 | #969696
29 | #505050
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | #121212
53 | White
54 | White
55 | Black
56 |
57 | #F7B548
58 | #FFD590
59 | #FFE5B9
60 | #28C2D1
61 | #7BDDEF
62 | #C3F2F4
63 | #3E8EED
64 | #72ACF1
65 | #A7CBF6
66 |
67 |
68 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0-android;net9.0-ios
5 | $(TargetFrameworks);net9.0-windows10.0.19041.0
6 |
7 | Exe
8 |
9 |
10 | true
11 | 9.0.90
12 | true
13 |
14 |
15 | enable
16 | latest
17 | enable
18 | SampleApp
19 |
20 |
21 | Segments
22 |
23 |
24 | eu.vapolia.segments
25 |
26 |
27 | 1.0
28 | 1
29 |
30 |
31 | 15.0
32 | 14.0
33 | 27.0
34 | 10.0.19041.0
35 | 10.0.19041.0
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/Vapolia.SegmentedViews.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | net9.0;net9.0-android;net9.0-ios;net9.0-maccatalyst
6 | $(TargetFrameworks);net9.0-windows10.0.19041.0
7 | true
8 | true
9 | enable
10 | enable
11 | true
12 |
13 | 15.0
14 | 14.0
15 | 27.0
16 | 10.0.19041.0
17 | 10.0.19041.0
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | true
33 | true
34 |
35 |
36 |
37 |
38 | 0.0.0-pre1
39 |
40 | $(DefineConstants);
41 |
42 |
43 |
44 | Vapolia.SegmentedViews
45 | Segmented control view for MAUI on iOS and Android
46 | segmented control maui
47 | Segmented control view for MAUI
48 | Powerful segmented control view for MAUI (Android, iOS)
49 | $(Version)$(VersionSuffix)
50 | SegmentedViews
51 | Vapolia
52 | Benjamin Mayrargue
53 | https://vapolia.eu
54 | en
55 | © 2024-2025 Vapolia
56 | https://github.com/vapolia/SegmentedViews
57 | false
58 | LICENSE.md
59 | README.md
60 | icon.png
61 | false
62 | https://github.com/vapolia/SegmentedViews
63 |
64 | 1.0.9: remove TextPropertyName as it uses reflection. Mark library as Trimmable.
65 | 1.0.8: fix typo in Segment which prevented the Width property to work for Segments.
66 | 1.0.7: net9.0 and Windows
67 | 1.0.6: upgrade nugets.
68 | 1.0.5: Upgrade nugets.
69 | 1.0.3: add net8.0 target for unit tests
70 | 1.0.1: Upgrade nugets. Remove dependency on maui compatibility.
71 | 1.0.0: Initial release
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/SampleApp/ViewModels/DynamicItemsPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.Runtime.CompilerServices;
5 | using System.Windows.Input;
6 | using SampleApp.Views;
7 |
8 | namespace SampleApp.ViewModels;
9 |
10 | public record Person(int Id, string FirstName, string LastName);
11 |
12 | ///
13 | /// Sample value converter.
14 | /// You can also override ToString() on the Person class instead of using this converter.
15 | ///
16 | public class PersonTextConverter : IValueConverter
17 | {
18 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
19 | {
20 | var person = (Person)value!;
21 | return $"{person.FirstName} {person.LastName}";
22 | }
23 |
24 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
25 | => throw new NotSupportedException();
26 | }
27 |
28 | public class DynamicItemsPageViewModel : INotifyPropertyChanged
29 | {
30 | private string infoText = string.Empty;
31 | private string infoText2 = string.Empty;
32 | private object? segmentSelectedItem;
33 | private int nextInt = 42;
34 | private int selectedIndexChangedCallCount = 0;
35 |
36 | public object? SegmentSelectedItem
37 | {
38 | get => segmentSelectedItem;
39 | set { segmentSelectedItem = value; OnPropertyChanged(); }
40 | }
41 |
42 | public ICommand SegmentSelectionChangedCommand { get; }
43 |
44 | public string InfoText
45 | {
46 | get => infoText;
47 | set { infoText = value; OnPropertyChanged(); }
48 | }
49 | public string InfoText2
50 | {
51 | get => infoText2;
52 | set { infoText2 = value; OnPropertyChanged(); }
53 | }
54 |
55 | public ICommand AddItemCommand { get; }
56 | public ICommand RemoveItemCommand { get; }
57 | public ICommand ClearCommand { get; }
58 | public ICommand GoTestDelayedInitPageCommand { get; }
59 | public ObservableCollection Persons { get; }
60 |
61 | public int SegmentSelectedIndex
62 | {
63 | get
64 | {
65 | //Not used
66 | throw new NotSupportedException();
67 | }
68 | set
69 | {
70 | //Only for info
71 | InfoText2 = $"Selected index #{++selectedIndexChangedCallCount}: {value}";
72 | }
73 | }
74 |
75 | public DynamicItemsPageViewModel(INavigation navigation)
76 | {
77 | Persons = new(new Person[]
78 | {
79 | new (1, "Johnny", "Halliday"),
80 | new (2, "Vanessa", "Paradis"),
81 | new (3, "Jose", "Garcia"),
82 | });
83 |
84 | //Testing: set selected item after a delay
85 | MainThread.BeginInvokeOnMainThread(async () =>
86 | {
87 | await Task.Delay(TimeSpan.FromSeconds(3));
88 | SegmentSelectedItem = Persons[1];
89 | });
90 |
91 | SegmentSelectionChangedCommand = new Command(() =>
92 | {
93 | InfoText = $"Selected item: {SegmentSelectedItem ?? "-"}";
94 | });
95 |
96 | AddItemCommand = new Command(() =>
97 | {
98 | Persons.Add(new(nextInt, "Any", $"One {nextInt++}"));
99 | });
100 |
101 | RemoveItemCommand = new Command(() =>
102 | {
103 | if(Persons.Any())
104 | Persons.RemoveAt(Persons.Count-1);
105 | });
106 |
107 | ClearCommand = new Command(() =>
108 | {
109 | Persons.Clear();
110 | });
111 |
112 |
113 | GoTestDelayedInitPageCommand = new Command(() =>
114 | {
115 | navigation.PushAsync(new TestDelayedInitPage());
116 | });
117 | }
118 |
119 | public event PropertyChangedEventHandler? PropertyChanged;
120 |
121 | private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
122 | => PropertyChanged?.Invoke(this, new(propertyName));
123 | }
--------------------------------------------------------------------------------
/SampleApp/Views/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
23 |
31 |
39 |
40 |
41 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/SampleApp/Views/DynamicItemsPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
25 |
33 |
34 |
35 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vapolia.SegmentedViews
2 |
3 | [![NuGet][nuget-img]][nuget-link]
4 | 
5 | [](https://github.com/vapolia/SegmentedViews/actions/workflows/main.yaml)
6 |
7 | ```cs
8 | dotnet add package Vapolia.SegmentedViews
9 |
10 | builder.UseSegmentedView();
11 | ```
12 |
13 | [nuget-link]: https://www.nuget.org/packages/Vapolia.SegmentedViews/
14 | [nuget-img]: https://img.shields.io/nuget/v/Vapolia.SegmentedViews
15 |
16 | 
17 | 
18 |
19 |
20 | Platforms:
21 | - Android API 27+
22 | - iOS 15+
23 | - MacOS 14.0+
24 | - Windows 10.0.19041.0+
25 |
26 | Supports both static segments and `ItemsSource` to build segments dynamically.
27 |
28 | # Quick start
29 |
30 | Add the above nuget package to your Maui project
31 | then add this line to your maui app builder:
32 |
33 | ```c#
34 | using Vapolia.SegmentedViews;
35 | ...
36 | builder.UseSegmentedView();
37 | ```
38 |
39 | # Examples
40 |
41 | See the SampleApp in this repo.
42 |
43 | Declare the namespace:
44 | ```xaml
45 |
48 | ```
49 |
50 | Add a static segment view:
51 | ```xaml
52 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | ```
67 |
68 | Or a dynamic segment view:
69 | ```xaml
70 |
77 | ```
78 |
79 | ## Width of segment items
80 |
81 | The width of a segment can be set in the following 3 ways, in reverse order of priority:
82 |
83 | * On the `ItemsDefaultWidth` property of `SegmentedView`
84 | ```xml
85 |
92 | ```
93 |
94 | * On the `ItemsWidthDefinitions` property of `SegmentedView`
95 | ```xml
96 |
103 | ```
104 | This width follow the format of a Grid's ColumnsDefinition, so it should be straightforward to use.
105 |
106 | * Directly on the `Width` property of a `Segment`
107 | ```xml
108 |
109 |
110 | ```
111 |
112 | # IsSelectionRequired feature
113 |
114 | By default, the control requires a selected item. By setting `IsSelectionRequired` to `False`, it won't try to constraint the SelectedIndex between 0 and the number of segments. The visual result is no segment is selected.
115 |
116 | TLDR: set `IsSelectionRequired="False"` and `SelectedIndex="-1"` to visually see no selection.
117 |
118 | # Highlight color on Android
119 |
120 | This is standard Material design on the native Android platform. Check the native doc for more info.
121 |
122 | For quick ref:
123 | 
124 |
125 | # FAQ
126 |
127 | ## Cannot resolve type "https://vapolia.eu/Vapolia.SegmentedViews:segmented:Segment"
128 |
129 | Make sure your SupportedOSPlatformVersion is at least these:
130 | ```xml
131 | 15.0
132 | 15.0
133 | 21.0
134 | 10.0.19041.0
135 | 10.0.19041.0
136 | ```
137 |
138 | ## (2) Cannot resolve type "https://vapolia.eu/Vapolia.SegmentedViews:segmented:Segment"
139 |
140 | replace `xmlns:segmented="https://vapolia.eu/Vapolia.SegmentedViews"`
141 | by
142 | `xmlns:segmented="clr-namespace:Vapolia.SegmentedViews;assembly=Vapolia.SegmentedViews"`
143 |
144 | This looks like a bug in the xamlc compiler.
145 |
146 |
147 | ## Windows
148 |
149 | On windows, this control uses `CommunityToolkit.WinUI.Controls.Segmented`
150 |
--------------------------------------------------------------------------------
/Vapolia.SegmentedViews/WeakEventManager.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace Vapolia.SegmentedViews;
5 |
6 | ///
7 | /// Manages weak event subscriptions to prevent memory leaks
8 | ///
9 | internal static class WeakEventManager
10 | {
11 | private static readonly ConditionalWeakTable