有些场景需要对画布边界做界限控制,此时需要计算画布的四个方向的界限和极值

先看效果图:

画布在通过RenderTransform 做变换,由于在变换的过程中,实际的宽高没有改变,需要通过Transform实时记录变换的状态

1、图中用到了是对Canvas做 RenderTransform的变换,支持缩放和移动

2、最外层需要包一层Border,主要是限定画布的实际的宽高,因为Canvas在做变换的时候,实际的宽高并不改变,画布canvas的移动的偏移量是通过计算Canvas与Border的宽高相对的位置

3、

  <Border x:Name="canvasGrid">

        <Canvas x:Name="writeBorad" Width="1920" Height="1080" MouseDown="WriteBorad_OnMouseDown"  MouseWheel="UIElement_OnMouseWheel" Background="{StaticResource test}" MouseUp="WriteBorad_OnMouseUp"  MouseMove="UIElement_OnMouseMove" >
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale"></ScaleTransform>
<TranslateTransform x:Name="trans"></TranslateTransform>
</TransformGroup>
</Canvas.RenderTransform> <Button Width="60" Height="60" Canvas.Left="100" Canvas.Top="100"></Button> </Canvas>
</Border>

后台计算逻辑:

 public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent(); Loaded += MainWindow_Loaded; this.writeBorad.Width = 3840;
this.writeBorad.Height = 2160;
} private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//取整设置成刚好的网格大小
var drawBrush = this.writeBorad.Background as DrawingBrush;
if (drawBrush != null)
{
var size = drawBrush.Viewport.Size;
//横向判断取整
var div = writeBorad.Width / size.Width;
var rem = writeBorad.Width % size.Width; if (rem != 0)
{
writeBorad.Width = size.Width *(Math.Floor(div) + 1);
}
//纵向判断取整
div = writeBorad.Height / size.Height;
rem = writeBorad.Height % size.Height;
if (rem != 0)
{
writeBorad.Height =size.Height* (Math.Floor(div) + 1);
}
} trans.X = -writeBorad.Width / 2;
trans.Y = -writeBorad.Height / 2;
} private void UIElement_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
Point point = e.GetPosition(writeBorad);
var delta = e.Delta * 0.001;
DowheelZoom(null, point, delta);
} double scaleSize = 1;
private void DowheelZoom(System.Windows.Media.TransformGroup group, Point point, double delta)
{
//按住ctrl缩放
// if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
if ((scale.ScaleX + delta) <= 1)
{
//return;
scale.ScaleX = 1;
scale.ScaleY = 1;
}
else if (scale.ScaleX + delta > 5)
{
scale.ScaleX = 5;
scale.ScaleY = 5;
}
else
{
scale.ScaleX += delta;
scale.ScaleY += delta;
}
var targetX = point.X * (scale.ScaleX - 1);
var targetY = point.Y * (scale.ScaleX - 1); trans.X = -targetX;
trans.Y = -targetY; Console.WriteLine($"{trans.X}==={trans.Y}");
scaleSize = scale.ScaleX;
}
} double tranX;
double tranY;
Point startPoint; //判断是否鼠标点击
private bool isMouseDown; private void UIElement_OnMouseMove(object sender, MouseEventArgs e)
{
if(e.LeftButton!= MouseButtonState.Pressed || !isMouseDown) return;
Point mousePoint = e.GetPosition(canvasGrid); var targetX = tranX + mousePoint.X - startPoint.X;
var targetY = tranY + mousePoint.Y - startPoint.Y; trans.X = targetX;
trans.Y = targetY; Console.WriteLine($"{trans.X}==={trans.Y}"); //边界检测
//缩放后的尺寸
var scaleWidth = writeBorad.ActualWidth * scaleSize;
var scaleHeight = writeBorad.ActualHeight * scaleSize; var gridWidth = canvasGrid.ActualWidth;
var gridHeight = canvasGrid.ActualHeight; //x轴最小x坐标
var minX = -(scaleWidth - gridWidth); //y轴最小坐标
var minY = -(scaleHeight - gridHeight); double resetX = 0;
double resetY = 0;

//计算边界值
//x轴左侧坐标越界,缓存到point点
if (targetX > 0)
{
////拖拽前在可拖动区域
//if(trans.X<0)
targetX = 0;
resetX = targetX;
isNeedResetX = true;
Console.WriteLine("左侧越界了");
} if (targetX < minX)
{
targetX = minX;
resetX = targetX;
isNeedResetX = true;
Console.WriteLine("右侧越界了");
}
if (targetY > 0)
{
targetY = 0;
resetY = targetY;
isNeedResetY = true;
Console.WriteLine("上侧越界了");
}
if (targetY < minY)
{
targetY = minY;
resetY = targetY;
Console.WriteLine("下侧越界了");
isNeedResetY = true;
}
ResetPoint=new Point(resetX,resetY); } private Point ResetPoint; private bool isNeedResetX; private bool isNeedResetY; private void WriteBorad_OnMouseDown(object sender, MouseButtonEventArgs e)
{
isMouseDown = true; writeBorad.CaptureMouse(); startPoint = e.GetPosition(canvasGrid);
tranX = trans.X;
tranY = trans.Y;
isNeedResetX = false;
isNeedResetY = false;
} private void AddElement(Point position)
{
Button btn=new Button();
btn.Height = 60;
btn.Width = 60;
Canvas.SetLeft(btn,position.X);
Canvas.SetTop(btn, position.Y); writeBorad.Children.Add(btn);
} private void WriteBorad_OnMouseUp(object sender, MouseButtonEventArgs e)
{ AddElement(e.GetPosition(writeBorad)); isMouseDown = false;
writeBorad.ReleaseMouseCapture(); if (isNeedResetX)
{
trans.X = ResetPoint.X;
} if (isNeedResetY)
{
trans.Y = ResetPoint.Y;
} isNeedResetY = isNeedResetX = false;
ResetPoint =new Point(); if (isNeedResetY || isNeedResetX)
{
Console.WriteLine($"{trans.X}==={trans.Y}");
}
}
}

总结:1、通过Border限定画布的实际宽高,画布做Transform做平移缩放,通过实时计算宽高来计算偏移量,从而计算是否触发边界

2、本次演示用到Canvas的 ScaleTransform 和  TranslateTransform 做的平移缩放的变换,后续修改为MatrixTransform,灵活性更高,支持多指触发 Manipulation 操作

3、后续增加超出边界触发回弹的效果,以及回弹动画的处理

WPF 基于Transform实现画布超出边界触发计算的更多相关文章

  1. 基于tcp的应用层消息边界如何定义

    聊聊基于tcp的应用层消息边界如何定义 背景 2018年笔者有幸接触一个项目要用到长连接实现云端到设备端消息推送,所以借机了解过相关的内容,最终是通过rabbitmq+mqtt实现了相关功能,同时在心 ...

  2. Flex布局如何让子类在超出边界时隐藏掉

    在flex4中,因为必须添加<s:Scroller>标签才能出现滚动条,如果一个容器例如Panel没有添加滚动条,那么添加到Panel中的child的位置如果超出了Panel的边界,那么这 ...

  3. vb.net WPF webbrowser window.close 关闭后不触发 WindowClosing 事件 WNDPROC解决方式

     vb.net WPF webbrowser window.close 关闭后不触发 WindowClosing 事件 WNDPROC解决方式 #Region "WPF 当浏览器窗体关闭 ...

  4. jqGrid选择列控件向右拖拽超出边界处理

    jqGrid选择列控件向右拖拽超出边界处理 $("#tb_DeviceInfo").jqGrid('navButtonAdd', '#jqGridPager', {         ...

  5. 封装:WPF基于MediaElement封装的视频播放器

    原文:封装:WPF基于MediaElement封装的视频播放器 一.目的:应用MediaElement创建媒体播放器 二.效果图 三.目前支持功能 播放.暂停.停止.快进.快退.声音大小.添加播放列表 ...

  6. 使用Python+Qt时解决QTreeWidget中的内容超出边界后自动隐藏的问题

    问题: 默认情况下,内容超出边界后会自动省略,以...代替,而且无法出现水平滚动条 解决方法: 把VerticalScrollBar和HorizontalScrollBar的值都设为ScrollBar ...

  7. WPF基于.Net Core

    WPF基于.Net Core 因为最近.net core的热门,所以想实现一下.net core框架下的WPF项目,还是MVVM模式,下面就开始吧,简单做一个计算器吧. 使用VS2019作为开发工具 ...

  8. win7基于mahout推荐之用户相似度计算

    http://www.douban.com/note/319219518/?type=like win7基于mahout推荐之用户相似度计算 2013-12-03 09:19:11    事情回到半年 ...

  9. Atitit.基于时间戳的农历日历历法日期计算

    Atitit.基于时间戳的农历日历历法日期计算 1. 农历xx年的大小月份根据万年历查询1 2. 农历xx年1月1日的时间戳获取1 3. 计算当年的时间戳与农历日期的对应表,时间戳为key,日期为va ...

  10. java对于07excel的读、改、写、并触发计算

    InputStream is = null; try { is = new FileInputStream(filePath); } catch (FileNotFoundException e1) ...

随机推荐

  1. script 标签中 defer 和 async 的区别

    https://www.cnblogs.com/huangtq/p/18422775 在 <script> 标签中,defer 和 async 是两个用于控制 JavaScript 脚本加 ...

  2. C# fleck websocket使用

    转载于:https://www.itspeeding.com/article/28 1.web页面 1 <html lang="en" xmlns="http:// ...

  3. C语言格式输出方式

    C语言格式输出 1.转换字符说明 C语言格式输出方式 2.常用的打印格式 在 C 语言中,格式输出主要依靠 printf 函数来实现. 以下是一些 C 语言格式输出的代码举例及相关说明: printf ...

  4. 自动化-Yaml文件写入函数封装

    1.文件布局 打开文件修改读取方式为w dump函数写入文件 写入中文 使用allow_unicode=True class ReadConfiYaml: def __init__(self,yaml ...

  5. FastAPI 参数别名与自动文档生成完全指南:从基础到高级实战 🚀

    title: FastAPI 参数别名与自动文档生成完全指南:从基础到高级实战 date: 2025/3/10 updated: 2025/3/10 author: cmdragon excerpt: ...

  6. 【Bug记录】[@vue/compiler-sfc] `defineProps` is a compiler macro and no longer needs to be imported.

    [Bug记录][@vue/compiler-sfc] defineProps is a compiler macro and no longer needs to be imported. Vue3项 ...

  7. python ImportError: libGL.so.1: cannot open shared object file: No such file or directory

    前言 python 报错python ImportError: libGL.so.1: cannot open shared object file: No such file or director ...

  8. 密码编码学与网络安全 原理与实践(第七版)William Stallings---读书笔记(1.1-1.5)

    密码编码学与网络安全 原理与实践(第七版)William Stallings---读书笔记 第一部分 概览 第1章 计算机与网络安全概念 密码算法与协议又可分为4个主要领域: 对称加密 加密任意大小的 ...

  9. CISCN&CCB半决赛_2025_PWN_WP

    CISCN&CCB半决赛_2025_PWN_WP 前言: 记录一下第一次打半决赛国赛,总结来说还是自己太菜了,还有check脚本是真的很shi,正规军白给了... typo break edi ...

  10. Linux centos 运行telnet命令command not found的解决方法

      Linux centos 运行telnet命令,出现下面的错误提示: 1 2 [root@localhost ~]# telnet 127.0.0.1 -bash: telnet: command ...