如何将WPF ZoomBox控件为?

摘要:实现 WPF 应用中画布的缩放控制与缩略图导航,支持滑块调节缩放比例、缩略图拖拽定位,实时同步主画布视图与缩略图视口位置。 ZoomBoxView.xaml 用户控件 点击查看代码 <UserControl x:Cla
实现 WPF 应用中画布的缩放控制与缩略图导航,支持滑块调节缩放比例、缩略图拖拽定位,实时同步主画布视图与缩略图视口位置。 ZoomBoxView.xaml 用户控件 点击查看代码 <UserControl x: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"> <Border MinHeight="30" Background="#F6F6F6" CornerRadius="6"> <Border.Effect> <DropShadowEffect BlurRadius="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控件的样式矩形 --> <Rectangle Fill="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> <!-- 滑块 --> <Slider Name="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" /> <!-- 绑定到滑块的值 --> <TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Text="{Binding ElementName=PART_ZoomSlider, Path=Value}" /> <!-- 显示百分号 --> <TextBlock Grid.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的内容作为Canvas this.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, // DPI PixelFormats.Pbgra32); // 像素格式 // 将 Canvas 渲染到 RenderTargetBitmap renderBitmap.Render(designerCanvas); // 缩略图大小的 BitmapSource TransformedBitmap transformedBitmap = new TransformedBitmap( renderBitmap, new ScaleTransform(0.2, 0.2)); // 缩放比例 // ImageBrush 并将缩略图设置为 ImageBrush 的 ImageSource ImageBrush 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> <ScrollViewer x: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}"> <!-- 圆形装饰组 --> <Ellipse Canvas.Left="600" Canvas.Top="300" Width="100" Height="100" Fill="YellowGreen" Opacity="0.7" /> <Ellipse Canvas.Left="200" Canvas.Top="775" Width="100" Height="100" Fill="Red" Opacity="0.7" /> </Canvas> </ScrollViewer> <!-- 视图缩略图控件 --> <local:ZoomBoxView Grid.Row="1" Width="220" Height="Auto" Margin="18" HorizontalAlignment="Right" ScrollViewer="{Binding ElementName=scrollViewer}" /> </Grid> 效果图