G9MAUIControls.Barcode 1.0.14

dotnet add package G9MAUIControls.Barcode --version 1.0.14
                    
NuGet\Install-Package G9MAUIControls.Barcode -Version 1.0.14
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="G9MAUIControls.Barcode" Version="1.0.14" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="G9MAUIControls.Barcode" Version="1.0.14" />
                    
Directory.Packages.props
<PackageReference Include="G9MAUIControls.Barcode" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add G9MAUIControls.Barcode --version 1.0.14
                    
#r "nuget: G9MAUIControls.Barcode, 1.0.14"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package G9MAUIControls.Barcode@1.0.14
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=G9MAUIControls.Barcode&version=1.0.14
                    
Install as a Cake Addin
#tool nuget:?package=G9MAUIControls.Barcode&version=1.0.14
                    
Install as a Cake Tool

G9MAUIControls.Barcode

Barcode entry field with scan state and format validation, for G9MAUIControls.

dotnet add package G9MAUIControls.Barcode

What it adds

G9BarcodeTextEntry — an outlined text field with an integrated scan affordance. It extends the core's G9TextEntry, so it inherits the whole outlined-field architecture (painted outline, floating label, icon slots, RTL column swapping) and adds:

  • a trailing scan button that raises ScanRequested so you can open your scanner;
  • trailing state feedback — idle glyph → spinner while scanning → accepted tick → error glyph — using the core's cached-icon-host contract, so a state change never flashes a tofu box;
  • optional format validation, so a scan that does not match is rejected at the field rather than downstream;
  • a multi-scan session mode for entering many codes in a row without reopening the scanner.

Why this is a separate package

Barcode entry is a scanning workflow, not a text field: a scan-state machine, an accept/reject rule, and a multi-scan session are opinions about a task, so they stay out of the core's general-purpose input surface. Keeping it separate also lets it version independently of the 25 core controls.

What it deliberately does NOT do: drive a camera. See "Usage" — the control raises ScanRequested and you supply the scanner, which means this package never dictates which camera library your app uses.

Platform setup

These are what your scanner will need. Nothing here is required by this package's own code; it is here because every consumer of this control ends up needing it.

AndroidAndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

required="false" matters: a hard requirement makes your app uninstallable on camera-less devices.

iOSInfo.plist:

<key>NSCameraUsageDescription</key>
<string>Scan barcodes to enter codes without typing them.</string>

Write a real reason. App review rejects placeholder text, and the string is shown to the user verbatim.

Windows / Mac Catalyst — nothing. The scanner reference is Android/iOS-only and these platforms compile against a no-op provider, so the scan button is hidden rather than offering a control that cannot work.

Usage

You own the camera; the control owns the field. G9BarcodeTextEntry does not open a scanner itself — it raises ScanRequested when the user taps its trailing scanner icon, and you show whatever scanner you already use. Feed results back with StartScan() / StopScan() and let the control validate them. That split is deliberate: a control that owned the camera would dictate which scanner package your app uses.

<barcode:G9BarcodeTextEntry
    x:Name="CodeEntry"
    Label="Sample code"
    Text="{Binding Code}"
    AcceptedCodeRegex="^\d{3}/\d{3}/\d{6}$"
    ScanMode="Multiple" />
// User tapped the scanner icon — open your scanner.
CodeEntry.ScanRequested += async (_, _) =>
{
    CodeEntry.StartScan();                    // trailing slot becomes a spinner
    try
    {
        var code = await myScanner.ScanOnceAsync();
        CodeEntry.Text = code;                // rejected silently if AcceptedCodeRegex doesn't match
    }
    finally
    {
        CodeEntry.StopScan();                 // always — otherwise the field stays busy
    }
};

// A code passed AcceptedCodeRegex.
CodeEntry.Accepted += (_, code) => viewModel.Add(code);

ScanMode.Single returns to Idle after one accepted code; ScanMode.Multiple stays armed so an operator can enter many codes without reopening the scanner. ScanState is a G9BarcodeTextEntryStateIdle / ScanBusy / Accepted / Error — and drives the field's chrome, so you can also drive it directly instead of using StartScan / StopScan.

Requirements

.NET 10 · net10.0-android, net10.0-ios, net10.0-maccatalyst, net10.0-windows10.0.19041.0

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0-android36.0 is compatible.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst26.0 is compatible.  net10.0-windows10.0.19041 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.14 71 9/12/2026
1.0.13 89 9/8/2026
1.0.12 79 9/8/2026
1.0.10 97 9/7/2026
1.0.9 92 9/4/2026
1.0.8 87 9/4/2026
1.0.7 89 9/2/2026
1.0.6 88 9/1/2026
1.0.5 115 8/19/2026
1.0.4 106 8/18/2026
1.0.3 101 8/18/2026
1.0.2 101 8/17/2026
1.0.1 113 8/15/2026
1.0.0 113 8/14/2026

1.0.14
       - A default field icon comes back after a subclass affordance has replaced it.

       G9OutlinedFieldBase caches the default leading/trailing G9IconView so a signature flip does not
       detach it and re-trigger the embedded font's rasterisation. A subclass affordance — G9ComboBox's
       clear "x", G9Editor's voice mic — is attached through SetIconHostContent, which removes every
       non-ripple child of the host, INCLUDING that cached view. The cached field still pointed at the
       detached instance, so on the way back ShowDefaultTrailingIcon saw non-null, took its
       "already built, just recolour" branch, and never re-added it to the host; the visibility loop
       below could not find it either. The slot was left holding nothing but the ripple layer.

       Visible as G9ComboBox losing its search glyph permanently once an item had been picked and then
       cleared: magnifier -> x -> NOTHING. A field that had never been touched still looked correct,
       which is what made it read as a combobox bug rather than a caching one. Both
       ShowDefaultTrailingIcon and ShowDefaultLeadingIcon now re-attach the cached view when it is no
       longer a child of its host. Any control that mixes a default icon with a subclass-supplied one
       is affected.

       - Build dependency: Microsoft.SourceLink.GitHub 8.0.0 -> 10.0.401.

       CVE-2026-62900 (GHSA-23fw-v26w-5fgq, published 2026-09-08) affects Microsoft.Build.Tasks.Git
       8.0.0, which SourceLink pulls in, and 8.0.0 has no patched release. 10.0.401 is stable, outside
       every affected range, and in the same band as the pinned SDK. Build-time only (PrivateAssets),
       so nothing about the shipped assemblies changes.

       1.0.13
       - A control that offers a G9Glyphs slot now reads that slot EVERYWHERE, including its own XAML.

       G9Glyphs.X is a settable slot so a consumer can point the suite at its own icon font. But
       Icon="Refresh" written in a library's own XAML is resolved by G9IconSourceTypeConverter to the
       built-in vector G9Glyph and never consults the slot — so a consumer's override reached some of a
       control's icons and not others. On the progress overlay's failure banner that was visible: the
       leading icon came from G9Glyphs.Refresh (the host's own font) while the retry button beside it
       carried the library's built-in drawing, in a different style and turning the opposite way. One
       banner, two refresh icons, disagreeing. G9ProgressOverlayView now assigns
       TerminalRetryIcon.Icon = G9Glyphs.Refresh in code — it has to be code rather than XAML, because
       the slots are configured during consumer startup, after InitializeComponent has run.

       - G9Glyph.Refresh is drawn correctly.

       Its arrow head hung off the START of the arc: the apex sat 3.3 units clear of the stroke and the
       two legs opened AWAY from the direction of travel, so at 18 dp with a round-capped stroke it
       rendered as a broken flag beside the ring rather than an arrow on it. The head is now a chevron
       at the END of the sweep, tangent-aligned, so the arrow continues the stroke. Only the glyph's
       geometry changed; its size, weight and colour behaviour are untouched.

       1.0.12
       - Cleaning up the codebase, removing unused files and improving maintainability.

       1.0.11
       - G9Editor.MaxEditorHeight: a text area that grows as you type, up to a ceiling, and then
       scrolls its own content.

       AutoSize=TextChanges grows the box with the text and never stops, which is right in a scrolling
       page and wrong in a form with anything BELOW the field: a long description pushed a bottom
       sheet's footer buttons off the screen, leaving the user typing into a control whose Save button
       they could no longer reach. Setting MaxEditorHeight constrains the MEASURE, which is what makes
       the platform text view scroll - it is already scrollable, it simply never had a reason to be.
       No ScrollView is added.

       Both the inner editor and the outlined box are capped, the box by the ceiling plus its inner
       padding, or the outline keeps growing around a field that has already stopped. Default 0 means
       no ceiling, so existing editors are unchanged. Ignored when AutoSize is Disabled, which already
       pins the height.

       1.0.10
       - Voice dictation is now a suite capability, not a search-box feature.

       The session engine moved out of G9SearchEntry into G9VoiceDictation (Localization/), a small
       control-agnostic class driven through three delegates. G9TextEntry declares the public surface —
       VoiceEnabled, VoiceCulture, IsListening, Start/Stop/ToggleVoiceAsync and the three voice events —
       and G9Editor gained the same. G9SearchEntry keeps only its default (VoiceEnabled = true) and
       inherits the rest, so nothing a consumer wrote against it changes. Why a class and not a shared
       base: G9Editor is a SIBLING of G9TextEntry under G9OutlinedFieldBase, whose job is outline and
       notch geometry — see ADR-0020.

       Two fixes came with it. The microphone no longer disappears mid-session: its visibility was
       keyed on the field being EMPTY, and a live transcript writes into Text, so the first
       recognized word removed the only control that could stop the session (LES-0044). And a missing
       G9Speech.Provider now really does hide the affordance, which the interface documentation had
       promised since it was written and no code checked.

       On G9Editor the microphone is deliberately NOT value-gated (no clear button competes for the slot,
       and a long description is what people dictate) and it is pinned to the bottom of the box rather
       than centred.

       - G9PopupInputField.Text no longer forces LeftToRight.

       It defaulted to LTR because its sibling factories do, where the pin is correct for a real reason:
       a phone number, an email and a password are written left-to-right in every language. Prose is
       not. In an RTL app the symptom was a "Title" box whose caret sat on the left and whose text ran
       away from its own label. Phone / Email / Password keep the pin. Text and TextArea also gained an
       `enableVoice:` parameter.

       1.0.9
       - G9Diagnostics gets its first call sites: three periods the suite already knew about, now
       reported as busy activities.

       1.0.8 added the seam and only G9SafeCommand raised it, so a host could see commands and nothing
       else. Three `using` scopes now cover the windows where the UI looks ready and is not:
       G9PageBase reports `page:` + the page type name for as long as the page-loading overlay is on screen,
       DeferredContentView reports `deferred:` + the view name across build AND reveal, and
       G9BottomSheetHelper.ShowFullScreenAsync reports `sheet:open` while the open animation runs and a
       factory sheet builds its body.

       Why the library and not the consumer: these three windows are the library's own — it owns the
       overlay, the deferred build and the sheet animation, and it is the only code that knows when each
       one ends. A host can observe that a command started; it cannot observe that a sheet has finished
       opening without reaching into internals that are deliberately private.

       Two details are load-bearing. The page token is released BEFORE the exit animation, not after:
       the page is usable the moment dismissal begins, and holding it through a 200 ms fade would make
       every page transition look like work still in flight. And the sheet scope is released in a
       continuation rather than a `finally`, because the work it covers is asynchronous — a `using`
       around the synchronous call would release before the sheet had opened, which is worse than not
       reporting at all.

       Unchanged from 1.0.8 and still true: nothing subscribes by default, both hooks are null until a
       host assigns them, every invocation is guarded, and the library still decides nothing about what
       busy means. No API, signature or rendering changes.

       1.0.8
       - G9Diagnostics: one observation seam, so a host can tell when its app is busy.

       New static hooks in Helpers/G9Diagnostics.cs — OperationStarted, OperationCompleted and
       ActivityChanged, plus an Activity(name) scope helper. G9SafeCommand raises the first two from the
       trace markers it already emitted.

       Why it belongs here rather than in the consumer: a host that wants to wait for the app to settle
       instead of sleeping, or to record what a user actually did as semantic actions rather than raw
       touches, has to observe the choke points — and in an app built on this suite those choke points
       are G9SafeCommand, G9TabBar, DeferredContentView and G9PageBase, which live in the library. The
       first consumer has 462 G9SafeCommand call sites. Wrapping them host-side would be a week of work
       that decays the moment somebody writes the 463rd; one hook where they already funnel covers every
       existing site and every future one.

       This is NOT a testing feature and must not become one. The library raises two events and decides
       nothing: it never references a test framework, never decides that a build is a test build, and
       never collects anything on its own. What counts as busy, what is worth recording, and whether any
       of it happens at all are the host's decisions, in the host's code and build configuration. The
       moment this file grows a policy, every consumer inherits somebody else's opinions about testing.

       Three details are load-bearing. Both hooks are plain static delegate fields that are null until
       somebody assigns them, so a build that never subscribes pays one predictable branch per operation
       — which is what makes it acceptable to ship in every configuration rather than behind a
       conditional. Every invocation is guarded, because an observer that throws must not be able to
       break the operation it was observing; a diagnostics hook taking down a user's save is the failure
       that would end the feature. And OperationCompleted is raised from G9SafeCommand's finally, so a
       host counting in-flight work can never leak a count when an operation throws and then wait
       forever for an app that is already idle. Anything added to these hooks must preserve that pairing.

       No behaviour change for existing consumers: nothing subscribes by default, and no existing API,
       signature or rendering is touched.

       1.0.7
       - Selection lists and picker triggers: honour a per-item icon colour, and stop the value text
       drifting away from its own icon.

       G9SelectionItem.IconTintColor was ignored by the picker/combobox LIST. The trigger honoured it and
       always had, so an item that carries its own colour — a soil type, a health state, an option a back
       office coloured on purpose — rendered grey in the list and then snapped to its real colour the
       moment it was chosen. The tint now wins in BOTH states, because an item that carries a colour
       carries it as its MEANING, not as decoration.

       ⛔ That took TWO edits, and the first one alone was invisible. Rows are BUILT by
       G9SelectionSheet.CreateRow and then RE-STYLED in place by UpdateRowVisuals on every selection
       change (which exists to avoid a rebuild blink), and the second carried its own copy of the rule —
       writing `selected ? Primary : TextSecondary` straight over the colour the first had just set.
       Both now call one ResolveRowIconColor. A rendered value with two writers has no owner; see
       LES-0043 for the diagnostic lesson, which is the part worth reading.

       G9ComboBox and G9Picker triggers: the value label no longer picks its alignment with a direction
       ternary, and no longer stretches. It carried
       `HorizontalTextAlignment = IsRtl ? End : Start` while ALSO being given the culture's
       FlowDirection — so under RTL it asked for the physical LEFT edge of a box that had just been told
       to read right-to-left. Combined with a Fill/Grow label claiming all the leftover width, the
       selected value ended up on the opposite side of the field from its own leading icon. Alignment is
       Start in both controls now (direction-relative, so the flow mirrors it), and the label is sized to
       its text so the glyph and its label stay one block on the reading edge. Same defect class as the
       G9CultureDateTimeLabel fix in 1.0.2; see LES-0042.

       No API change — all of it is rendering behaviour inside the shared selection row and the two
       triggers. A consumer that had compensated for the old alignment with a PHYSICAL alignment should
       re-check those call sites, exactly as in 1.0.2.

       1.0.6
       - G9SheetView / G9BottomSheetHelper: a multi-detent sheet now behaves like a platform sheet.

       Three defects, one shape. A sheet opened with States = [Peek, Medium] could be dragged past
       its own largest detent all the way to the top of the window, was then snapped back down to
       whatever ratio the caller had guessed for Medium (leaving a band of empty sheet under the
       content), and a downward drag from Medium raised a CLOSE request instead of stepping back to
       Peek. All three came from the same place: the control derived its drag limits from the state
       it happened to be IN, and AllowedState could not express "there is a peek step below this".

       * The drag is clamped to the largest ALLOWED detent (and, for a non-cancelable sheet, to the
       smallest). Below the smallest detent of a cancelable sheet the body now slides off instead
       of shrinking, so a dismiss drag no longer re-lays the content out per frame.
       * G9BottomSheetOptions.ExpandedFitsContent (new, default off) sizes the largest detent to the
       MEASURED content, capped by MaxFitToContentHeightRatio — Material's fitToContents applied
       to the top detent. Content taller than the cap stops at the cap and scrolls inside a
       viewport the helper hosts for it; content shorter than PeekHeight also lowers the peek, so
       neither step can show dead space.
       * G9BottomSheetOptions.ScrollingExpandsSheet (new, default ON) makes a drag on a scrollable
       body expand the sheet before the content scrolls — UIKit's
       prefersScrollingExpandsWhenScrolledToEdge, whose default is likewise on, and Material's
       nested-scroll contract. It is a NO-OP for every single-detent sheet by construction (they
       are always at their maximum detent), so full-screen, single-state and fit-to-content sheets
       scroll exactly as before. Turn it off for a multi-detent sheet whose body must scroll at
       every step.

       Also: the helper now applies IsCancelable / DragCloseThreshold to the control, which it had
       always been documented to do; and the iOS / Windows handlers skip horizontal-only scrollers
       when resolving the scroller under the finger, so a side-scrolling row inside a body can no
       longer swallow that body's vertical scrolling.

       CONSUMER ACTION: none for a single-detent sheet. A multi-detent sheet gains the clamp and the
       scroll-expands behaviour; both are what the platforms do, and ScrollingExpandsSheet = false
       restores the previous gesture priority if a call site depended on it.

       1.0.5
       - G9TabBar: new FabTapped event, raised BEFORE the control acts on a centre-FAB tap. Setting
       G9TabBarFabTappedEventArgs.Handled suppresses the built-in sub-menu fan-out (and the implicit
       move of SelectedIndex to the FAB slot) so a host can give the '+' its own meaning without the
       fan-out flashing open and shut. Additive: unhandled taps behave exactly as in 1.0.4.
       1.0.4 — Icons: restore the affordances the extraction had quietly replaced, and make each one
       themable.

       Extracting these controls out of their host app swapped every host icon reference for the
       package's own built-in vector set. Where an icon had no built-in equivalent, the nearest
       available glyph was substituted — and three of those substitutions changed what the control
       MEANT rather than merely how it looked:

       * G9BarcodeTextEntry showed the SEARCH magnifier at rest. The field opens a camera scanner;
       promising "search" for that is a different action, not a different drawing.
       * G9IntroCarousel's language switcher showed the INFO mark, which reads as help, not as a
       choice of language.
       * G9ProgressOverlayHelper's cancelled-sync toast showed the INFO mark instead of an
       offline/no-connection cloud.

       Two more collapsed distinct affordances onto one slot: the date picker sheet's "jump to today"
       and the time-span picker's "date range" both resolved to the generic Calendar glyph.

       This release adds the built-in glyphs ScanCode, Language and CloudOff (drawn in the same 24x24
       stroke style as the rest), exposes G9Glyphs.ScanCode / Language / CloudOff / CalendarToday /
       DateRange so a host can substitute its own icon font per affordance, and points the affected
       controls at those slots. G9IntroCarousel and G9ProgressOverlayHelper had hard-coded G9Glyph
       values, which no host could override at all; they now go through G9Glyphs like everything else.

       No API removed and no behaviour changed beyond the artwork. A host that sets nothing gets the
       new built-in drawings; a host that wants the pre-extraction icons assigns its own to the five
       new slots.

       1.0.2 — G9CultureDateTimeLabel no longer pins its own FlowDirection.

       The label used to set FlowDirection = LeftToRight for every absolute (Date / Time / DateTime)
       value, to keep a numeric date reading left-to-right inside a right-to-left screen. That is a
       paragraph-direction switch, so it also pinned the label's ALIGNMENT: HorizontalTextAlignment and
       HorizontalOptions resolve Start/End against the view's own effective flow direction, so a consumer
       asking for "Start" got the physical LEFT edge under an RTL culture too. A date could therefore not
       be aligned with the plain Label beside it in both languages — whichever alignment the consumer
       wrote, one language was wrong. The order is now kept by wrapping the formatted value in a Unicode
       LTR embedding (U+202A/U+202C), so the label is an ordinary Label for layout and its logical
       alignment mirrors with the rest of the screen. Relative mode is unchanged (its phrase is localized
       words and must read in the culture's direction), and nothing is wrapped under an LTR culture.

       CONSUMER ACTION: a consumer that compensated by writing a PHYSICAL alignment (e.g.
       HorizontalTextAlignment="End" purely to reach the right edge under Persian) now gets the trailing
       edge in both languages, which is probably not what that call site meant. Express the intent
       logically — Start to align with the label above it, End to sit against the trailing edge — and it
       is correct in both directions.

       1.0.1 — packaging fix, no API or behaviour change.

       G9MAUIControls.Persistence.Sqlite 1.0.0 shipped a Windows resource index (.pri) that named
       `G9MAUIControls.Persistence.Sqlite\icon.png`, a path the package does not contain — the icon is
       packed at the package root, as PackageIcon requires. Every consumer building a net10.0-windows*
       target failed with MSB3030 ("could not copy ... icon.png ... because it was not found"). The
       packaging icon is now kept out of the SDK's default item globs family-wide, so it can no longer be
       indexed as a WinUI resource. Only that one package was affected, because it is the only
       Essentials-only project — the Controls-based four had the image swept back out of  by
       the MAUI targets. Consumers on 1.0.0 that added a build-side workaround can remove it after
       upgrading.

       1.0.0 — first stable release. The controls themselves are not new: they have been carrying a
       production application for a long time, and this version is the point at which the extracted
       PACKAGES have been driven by that same application end to end rather than merely compiling.

       That pass found 21 defects in the library, all fixed here. The most significant were directional:
       G9IconView now pins both of its children to LeftToRight, because an RTL parent made the platform
       mirror the glyph canvas ON TOP of a control's already-correct directional choice — a double flip
       that reversed every chevron and arrow in the suite and silently mirrored the non-directional glyphs
       too. G9Glyph gains ArrowBack / ArrowForward (shaft + head) with overridable G9Glyphs slots, and the
       bottom-sheet header uses them: a bare chevron in a header reads as an expander, not as "go back".

       Verified on Android. iOS, Mac Catalyst and Windows build and pack but have not been rendered — see
       AiGuides/09-Progress.md, which tracks that gap honestly.