How to Build a WinUI 3 Barcode Scanner in C# (Image, PDF, and Webcam)

You can build a WinUI 3 barcode scanner in C# by installing the Dynamsoft.DotNet.BarcodeReader.Bundle NuGet package and calling its CaptureVisionRouter API: CaptureMultiPages() decodes image and multi-page PDF files picked by the user, and Capture() decodes webcam frame bytes in memory. One license activation and a few dozen lines of code are all it takes.

The Windows UI Library (WinUI) is a native user experience (UX) framework for both Windows desktop and UWP applications. It is Microsoft’s latest technology to build desktop apps. WinUI comes in two versions. WinUI 3 can be used to build production-ready desktop/Win32 Windows apps while WinUI 2 is mainly used by UWP apps.

In this article, we are going to build a WinUI 3 barcode scanner with Dynamsoft Barcode Reader. It can be used on devices like rugged tablets to perform everyday tasks involving barcodes.

WinUI 3 barcode scanner reading barcodes from an image

What you’ll build: A WinUI 3 desktop app that reads barcodes three ways — from a picked image file, from a (multi-page) PDF document, and from a live webcam preview. The app uses CaptureVisionRouter from Dynamsoft.DotNet.BarcodeReader.Bundle (v11.x) and shows each barcode’s format and text in the window.

Key Takeaways

  • The current .NET package for Dynamsoft Barcode Reader is Dynamsoft.DotNet.BarcodeReader.Bundle (v11.x). Since version 10, the SDK is built on the Dynamsoft Capture Vision architecture and exposes barcode reading through CaptureVisionRouter instead of the legacy BarcodeReader class.
  • CaptureVisionRouter.CaptureMultiPages(path, PresetTemplate.PT_READ_BARCODES) decodes barcodes from image files and multi-page PDF documents in one call; results are read from GetDecodedBarcodesResult() as BarcodeResultItem objects.
  • CaptureVisionRouter.Capture(bytes, PresetTemplate.PT_READ_BARCODES) decodes barcodes from image bytes in memory, which is how webcam frames from MediaCapture are processed without saving files to disk.
  • In a WinUI 3 desktop app, WinRT’s file picker must be initialized with the window’s HWND via WinRT.Interop.WindowNative.GetWindowHandle() and InitializeWithWindow.
  • A valid license key is required; initialize it once with LicenseManager.InitLicense() before creating the router.

Common Developer Questions

How do I read barcodes from an image or PDF file in a WinUI 3 app?

Install the Dynamsoft.DotNet.BarcodeReader.Bundle NuGet package, initialize a license with LicenseManager.InitLicense(), then call CaptureVisionRouter.CaptureMultiPages(filePath, PresetTemplate.PT_READ_BARCODES). The method returns one CapturedResult per page; call GetDecodedBarcodesResult() on each result and read BarcodeResultItem.GetFormatString() and GetText() for every barcode found. In a WinUI 3 desktop app, remember to associate the file picker’s HWND with your window using WinRT.Interop, otherwise the picker will not open.

How do I scan barcodes from the webcam in WinUI 3?

Use MediaCapture with a MediaFrameReader to receive preview frames in the FrameArrived event, encode each SoftwareBitmap to PNG bytes with BitmapEncoder, and pass the bytes to CaptureVisionRouter.Capture(bytes, PresetTemplate.PT_READ_BARCODES). Set MemoryPreference to Cpu in MediaCaptureInitializationSettings so that SoftwareBitmap frames are available, and guard the decoding loop with a flag so only one frame is decoded at a time.

Which NuGet package should I use for barcode reading in a .NET desktop app?

Use Dynamsoft.DotNet.BarcodeReader.Bundle (v11.x). The older Dynamsoft.DotNet.Barcode package (v9.x) is deprecated; the new bundle uses the CaptureVisionRouter API with PresetTemplate.PT_READ_BARCODES and requires a new license key.

Do I need a license to run this sample?

Yes. Dynamsoft Barcode Reader requires a license, which you activate once in code via LicenseManager.InitLicense(key, out errorMsg). The code checks the returned error code against EnumErrorCode.EC_OK and EC_LICENSE_WARNING — see the Prerequisites section for how to get a trial license.

Prerequisites

  • Visual Studio 2022 with the Windows App SDK workload, so the WinUI 3 project templates are available.
  • .NET 6 SDK or later; the sample targets net6.0-windows10.0.19041.0.
  • A Dynamsoft Barcode Reader license key — Get a 30-day free trial license.

Step 1: Create a New WinUI 3 Project

Open Visual Studio 2022 and create a new WinUI 3 project.

New project

Step 2: Install the Barcode Reader NuGet Package

Install Dynamsoft Barcode Reader via NuGet. Since version 10, the SDK is built on the Dynamsoft Capture Vision architecture and the .NET package is Dynamsoft.DotNet.BarcodeReader.Bundle. It exposes the barcode reading functionality through the CaptureVisionRouter API.

Nuget

Step 3: Initialize CaptureVisionRouter with a License

Open MainWindow.xaml.cs and initialize an instance of CaptureVisionRouter. The following namespaces are required:

using Dynamsoft.Core;
using Dynamsoft.CVR;
using Dynamsoft.DBR;
using Dynamsoft.License;
public sealed partial class MainWindow : Window
{
    public CaptureVisionRouter Router { get; set; }
    public MainWindow()
    {
        this.InitializeComponent();
        this.InitBarcodeReader();
    }
    
    private void InitBarcodeReader() {
        string errorMsg;
        int errorCode = LicenseManager.InitLicense("LICENSE-KEY", out errorMsg); //using a one-day trial license
        if (errorCode != (int)EnumErrorCode.EC_OK && errorCode != (int)EnumErrorCode.EC_LICENSE_WARNING)
        {
            // Add your code for license error processing;
            System.Diagnostics.Debug.WriteLine(errorMsg);
        }
        Router = new CaptureVisionRouter();
    }
}

A valid license key is required to use Dynamsoft Barcode Reader — get one as described in the Prerequisites section.

Step 4: Read Barcodes from Image or PDF Files

  1. Open MainWindow.xaml and add a button and a text block.

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="PickImageButton" Click="PickImageButton_Click">Pick an image to read barcodes</Button>
        <TextBlock x:Name="DecodingResultsTextBox" />
    </StackPanel>
    
  2. If the PickImageButton is clicked, show a file selection dialog, read barcodes from that file and display the barcode results. Note that we need to use Interop to call WinRT’s Picker API from a WinUI 3 app (learn more about it here).

    private async void PickImageButton_Click(object sender, RoutedEventArgs e)
    {
        var picker = new Windows.Storage.Pickers.FileOpenPicker();
        // Get the current window's HWND by passing in the Window object
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
    
        // Associate the HWND with the file picker
        WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
    
        picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail;
        picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
        picker.FileTypeFilter.Add(".jpg");
        picker.FileTypeFilter.Add(".jpeg");
        picker.FileTypeFilter.Add(".png");
        picker.FileTypeFilter.Add(".bmp");
        picker.FileTypeFilter.Add(".pdf");
    
        Windows.Storage.StorageFile file = await picker.PickSingleFileAsync();
        if (file != null) {
            List<BarcodeResultItem> results = DecodeFile(file.Path);
            DecodingResultsTextBox.Text = BuildResultsString(results);
        }
    }
    
    // Decode an image or a (multi-page) PDF file
    private List<BarcodeResultItem> DecodeFile(string path)
    {
        List<BarcodeResultItem> allItems = new List<BarcodeResultItem>();
        CapturedResult[] results = Router.CaptureMultiPages(path, PresetTemplate.PT_READ_BARCODES);
        if (results != null)
        {
            foreach (CapturedResult result in results)
            {
                DecodedBarcodesResult barcodesResult = result.GetDecodedBarcodesResult();
                if (barcodesResult != null)
                {
                    allItems.AddRange(barcodesResult.GetItems());
                }
            }
        }
        return allItems;
    }
    
    private string BuildResultsString(List<BarcodeResultItem> results) {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("Found " + results.Count
            .ToString() + " result(s).");
        for (int i = 0; i < results.Count; i++)
        {
            BarcodeResultItem result = results[i];
            sb.Append(i + 1);
            sb.Append(". ");
            sb.Append(result.GetFormatString());
            sb.Append(": ");
            sb.Append(result.GetText());
            sb.Append('\n');
        }
        return sb.ToString();
    }
    

Image decoding

Step 5: Read Barcodes from the Webcam Preview

  1. Open a MainWindow.xaml. Add a LiveScan button and a MediaPlayerElement. The MediaPlayerElement is used as the container for the camera preview. It is in the CameraPanel which is hidden by default and will be displayed when the camera is open.

    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
        <StackPanel x:Name="DefaultPanel" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button x:Name="PickImageButton" Click="PickImageButton_Click">Pick an image to read barcodes</Button>
                <Button x:Name="LiveScanButton" Click="LiveScanButton_Click">Live scan</Button>
            </StackPanel>
            <TextBlock x:Name="DecodingResultsTextBox" />
        </StackPanel>
        <StackPanel x:Name="CameraPanel" Visibility="Collapsed" >
            <MediaPlayerElement
            x:Name="player"
            AutoPlay="True" />
        </StackPanel>
    </StackPanel>
    
  2. Start the camera when the LiveScan button is clicked. We can get the camera frame in the FrameArrived event.

    private MediaCapture _capture;
    private MediaFrameReader _frameReader;
    private MediaSource _mediaSource;
    private async void LiveScanButton_Click(object sender, RoutedEventArgs e) {
        ToggleCameraPanel(true);
        await InitializeCaptureAsync();
    }
       
    private void ToggleCameraPanel(bool on) 
    {
        CameraPanel.Visibility = on ? Visibility.Visible: Visibility.Collapsed;
        DefaultPanel.Visibility = on ? Visibility.Collapsed : Visibility.Visible;
    }
       
    //https://stackoverflow.com/questions/76956862/how-to-scan-a-qr-code-in-winui-3-using-webcam
    private async Task InitializeCaptureAsync()
    {
        // get the first capture device (change this if you want)
        var sourceGroup = (await MediaFrameSourceGroup.FindAllAsync())?.FirstOrDefault();
        if (sourceGroup == null)
            return; // not found!
    
        // init capture & initialize
        _capture = new MediaCapture();
        await _capture.InitializeAsync(new MediaCaptureInitializationSettings
        {
            SourceGroup = sourceGroup,
            SharingMode = MediaCaptureSharingMode.SharedReadOnly,
            MemoryPreference = MediaCaptureMemoryPreference.Cpu, // to ensure we get SoftwareBitmaps
        });
    
        // initialize source
        var source = _capture.FrameSources[sourceGroup.SourceInfos[0].Id];
    
        // create a reader to get frames & pass the reader to player to visualize the webcam
        _frameReader = await _capture.CreateFrameReaderAsync(source, MediaEncodingSubtypes.Bgra8);
        _frameReader.FrameArrived += OnFrameArrived;
        await _frameReader.StartAsync();
    
        _mediaSource = MediaSource.CreateFromMediaFrameSource(source);
        player.Source = _mediaSource;
    }
       
    private async void OnFrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
    {
        var bmp = sender.TryAcquireLatestFrame()?.VideoMediaFrame?.SoftwareBitmap;
        if (bmp == null)
            return;
    }
    
  3. Use Dynamsoft Barcode Reader to read barcodes from the camera frame. If a barcode is found, stop the camera and display the barcode results.

    private bool decoding = false; //use this property to avoid decoding several frames at the same time.
    private async void OnFrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
    {
        var bmp = sender.TryAcquireLatestFrame()?.VideoMediaFrame?.SoftwareBitmap;
        if (bmp == null)
            return;
        if (decoding == true) {
            return;
        }
        decoding = true;
        using (var stream = new InMemoryRandomAccessStream())
        {
            BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
            encoder.SetSoftwareBitmap(bmp);
            await encoder.FlushAsync();
            var bytes = new byte[stream.Size];
            await stream.ReadAsync(bytes.AsBuffer(), (uint)stream.Size, InputStreamOptions.None);
            List<BarcodeResultItem> results = DecodeBytes(bytes);
            System.Diagnostics.Debug.WriteLine(results.Count);
            if (results.Count > 0)
            {
                DispatcherQueue.TryEnqueue(async () => { 
                    DecodingResultsTextBox.Text = BuildResultsString(results);
                    await TerminateCaptureAsync();
                    ToggleCameraPanel(false);
                });
                   
            }
        }
        decoding = false;
    }
    
    // Decode barcode(s) from image bytes in memory (e.g. a camera frame)
    private List<BarcodeResultItem> DecodeBytes(byte[] bytes)
    {
        List<BarcodeResultItem> allItems = new List<BarcodeResultItem>();
        CapturedResult result = Router.Capture(bytes, PresetTemplate.PT_READ_BARCODES);
        DecodedBarcodesResult barcodesResult = result?.GetDecodedBarcodesResult();
        if (barcodesResult != null)
        {
            allItems.AddRange(barcodesResult.GetItems());
        }
        return allItems;
    }
    
    private async Task TerminateCaptureAsync()
    {
        player.Source = null;
    
        _mediaSource?.Dispose();
        _mediaSource = null;
    
        if (_frameReader != null)
        {
            _frameReader.FrameArrived -= OnFrameArrived;
            await _frameReader.StopAsync();
            _frameReader?.Dispose();
            _frameReader = null;
        }
    
        _capture?.Dispose();
        _capture = null;
    }
    

Demo video:

Common Issues & Edge Cases

  • The file picker never opens or throws an exception. In a WinUI 3 desktop (Win32) app, WinRT’s FileOpenPicker must be associated with the window’s HWND via WinRT.Interop.WindowNative.GetWindowHandle(this) and InitializeWithWindow.Initialize(picker, hwnd). Skipping this step is the most common cause of picker failures.
  • No barcode is ever decoded from the camera. Make sure MediaCaptureInitializationSettings.MemoryPreference is set to Cpu; otherwise VideoMediaFrame.SoftwareBitmap can be null and the frame is skipped. Also keep the decoding flag so that only one frame is decoded at a time — concurrent Capture() calls from FrameArrived can starve the UI thread.
  • Decoding returns zero results for everything. Check the return code of LicenseManager.InitLicense(). The public trial license requires a network connection for activation, and a key issued for the old v9 Dynamsoft.DotNet.Barcode package does not work with the v11 Dynamsoft.DotNet.BarcodeReader.Bundle package.
  • UI updates from the camera callback do not appear. FrameArrived is raised on a worker thread; marshal result updates back with DispatcherQueue.TryEnqueue() as shown in Step 5.

Source Code

The source code of the project is available here: Get the complete sample project source code on GitHub