Improve shared MAUI controls and tests

This commit is contained in:
Andy Howard
2026-10-06 14:13:28 +01:00
parent 0406adfc99
commit 5daf7ce97f
106 changed files with 2299 additions and 805 deletions
+52 -44
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -8,15 +8,18 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.App
{
//=======================================================================
// Colour references:
// http://www.html-color-names.com/skyblue.php
// http://www.html-color-names.com/lightsteelblue.php
//=======================================================================
public static class Sam
{
//-------------------------------------------------------------------
// Definitions.
// --- Configuration ---
// Values in this class are intended to be configured during application
// startup. Controls normally copy visual defaults when they are created;
// changing a value later does not automatically update existing controls.
// --- Definitions ---
public enum Platform
{
Unknown = 0,
@@ -25,26 +28,30 @@ namespace Sarand.Net.Mobile.Maui.App
MacCatalyst,
Windows
}
// Utilities.
/// <summary>Shared random-number generator for common MAUI code.</summary>
public static Random Random { get; set; } = new Random();
//-------------------------------------------------------------------
// Localisation.
// --- Localisation ---
/// <summary>Application-wide culture settings used by consumers that opt in.</summary>
public static class Locale
{
/// <summary>The culture used for application-level formatting.</summary>
public static CultureInfo CultureInfo { get; set; } = CultureInfo.CurrentCulture;
/// <summary>Text rules associated with <see cref="CultureInfo"/>.</summary>
public static TextInfo TextInfo { get; set; } = CultureInfo.TextInfo;
}
//-------------------------------------------------------------------
// Visuals.
// --- Visuals ---
/// <summary>Supported positions for optional page content.</summary>
public enum DisplayPosition { None, Left, Middle, Right }
/// <summary>Shared visual defaults for common MAUI components.</summary>
public static class Visuals
{
// Application styling.
// --- Application styling ---
public static class Application
{
// Application bar.
// --- Application bar ---
public static class Bar
{
public static double Height { get; set; } = 28;
@@ -54,7 +61,7 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color BorderColor { get; set; } = Colors.DarkSlateBlue;
public static double BorderBottom { get; set; } = 1.0;
}
// Menu.
// --- Menu ---
public static class Menu
{
public static Color BorderColor { get; set; } = Colors.DarkSlateBlue;
@@ -63,28 +70,28 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#E0E0E0");
}
}
// Page layout.
// --- Page layout ---
public static class Page
{
public static Color BaseColor { get; set; } = Color.FromArgb("#c8e8f5");
public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
// Header.
// --- Header ---
public static class Header
{
// Text.
// --- Text ---
public static String TextLeft { get; set; } = "";
public static String TextRight { get; set; } = "";
// Toolbar.
// --- Toolbar ---
public static DisplayPosition ToolbarDisplay { get; set; } = Sam.DisplayPosition.None;
public static bool Display_ToolbarItem_Back { get; set; } = false;
// Styling.
// --- Styling ---
public static Color BackgroundColor { get; set; } = Colors.Honeydew;
public static Color BorderColor { get; set; } = Colors.LightSlateGray;
public static double BorderStrokeWidthTop { get; set; } = 2.0;
public static double BorderStrokeWidthBottom { get; set; } = 1.0;
public static Thickness Padding { get; set; } = new Thickness(8, 4);
}
// Caption.
// --- Caption ---
public static class Caption
{
public static Color BackgroundColor { get; set; } = Color.FromArgb("#558395");
@@ -93,7 +100,7 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color TextColor { get; set; } = Color.FromArgb("#f8f8f8");
public static double TextSizeRatio { get; set; } = 1.25;
}
// Navigation.
// --- Navigation ---
public static class Navigation
{
public static double BorderTopWidth { get; set; } = 0.75;
@@ -101,13 +108,13 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color BorderColor { get; set; } = Color.FromArgb("#808080");
public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
}
// Body.
// --- Body ---
public static class Body
{
public static double PaddingVerticalMin { get; set; } = 4.0;
public static double PaddingHorizontalMin { get; set; } = 4.0;
}
// Footer.
// --- Footer ---
public static class Footer
{
public static String TextLeft { get; set; } = "";
@@ -122,18 +129,20 @@ namespace Sarand.Net.Mobile.Maui.App
public static Thickness Padding { get; set; } = new Thickness(8, 4);
}
}
// Fonts.
// --- Fonts ---
public static class Fonts
{
// Dynamic font sizing.
// Values are calculated from the current device dimensions and are not
// copied from a mutable setting.
// --- Dynamic font sizing ---
public static string HtmlFontSize
{
get
{
if (SamApplication.MinDeviceDimension > 2000) return "14pt";
if (SamApplication.MinDeviceDimension > 1600) return "13pt";
if (SamApplication.MinDeviceDimension > 1200) return "12pt";
if (SamApplication.MinDeviceDimension > 1000) return "11pt";
if (SamApplication.MinDeviceDimensionPx > 2000) return "14pt";
if (SamApplication.MinDeviceDimensionPx > 1600) return "13pt";
if (SamApplication.MinDeviceDimensionPx > 1200) return "12pt";
if (SamApplication.MinDeviceDimensionPx > 1000) return "11pt";
return "10pt";
}
}
@@ -141,25 +150,25 @@ namespace Sarand.Net.Mobile.Maui.App
{
get
{
if (SamApplication.MinDeviceDimension > 2000) return 1.75;
if (SamApplication.MinDeviceDimension > 1600) return 1.50;
if (SamApplication.MinDeviceDimension > 1200) return 1.25;
if (SamApplication.MinDeviceDimensionPx > 2000) return 1.75;
if (SamApplication.MinDeviceDimensionPx > 1600) return 1.50;
if (SamApplication.MinDeviceDimensionPx > 1200) return 1.25;
return 1.0;
}
}
}
// Data display visuals.
// --- Data display visuals ---
public static class Data
{
// Item list.
// --- Item list ---
public static class ItemList
{
public static double HeaderBorderWidth { get; set; } = 2.0;
public static float HeaderBorderRadius = 6.0f;
public static Color HeaderBorderColor = Sam.Visuals.Application.Bar.BackgroundColor;
public static Color HeaderBackgroundColor = Color.FromArgb("#F0F0F0");
public static float HeaderBorderRadius { get; set; } = 6.0f;
public static Color HeaderBorderColor { get; set; } = Sam.Visuals.Application.Bar.BackgroundColor;
public static Color HeaderBackgroundColor { get; set; } = Color.FromArgb("#F0F0F0");
}
// Associated items panel.
// --- Associated items panel ---
public static class AssociatedItems
{
public static Color CaptionBackgroundColor { get; set; } = Color.FromArgb("#becee4");
@@ -168,30 +177,30 @@ namespace Sarand.Net.Mobile.Maui.App
public static Color BackgroundColor { get; set; } = Color.FromArgb("#dbe4f0");
}
}
// Form.
// --- Form ---
public static class Form
{
public static Color BackgroundColor { get; set; } = Color.FromArgb("#e9f6fb");
public static double GridRowSpacing { get; set; } = 4.0;
public static double GridColumnSpacing { get; set; } = 4.0;
// Label.
// --- Label ---
public static class Label
{
public static Thickness Margin { get; set; } = new Thickness(8.0, 4.0, 8.0, 2.0);
}
// Controls.
// --- Controls ---
public static class Controls
{
public static Thickness Padding { get; set; } = new Thickness(4.0, 2.0, 4.0, 1.0);
public static Color BackgroundColor { get; set; } = Color.FromArgb("#F8F8F8");
public static Color BorderColor { get; set; } = Color.FromArgb("#E0E0E0");
public static double BorderWidth { get; set; } = 0.5;
// Native behaviour modified by SamHandler.
// --- Native behaviour modified by SamHandler ---
public static bool NativeNoBorder { get; set; } = false;
public static bool NativeNoUnderline { get; set; } = false;
public static bool NativeBackgroundClear { get; set; } = false;
}
// Tab controls.
// --- Tab controls ---
public static class TabControl
{
public static Color HeaderBackgroundColor { get; set; } = Colors.Transparent;
@@ -202,7 +211,6 @@ namespace Sarand.Net.Mobile.Maui.App
}
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+21 -22
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,20 +7,19 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.App
{
//=======================================================================
// --- Shared MAUI application base ---
public class SamApplication : Application
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private Sam.Platform platform;
private readonly Sam.Platform platform;
// --- Construction ---
//-------------------------------------------------------------------
protected SamApplication()
{
// Determine platform.
// --- Platform ---
#if ANDROID
platform = Sam.Platform.Android;
#elif IOS
@@ -31,30 +30,30 @@ namespace Sarand.Net.Mobile.Maui.App
platform = Sam.Platform.Windows;
#endif
// Log environment.
// --- Startup diagnostics ---
SaLogger.Debug("Platform: " + platform.ToString());
SaLogger.Debug("AppInfo.AppName: " + AppInfo.Name);
SaLogger.Debug("AppInfo.PackageName: " + AppInfo.PackageName);
SaLogger.Debug("AppInfo.Version: " + AppInfo.VersionString);
SaLogger.Debug("AppInfo.Build: " + AppInfo.BuildString);
SaLogger.Debug("DeviceWidth: " + DeviceWidth);
SaLogger.Debug("DeviceHeight: " + DeviceHeight);
SaLogger.Debug("DeviceWidth: " + DeviceWidthPx);
SaLogger.Debug("DeviceHeight: " + DeviceHeightPx);
SaLogger.Debug("DeviceDensity: " + DeviceDisplay.MainDisplayInfo.Density);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
public Sam.Platform Platform { get { return platform; } }
// Device screen size and orientation.
public static bool Landscape => DeviceWidth > DeviceHeight;
public static double DeviceWidth => DeviceDisplay.MainDisplayInfo.Width;
public static double DeviceHeight => DeviceDisplay.MainDisplayInfo.Height;
public static double MinDeviceDimension => Math.Min(DeviceWidth, DeviceHeight);
public static double MaxDeviceDimension => Math.Max(DeviceWidth, DeviceHeight);
// --- Display ---
//-------------------------------------------------------------------
public static bool Landscape => DeviceWidthPx > DeviceHeightPx;
public static double DeviceWidthPx => DeviceDisplay.MainDisplayInfo.Width;
public static double DeviceHeightPx => DeviceDisplay.MainDisplayInfo.Height;
public static double MinDeviceDimensionPx => Math.Min(DeviceWidthPx, DeviceHeightPx);
public static double MaxDeviceDimensionPx => Math.Max(DeviceWidthPx, DeviceHeightPx);
// --- The End ---
}
//=======================================================================
}
+2 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -41,7 +41,7 @@ namespace Sarand.Net.Mobile.Maui.Base
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
protected virtual void SetControlActivity()
protected virtual void SetControlActivity(bool enabled)
{
}
protected virtual void SetControlsReadOnly()
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -0,0 +1,10 @@
namespace Sarand.Net.Mobile.Maui.Controls
{
// --- Definitions ---
public interface ISamControl
{
void SetControlActivity(bool enabled);
// --- The End ---
}
}
@@ -0,0 +1,19 @@
namespace Sarand.Net.Mobile.Maui.Controls
{
// --- Native control styling ---
/// <summary>
/// Exposes native styles applied when a MAUI handler is mapped. These plain properties
/// are read during handler mapping and changes made afterward do not automatically reapply them.
/// </summary>
public interface ISamNativeControlStyle
{
/// <summary>Removes the native control border where supported.</summary>
bool NativeNoBorder { get; set; }
/// <summary>Removes an Android-style underline where supported; other platforms may have no equivalent.</summary>
bool NativeNoUnderline { get; set; }
/// <summary>Makes the native control background transparent where supported.</summary>
bool NativeBackgroundClear { get; set; }
// --- The End ---
}
}
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls.Views;
using Sarand.Net.Mobile.Maui.Controls.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -9,19 +9,15 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls.Panels
{
//=======================================================================
public class SamCheckBoxPanel : SamControlPanel
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private readonly SamCheckBox checkbox = new SamCheckBox() { };
private readonly TapGestureRecognizer gestureSuffixTap = new TapGestureRecognizer() { };
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamCheckBoxPanel()
{
// View configuration.
@@ -40,11 +36,11 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels
LabelSuffix.GestureRecognizers.Add(gestureSuffixTap);
ConfigureComponents();
SetControlActivity();
SetControlActivity(IsEnabled);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
protected override View ControlView { get { return checkbox; } }
public bool IsChecked
{
@@ -57,41 +53,16 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels
set { checkbox.HorizontalOptions = value; ReadOnlyLabel.HorizontalOptions = checkbox.HorizontalOptions; }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
/// <summary>Control configuration that can be changed dynamically.</summary>
public override void ConfigureComponents()
{
base.ConfigureComponents();
}
// --- Control events ---
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
{
base.SetControlActivity();
SetControlsReadOnly();
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
private void Entry_Focused(object? sender, FocusEventArgs e)
{
Control_Focused(sender, e);
}
//-------------------------------------------------------------------
private void Entry_Unfocused(object? sender, FocusEventArgs e)
{
Control_Unfocused(sender, e);
}
//-------------------------------------------------------------------
// Checked changed.
public event EventHandler<CheckedChangedEventArgs>? CheckedChanged;
private void Checkbox_CheckedChanged(object? sender, CheckedChangedEventArgs e)
@@ -106,42 +77,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Panels
checkbox.IsChecked = !checkbox.IsChecked;
}
//-------------------------------------------------------------------
// V i e w E v e n t s
//-------------------------------------------------------------------
protected override void OnParentSet()
{
base.OnParentSet();
}
//-------------------------------------------------------------------
protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanging(propertyName);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
//Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName);
}
//-------------------------------------------------------------------
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
}
protected override void OnChildRemoved(Element child, int oldLogicalIndex)
{
base.OnChildRemoved(child, oldLogicalIndex);
}
//-------------------------------------------------------------------
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -10,14 +10,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls.Views
{
//=======================================================================
public abstract class SamControlPanel : SamContentView
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
// Components.
private HorizontalStackLayout controlLayout = new HorizontalStackLayout();
private SamContentView controlBorder = new SamContentView();
@@ -27,8 +23,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
private Color controlBorderColor = Sam.Visuals.Form.Controls.BorderColor;
private Color controlBackgroundColor = Sam.Visuals.Form.Controls.BackgroundColor;
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
protected SamControlPanel()
{
// Control layout: [Border[Panel]] [Suffix].
@@ -36,14 +32,12 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
controlLayout.Add(controlBorder);
controlLayout.Add(LabelSuffix);
// Main layout.
// --- Main layout ---
Content = controlLayout;
SetControlActivity();
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
// Label.
public SamLabel Label { get; } = new SamLabel() { Margin = Sam.Visuals.Form.Label.Margin };
@@ -92,9 +86,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
set { if (ReadOnlyLabel.Text.Equals(value)) return; ReadOnlyLabel.Text = value; }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
// --- Component configuration ---
/// <summary>Control configuration that can be changed dynamically.</summary>
public override void ConfigureComponents()
{
@@ -104,13 +97,8 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
controlPanel.BackgroundColor = controlBackgroundColor;
}
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
{
base.SetControlActivity();
}
// --- Control activity ---
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
@@ -118,24 +106,21 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
else controlPanel.Content = ControlView;
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
public new event EventHandler<FocusEventArgs>? Focused;
protected void Control_Focused(object? sender, FocusEventArgs e)
{
if (Focused != null) Focused.Invoke(sender, e);
}
//-------------------------------------------------------------------
public new event EventHandler<FocusEventArgs>? Unfocused;
protected void Control_Unfocused(object? sender, FocusEventArgs e)
{
if (Unfocused != null) Unfocused.Invoke(sender, e);
}
//-------------------------------------------------------------------
// V i e w E v e n t s
//-------------------------------------------------------------------
// --- View events ---
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
@@ -156,7 +141,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
}
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,18 +8,14 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls.Views
{
//=======================================================================
public class SamEntryPanel : SamControlPanel
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private readonly SamEntry entry = new SamEntry() { HorizontalOptions = LayoutOptions.Fill };
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamEntryPanel()
{
// View configuration.
@@ -34,11 +30,12 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
entry.TextChanged += Entry_TextChanged;
entry.Completed += Entry_Completed;
SetControlActivity();
ConfigureComponents();
SetControlActivity(IsEnabled);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
protected override View ControlView { get { return entry; } }
public int MaxLength
{
@@ -86,41 +83,16 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
set { entry.HorizontalTextAlignment = value; ReadOnlyLabel.HorizontalTextAlignment = entry.HorizontalTextAlignment; }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
/// <summary>Control configuration that can be changed dynamically.</summary>
public override void ConfigureComponents()
{
base.ConfigureComponents();
}
// --- Control events ---
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
{
base.SetControlActivity();
SetControlsReadOnly();
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
private void Entry_Focused(object? sender, FocusEventArgs e)
{
Control_Focused(sender, e);
}
//-------------------------------------------------------------------
private void Entry_Unfocused(object? sender, FocusEventArgs e)
{
Control_Unfocused(sender, e);
}
//-------------------------------------------------------------------
// Text changed.
public event EventHandler<TextChangedEventArgs>? TextChanged;
private void Entry_TextChanged(object? sender, TextChangedEventArgs e)
@@ -129,7 +101,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
if (ReadOnly) return;
if (TextChanged != null) TextChanged.Invoke(sender, e);
}
//-------------------------------------------------------------------
// Completed.
public event EventHandler? Completed;
private void Entry_Completed(object? sender, EventArgs e)
@@ -138,42 +109,6 @@ namespace Sarand.Net.Mobile.Maui.Controls.Views
if (Completed != null) Completed.Invoke(sender, e);
}
//-------------------------------------------------------------------
// V i e w E v e n t s
//-------------------------------------------------------------------
protected override void OnParentSet()
{
base.OnParentSet();
}
//-------------------------------------------------------------------
protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanging(propertyName);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
//Logger.Debug(GetType().Name + ".OnPropertyChanged Name=" + propertyName);
}
//-------------------------------------------------------------------
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
}
protected override void OnChildRemoved(Element child, int oldLogicalIndex)
{
base.OnChildRemoved(child, oldLogicalIndex);
}
//-------------------------------------------------------------------
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+17 -18
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,31 +6,30 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamButton : Button
/// <summary>
/// Application button extension point. It currently adds no behavior beyond
/// the MAUI <see cref="Button"/> contract.
/// </summary>
public class SamButton : Button, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Construction ---
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SamButton()
{
DefaultFontSize = FontSize;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Properties ---
public double DefaultFontSize { get; }
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+13 -16
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,28 +6,25 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//======================================================================
public class SamCheckBox : CheckBox
/// <summary>
/// Application check-box extension point. It currently adds no behavior
/// beyond the MAUI <see cref="CheckBox"/> contract.
/// </summary>
public class SamCheckBox : CheckBox, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Construction ---
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SamCheckBox()
{
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
//-------------------------------------------------------------------
// Control events.
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
//-------------------------------------------------------------------
// --- The End ---
}
//======================================================================
}
+22 -43
View File
@@ -1,4 +1,5 @@
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -9,24 +10,17 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamDatePicker : SamContentView
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Data members.
// --- Data Members ---
private bool allowInvalidDate = false;
private bool invalidDate = true;
private DateTime previousDate = DateTime.MinValue;
private SamDateValueState dateState = new SamDateValueState();
private SamStackLayout controlLayout = new SamStackLayout() { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.Fill };
private DatePicker pickerDate = new DatePicker() { IsVisible = false };
private SamEntry entryDate = new SamEntry() { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.Fill };
private SamLabel labelResetDate = new SamLabel() { Text = "X", FontAttributes = FontAttributes.Bold, TextColor = Colors.Red, HorizontalOptions = LayoutOptions.Center, VerticalTextAlignment = TextAlignment.Center };
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamDatePicker()
{
// Initialise controls.
@@ -58,17 +52,16 @@ namespace Sarand.Net.Mobile.Maui.Controls
RefreshDateDisplay();
}
//-------------------------------------------------------------------
// Properties.
// Configuration.
// --- Properties ---
public Boolean AllowInvalid
{
get { return allowInvalidDate; }
set
{
if (allowInvalidDate = value) return;
if (allowInvalidDate == value) return;
allowInvalidDate = value;
if (!allowInvalidDate) invalidDate = false;
if (!allowInvalidDate && dateState.IsInvalid)
dateState.SetDate(pickerDate.Date ?? DateTime.Today);
RefreshDateDisplay();
}
}
@@ -91,34 +84,29 @@ namespace Sarand.Net.Mobile.Maui.Controls
get { return pickerDate.MaximumDate ?? DateTime.MaxValue; }
set { pickerDate.MaximumDate = value; }
}
//-------------------------------------------------------------------
// Date value.
// --- Date value ---
public DateTime Date
{
get
{
if (invalidDate) return DateTime.MinValue;
return pickerDate.Date ?? DateTime.MinValue;
return dateState.Date;
}
set
{
if (Date.Equals(value)) return;
if (!DateUtil.IsValid(value))
if (!dateState.SetDate(value)) return;
if (dateState.IsInvalid)
{
invalidDate = true;
pickerDate.Date = DateTime.Today;
}
else
{
invalidDate = false;
pickerDate.Date = value;
}
previousDate = value;
RefreshDateDisplay();
}
}
public String FormattedDate { get { return !invalidDate ? DateUtil.Format(Date, pickerDate.Format) : ""; } }
//-------------------------------------------------------------------
// Font.
public String FormattedDate { get { return !dateState.IsInvalid ? DateUtil.Format(Date, pickerDate.Format) : ""; } }
// --- Font ---
public double DefaultFontSize { get; }
public double FontSize
{
@@ -161,8 +149,7 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// Utilities.
// --- Utilities ---
private void RefreshDateDisplay()
{
if (allowInvalidDate)
@@ -178,44 +165,36 @@ namespace Sarand.Net.Mobile.Maui.Controls
labelResetDate.IsVisible = false;
}
String dateValue = "";
if (!invalidDate)
if (!dateState.IsInvalid)
dateValue = DateUtil.Format(Date, pickerDate.Format);
entryDate.Text = dateValue;
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e)
{
// Date has changed in control.
invalidDate = !DateUtil.IsValid(e.NewDate);
dateState.SetDate(e.NewDate ?? DateTime.MinValue);
RefreshDateDisplay();
if (DateSelected != null) DateSelected.Invoke(sender, e);
}
public event EventHandler<DateChangedEventArgs>? DateSelected;
//-------------------------------------------------------------------
private void EntryDate_Focused(object? sender, FocusEventArgs e)
{
// Control has gained focus.
entryDate.Unfocus();
pickerDate.Focus();
invalidDate = false;
dateState.SetDate(pickerDate.Date ?? DateTime.Today);
RefreshDateDisplay();
}
//-------------------------------------------------------------------
private void LabelValidDate_Tapped(object? sender, EventArgs e)
{
// 'Valid' label tapped.
if (invalidDate) return;
invalidDate = true;
DateTime oldDate = previousDate;
if (!dateState.TryInvalidate(out DateTime oldDate)) return;
RefreshDateDisplay();
if (DateSelected != null) DateSelected.Invoke(sender, new DateChangedEventArgs(oldDate, Date));
previousDate = Date;
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
@@ -9,22 +9,18 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamDateTimePicker : SamContentView
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
// Control layout.
private SamStackLayout layoutControl = new SamStackLayout() { Orientation = StackOrientation.Horizontal };
private SamDatePicker pickerDate = new SamDatePicker();
private SamLabel labelTimePrefix = new SamLabel();
private SamTimePicker pickerTime = new SamTimePicker();
//-------------------------------------------------------------------
// Data members.
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamDateTimePicker()
{
DefaultFontSize = pickerDate.FontSize;
@@ -39,8 +35,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
pickerTime.TimeChanged += PickerTime_TimeChanged;
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
public string DateFormat
{
get { return pickerDate.Format; }
@@ -50,6 +46,11 @@ namespace Sarand.Net.Mobile.Maui.Controls
pickerDate.Format = value;
}
}
public bool AllowInvalid
{
get { return pickerDate.AllowInvalid; }
set { pickerDate.AllowInvalid = value; }
}
public DateTime MinimumDate
{
get { return pickerDate.MinimumDate; }
@@ -69,7 +70,7 @@ namespace Sarand.Net.Mobile.Maui.Controls
pickerTime.Format = value;
}
}
// Control value.
// --- Control value ---
public DateTime DateTime
{
get { return pickerDate.Date.Date + (pickerTime.Time ?? TimeSpan.Zero); }
@@ -81,8 +82,15 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
public String DateTimeDelim { get; set; } = " ";
public String FormattedDateTime { get { return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime; } }
// Styling.
public String FormattedDateTime
{
get
{
if (StringUtil.IsEmpty(pickerDate.FormattedDate)) return "";
return pickerDate.FormattedDate + DateTimeDelim + pickerTime.FormattedTime;
}
}
// --- Styling ---
public double DefaultFontSize { get; }
public double FontSize
{
@@ -101,9 +109,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
set { value = StringUtil.TrimAll(value); if (labelTimePrefix.Text.Equals(value)) return; labelTimePrefix.Text = value; ConfigureComponents(); }
}
//-------------------------------------------------------------------
// C o n f i g u r e C o m p o n e n t s
//-------------------------------------------------------------------
// --- Component configuration ---
public override void ConfigureComponents()
{
base.ConfigureComponents();
@@ -127,34 +134,30 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
// --- Control activity ---
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity();
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void PickerDate_DateSelected(object? sender, DateChangedEventArgs e)
{
if (DateSelected != null) DateSelected.Invoke(sender, e);
}
public event EventHandler<DateChangedEventArgs>? DateSelected;
//-------------------------------------------------------------------
private void PickerTime_TimeChanged(object? sender, SamTimePicker.TimeChangedEventArgs e)
{
if (TimeChanged != null) TimeChanged.Invoke(sender, e);
}
public event EventHandler<SamTimePicker.TimeChangedEventArgs>? TimeChanged;
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+18 -18
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -8,19 +8,15 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamEditor : Editor
public class SamEditor : Editor, ISamNativeControlStyle, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private bool trimTextOnCompletion = true;
private int NumberOfLinesPrevious = 0;
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamEditor()
{
DefaultFontSize = FontSize;
@@ -29,8 +25,15 @@ namespace Sarand.Net.Mobile.Maui.Controls
Completed += SamEditor_Completed;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Properties ---
public double DefaultFontSize { get; }
@@ -44,14 +47,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
// Native behaviour modified by SamHandler.
// --- Native styling ---
public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder;
public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline;
public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear;
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
// Key Press event is not supported directly in Xamarin.
// Requires O/S specific implementation.
// See: Sarand.LogisticsMgr.Mobile.Droid.Forms.Renderers.SlmEditorRenderer
@@ -60,7 +62,6 @@ namespace Sarand.Net.Mobile.Maui.Controls
// Resize control based on entered text.
InvalidateMeasure();
}
//-------------------------------------------------------------------
private void SamEditor_TextChanged(object? sender, TextChangedEventArgs e)
{
// If the number of lines of text has changed, resize the control.
@@ -78,7 +79,6 @@ namespace Sarand.Net.Mobile.Maui.Controls
InvalidateMeasure();
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+32 -53
View File
@@ -1,4 +1,5 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -10,11 +11,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamEntry : Entry
public class SamEntry : Entry, ISamNativeControlStyle, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Definitions ---
public enum TextCaseType
{
Normal,
@@ -23,16 +23,16 @@ namespace Sarand.Net.Mobile.Maui.Controls
Title
}
//-------------------------------------------------------------------
// Data members.
// --- Data Members ---
private bool trimTextOnCompletion = true;
private TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo;
private TextCaseType textCase = TextCaseType.Normal;
private bool numericOnly = false;
private byte decimals = 0;
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamEntry()
{
DefaultFontSize = FontSize;
@@ -40,8 +40,15 @@ namespace Sarand.Net.Mobile.Maui.Controls
Completed += SamEntry_Completed;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Properties ---
public bool TrimText
{
get { return trimTextOnCompletion; }
@@ -95,52 +102,26 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
// Native behaviour modified by SamHandler.
// --- Native styling ---
public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder;
public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline;
public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear;
//-------------------------------------------------------------------
// F o r m a t T e x t
//-------------------------------------------------------------------
// --- Format text ---
private void FormatText(bool trimText = false)
{
String fn = GetType().Name + ".FormatText";
String originalText = Text;
String formattedText = originalText;
if (StringUtil.IsEmpty(formattedText)) return;
if (trimText)
formattedText = StringUtil.TrimAll(formattedText);
if (numericOnly)
SamEntryTextFormatOptions options = new SamEntryTextFormatOptions()
{
if (decimals > 0)
{
decimal value = StringUtil.ToDecimal(formattedText);
if (value != 0.0m)
formattedText = value.ToString("N" + decimals);
else if (!formattedText.Equals("0"))
formattedText = "";
}
else
{
long value = StringUtil.ToLong(formattedText);
if (value != 0)
formattedText = value.ToString();
else if (!formattedText.Equals("0"))
formattedText = "";
}
}
else
{
if (textCase == TextCaseType.Upper)
formattedText = textInfo.ToUpper(formattedText);
else if (textCase == TextCaseType.Lower)
formattedText = textInfo.ToLower(formattedText);
else if (textCase == TextCaseType.Title)
formattedText =
StringUtil.ToTitleCase(formattedText, textInfo);
}
TextCase = textCase,
NumericOnly = numericOnly,
Decimals = decimals,
TrimText = trimText,
TextInfo = textInfo
};
String formattedText = SamEntryTextFormatter.Format(originalText, options);
if (!originalText.Equals(formattedText))
{
@@ -149,9 +130,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void SamEntry_TextChanged(object? sender, TextChangedEventArgs e)
{
if (FormatTextOnChange) FormatText();
@@ -161,7 +141,6 @@ namespace Sarand.Net.Mobile.Maui.Controls
FormatText(trimTextOnCompletion);
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+16 -17
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -8,33 +8,32 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamImageButton : ImageButton
/// <summary>
/// Application image-button extension point. It currently adds no active
/// behavior beyond the MAUI <see cref="ImageButton"/> contract.
/// </summary>
public class SamImageButton : ImageButton, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Construction ---
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SamImageButton()
{
//Clicked += Button_Clicked;
}
//-------------------------------------------------------------------
// Properties.
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- Events ---
//-------------------------------------------------------------------
// Events.
protected virtual void Button_Clicked(object? sender, EventArgs e)
{
SaLogger.Info(SysUtil.Fn + " Button clicked.");
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+36 -27
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,19 +8,16 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamLabel : Label
public class SamLabel : Label, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private double fontSize;
private double fontSizeProportion = 1.0;
private bool applyingFontSize;
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamLabel()
{
fontSize = base.FontSize;
@@ -28,8 +25,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
Text = "";
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
/// <summary>Default (initial) font size.</summary>
public double FontSizeDefault { get; private set; }
@@ -37,42 +34,54 @@ namespace Sarand.Net.Mobile.Maui.Controls
public new double FontSize
{
get { return fontSize; }
set { fontSize = value; base.FontSize = fontSize * fontSizeProportion; }
set
{
if (fontSize == value) return;
fontSize = value;
ApplyFontSize();
}
}
/// <summary>Font size proportion.</summary>
public double FontSizeProportion
{
get { return fontSizeProportion; }
set { fontSizeProportion = value; base.FontSize = fontSize * fontSizeProportion; }
set
{
if (fontSizeProportion == value) return;
fontSizeProportion = value;
ApplyFontSize();
}
}
//-------------------------------------------------------------------
// Control events.
// --- Font sizing ---
//-------------------------------------------------------------------
// View events.
protected override void OnPropertyChanging([CallerMemberName] string propertyName = "")
private void ApplyFontSize()
{
base.OnPropertyChanging(propertyName);
if (propertyName.EqualsCI("FontSize"))
{
}
applyingFontSize = true;
base.FontSize = fontSize * fontSizeProportion;
applyingFontSize = false;
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
base.OnPropertyChanged(propertyName);
if (propertyName.EqualsCI("FontSize"))
if (propertyName.EqualsCI("FontSize") && !applyingFontSize)
{
if (FontSizeDefault <= 0)
FontSizeDefault = base.FontSize;
fontSize = base.FontSize;
if (fontSizeProportion != 0)
fontSize = base.FontSize / fontSizeProportion;
}
}
//-------------------------------------------------------------------
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- The End ---
}
//=======================================================================
}
+51 -44
View File
@@ -1,4 +1,5 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -9,11 +10,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamPicker : Picker
public class SamPicker : Picker, ISamNativeControlStyle, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Definitions ---
public class SamPickerItem
{
public SysId Id { get; set; } = new SysId();
@@ -26,38 +26,37 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// Data members.
private readonly List<SamPickerItem> pickerItems = new List<SamPickerItem>();
// --- Data Members ---
private readonly SamPickerItemCollection pickerItems = new SamPickerItemCollection();
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamPicker()
{
DefaultFontSize = FontSize;
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
public double DefaultFontSize { get; }
/// <summary>Whether the picker is currently replacing its item list.</summary>
public bool BeingPopulated { get; protected set; } = false;
// Native behaviour modified by SamHandler.
// --- Native styling ---
public bool NativeNoBorder { get; set; } = Sam.Visuals.Form.Controls.NativeNoBorder;
public bool NativeNoUnderline { get; set; } = Sam.Visuals.Form.Controls.NativeNoUnderline;
public bool NativeBackgroundClear { get; set; } = Sam.Visuals.Form.Controls.NativeBackgroundClear;
//-------------------------------------------------------------------
// P o p u l a t e I t e m s
//-------------------------------------------------------------------
// --- Item population ---
/// <summary>
/// Does the list contain an item with the same Id.
/// <para>NB Invalid Id values are matched.</para>
/// </summary>
public bool ContainsItemId(SysId id)
{
return pickerItems.Exists(item => SysId.ToInt(item.Id) == SysId.ToInt(id));
return pickerItems.ContainsId(id);
}
/// <summary>
/// Does the list contain an item with the same code.
@@ -65,9 +64,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
/// </summary>
public bool ContainsItemCode(String code)
{
return pickerItems.Exists(item => item.Code.EqualsCI(code));
return pickerItems.ContainsCode(code);
}
//-------------------------------------------------------------------
/// <summary>Remove all items from the picker.</summary>
public void ClearItems()
{
@@ -75,22 +73,31 @@ namespace Sarand.Net.Mobile.Maui.Controls
ItemsSource = null;
pickerItems.Clear();
}
/// <summary>Replace existing picker item list.</summary>
/// <summary>Replace existing picker item list and raise <see cref="Populated"/>.</summary>
public void SetItems(List<SamPickerItem> pickerItems)
{
ClearItems();
this.pickerItems.AddRange(pickerItems);
RefreshPickerItems();
BeingPopulated = true;
bool populated = false;
try
{
ClearItems();
this.pickerItems.Replace(pickerItems);
RefreshPickerItems();
populated = true;
}
finally
{
BeingPopulated = false;
if (populated) InvokePopulatedEvent();
}
}
//-------------------------------------------------------------------
/// <summary>
/// Add an item identified by a SysId value.
/// <para>NB Fails if an item already exists with the same id.</para>
/// </summary>
public bool AddItem(SysId id, String caption)
{
if (ContainsItemId(id)) return false;
pickerItems.Add(new SamPickerItem() { Id = id, Caption = caption });
if (!pickerItems.AddById(new SamPickerItem() { Id = id, Caption = caption })) return false;
RefreshPickerItems();
return ContainsItemId(id);
}
@@ -100,25 +107,20 @@ namespace Sarand.Net.Mobile.Maui.Controls
/// </summary>
public bool AddItem(String code, String caption)
{
if (ContainsItemCode(code)) return false;
pickerItems.Add(new SamPickerItem() { Code = code, Caption = caption });
if (!pickerItems.AddByCode(new SamPickerItem() { Code = code, Caption = caption })) return false;
RefreshPickerItems();
return ContainsItemCode(code);
}
//-------------------------------------------------------------------
private void RefreshPickerItems()
{
//SetBinding(Picker.ItemsSourceProperty, BindableProperty.Create());
//ItemDisplayBinding = new Binding("Caption");
// Picker items doesn't automatically keep up to date with teh ItemsSource list.
// So clear it and reset to force the refresh.
// Picker items do not automatically keep the ItemsSource display in sync.
// Clear and reset the source to force a refresh.
ItemsSource = null;
ItemsSource = pickerItems;
ItemsSource = pickerItems.Items;
}
//-------------------------------------------------------------------
// I t e m S e l e c t i o n
//-------------------------------------------------------------------
// --- Item selection ---
public SamPickerItem? SelectedPickerItem
{
get
@@ -143,7 +145,7 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
set
{
SelectedPickerItem = pickerItems.Find(item => item.Id.Equals(value));
SelectedPickerItem = pickerItems.FindById(value);
}
}
public String SelectedItemCode
@@ -155,13 +157,12 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
set
{
SelectedPickerItem = pickerItems.Find(item => item.Code.EqualsCI(value));
SelectedPickerItem = pickerItems.FindByCode(value);
}
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
// Picker population completed.
public class PopulatedEventArgs : EventArgs { }
public delegate void PopulatedEventHandler(object? sender, PopulatedEventArgs e);
@@ -172,7 +173,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
if (Populated != null) Populated.Invoke(this, new PopulatedEventArgs() { });
}
//-------------------------------------------------------------------
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- The End ---
}
//=======================================================================
}
+35 -35
View File
@@ -1,5 +1,6 @@
using Microsoft.Maui;
using Microsoft.Maui;
using Microsoft.Maui.Layouts;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Panels;
using Sarand.Net.Mobile.Maui.Views;
@@ -11,14 +12,10 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamTabControl : ContentView
public class SamTabControl : ContentView, ISamView
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
private readonly List<SamTabPanel> tabPanels = new List<SamTabPanel>();
private short currentTabIdx = -1;
// Main layout to host header and body.
@@ -28,39 +25,40 @@ namespace Sarand.Net.Mobile.Maui.Controls
// Body - Content view to host tab panel.
private SamContentView bodyPanel = new SamContentView();
//-------------------------------------------------------------------
// Construction.
// --- Construction ---
public SamTabControl()
{
// Main layout - vertical stack with two rows.
// 1. Header - tab panel captions.
// 2. Body - current tab panel.
// --- Main layout ---
VerticalOptions = LayoutOptions.FillAndExpand;
//BackgroundColor = Color.LightSkyBlue;
mainLayout.VerticalOptions = LayoutOptions.FillAndExpand;
mainLayout.Spacing = 2.0;
Content = mainLayout;
// Header panel - tab captions.
//headerPanel.HorizontalOptions = LayoutOptions.Fill;
// --- Header panel ---
headerPanel.BackgroundColor = Sam.Visuals.Form.TabControl.HeaderBackgroundColor;
mainLayout.Children.Add(headerPanel);
// Default label font size.
// --- Default font size ---
DefaultHeaderFontSize = new Label().FontSize;
HeaderFontSize = DefaultHeaderFontSize;
// Body panel.
// --- Body panel ---
bodyPanel.HorizontalOptions = LayoutOptions.Fill;
bodyPanel.VerticalOptions = LayoutOptions.FillAndExpand;
bodyPanel.BackgroundColor = Colors.LightYellow;
bodyPanel.BackgroundColor = Sam.Visuals.Form.TabControl.BodyBackgroundColor;
mainLayout.Children.Add(bodyPanel);
}
//-------------------------------------------------------------------
// Properties.
// --- Properties ---
// Header.
public Color HeaderBackgroundColor { get { return headerPanel.BackgroundColor; } set { headerPanel.BackgroundColor = value; } }
public Color CaptionBackgroundColor { get; set; } = Colors.Transparent;
public Color CaptionBackgroundColor { get; set; } = Sam.Visuals.Form.TabControl.CaptionBackgroundColor;
public double DefaultHeaderFontSize { get; }
public double HeaderFontSize { get; set; }
public double HeaderMarginBottom
@@ -84,9 +82,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
set { ChangeTabPanel(value); }
}
//-------------------------------------------------------------------
// T a b P a n e l s
//-------------------------------------------------------------------
// --- Tab panels ---
public void ClearPanels()
{
tabPanels.Clear();
@@ -106,9 +103,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
RefreshLayout();
}
//-------------------------------------------------------------------
// R e f r e s h L a y o u t
//-------------------------------------------------------------------
// --- Refresh layout ---
private void RefreshLayout()
{
// Clear existing components.
@@ -152,9 +148,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
ChangeTabPanel(currentTabIdx);
}
//-------------------------------------------------------------------
// C h a n g e T a b P a n e l
//-------------------------------------------------------------------
// --- Change tab panel ---
private void ChangeTabPanel(short tabPanelIdx)
{
// Validate requested panel index.
@@ -183,11 +178,16 @@ namespace Sarand.Net.Mobile.Maui.Controls
currentTabIdx = tabPanelIdx;
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Component state ---
//-------------------------------------------------------------------
public void ConfigureComponents() { }
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public void SetControlsReadOnly() { }
// --- The End ---
}
//=======================================================================
}
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamTimePicker : TimePicker
public class SamTimePicker : TimePicker, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
@@ -47,6 +47,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
base.OnPropertyChanged(propertyName);
}
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
//-------------------------------------------------------------------
}
//=======================================================================
+27 -26
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -8,36 +8,33 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Controls
{
//=======================================================================
public class SamWebView : WebView
public class SamWebView : WebView, ISamControl
{
//-------------------------------------------------------------------
// Definitions.
// --- Data Members ---
//-------------------------------------------------------------------
// Data members.
public String html = "";
private HtmlWebViewSource htmlSource;
private UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" };
// Managers.
private readonly HtmlWebViewSource htmlSource;
private readonly UrlWebViewSource aboutBlankSource = new UrlWebViewSource() { Url = "about:blank" };
// --- Construction ---
//-------------------------------------------------------------------
// Construction.
public SamWebView()
{
// Events.
// --- Events ---
Navigating += WebView_Navigating;
Navigated += WebView_Navigated;
// HTML source.
// --- HTML source ---
htmlSource = new HtmlWebViewSource() { Html = html };
Source = aboutBlankSource;
}
//-------------------------------------------------------------------
// Properties.
// HTML Panel.
// --- Properties ---
public static String AppActionPrefix => "App:";
/// <summary>Gets or sets the HTML displayed by the web view.</summary>
public String Html
{
get { return html; }
@@ -51,9 +48,8 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// N a v i g a t i o n
//-------------------------------------------------------------------
// --- Navigation ---
public class AppNavigatingEventArgs : EventArgs
{
public AppNavigatingEventArgs(String appAction)
@@ -70,13 +66,12 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
private String GetAppAction(String location)
{
if (!location.StartsWithCI(AppActionPrefix)) return "";
if (String.IsNullOrEmpty(location) || !location.StartsWithCI(AppActionPrefix)) return "";
return location.Right(location.Length - AppActionPrefix.Length);
}
//-------------------------------------------------------------------
// C o n t r o l E v e n t s
//-------------------------------------------------------------------
// --- Control events ---
private void WebView_Navigating(object? sender, WebNavigatingEventArgs e)
{
String appAction = GetAppAction(e.Url);
@@ -97,7 +92,13 @@ namespace Sarand.Net.Mobile.Maui.Controls
}
}
//-------------------------------------------------------------------
// --- Component state ---
public void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
}
// --- The End ---
}
//=======================================================================
}
+33 -26
View File
@@ -1,4 +1,4 @@
#if ANDROID
#if ANDROID
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
#endif
using Sarand.Net.Mobile.Maui.Controls;
@@ -10,26 +10,25 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Handlers
{
//========================================================================
// Ref. https://dev.to/vhugogarcia/remove-entry-and-picker-borders-in-net-maui-2pk2
// Ref. https://github.com/dotnet/maui/issues/7906
public static class SamFormHandler
{
//--------------------------------------------------------------------
// Definitions.
// --- Control handler registration ---
//--------------------------------------------------------------------
/// <summary>
/// Remove borders around entry and picker controls.
/// Apply configured native styling to Sam entry, editor, and picker controls.
/// <para>Also make background transparent.</para>
/// </summary>
public static void AssignControlHandlers()
{
//---------------------------------------------------------------
// Entry handler.
// --- Entry handler ---
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("SamEntryHandler", (handler, view) =>
{
if ((view as SamEntry)?.NativeNoBorder ?? false)
if (view is not ISamNativeControlStyle style) return;
if (style.NativeNoBorder)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
@@ -40,15 +39,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, };
#endif
}
if ((view as SamEntry)?.NativeNoUnderline ?? false)
if (style.NativeNoUnderline)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
// UITextField/UITextView do not expose a separate Android-style underline.
#elif WINDOWS
// WinUI text controls use a border, not a separate underline; use NativeNoBorder to remove it.
#endif
}
if ((view as SamEntry)?.NativeBackgroundClear ?? false)
if (style.NativeBackgroundClear)
{
#if ANDROID
handler.PlatformView.Background = null;
@@ -56,34 +57,37 @@ namespace Sarand.Net.Mobile.Maui.Handlers
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
#elif WINDOWS
handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
#endif
}
});
//---------------------------------------------------------------
// Editor handler.
// --- Editor handler ---
Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("SamEditorHandler", (handler, view) =>
{
if ((view as SamEditor)?.NativeNoBorder ?? false)
if (view is not ISamNativeControlStyle style) return;
if (style.NativeNoBorder)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
handler.PlatformView.Layer.BorderWidth = 0;
//handler.PlatformView.BorderStyle = UIKit.UITextViewBorderStyle.None;
#elif WINDOWS
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, };
#endif
}
if ((view as SamEntry)?.NativeNoUnderline ?? false)
if (style.NativeNoUnderline)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
// UITextView has no separate underline to remove.
#elif WINDOWS
// WinUI text controls use a border, not a separate underline; use NativeNoBorder to remove it.
#endif
}
if ((view as SamEntry)?.NativeBackgroundClear ?? false)
if (style.NativeBackgroundClear)
{
#if ANDROID
handler.PlatformView.Background = null;
@@ -91,15 +95,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
#elif WINDOWS
handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
#endif
}
});
//---------------------------------------------------------------
// Picker handler.
Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("Borderless", (handler, view) =>
// --- Picker handler ---
Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("SamPickerHandler", (handler, view) =>
{
if ((view as SamEditor)?.NativeNoBorder ?? false)
if (view is not ISamNativeControlStyle style) return;
if (style.NativeNoBorder)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
@@ -110,15 +116,17 @@ namespace Sarand.Net.Mobile.Maui.Handlers
handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() { Bottom = 0, Top = 0, Left = 0, Right = 0, };
#endif
}
if ((view as SamEntry)?.NativeNoUnderline ?? false)
if (style.NativeNoUnderline)
{
#if ANDROID
handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToAndroid());
#elif IOS || MACCATALYST
// The native picker has a full border, not a separate underline; use NativeNoBorder to remove it.
#elif WINDOWS
// The WinUI picker has a full border, not a separate underline; use NativeNoBorder to remove it.
#endif
}
if ((view as SamEntry)?.NativeBackgroundClear ?? false)
if (style.NativeBackgroundClear)
{
#if ANDROID
handler.PlatformView.Background = null;
@@ -126,14 +134,13 @@ namespace Sarand.Net.Mobile.Maui.Handlers
#elif IOS || MACCATALYST
handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
#elif WINDOWS
handler.PlatformView.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Transparent);
#endif
}
});
//---------------------------------------------------------------
}
//--------------------------------------------------------------------
// --- The End ---
}
//========================================================================
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+8 -12
View File
@@ -1,4 +1,4 @@
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Compatibility;
namespace Sarand.Net.Mobile.Maui.Layouts
{
@@ -184,17 +184,13 @@ namespace Sarand.Net.Mobile.Maui.Layouts
{
child = c,
size = c.Measure(widthConstraint, heightConstraint)
});
}).ToList();
var nextChildren = visibleChildren.Skip(1).ToList();
nextChildren.Add(null); //make element count same
var zipChildren = visibleChildren.Zip(nextChildren, (c, n) => new { current = c, next = n });
foreach (var childBlock in zipChildren)
for (var childIndex = 0; childIndex < visibleChildren.Count; childIndex++)
{
var child = childBlock.current.child;
var size = childBlock.current.size;
var childBlock = visibleChildren[childIndex];
var child = childBlock.child;
var size = childBlock.size;
var itemWidth = size.Width;
var itemHeight = size.Height;
@@ -210,7 +206,7 @@ namespace Sarand.Net.Mobile.Maui.Layouts
LayoutChildIntoBoundingRegion(child, region);
}
if (childBlock.next == null)
if (childIndex == visibleChildren.Count - 1)
{
totalHeight += rowHeight;
totalWidth = Math.Max(totalWidth, rowWidth);
@@ -218,7 +214,7 @@ namespace Sarand.Net.Mobile.Maui.Layouts
}
xPos += itemWidth + SpacingX;
var nextWitdh = childBlock.next.size.Width;
var nextWitdh = visibleChildren[childIndex + 1].size.Width;
if (xPos + nextWitdh - x > widthConstraint)
{
@@ -0,0 +1,44 @@
using Sarand.Net.Standard.Base.Utils;
using System;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Date value state ---
public sealed class SamDateValueState
{
private DateTime date = DateTime.MinValue;
private bool invalid = true;
public DateTime Date { get { return invalid ? DateTime.MinValue : date; } }
public bool IsInvalid { get { return invalid; } }
public bool SetDate(DateTime value)
{
if (Date.Equals(value)) return false;
if (DateUtil.IsValid(value))
{
date = value;
invalid = false;
}
else
{
date = DateTime.MinValue;
invalid = true;
}
return true;
}
public bool TryInvalidate(out DateTime previousDate)
{
if (invalid)
{
previousDate = DateTime.MinValue;
return false;
}
previousDate = date;
date = DateTime.MinValue;
invalid = true;
return true;
}
}
}
@@ -0,0 +1,18 @@
using Sarand.Net.Mobile.Maui.Controls;
using System;
using System.Globalization;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Entry text format options ---
public sealed class SamEntryTextFormatOptions
{
public SamEntry.TextCaseType TextCase { get; init; } = SamEntry.TextCaseType.Normal;
public bool NumericOnly { get; init; }
public byte Decimals { get; init; }
public bool TrimText { get; init; }
public TextInfo TextInfo { get; init; } = CultureInfo.CurrentCulture.TextInfo;
// --- The End ---
}
}
@@ -0,0 +1,48 @@
using Sarand.Net.Standard.Base.Utils;
using System;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Entry text formatting ---
public static class SamEntryTextFormatter
{
public static String Format(String? text, SamEntryTextFormatOptions options)
{
String originalText = text ?? "";
String formattedText = originalText;
if (StringUtil.IsEmpty(formattedText)) return formattedText;
if (options.TrimText)
formattedText = StringUtil.TrimAll(formattedText);
if (options.NumericOnly)
{
if (options.Decimals > 0)
{
decimal value = StringUtil.ToDecimal(formattedText);
if (value != 0.0m)
formattedText = value.ToString("N" + options.Decimals);
else if (!formattedText.Equals("0"))
formattedText = "";
}
else
{
long value = StringUtil.ToLong(formattedText);
if (value != 0)
formattedText = value.ToString();
else if (!formattedText.Equals("0"))
formattedText = "";
}
}
else if (options.TextCase == Controls.SamEntry.TextCaseType.Upper)
formattedText = options.TextInfo.ToUpper(formattedText);
else if (options.TextCase == Controls.SamEntry.TextCaseType.Lower)
formattedText = options.TextInfo.ToLower(formattedText);
else if (options.TextCase == Controls.SamEntry.TextCaseType.Title)
formattedText = StringUtil.ToTitleCase(formattedText, options.TextInfo);
return formattedText;
}
// --- The End ---
}
}
@@ -0,0 +1,61 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Picker item collection ---
public sealed class SamPickerItemCollection
{
private readonly List<SamPicker.SamPickerItem> items = new List<SamPicker.SamPickerItem>();
public List<SamPicker.SamPickerItem> Items { get { return items; } }
public void Clear()
{
items.Clear();
}
public void Replace(IEnumerable<SamPicker.SamPickerItem> replacementItems)
{
items.Clear();
items.AddRange(replacementItems);
}
public bool ContainsId(SysId id)
{
return items.Exists(item => SysId.ToInt(item.Id) == SysId.ToInt(id));
}
public bool ContainsCode(String code)
{
return items.Exists(item => item.Code.EqualsCI(code));
}
public bool AddById(SamPicker.SamPickerItem item)
{
if (ContainsId(item.Id)) return false;
items.Add(item);
return true;
}
public bool AddByCode(SamPicker.SamPickerItem item)
{
if (ContainsCode(item.Code)) return false;
items.Add(item);
return true;
}
public SamPicker.SamPickerItem? FindById(SysId id)
{
return items.Find(item => item.Id.Equals(id));
}
public SamPicker.SamPickerItem? FindByCode(String code)
{
return items.Find(item => item.Code.EqualsCI(code));
}
}
}
@@ -0,0 +1,36 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Controls;
using System.Collections.Generic;
namespace Sarand.Net.Mobile.Maui.Logic
{
// --- Sam view tree operations ---
public static class SamViewTreeUtil
{
public static void ConfigureChildren(Element? root)
{
foreach (Element element in GetElements(root))
if (element is ISamView samView)
samView.ConfigureComponents();
}
public static void SetChildControlActivity(Element? root, bool enabled)
{
foreach (Element element in GetElements(root))
{
if (element is ISamView samView)
samView.SetControlActivity(enabled);
if (element is ISamControl samControl)
samControl.SetControlActivity(enabled);
}
}
private static IEnumerable<Element> GetElements(Element? root)
{
if (root == null) yield break;
yield return root;
foreach (Element descendant in root.GetVisualTreeDescendants())
yield return descendant;
}
}
}
+22 -47
View File
@@ -1,4 +1,5 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls;
using Sarand.Net.Mobile.Maui.Logic;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -11,7 +12,7 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Pages
{
//=======================================================================
public abstract class SamContentPage : ContentPage
public abstract class SamContentPage : ContentPage, ISamView
{
//-------------------------------------------------------------------
// Definitions.
@@ -38,61 +39,35 @@ namespace Sarand.Net.Mobile.Maui.Pages
protected virtual void ConfigureChildComponents()
{
// Configure child components.
if (Content != null)
{
ConfigureChildComponent(Content);
foreach (var descendent in Content.GetVisualTreeDescendants())
{
View? childView = descendent as View;
if (childView != null)
ConfigureChildComponent(childView);
}
}
}
private void ConfigureChildComponent(View view)
{
if (view is ISamView)
{
ISamView? samView = view as ISamView;
if (samView != null)
samView.ConfigureComponents();
}
SamViewTreeUtil.ConfigureChildren(Content);
}
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
protected virtual void SetControlActivity()
protected virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
// Child component control activity.
if (Content != null)
{
SetChildControlActivity(Content);
foreach (var descendent in Content.GetVisualTreeDescendants())
{
View? childView = descendent as View;
if (childView != null)
SetChildControlActivity(childView);
}
}
}
private void SetChildControlActivity(View view)
{
if (view is ISamView)
{
ISamView? samView = view as ISamView;
if (samView != null)
samView.SetControlActivity();
}
SamViewTreeUtil.SetChildControlActivity(Content, enabled);
}
protected virtual void SetControlsReadOnly()
{
}
void ISamView.ConfigureComponents()
{
ConfigureComponents();
}
void ISamView.SetControlActivity(bool enabled)
{
SetControlActivity(enabled);
}
void ISamView.SetControlsReadOnly()
{
SetControlsReadOnly();
}
//-------------------------------------------------------------------
// P o p u p M e s s a g e s
//-------------------------------------------------------------------
@@ -107,7 +82,7 @@ namespace Sarand.Net.Mobile.Maui.Pages
String cancel = "OK";
if (alertMode == AlertMode.Close)
cancel = "Close";
return DisplayAlert(title, message, cancel);
return DisplayAlertAsync(title, message, cancel);
}
public enum ConfirmMode
{
@@ -124,7 +99,7 @@ namespace Sarand.Net.Mobile.Maui.Pages
accept = "OK";
cancel = "Cancel";
}
return await DisplayAlert(title, message, accept, cancel);
return await DisplayAlertAsync(title, message, accept, cancel);
}
//-------------------------------------------------------------------
+4 -2
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Views;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -37,11 +37,13 @@ namespace Sarand.Net.Mobile.Maui.Panels
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Layouts;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Standard.Base.Utils;
using System;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Mobile.Maui.Views;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Mobile.Maui.Views;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Standard.Base.Core;
using System;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Utils;
@@ -1,4 +1,4 @@
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Internals;
using Sarand.Net.Mobile.Maui.App;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Views;
using System;
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Controls;
using Sarand.Net.Mobile.Maui.Layouts;
using Sarand.Net.Mobile.Maui.Views;
using System;
+4 -2
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using System;
using System.Collections.Generic;
@@ -38,11 +38,13 @@ namespace Sarand.Net.Mobile.Maui.Panels
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -165,11 +165,13 @@ namespace Sarand.Net.Mobile.Maui.Panels
//-------------------------------------------------------------------
// C o n t r o l A c t i v i t y
//-------------------------------------------------------------------
public override void SetControlActivity()
public override void SetControlActivity(bool enabled)
{
base.SetControlActivity(enabled);
}
public override void SetControlsReadOnly()
{
base.SetControlsReadOnly();
}
//-------------------------------------------------------------------
+1 -1
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Views;
using Sarand.Net.Mobile.Maui.Views;
using System;
using System.Collections.Generic;
using System.Linq;
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+13 -27
View File
@@ -1,9 +1,6 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Utils
@@ -11,39 +8,28 @@ namespace Sarand.Net.Mobile.Maui.Utils
//=======================================================================
public static class SamFormsUtil
{
//-------------------------------------------------------------------
// D i s p l a y A l e r t a n d W a i t
//-------------------------------------------------------------------
public static void DisplayAlertSync(String title, String message, String cancel = "OK")
// --- Display dialogs ---
public static Task DisplayAlertAsync(String title, String message, String cancel = "OK")
{
if (Application.Current?.MainPage == null)
Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page;
if (mainPage == null)
{
SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message);
return;
return Task.CompletedTask;
}
SamThreadUtil.InvokeOnMainThreadSync(async () => await Application.Current.MainPage.DisplayAlert(title, message, cancel));
return SamThreadUtil.InvokeOnMainThreadAsync(() => mainPage.DisplayAlertAsync(title, message, cancel));
}
public static bool DisplayConfirmSync(String title, String message, String accept = "Yes", String cancel = "No")
public static Task<bool> DisplayConfirmAsync(String title, String message, String accept = "Yes", String cancel = "No")
{
if (Application.Current?.MainPage == null)
Page? mainPage = Application.Current?.Windows.FirstOrDefault()?.Page;
if (mainPage == null)
{
SaLogger.Warn(SysUtil.Fn + " Null main page. Message: " + message);
return false;
return Task.FromResult(false);
}
var tcs = new TaskCompletionSource<bool>();
MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
bool result = await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
tcs.SetResult(result);
}
catch (Exception ex) { tcs.SetException(ex); }
});
return tcs.Task.Result;
return SamThreadUtil.InvokeOnMainThreadAsync(() => mainPage.DisplayAlertAsync(title, message, accept, cancel));
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
+13 -32
View File
@@ -1,61 +1,42 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Utils
{
//=======================================================================
public class SamResourceUtil
{
//-------------------------------------------------------------------
// Resource as Stream.
// --- Resource stream ---
public static Stream? GetResourceStream(String resourcePath)
{
return GetResourceStream(Assembly.GetExecutingAssembly(), resourcePath);
}
//-------------------------------------------------------------------
public static Stream? GetResourceStream(Assembly assembly, String resourcePath)
{
if (String.IsNullOrWhiteSpace(resourcePath)) return null;
Stream? stream = assembly.GetManifestResourceStream(resourcePath);
if (stream != null) return stream;
foreach (var res in assembly.GetManifestResourceNames())
foreach (String resourceName in assembly.GetManifestResourceNames())
{
//SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}");
// Read source file
stream = assembly.GetManifestResourceStream(res);
//using var reader = new StreamReader(stream);
//var content = reader.ReadToEnd();
// generate target path inside data folder
//string targetFile = System.IO.Path.Combine(FileSystem.Current.AppDataDirectory, res);
// write file into data folder
//using FileStream outputStream = System.IO.File.OpenWrite(targetFile);
//using StreamWriter streamWriter = new StreamWriter(outputStream);
//streamWriter.Write(content);
if (!resourceName.EndsWith(resourcePath, StringComparison.OrdinalIgnoreCase)) continue;
return assembly.GetManifestResourceStream(resourceName);
}
return null;
}
//-------------------------------------------------------------------
// Log resources.
// --- Resource diagnostics ---
public static void LogResources(Assembly assembly)
{
foreach (var res in assembly.GetManifestResourceNames())
SaLogger.Info(SysUtil.Fn + $" Resource: {res.ToString()}");
foreach (String resourceName in assembly.GetManifestResourceNames())
SaLogger.Info(SysUtil.Fn + $" Resource: {resourceName}");
}
//-------------------------------------------------------------------
// --- The End ---
}
//=======================================================================
}
+25 -36
View File
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System;
using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Utils
@@ -19,12 +16,10 @@ namespace Sarand.Net.Mobile.Maui.Utils
return MainThread.IsMainThread;
}
//-------------------------------------------------------------------
// I n v o k e o n M a i n T h r e a d a n d W a i t
//-------------------------------------------------------------------
// --- Fire-and-forget dispatch ---
/// <summary>Invoke task on main thread.</summary>
public static void InvokeOnMainThread(Action taskFn)
/// <summary>Invoke an action on the main thread without waiting for completion.</summary>
public static void PostToMainThread(Action taskFn)
{
MainThread.BeginInvokeOnMainThread(() =>
{
@@ -32,38 +27,32 @@ namespace Sarand.Net.Mobile.Maui.Utils
});
}
/// <summary>Invoke task on main thread and wait for completion.</summary>
public static void InvokeOnMainThreadSync(Action taskFn)
// --- Awaitable dispatch ---
/// <summary>Invoke an action on the main thread and await its completion.</summary>
public static Task InvokeOnMainThreadAsync(Action taskFn)
{
var tcs = new TaskCompletionSource<Object>();
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
taskFn();
tcs.SetResult(true);
}
catch (Exception ex) { tcs.SetException(ex); }
});
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
/// <summary>Invoke task on main thread and wait for completion.</summary>
public static T InvokeOnMainThreadSync<T>(Func<T> taskFn)
/// <summary>Invoke an asynchronous function on the main thread and await its completion.</summary>
public static Task InvokeOnMainThreadAsync(Func<Task> taskFn)
{
var tcs = new TaskCompletionSource<T>();
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
var result = taskFn();
tcs.SetResult(result);
}
catch (Exception ex) { tcs.SetException(ex); }
});
return tcs.Task.Result;
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
//-------------------------------------------------------------------
/// <summary>Invoke a function on the main thread and await its result.</summary>
public static Task<T> InvokeOnMainThreadAsync<T>(Func<T> taskFn)
{
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
/// <summary>Invoke an asynchronous function on the main thread and await its result.</summary>
public static Task<T> InvokeOnMainThreadAsync<T>(Func<Task<T>> taskFn)
{
return MainThread.InvokeOnMainThreadAsync(taskFn);
}
// --- The End ---
}
//=======================================================================
}
+1 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+2 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -18,7 +18,7 @@ namespace Sarand.Net.Mobile.Maui.Views
/// </summary>
void ConfigureComponents();
void SetControlActivity();
void SetControlActivity(bool enabled);
void SetControlsReadOnly();
+4 -3
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Sarand.Net.Mobile.Maui.Views
{
//=======================================================================
public class SamBorder : Border
public class SamBorder : Border, ISamView
{
//-------------------------------------------------------------------
// Definitions.
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
+3 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
+5 -4
View File
@@ -1,4 +1,4 @@
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Mobile.Maui.Utils;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
@@ -33,7 +33,7 @@ namespace Sarand.Net.Mobile.Maui.Views
public bool ReadOnly
{
get { return readOnly; }
set { if (readOnly == value) return; readOnly = value; SetControlActivity(); }
set { if (readOnly == value) return; readOnly = value; SetControlActivity(IsEnabled); }
}
//-------------------------------------------------------------------
@@ -44,8 +44,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
@@ -60,7 +61,7 @@ namespace Sarand.Net.Mobile.Maui.Views
base.OnPropertyChanged(propertyName);
if (propertyName.EqualsCI(IsEnabledProperty.PropertyName))
{
SetControlActivity();
SetControlActivity(IsEnabled);
}
}
+3 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()
+3 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -32,8 +32,9 @@ namespace Sarand.Net.Mobile.Maui.Views
//-------------------------------------------------------------------
// Control activity.
public virtual void SetControlActivity()
public virtual void SetControlActivity(bool enabled)
{
if (IsEnabled != enabled) IsEnabled = enabled;
SetControlsReadOnly();
}
public virtual void SetControlsReadOnly()