Files
EnvelopeGenerator/DEVEXPRESS_V25_LIMITATIONS.md
TekH 99fbb33f1c Migrate PDF.js to DevExpress DxPdfViewer
Transitioned PDF rendering in `EnvelopeReceiverPage.razor`
from `PDF.js` to `DevExpress DxPdfViewer`. Updated code
and documentation to reflect the verified API of
`DevExpress.Blazor.PdfViewer` v25.2.3, addressing its
limitations (e.g., lack of `GoToPageAsync`, `PageNumberChanged`).

Implemented `CustomizeToolbar` for navigation/zoom controls
and manual state tracking for `_currentPage` and `_viewerZoomLevel`.
Replaced JavaScript interop for page count with the `PageCount`
property. Retained the custom thumbnail sidebar due to API
constraints.

Added temporary debug tools for DOM analysis and navigation
testing. Updated `TESTING_CHECKLIST.md` and added
`DEVEXPRESS_V25_LIMITATIONS.md` to document the new strategy,
API limitations, and testing scenarios. Cross-page signature
navigation implemented with state updates, though visible
page changes remain manual.

Prepared for future improvements while ensuring functional
migration to `DxPdfViewer`.
2026-06-30 16:12:05 +02:00

7.2 KiB
Raw Blame History

DevExpress Blazor PdfViewer v25.2.3 - Verified API Reference

Source: All information in this document has been verified from the actual source code of DevExpress.Blazor.PdfViewer v25.2.3 package. AI-generated API suggestions (GoToPageAsync, PageNumberChanged, etc.) are NOT real do not use them.


Verified Available Parameters

Property Type Access Default Description
DocumentContent byte[] [Parameter] GET/SET Feeds PDF content as byte array
CssClass string [Parameter] GET/SET Assigns CSS class to component
DocumentName string [Parameter] GET/SET "Document" Download filename
IsSinglePagePreview bool [Parameter] GET/SET false Single page mode
SizeMode SizeMode? [Parameter] GET/SET null Small, Medium, Large
ZoomLevel double [Parameter] GET/SET -1 Factor (not percentage). 1.5 = 150%
ActivePageIndex int GET only Active page index (0-based). No SET.
PageCount int GET only Total page count in document

Missing Events (NOT AVAILABLE in v25.2.3)

  • PageNumberChanged Not available
  • ZoomLevelChanged Not available
  • User scrolling or native toolbar page changes do not trigger C# code

Missing Properties (NOT AVAILABLE in v25.2.3)

  • ToolbarVisible Not available (toolbar cannot be completely hidden)
  • ActivePageIndex (settable) Read-only; no programmatic page navigation

Missing Methods (NOT AVAILABLE in v25.2.3)

  • GoToPageAsync() Not available
  • GoToNextPageAsync() Not available
  • ZoomAsync() Not available

Available Event

  • CustomizeToolbar Only available event for toolbar customization

Critical Integration Notes

ZoomLevel takes factor, not percentage

// CORRECT
_viewerZoomLevel = 1.5;   // viewer displays "150%"
_viewerZoomLevel = _currentZoom / 100d;  // _currentZoom=150 -> 1.5

// WRONG
_viewerZoomLevel = 150;   // viewer displays "15000%"

PageCount replaces JS call

// CORRECT - read directly from component (no JS needed)
_totalPages = _pdfViewer.PageCount;

// OLD method (no longer needed for this purpose)
// _totalPages = await JSRuntime.InvokeAsync<int>("pdfViewer.getTotalPages");

ActivePageIndex is read-only

// CORRECT - read for state synchronization
var currentPage = _pdfViewer.ActivePageIndex + 1; // convert to 1-based

// COMPILE ERROR - no setter
// _pdfViewer.ActivePageIndex = 3; // COMPILE ERROR

DocumentContent byte[] feeding

<DxPdfViewer @ref="_pdfViewer"
             DocumentContent="@_pdfDocumentContent"
             ZoomLevel="@_viewerZoomLevel"
             IsSinglePagePreview="true" />

@code {
    DxPdfViewer? _pdfViewer;
    byte[]? _pdfDocumentContent;  // populate in OnInitializedAsync
    double _viewerZoomLevel = 1.5;  // 150%
}

Impact on EnvelopeReceiverPage

Features That Don't Work

  1. Event-driven overlay updates No page/zoom change events
  2. Thumbnail click navigation Cannot navigate viewer to specific page via C# API
  3. Cross-page signature navigation No programmatic page change API
  4. Automatic overlay synchronization User scroll/native toolbar doesn't trigger C#

Features That Work

  1. ZoomLevel binding Custom zoom buttons can update viewer zoom
  2. PageCount Total pages can be read directly from component
  3. IsSinglePagePreview Single page mode works
  4. DocumentContent byte[] feeding works perfectly
  5. CustomizeToolbar Only way to add custom buttons to toolbar

Workaround Strategy

CustomizeToolbar event is used to add custom navigation/zoom buttons. Manual state tracking (_currentPage, _currentZoom, _viewerZoomLevel) is kept in C#. Overlay refresh is manually triggered only after button clicks.

protected void OnCustomizeToolbar(ToolbarModel toolbarModel)
{
    toolbarModel.AllItems.Clear();

    var prevButton = new ToolbarItem
    {
        Text = "Previous",
        IconCssClass = "dx-icon-chevronprev",
        Enabled = _currentPage > 1,
        Click = async (args) =>
        {
            if (_currentPage > 1)
            {
                _currentPage--;
                _viewerZoomLevel = _currentZoom / 100d;
                await InvokeAsync(StateHasChanged);
                await RenderSignatureButtonsAsync();
            }
        }
    };

    var nextButton = new ToolbarItem
    {
        Text = "Next",
        IconCssClass = "dx-icon-chevronnext",
        Enabled = _currentPage < _totalPages,
        Click = async (args) =>
        {
            if (_currentPage < _totalPages)
            {
                _currentPage++;
                _viewerZoomLevel = _currentZoom / 100d;
                await InvokeAsync(StateHasChanged);
                await RenderSignatureButtonsAsync();
            }
        }
    };

    var zoomInButton = new ToolbarItem
    {
        IconCssClass = "dx-icon-plus",
        Enabled = _currentZoom < 300,
        Click = async (args) =>
        {
            _currentZoom = Math.Min(_currentZoom + 10, 300);
            _viewerZoomLevel = _currentZoom / 100d;  // 150 -> 1.5
            await InvokeAsync(StateHasChanged);
            await RenderSignatureButtonsAsync();
        }
    };

    var zoomOutButton = new ToolbarItem
    {
        IconCssClass = "dx-icon-minus",
        Enabled = _currentZoom > 50,
        Click = async (args) =>
        {
            _currentZoom = Math.Max(_currentZoom - 10, 50);
            _viewerZoomLevel = _currentZoom / 100d;  // 150 -> 1.5
            await InvokeAsync(StateHasChanged);
            await RenderSignatureButtonsAsync();
        }
    };

    toolbarModel.AllItems.Add(prevButton);
    toolbarModel.AllItems.Add(nextButton);
    toolbarModel.AllItems.Add(zoomInButton);
    toolbarModel.AllItems.Add(zoomOutButton);
}

PageCount reading example (in OnAfterRenderAsync)

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!_pdfLoaded && _pdfDocumentContent is { Length: > 0 })
    {
        await Task.Delay(300); // wait for DxPdfViewer to load

        if (_pdfViewer is not null && _pdfViewer.PageCount > 0)
        {
            _totalPages = _pdfViewer.PageCount;  // read directly instead of JS
            _pdfLoaded = true;
            await InvokeAsync(StateHasChanged);
            await RenderThumbnailsAsync();
            await RenderSignatureButtonsAsync();
        }
    }
}

Known Acceptable Limitations

  1. If user scrolls PDF, C# _currentPage does not synchronize
  2. Thumbnail clicks update state but cannot move DevExpress viewer to target page
  3. Browser zoom gestures do not trigger overlay updates
  4. Custom toolbar buttons correctly trigger overlay updates

References