背景:群友询问:怎样子实现在批量选择完管道,在点击放置标签时,想有一个可视化跟随的动画效果。然后研究了一番,下面提供了一个简易版的实行方案,大家可以参考学习。 源码
目标
实现如下图所示的选择完管件后,鼠标动画跟随效果
关键思路和代码
1.读取当前视图的边框投射到屏幕上的点坐标
其中Rectangle是当前视图边框投影到屏幕上的正方形,可以直接取Left属性和Top属性来定位屏幕的位置
var uiviews = uidoc.GetOpenUIViews();
Rectangle rectangle = uiviews[0].GetWindowRectangle();
2.创建一个完全透明的窗体,只有一个画板控件
<Window
x:Class="FloatDemo.Views.FloatView"
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:FloatDemo.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="800"
Height="450"
Closed="Window_Closed"
mc:Ignorable="d">
<Canvas Name="Canvas" />
</Window>
namespace FloatDemo.Views
{
/// <summary>
/// FloatView.xaml 的交互逻辑
/// </summary>
public partial class FloatView : Window
{
public FloatView()
{
InitializeComponent();
new System.Windows.Interop.WindowInteropHelper(this).Owner = Autodesk.Windows.ComponentManager.ApplicationWindow;
AllowsTransparency = true;
WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.NoResize;
ShowInTaskbar = false;
Background = Brushes.Transparent;
this.SourceInitialized += delegate
{
IntPtr hwnd = new WindowInteropHelper(this).Handle;
uint extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
};
}
private void Window_Closed(object sender, EventArgs e)
{
}
private const int WS_EX_TRANSPARENT = 0x20;
private const int GWL_EXSTYLE = -20;
[DllImport("user32", EntryPoint = "SetWindowLong")]
private static extern uint SetWindowLong(IntPtr hwnd, int nIndex, uint dwNewLong);
[DllImport("user32", EntryPoint = "GetWindowLong")]
private static extern uint GetWindowLong(IntPtr hwnd, int nIndex);
}
}
其中SourceInitialized事件,是为了使鼠标可以完全透过窗体(百度得到的解决方案)
3.根据Revit中得到的屏幕坐标,修改自定义窗体的位置,使窗体完全覆盖在Revit的视图上
/// <summary>
/// 视图移动后窗口重新定位
/// </summary>
/// <param name="rec"></param>
public void ChangeLocation(Rectangle rec)
{
MainWindows.Left = rec.Left;
MainWindows.Top = rec.Top;
MainWindows.Width = rec.Right - rec.Left;
MainWindows.Height = rec.Bottom - rec.Top;
MainWindows.Canvas.Children.Clear();
if (CacheCanvasMousePoint != null)
CacheCanvasMousePoint = new Point(CanvasMousePoint.X, CanvasMousePoint.Y);
}
这里也做了一个点位的缓存,大伙可以查看源代码。
4.通过Hook方法,实现对鼠标移动的监控
namespace FloatDemo.Methods
{
public class MouseHook
{
private int hHook;
public const int WH_MOUSE_LL = 14;
public Win32Api.HookProc hProc;
public MouseHook()
{
}
public int SetHook()
{
hProc = new Win32Api.HookProc(MouseHookProc);
hHook = Win32Api.SetWindowsHookEx(WH_MOUSE_LL, hProc, IntPtr.Zero, 0);
return hHook;
}
public void UnHook()
{
Win32Api.UnhookWindowsHookEx(hHook);
}
private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
Win32Api.MouseHookStruct MyMouseHookStruct = (Win32Api.MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(Win32Api.MouseHookStruct));
Point point = new Point(MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y);
MouseMove?.Invoke(point);
return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
}
public Action<Point> MouseMove { get; set; }
}
public class Win32Api
{
[StructLayout(LayoutKind.Sequential)]
public class POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public int hwnd;
public int wHitTestCode;
public int dwExtraInfo;
}
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
//安装钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
//卸载钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
//调用下一个钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
}
}
当然这边仅提供了对鼠标移动的监控,大家自己完善的时候,是可以监控滚轮的操作等,来实现更加完善的操作。
5.通过移动点在窗体绘制线段
拿到鼠标移动点的时候,需要做一个点的平移,得到鼠标在窗体上的点,因为屏幕点的坐标系原点和我们自定义窗体的坐标系原点是不一样的。(后面可以自己扩充:点不在窗体范围时,自动跳出方法,不往下执行)
var uiviews = uidoc.GetOpenUIViews();
Rectangle rectangle = uiviews[0].GetWindowRectangle();
System.Windows.Point tempPoint = new System.Windows.Point(point.X - rectangle.Left, point.Y - rectangle.Top);
拿到鼠标在窗体的点,然后转成Revit坐标系下
/// <summary>
/// 窗体中的点转到Revit中
/// </summary>
/// <param name="point"></param>
/// <param name="uView"></param>
/// <returns></returns>
public static XYZ GetRevitPoint(UIDocument uidoc, System.Windows.Point point)
{
UIView uView = uidoc.GetOpenUIViews()[0];
var corners = uView.GetZoomCorners();
double minX = corners.Min(x => x.X);
double minY = corners.Min(x => x.Y);
double maxX = corners.Max(x => x.X);
double maxY = corners.Max(x => x.Y);
double resultX = minX + (maxX - minX) * (point.X / FloatWindows.Instance.Width);
double resultY = minY + (maxY - minY) * (1 - (point.Y / FloatWindows.Instance.Height));
XYZ result = new XYZ(resultX,resultY,0);
return result;
}
拿到选择的管线,这里默认是平行的,有特殊情况可以再自行考虑。然后计算得到鼠标的Revit点投影到线上的点,再通过得到的投影点转成窗体点,然后绘制出来。
public void MouseMoveChange(System.Windows.Point point)
{
Rectangle rectangle = GetRectangle(_uidoc);
System.Windows.Point tempPoint = new System.Windows.Point(point.X - rectangle.Left, point.Y - rectangle.Top);
if (tempPoint.X < 0 || tempPoint.Y < 0) return;
FloatWindows.Instance.CanvasMousePoint = tempPoint;
if (!FloatWindows.Instance.PointIsMove()) return;
FloatWindows.Instance.ChangeLocation(rectangle);
XYZ movePoint = CanvasPointMethod.GetRevitPoint(_uidoc, FloatWindows.Instance.CanvasMousePoint);
CanvasPoint moveCanvasPoint = CanvasPointMethod.GetCanvasPoint(_uidoc, movePoint);
double dd = 0;
foreach (var pipeLine in _pipeLineList)
{
if (!pipeLine.CanProject(movePoint)) continue;
XYZ proPoint = pipeLine.GetProjectPoint(movePoint);
XYZ pointMoveDirection = (proPoint - movePoint).Normalize();
CanvasPoint p2 = CanvasPointMethod.GetCanvasPoint(_uidoc, proPoint);
CanvasPoint p3 = CanvasPointMethod.GetCanvasPoint(_uidoc, movePoint.Add(pointMoveDirection * dd));
CanvasPoint p4 = CanvasPointMethod.GetCanvasPoint(_uidoc, movePoint.Add(pointMoveDirection * dd).Add(XYZ.BasisX * 10));
FloatWindows.Instance.Add(p2, moveCanvasPoint);
FloatWindows.Instance.Add(p3, p4);
dd += (300/304.8);
}
}
public class CanvasPointMethod
{
/// <summary>
/// 返回屏幕坐标点
/// </summary>
/// <param name="targetPoint"></param>
/// <param name="uView"></param>
/// <returns></returns>
public static CanvasPoint GetCanvasPoint(UIDocument uidoc, XYZ targetPoint)
{
UIView uView = uidoc.GetOpenUIViews()[0];
var corners = uView.GetZoomCorners();
double minX = corners.Min(x => x.X);
double minY = corners.Min(x => x.Y);
double maxX = corners.Max(x => x.X);
double maxY = corners.Max(x => x.Y);
CanvasPoint cp1 = new CanvasPoint();
cp1.Top = 0 + (int)(FloatWindows.Instance.Height * (1 - (targetPoint.Y - minY) / (maxY - minY)));
cp1.Left = 0 + (int)(FloatWindows.Instance.Width * (targetPoint.X - minX) / (maxX - minX));
return cp1;
}
/// <summary>
/// 窗体中的点转到Revit中
/// </summary>
/// <param name="point"></param>
/// <param name="uView"></param>
/// <returns></returns>
public static XYZ GetRevitPoint(UIDocument uidoc, System.Windows.Point point)
{
UIView uView = uidoc.GetOpenUIViews()[0];
var corners = uView.GetZoomCorners();
double minX = corners.Min(x => x.X);
double minY = corners.Min(x => x.Y);
double maxX = corners.Max(x => x.X);
double maxY = corners.Max(x => x.Y);
double resultX = minX + (maxX - minX) * (point.X / FloatWindows.Instance.Width);
double resultY = minY + (maxY - minY) * (1 - (point.Y / FloatWindows.Instance.Height));
XYZ result = new XYZ(resultX,resultY,0);
return result;
}
}
6.每次移动,都会删除之前绘制的线,再新建,达到更新的效果。
通过MouseHook类的MouseMove委托来实现。
最后
这里仅提供了一个解决方案,讲述了一个思路,还存在很多需要改进的地方。而且主要还是要看代码文件,这篇文章配合源码来看。如果要积分的话,直接私信我
下载源码