WPF 基于Transform实现画布超出边界触发计算
有些场景需要对画布边界做界限控制,此时需要计算画布的四个方向的界限和极值
先看效果图:

画布在通过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实现画布超出边界触发计算的更多相关文章
- 基于tcp的应用层消息边界如何定义
聊聊基于tcp的应用层消息边界如何定义 背景 2018年笔者有幸接触一个项目要用到长连接实现云端到设备端消息推送,所以借机了解过相关的内容,最终是通过rabbitmq+mqtt实现了相关功能,同时在心 ...
- Flex布局如何让子类在超出边界时隐藏掉
在flex4中,因为必须添加<s:Scroller>标签才能出现滚动条,如果一个容器例如Panel没有添加滚动条,那么添加到Panel中的child的位置如果超出了Panel的边界,那么这 ...
- vb.net WPF webbrowser window.close 关闭后不触发 WindowClosing 事件 WNDPROC解决方式
vb.net WPF webbrowser window.close 关闭后不触发 WindowClosing 事件 WNDPROC解决方式 #Region "WPF 当浏览器窗体关闭 ...
- jqGrid选择列控件向右拖拽超出边界处理
jqGrid选择列控件向右拖拽超出边界处理 $("#tb_DeviceInfo").jqGrid('navButtonAdd', '#jqGridPager', { ...
- 封装:WPF基于MediaElement封装的视频播放器
原文:封装:WPF基于MediaElement封装的视频播放器 一.目的:应用MediaElement创建媒体播放器 二.效果图 三.目前支持功能 播放.暂停.停止.快进.快退.声音大小.添加播放列表 ...
- 使用Python+Qt时解决QTreeWidget中的内容超出边界后自动隐藏的问题
问题: 默认情况下,内容超出边界后会自动省略,以...代替,而且无法出现水平滚动条 解决方法: 把VerticalScrollBar和HorizontalScrollBar的值都设为ScrollBar ...
- WPF基于.Net Core
WPF基于.Net Core 因为最近.net core的热门,所以想实现一下.net core框架下的WPF项目,还是MVVM模式,下面就开始吧,简单做一个计算器吧. 使用VS2019作为开发工具 ...
- win7基于mahout推荐之用户相似度计算
http://www.douban.com/note/319219518/?type=like win7基于mahout推荐之用户相似度计算 2013-12-03 09:19:11 事情回到半年 ...
- Atitit.基于时间戳的农历日历历法日期计算
Atitit.基于时间戳的农历日历历法日期计算 1. 农历xx年的大小月份根据万年历查询1 2. 农历xx年1月1日的时间戳获取1 3. 计算当年的时间戳与农历日期的对应表,时间戳为key,日期为va ...
- java对于07excel的读、改、写、并触发计算
InputStream is = null; try { is = new FileInputStream(filePath); } catch (FileNotFoundException e1) ...
随机推荐
- 洛谷P11250 [GESP202409 八级] 手套配对 题解
题目传送门. 非常简单的组合数学题. 首先从 \(n\) 对手套中恰好选出 \(k\) 对手套的方案数为 \(C_n^k\),然后由于我们要取出 \(m\) 只手套,那么取了 \(k\) 对手套后还要 ...
- PHP对表单提交特殊字符的过滤和处理方法汇总
http://www.jb51.net/article/46921.htm PHP关于表单提交特殊字符的处理方法做个汇总,主要涉及htmlspecialchars/addslashes/stripsl ...
- FastAPI Cookie 和 Header 参数完全指南:从基础到高级实战 🚀
title: FastAPI Cookie 和 Header 参数完全指南:从基础到高级实战 date: 2025/3/9 updated: 2025/3/9 author: cmdragon exc ...
- 【vscode】vscode配置汇编环境
[vscode]vscode配置汇编环境 前言 因为近来个人的课程涉及到汇编语言,加上个人目前是个vscode的重度使用者,所以,要捣鼓一下汇编的配置. 自然,有很多博客写过如何配置,但是每个人在 ...
- iframe高度自适应 完美解决
前言 一直被iframe的高度自适应的问题困扰着,今天终于找到完美解决方案,加上以下css即可. css iframe { display: block; border: none; height: ...
- PIL或Pillow学习2
接着学习下Pillow常用方法: PIL_test1.py : ''' 9, Pillow图像降噪处理 由于成像设备.传输媒介等因素的影响,图像总会或多或少的存在一些不必要的干扰信息,我们将这些干扰信 ...
- linux服务器通过X11实现图形化界面显示 1 背景描述
有些LINUX服务器出于性能和效率的考虑,通常都是没有安装图形化界面的,那么图形化程序在服务器上压根儿就跑不起来,或者无法直接显示出来,这就很尴尬了!那么如何解决这个问题呢?可以基于X11 Forwa ...
- Delphi 让窗体自适应屏幕显示
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System ...
- Netty源码—5.Pipeline和Handler
大纲 1.Pipeline和Handler的作用和构成 2.ChannelHandler的分类 3.几个特殊的ChannelHandler 4.ChannelHandler的生命周期 5.Channe ...
- osmts:OERV之一站式管理测试脚本
最近团队里面实习的小伙伴开发了一个新的项目,可以用来一键式运行各种测试脚本并且完成数据总结,我也尝试部署了一下,遇到了一些问题,接下来一起解析一下这个项目. 首先是获取osmts git cl ...