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`.
234 lines
7.2 KiB
Markdown
234 lines
7.2 KiB
Markdown
# 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
|
||
|
||
```csharp
|
||
// CORRECT
|
||
_viewerZoomLevel = 1.5; // viewer displays "150%"
|
||
_viewerZoomLevel = _currentZoom / 100d; // _currentZoom=150 -> 1.5
|
||
|
||
// WRONG
|
||
_viewerZoomLevel = 150; // viewer displays "15000%"
|
||
```
|
||
|
||
### PageCount replaces JS call
|
||
|
||
```csharp
|
||
// 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
|
||
|
||
```csharp
|
||
// 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
|
||
|
||
```razor
|
||
<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.
|
||
|
||
```csharp
|
||
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)
|
||
|
||
```csharp
|
||
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
|
||
|
||
- DevExpress official documentation: https://docs.devexpress.com/Blazor/DevExpress.Blazor.PdfViewer.DxPdfViewer
|
||
- Verified package: `DevExpress.Blazor.PdfViewer` v25.2.3
|
||
- **Note:** AI-suggested APIs (GoToPageAsync, PageNumberChanged, ZoomLevelChanged, ZoomAsync, ToolbarVisible) are NOT real. Do not use.
|