PanAndZoom control for Avalonia
PanAndZoom is delivered as a NuGet package.
You can find the NuGet packages here for Avalonia or by using nightly build feed:
- Add
https://www.myget.org/F/panandzoom-nightly/api/v2to your package sources - Alternative nightly build feed
https://pkgs.dev.azure.com/wieslawsoltes/GitHub/_packaging/Nightly/nuget/v3/index.json - Update your package using
PanAndZoomfeed
You can install the package for Avalonia based projects like this:
Install-Package PanAndZoom -Pre
MainWindow.xaml
<Window x:Class="AvaloniaDemo.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:paz="using:Avalonia.Controls.PanAndZoom"
WindowStartupLocation="CenterScreen" UseLayoutRounding="True"
Title="PanAndZoom" Height="640" Width="640">
<Grid RowDefinitions="Auto,12,Auto,12,*,12" ColumnDefinitions="50,*,50">
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center" Grid.Row="0" Grid.Column="1">
<TextBlock Text="F - Fill"/>
<TextBlock Text="U - Uniform"/>
<TextBlock Text="R - Reset"/>
<TextBlock Text="T - Toggle Stretch Mode"/>
<TextBlock Text="Mouse Wheel - Zoom to Point"/>
<TextBlock Text="Mouse Left Button Down - Pan"/>
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" Grid.Row="2" Grid.Column="1">
<TextBlock Text="PanButton:" VerticalAlignment="Center"/>
<ComboBox Items="{x:Static paz:ZoomBorder.ButtonNames}"
SelectedItem="{Binding #ZoomBorder.PanButton, Mode=TwoWay}"
Margin="2">
</ComboBox>
<TextBlock Text="Stretch:" VerticalAlignment="Center"/>
<ComboBox Items="{x:Static paz:ZoomBorder.StretchModes}"
SelectedItem="{Binding #ZoomBorder.Stretch, Mode=TwoWay}"
Margin="2">
</ComboBox>
<TextBlock Text="ZoomSpeed:" VerticalAlignment="Center"/>
<TextBox Text="{Binding #ZoomBorder.ZoomSpeed, Mode=TwoWay}"
TextAlignment="Center" Width="50" Margin="2"/>
<CheckBox IsChecked="{Binding #ZoomBorder.EnablePan}"
Content="EnablePan" VerticalAlignment="Center"/>
<CheckBox IsChecked="{Binding #ZoomBorder.EnableZoom}"
Content="EnableZoom" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer Grid.Row="4" Grid.Column="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<paz:ZoomBorder Name="ZoomBorder" Stretch="None" ZoomSpeed="1.2"
Background="SlateBlue" ClipToBounds="True" Focusable="True"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Canvas Background="LightGray" Width="300" Height="300">
<Rectangle Canvas.Left="100" Canvas.Top="100" Width="50" Height="50" Fill="Red"/>
<StackPanel Canvas.Left="100" Canvas.Top="200">
<TextBlock Text="Text1" Width="100" Background="Red" Foreground="WhiteSmoke"/>
<TextBlock Text="Text2" Width="100" Background="Red" Foreground="WhiteSmoke"/>
</StackPanel>
</Canvas>
</paz:ZoomBorder>
</ScrollViewer>
</Grid>
</Window>MainWindow.xaml.cs
using System.Diagnostics;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
namespace AvaloniaDemo
{
public class MainWindow : Window
{
private readonly ZoomBorder? _zoomBorder;
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools();
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
if (_zoomBorder != null)
{
_zoomBorder.KeyDown += ZoomBorder_KeyDown;
_zoomBorder.ZoomChanged += ZoomBorder_ZoomChanged;
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void ZoomBorder_KeyDown(object? sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.F:
_zoomBorder?.Fill();
break;
case Key.U:
_zoomBorder?.Uniform();
break;
case Key.R:
_zoomBorder?.ResetMatrix();
break;
case Key.T:
_zoomBorder?.ToggleStretchMode();
_zoomBorder?.AutoFit();
break;
}
}
private void ZoomBorder_ZoomChanged(object sender, ZoomChangedEventArgs e)
{
Debug.WriteLine($"[ZoomChanged] {e.ZoomX} {e.ZoomY} {e.OffsetX} {e.OffsetY}");
}
}
}To get current zoom ratio use ZoomX and ZoomY properties.
To get current pan offset use OffsetX and OffsetY properties.
To constrain zoom ratio use MinZoomX, MaxZoomX, MinZoomY and MaxZoomY properties.
To constrain pan offset use MinOffsetX, MaxOffsetX, MinOffsetY and MaxOffsetY properties.
To enable or disable constrains use EnableConstrains flag.
Enable smooth animations for zoom and pan operations:
zoomBorder.EnableAnimations = true;
zoomBorder.AnimationDuration = TimeSpan.FromMilliseconds(300);Configure double-click zoom behavior:
zoomBorder.EnableDoubleClickZoom = true;
zoomBorder.DoubleClickZoomMode = DoubleClickZoomMode.ZoomInOut; // ZoomIn, ZoomOut, ZoomInOut, ZoomToFit, None
zoomBorder.DoubleClickZoomFactor = 2.0;Prevent panning beyond content boundaries:
zoomBorder.BoundsMode = ContentBoundsMode.KeepContentVisible; // Unrestricted, KeepContentVisible, FillViewport, KeepCentered, Custom
zoomBorder.BoundsPadding = new Thickness(10);
zoomBorder.MinimumVisibleContentPercentage = 0.1; // 10% of content must remain visibleBounds Modes:
- Unrestricted: No bounds checking (default)
- KeepContentVisible: Ensures minimum percentage of content stays visible
- FillViewport: Centers small content and prevents empty space for large content
- KeepCentered: Always keeps content centered
- Custom: Override
GetContentBounds()andValidateTransform()for custom logic
Control how the view adjusts when the control is resized:
zoomBorder.ResizeBehavior = ResizeBehaviorMode.MaintainCenter; // None, MaintainCenter, MaintainTopLeft, MaintainZoom, ReapplyStretch, CustomResize Modes:
- None: No special handling (default)
- MaintainCenter: Keeps the center point stable during resize
- MaintainTopLeft: Keeps the top-left position stable
- MaintainZoom: Maintains zoom level and adjusts position proportionally
- ReapplyStretch: Reapplies the current stretch mode (calls AutoFit)
- Custom: Override
OnResized(Size oldSize, Size newSize)for custom logic
Customize mouse wheel behavior with modifier key support:
zoomBorder.WheelBehavior = WheelBehaviorMode.Zoom; // Zoom, PanVertical, PanHorizontal, None
zoomBorder.WheelWithCtrl = WheelBehaviorMode.Zoom;
zoomBorder.WheelWithShift = WheelBehaviorMode.PanHorizontal;
zoomBorder.WheelZoomSensitivity = 1.0;
zoomBorder.WheelPanSensitivity = 1.0;Wheel Modes:
- Zoom: Zoom in/out (default)
- PanVertical: Pan up/down
- PanHorizontal: Pan left/right
- None: Disable wheel
Example: By default, Shift+Wheel pans horizontally while regular wheel zooms.
Navigate and zoom using keyboard shortcuts:
zoomBorder.EnableKeyboardNavigation = true;
zoomBorder.KeyboardPanStep = 50.0;
zoomBorder.KeyboardZoomStep = 1.1;Built-in Keyboard Shortcuts:
- Arrow Keys: Pan in the respective direction
+/=: Zoom in-: Zoom outCtrl+0: Reset view to identity matrixHome: Fit content to viewportCtrl+Left: Navigate back in view historyCtrl+Right: Navigate forward in view history
Track navigation history with undo/redo support:
zoomBorder.EnableViewHistory = true;
zoomBorder.ViewHistorySize = 50; // Maximum history entries
// Navigate through history
if (zoomBorder.CanNavigateBack)
zoomBorder.NavigateBack();
if (zoomBorder.CanNavigateForward)
zoomBorder.NavigateForward();
// Clear history
zoomBorder.ClearViewHistory();
// Listen for history changes
zoomBorder.ViewHistoryChanged += (sender, args) =>
{
// Update UI state for back/forward buttons
};Programmatically center the viewport on specific points, rectangles, or elements:
// Center on a point in content coordinates
zoomBorder.CenterOn(new Point(100, 100));
// Center on a point with specific zoom
zoomBorder.CenterOn(new Point(100, 100), zoom: 2.0);
// Center on a rectangle (automatically calculates appropriate zoom)
zoomBorder.CenterOn(new Rect(50, 50, 200, 150));
// Center on a control element
var targetControl = this.FindControl<Control>("MyElement");
zoomBorder.CenterOn(targetControl);
// Add padding when centering
zoomBorder.CenterPadding = new Thickness(20);Convert between viewport and content coordinate systems:
// Point conversions
Point contentPoint = zoomBorder.ViewportToContent(viewportPoint);
Point viewportPoint = zoomBorder.ContentToViewport(contentPoint);
// Rectangle conversions
Rect contentRect = zoomBorder.ViewportToContent(viewportRect);
Rect viewportRect = zoomBorder.ContentToViewport(contentRect);
// Vector conversions
Vector contentVector = zoomBorder.ScreenToContent(screenVector);
Vector screenVector = zoomBorder.ContentToScreen(contentVector);
// Size conversions
Size contentSize = zoomBorder.ScreenToContent(screenSize);
Size screenSize = zoomBorder.ContentToScreen(contentSize);
// Get transformation matrices
Matrix contentToScreen = zoomBorder.GetContentToScreenMatrix();
Matrix screenToContent = zoomBorder.GetScreenToContentMatrix();
// Get bounds
Rect visibleContent = zoomBorder.GetVisibleContentBounds();
Rect viewport = zoomBorder.GetViewportBounds();Use built-in ICommand implementations for MVVM scenarios:
<paz:ZoomBorder Name="ZoomBorder">
<!-- Your content -->
</paz:ZoomBorder>
<StackPanel>
<Button Content="Zoom In" Command="{Binding #ZoomBorder.ZoomInCommand}"/>
<Button Content="Zoom Out" Command="{Binding #ZoomBorder.ZoomOutCommand}"/>
<Button Content="Reset" Command="{Binding #ZoomBorder.ResetCommand}"/>
<Button Content="Fit" Command="{Binding #ZoomBorder.FitCommand}"/>
<Button Content="Fill" Command="{Binding #ZoomBorder.FillCommand}"/>
<Button Content="Uniform" Command="{Binding #ZoomBorder.UniformCommand}"/>
<Button Content="Uniform To Fill" Command="{Binding #ZoomBorder.UniformToFillCommand}"/>
<Button Content="Back" Command="{Binding #ZoomBorder.NavigateBackCommand}"/>
<Button Content="Forward" Command="{Binding #ZoomBorder.NavigateForwardCommand}"/>
<Button Content="Toggle Stretch" Command="{Binding #ZoomBorder.ToggleStretchCommand}"/>
</StackPanel>Available Commands:
ZoomInCommand- Zoom in at centerZoomOutCommand- Zoom out at centerResetCommand- Reset to identity matrixFitCommand- Fit content to viewportFillCommand- Fill viewportUniformCommand- Apply uniform stretchUniformToFillCommand- Apply uniform to fill stretchNavigateBackCommand- Navigate back in historyNavigateForwardCommand- Navigate forward in historyToggleStretchCommand- Cycle through stretch modes
All commands respect EnableZoom, EnablePan, and EnableViewHistory settings.
Override these methods to implement custom logic:
public class CustomZoomBorder : ZoomBorder
{
protected override Rect GetContentBounds()
{
// Return custom content bounds for Custom BoundsMode
return base.GetContentBounds();
}
protected override bool ValidateTransform(Matrix newMatrix)
{
// Validate proposed transform matrix
return base.ValidateTransform(newMatrix);
}
protected override void OnResized(Size oldSize, Size newSize)
{
// Custom resize handling for Custom ResizeBehavior
base.OnResized(oldSize, newSize);
}
}Zoom to fit or exactly match specific content rectangles:
// Zoom to fit a rectangle in the viewport
var contentRect = new Rect(100, 100, 200, 150);
zoomBorder.ZoomToRectangle(contentRect);
// Zoom with padding
zoomBorder.ZoomToRectangle(contentRect, padding: new Thickness(20));
// Zoom to exact viewport dimensions
var viewportRect = new Rect(50, 50, 300, 200);
zoomBorder.ZoomToRectangleExact(contentRect, viewportRect);Save and restore named view states for quick navigation:
// Save the current view
zoomBorder.SaveView("DetailView", "Close-up of the detail section");
// Restore a saved view
bool restored = zoomBorder.RestoreView("DetailView");
// Get saved view information
SavedView? view = zoomBorder.GetSavedView("DetailView");
if (view.HasValue)
{
Console.WriteLine($"Name: {view.Value.Name}");
Console.WriteLine($"Description: {view.Value.Description}");
Console.WriteLine($"Saved at: {view.Value.Timestamp}");
}
// List all saved views
string[] viewNames = zoomBorder.GetSavedViewNames();
var allViews = zoomBorder.GetSavedViews();
// Delete a saved view
zoomBorder.DeleteSavedView("DetailView");
// Clear all saved views
zoomBorder.ClearSavedViews();Enable predefined zoom levels for consistent, predictable zooming:
// Enable discrete zoom levels
zoomBorder.EnableDiscreteZoomLevels = true;
// Use default levels: 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0
// Or set custom levels
zoomBorder.DiscreteZoomLevels = new[] { 0.5, 1.0, 2.0, 4.0, 8.0 };
// Navigate between levels
double nextLevel = zoomBorder.GetNextDiscreteZoomLevel();
double previousLevel = zoomBorder.GetPreviousDiscreteZoomLevel();
double nearestLevel = zoomBorder.GetNearestDiscreteZoomLevel(1.3); // Returns 1.0
// Zoom to specific level
zoomBorder.ZoomToLevel(2.0, centerX: 100, centerY: 75);When EnableDiscreteZoomLevels is enabled, all zoom operations will snap to the nearest discrete level.
Query visibility for performance optimization and UI updates:
// Check if a rectangle is visible in the viewport
var rect = new Rect(100, 100, 50, 50);
bool isVisible = zoomBorder.IsRectangleVisible(rect);
// Check if a point is visible
var point = new Point(150, 150);
bool isPointVisible = zoomBorder.IsPointVisible(point);
// Get the visible portion of a rectangle
Rect visiblePortion = zoomBorder.GetVisiblePortion(rect);
// Example: Conditionally render items based on visibility
foreach (var item in items)
{
if (zoomBorder.IsRectangleVisible(item.Bounds))
{
RenderItem(item);
}
}Automatically calculate zoom limits based on content and viewport size:
// Enable auto-calculation of minimum zoom (prevents zooming out beyond content fit)
zoomBorder.AutoCalculateMinZoom = true;
// Enable auto-calculation of maximum zoom (prevents excessive zoom-in)
zoomBorder.AutoCalculateMaxZoom = true;
// Set maximum zoom pixel size (1 content pixel = N screen pixels)
zoomBorder.MaxZoomPixelSize = 4.0; // Default is 4.0
// The effective zoom limits will be automatically calculated and applied
// MinZoom will be set to fit the content in the viewport
// MaxZoom will be set to MaxZoomPixelSizeBenefits:
- Content-aware zoom constraints
- Prevents over-zooming and under-zooming
- Better default behavior for varying content sizes
Display current zoom level with helper methods for custom indicators:
// Enable zoom indicator (note: rendering must be implemented separately)
zoomBorder.ShowZoomIndicator = true;
// Configure indicator position
zoomBorder.ZoomIndicatorPosition = ZoomIndicatorPosition.BottomRight; // TopLeft, TopRight, BottomLeft, BottomRight, Custom
// Set display format
zoomBorder.ZoomIndicatorFormat = "{0:P0}"; // "100%" (default)
zoomBorder.ZoomIndicatorFormat = "{0:F1}x"; // "1.5x"
// Configure auto-hide duration
zoomBorder.ZoomIndicatorAutoHideDuration = TimeSpan.FromSeconds(2);
// Get formatted zoom indicator text
string zoomText = zoomBorder.GetZoomIndicatorText();Note: The ShowZoomIndicator property provides the configuration, but rendering must be implemented using adorners or custom controls that call GetZoomIndicatorText().
Enable grid display and snap-to-grid functionality:
// Show grid overlay (note: rendering must be implemented separately)
zoomBorder.ShowGrid = true;
// Enable snap-to-grid for positioning
zoomBorder.EnableSnapToGrid = true;
// Configure grid
zoomBorder.GridSize = 50.0; // Grid spacing in content coordinates
zoomBorder.GridBrush = Brushes.LightGray;
zoomBorder.GridThickness = 1.0;
zoomBorder.GridOpacity = 0.3;
// Configure major grid lines (every N grid units)
zoomBorder.MajorGridInterval = 5;
zoomBorder.MajorGridBrush = Brushes.Gray;
zoomBorder.MajorGridThickness = 2.0;
// Use snap-to-grid methods
double snappedValue = zoomBorder.SnapToGrid(123.5); // Returns 100.0 or 150.0
Point snappedPoint = zoomBorder.SnapToGrid(new Point(123.5, 67.3));
Rect snappedRect = zoomBorder.SnapToGrid(new Rect(10, 20, 100, 80));Note: The ShowGrid property provides the configuration, but grid rendering must be implemented using adorners or custom rendering that uses the grid properties and coordinate conversion methods.
Snap Methods:
SnapToGrid(double)- Snap a single value to the nearest grid pointSnapToGrid(Point)- Snap a point to the nearest grid intersectionSnapToGrid(Rect)- Snap a rectangle's corners to the nearest grid points
Enable rotation support with constraints and snapping:
// Enable rotation gesture
zoomBorder.EnableGestureRotation = true;
// Set rotation constraints
zoomBorder.MinRotation = -180.0;
zoomBorder.MaxRotation = 180.0;
// Enable rotation snapping
zoomBorder.EnableRotationSnapping = true;
zoomBorder.RotationSnapAngle = 45.0; // Snap to 45° increments
// Rotate content
zoomBorder.Rotate(45.0); // Rotate by 45 degrees
zoomBorder.RotateAt(90.0, new Point(100, 100)); // Rotate around a point
// Reset rotation
zoomBorder.ResetRotation();
// Snap current rotation to nearest angle
zoomBorder.SnapRotation();
// Get current rotation
double currentRotation = zoomBorder.Rotation;Note: The current implementation provides rotation state management and constraints. Full rotation transformation would require additional matrix modifications.
Configure multi-touch gesture behavior:
// Enable simultaneous pan and zoom
zoomBorder.EnableSimultaneousPanZoom = true; // Default: true
// Configure touch point limits
zoomBorder.MinimumTouchPoints = 1;
zoomBorder.MaximumTouchPoints = 2;
// Set gesture recognition delay
zoomBorder.GestureRecognitionDelay = TimeSpan.FromMilliseconds(50);Provide screen reader support and accessibility descriptions:
// Update accessibility descriptions
zoomBorder.UpdateAccessibilityDescriptions();
// Get accessibility description
string description = zoomBorder.GetAccessibilityDescription();
// Returns: "Zoom level: 100%. Pan position: X=0, Y=0"
// Access individual descriptions
string zoomInfo = zoomBorder.ZoomLevelDescription;
string panInfo = zoomBorder.PanPositionDescription;
// Enable high contrast mode support
zoomBorder.UseHighContrastMode = true;Accessibility Features:
- Automatic zoom level descriptions for screen readers
- Pan position descriptions
- High contrast mode support
- Custom accessibility text via properties
Export and import complete control state for persistence or sharing:
// Export current state
ZoomBorderState state = zoomBorder.ExportState();
// State includes:
// - Transformation matrix
// - Stretch mode
// - Zoom speed
// - Enable flags (pan, zoom)
// - Rotation angle
// - Zoom limits
// - Animation settings
// - Timestamp
// Import state
zoomBorder.ImportState(state, animate: true);
// Serialize to JSON (requires System.Text.Json or Newtonsoft.Json)
string json = JsonSerializer.Serialize(state);
// Deserialize from JSON
ZoomBorderState restoredState = JsonSerializer.Deserialize<ZoomBorderState>(json);
zoomBorder.ImportState(restoredState);Use Cases:
- Save/load view configurations
- Share view states between users
- Persist user preferences
- Implement undo/redo functionality
- Session state management
PanAndZoom is licensed under the MIT license.