当前位置: 首页 > news >正文

WPF 视图缩略图控件(支持缩放调节与拖拽定位)

实现 WPF 应用中画布的缩放控制与缩略图导航,支持滑块调节缩放比例、缩略图拖拽定位,实时同步主画布视图与缩略图视口位置。

  1. 缩放控制:通过 Slider 值变化计算缩放比例,同步更新主画布 ScaleTransform,并调整 ScrollViewer 偏移,确保缩放中心对齐视图中心。

  2. 缩略图同步: - 主画布 LayoutUpdated 时,计算缩略图缩放比例,更新 Thumb(视口)的大小与位置; - 拖拽 Thumb 时,反向计算主画布 ScrollViewer 的偏移量,实现快速定位。

  3. 依赖注入:通过 ScrollViewer 依赖属性,建立自定义控件与主画布的关联,解耦控件与业务画布。

关键点

  1. VisualBrush/RenderTargetBitmap:两种方式生成主画布缩略图(XAML 用 VisualBrush 实时同步,代码中 CreateThumbnail 方法用 RenderTargetBitmap 静态渲染)。

  2. 模板绑定:重写 OnApplyTemplate 获取模板内命名元素(PART_前缀),确保控件结构合法性。

  3. 坐标换算:通过主画布与缩略图的尺寸比例,实现主视图偏移量与缩略图视口位置的双向映射。

添加用户控件 ZoomBoxView.xaml

<UserControlx:Class="WpfMiniaturesDemo.ZoomBoxView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:WpfMiniaturesDemo"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d"><BorderMinHeight="30"Background="#F6F6F6"CornerRadius="6"><Border.Effect><DropShadowEffectBlurRadius="3"ShadowDepth="1"Color="#E8E8E8" /></Border.Effect><!--  可展开的区域  默认关闭  --><Expander Background="Transparent" IsExpanded="False"><Border Height="220" Background="White"><!--  Canvas画布,,并设置边距  --><Canvas Name="PART_ZoomCanvas"><Canvas.Background><VisualBrush Stretch="Uniform" Visual="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ZoomBoxView}}, Path=ScrollViewer.Content}" /></Canvas.Background><!--  Thumb控件,并设置鼠标样式  --><Thumb Name="PART_ZoomThumb" Cursor="SizeAll"><Thumb.Style><Style TargetType="Thumb"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Thumb"><!--  设置Thumb控件的样式矩形  --><RectangleFill="Transparent"Stroke="Black"StrokeThickness="1" /></ControlTemplate></Setter.Value></Setter></Style></Thumb.Style></Thumb></Canvas></Border><!--  设置可展开区域的头部  --><Expander.Header><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><!--  滑块  --><SliderName="PART_ZoomSlider"MinWidth="110"Margin="0"HorizontalAlignment="Center"VerticalAlignment="Center"IsMoveToPointEnabled="False"IsSnapToTickEnabled="True"Maximum="150"Minimum="80"Ticks="80,85,90,95,100,105,110,115,120,125,130,135,140,145,150"Value="100" /><!--  绑定到滑块的值  --><TextBlockGrid.Column="1"HorizontalAlignment="Right"VerticalAlignment="Center"Text="{Binding ElementName=PART_ZoomSlider, Path=Value}" /><!--  显示百分号  --><TextBlockGrid.Column="1"Margin="1,0,-10,0"HorizontalAlignment="Right"VerticalAlignment="Center"Text="%" /></Grid></Expander.Header></Expander></Border>
</UserControl>
/// <summary>
/// ZoomBoxView.xaml 的交互逻辑
/// </summary>
public partial class ZoomBoxView : UserControl
{public ZoomBoxView(){InitializeComponent();} #region 控件成员变量定义 /// <summary>/// 滑块控件/// </summary>private Thumb zoomThumb;/// <summary>/// 缩略图画布/// </summary>private Canvas zoomCanvas;/// <summary>/// 滑条控件/// </summary>private Slider zoomSlider;/// <summary>/// 缩放变换/// </summary>private ScaleTransform scaleTransform;/// <summary>/// 原始画布/// </summary>private Canvas designerCanvas;#endregion#region ScrollViewer的依赖属性定义    public ScrollViewer ScrollViewer/// <summary>/// ScrollViewer的依赖属性定义/// </summary>public ScrollViewer ScrollViewer{get { return (ScrollViewer)GetValue(ScrollViewerProperty); }set { SetValue(ScrollViewerProperty, value); }}/// <summary>/// ScrollViewer依赖属性的注册/// </summary>public static readonly DependencyProperty ScrollViewerProperty =DependencyProperty.Register("ScrollViewer", typeof(ScrollViewer), typeof(ZoomBoxView));#endregion#region 重构OnApplyTemplate方法 public override void OnApplyTemplate()/// <summary>/// 重构OnApplyTemplate方法, Template 属性发生变化时/// </summary>/// <exception cref="Exception"></exception>public override void OnApplyTemplate(){try{base.OnApplyTemplate();// 确保ScrollViewer不为空if (this.ScrollViewer == null)return;// 尝试获取ScrollViewer的内容作为Canvasthis.designerCanvas = this.ScrollViewer.Content as Canvas;if (this.designerCanvas == null)throw new Exception("Canvas must not be null!");// 获取模板中的控件this.zoomThumb = this.PART_ZoomThumb;if (this.zoomThumb == null)throw new Exception("PART_ZoomThumb template is missing!");this.zoomCanvas = this.PART_ZoomCanvas;if (this.zoomCanvas == null)throw new Exception("PART_ZoomCanvas template is missing!");this.zoomSlider = this.PART_ZoomSlider;if (this.zoomSlider == null)throw new Exception("PART_ZoomSlider template is missing!");// 监听相关事件this.designerCanvas.LayoutUpdated += new EventHandler(this.DesignerCanvas_LayoutUpdated);this.zoomThumb.DragDelta += new DragDeltaEventHandler(this.Thumb_DragDelta);this.zoomSlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(this.ZoomSlider_ValueChanged);// 初始化缩放变换this.scaleTransform = new ScaleTransform();this.designerCanvas.LayoutTransform = this.scaleTransform;}catch (Exception ex){MessageBox.Show(ex.Message);}}#endregion#region 缩放比例,滑条值变化时的处理函数  private void ZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)/// <summary>/// 滑块值变化时的处理函数/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void ZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e){try{if (e.OldValue == 0) return; // 避免除以0的情况// 计算新的缩放比例double scale = e.NewValue / e.OldValue;// 计算并设置新的视口偏移double halfViewportHeight = this.ScrollViewer.ViewportHeight / 2;double newVerticalOffset = ((this.ScrollViewer.VerticalOffset + halfViewportHeight) * scale - halfViewportHeight);double halfViewportWidth = this.ScrollViewer.ViewportWidth / 2;double newHorizontalOffset = ((this.ScrollViewer.HorizontalOffset + halfViewportWidth) * scale - halfViewportWidth);// 应用新的缩放比例this.scaleTransform.ScaleX *= scale;this.scaleTransform.ScaleY *= scale;// 滚动到新的偏移位置this.ScrollViewer.ScrollToHorizontalOffset(newHorizontalOffset);this.ScrollViewer.ScrollToVerticalOffset(newVerticalOffset);}catch (Exception ex){MessageBox.Show(ex.Message);}}#endregion#region 缩略图拖动及布局更新时的处理函数/// <summary>/// 缩略图拖动时的处理函数/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void Thumb_DragDelta(object sender, DragDeltaEventArgs e){try{double scale, xOffset, yOffset;this.InvalidateScale(out scale, out xOffset, out yOffset);// 根据缩放比例和拖动量计算新的滚动偏移this.ScrollViewer.ScrollToHorizontalOffset(this.ScrollViewer.HorizontalOffset + e.HorizontalChange / scale);this.ScrollViewer.ScrollToVerticalOffset(this.ScrollViewer.VerticalOffset + e.VerticalChange / scale);}catch (Exception ex){MessageBox.Show(ex.Message);}}/// <summary>/// Canvas布局更新时的处理函数/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void DesignerCanvas_LayoutUpdated(object sender, EventArgs e){try{double scale, xOffset, yOffset;this.InvalidateScale(out scale, out xOffset, out yOffset);// 根据缩放比例和偏移量更新缩略图的位置和大小this.zoomThumb.Width = this.ScrollViewer.ViewportWidth * scale;this.zoomThumb.Height = this.ScrollViewer.ViewportHeight * scale;Canvas.SetLeft(this.zoomThumb, xOffset + this.ScrollViewer.HorizontalOffset * scale);Canvas.SetTop(this.zoomThumb, yOffset + this.ScrollViewer.VerticalOffset * scale);}catch (Exception ex){MessageBox.Show(ex.Message);}}#endregion#region  计算缩放比例和偏移量  private void InvalidateScale(out double scale, out double xOffset, out double yOffset)/// <summary>/// 计算缩放比例和偏移量/// </summary>/// <param name="scale"></param>/// <param name="xOffset"></param>/// <param name="yOffset"></param>private void InvalidateScale(out double scale, out double xOffset, out double yOffset){try{// 计算设计画布和缩放后的尺寸double w = this.designerCanvas.ActualWidth * this.scaleTransform.ScaleX;double h = this.designerCanvas.ActualHeight * this.scaleTransform.ScaleY;// 计算缩略图画布的尺寸double x = this.zoomCanvas.ActualWidth;double y = this.zoomCanvas.ActualHeight;// 计算缩放比例double scaleX = x / w;double scaleY = y / h;scale = (scaleX < scaleY) ? scaleX : scaleY;// 计算偏移量xOffset = (x - scale * w) / 2;yOffset = (y - scale * h) / 2;}catch (Exception ex){scale = xOffset = yOffset = 0;MessageBox.Show(ex.Message);}}#endregion#region 画布内容生成缩略图   private void CreateThumbnail()/// <summary>/// 画布内容生成缩略图/// </summary>private void CreateThumbnail(){try{// RenderTargetBitmap 对象,用于将 Canvas 渲染成位图RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)designerCanvas.Width, (int)designerCanvas.Height, // 宽度和高度96d, 96d, // DPIPixelFormats.Pbgra32); // 像素格式// 将 Canvas 渲染到 RenderTargetBitmaprenderBitmap.Render(designerCanvas);// 缩略图大小的 BitmapSourceTransformedBitmap transformedBitmap = new TransformedBitmap(renderBitmap,new ScaleTransform(0.2, 0.2)); // 缩放比例// ImageBrush 并将缩略图设置为 ImageBrush 的 ImageSourceImageBrush thumbnailBrush = new ImageBrush(transformedBitmap);// 将 ImageBrush 设置为 Canvas 的背景PART_ZoomCanvas.Background = thumbnailBrush;}catch (Exception ex){MessageBox.Show(ex.Message);}}#endregion}

修改MainWindow.xaml 文件,添加缩略图控件

<Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><ScrollViewerx:Name="scrollViewer"Grid.RowSpan="2"Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=UserControl}}"Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=UserControl}}"CanContentScroll="True"HorizontalScrollBarVisibility="Hidden"PanningMode="None"VerticalScrollBarVisibility="Hidden"><!--  画布视图  --><Canvas x:Name="canvas" Style="{StaticResource canvasBackground}"><!--  圆形装饰组  --><EllipseCanvas.Left="600"Canvas.Top="300"Width="100"Height="100"Fill="YellowGreen"Opacity="0.7" /> <EllipseCanvas.Left="200"Canvas.Top="775"Width="100"Height="100"Fill="Red"Opacity="0.7" /> </Canvas></ScrollViewer><!--  视图缩略图控件  --><local:ZoomBoxViewGrid.Row="1"Width="220"Height="Auto"Margin="18"HorizontalAlignment="Right"ScrollViewer="{Binding ElementName=scrollViewer}" /></Grid>

效果图

image

http://www.hskmm.com/?act=detail&tid=10579

相关文章:

  • ik中文分词器使用
  • 动态水印也能去除?ProPainter一键视频抠图整合包下载
  • SpringBoot整合RustFS:全方位优化文件上传性能
  • windows使用es-client插件
  • AI学习日记 - 实践
  • es中的端点
  • 解码C语言宏
  • es中的索引
  • es中的数据类型
  • 防御安全播客第214期:数据泄露与漏洞攻防实战
  • windows使用kibana
  • 03作业
  • 软工作业个人项目
  • YOLO进阶提升 5标注与配置
  • rapidxml中接口函数
  • YOLO进阶提升 6模型训练与测试
  • YOLO进阶提升 4训练准备与数据处理
  • windows安装elasticsearch
  • YOLO进阶提升 5标注与配置补充
  • YOLO进阶提升 3YOLOv4 改进
  • 解码C语言位字段
  • Sql Server 多层嵌套事务的执行结果
  • 面向对象
  • es入门
  • YOLO进阶提升 1YOLOv2 改进
  • C# Avalonia 15- Animation- AnimationPlayerTest
  • 基于Python+Vue开发的体育场馆预约管理系统源码+运行步骤
  • JSONArray集合根据某个字段查询对象
  • 详细介绍:Parasoft C/C++test 针对嵌入式开发的内存错误检测解决方案
  • [WC2006] 水管局长