using netDxf.Entities;
using OxyPlot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WpfLine = System.Windows.Shapes.Line;
using DxfLine = netDxf.Entities.Line;
using DxfLwPolyline = netDxf.Entities.Polyline2D;
using Microsoft.Win32;
using System.Drawing;
namespace TeamAAS_VP.DxfModule
{
///
/// DxfView.xaml 的交互逻辑
///
public partial class DxfView : UserControl
{
private DxfParserService _dxfParser;
private InteractionController _interactionController;
private double _currentScale = 1.0;
private double _zoomFactor = 1.2;
public DxfView()
{
InitializeComponent();
_dxfParser = new DxfParserService();
InitializeInteractionController();
}
///
/// 选中的点位
///
public List Points { get; private set; }=new List();
///
/// 需要导入的点位表开始点编号
///
public int StartIndex { get;private set; } = 1;
private void InitializeInteractionController()
{
_interactionController = new InteractionController(DrawingCanvas);
_interactionController.SelectionChanged += OnSelectionChanged;
}
private void BtnLoadDxf_Click(object sender, RoutedEventArgs e)
{
var openFileDialog = new OpenFileDialog
{
Filter = "DXF文件 (*.dxf)|*.dxf|所有文件 (*.*)|*.*",
Title = "选择DXF文件"
};
if (openFileDialog.ShowDialog() == true)
{
LoadDxfFile(openFileDialog.FileName);
}
}
private void LoadDxfFile(string filePath)
{
TxtStatus.Text = "正在加载DXF文件...";
DrawingCanvas.Children.Clear();
_interactionController?.ClearSelection();
if (_dxfParser.LoadDxfFile(filePath))
{
var entities = _dxfParser.GetAllEntities();
TxtStatus.Text = $"成功加载: {System.IO.Path.GetFileName(filePath)}";
TxtEntityCount.Text = $"实体数: {entities.Count}";
// Calculate bounds and scale
var bounds = _dxfParser.GetBounds();
double boundsWidth = bounds.maxX - bounds.minX;
double boundsHeight = bounds.maxY - bounds.minY;
if (boundsWidth > 0 && boundsHeight > 0)
{
double scaleX = (DrawingCanvas.Width * 0.8) / boundsWidth;
double scaleY = (DrawingCanvas.Height * 0.8) / boundsHeight;
_currentScale = Math.Min(scaleX, scaleY);
}
// Render entities
foreach (var entity in entities)
{
var renderedElements = GraphicsRenderer.RenderEntity(entity, _currentScale);
foreach (var element in renderedElements)
{
DrawingCanvas.Children.Add(element);
}
}
// Center the drawing
CenterDrawing(bounds);
// Update statistics
UpdateStatistics();
}
else
{
TxtStatus.Text = "加载DXF文件失败";
MessageBox.Show("无法加载DXF文件,请检查文件是否有效。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void CenterDrawing((double minX, double minY, double maxX, double maxY) bounds)
{
double centerX = (bounds.minX + bounds.maxX) / 2 * _currentScale;
double centerY = -(bounds.minY + bounds.maxY) / 2 * _currentScale;
double offsetX = DrawingCanvas.Width / 2 - centerX;
double offsetY = DrawingCanvas.Height / 2 - centerY;
// Translate all elements
foreach (UIElement element in DrawingCanvas.Children)
{
if (element is WpfLine line)
{
line.X1 += offsetX;
line.X2 += offsetX;
line.Y1 += offsetY;
line.Y2 += offsetY;
}
else if (element is FrameworkElement fe)
{
double left = Canvas.GetLeft(fe);
double top = Canvas.GetTop(fe);
Canvas.SetLeft(fe, left + offsetX);
Canvas.SetTop(fe, top + offsetY);
}
}
}
private void UpdateStatistics()
{
var stats = _dxfParser.GetEntityStatistics();
var sb = new StringBuilder();
foreach (var stat in stats.OrderByDescending(s => s.Value))
{
if (stat.Value > 0)
{
sb.AppendLine($"{stat.Key}: {stat.Value}");
}
}
TxtStatistics.Text = sb.ToString();
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
TxtSelectedCount.Text = $"选中: {e.SelectedEntities.Count}";
var sb = new StringBuilder();
if (e.SelectedEntities.Count > 0)
{
sb.AppendLine($"已选择 {e.SelectedEntities.Count} 个图形实体:\n");
var grouped = e.SelectedEntities.GroupBy(entity => entity.Type.ToString());
foreach (var group in grouped)
{
sb.AppendLine($"{group.Key}: {group.Count()}");
}
sb.AppendLine("\n详细信息:");
int index = 1;
foreach (var entity in e.SelectedEntities.Take(10))
{
sb.AppendLine($"\n[{index}] {GetEntityInfo(entity)}");
index++;
}
if (e.SelectedEntities.Count > 10)
{
sb.AppendLine($"\n... 还有 {e.SelectedEntities.Count - 10} 个实体");
}
//如果选中的是圆,则输出圆心坐标,如果是直线,则输出中心点坐标,如果是矩形,则输出中心点坐标,如果是点,则输出点坐标,如果是圆弧,则输出中心点坐标
if (Points!=null)
{
Points = new List();
}
Points.Clear();
foreach (var item in e.SelectedEntities)
{
if (item is Circle circle)
{
Points.Add(new PointF((float)circle.Center.X, (float)circle.Center.Y));
}
else if (item is DxfLine line)
{
float centerX = (float)((line.StartPoint.X + line.EndPoint.X) / 2);
float centerY = (float)((line.StartPoint.Y + line.EndPoint.Y) / 2);
Points.Add(new PointF(centerX, centerY));
}
else if (item is DxfLwPolyline polyline && polyline.IsClosed)
{
//计算多边形中心点
float centerX = 0;
float centerY = 0;
foreach (var vertex in polyline.Vertexes)
{
centerX += (float)vertex.Position.X;
centerY += (float)vertex.Position.Y;
}
centerX /= polyline.Vertexes.Count;
centerY /= polyline.Vertexes.Count;
Points.Add(new PointF(centerX, centerY));
}
else if (item is netDxf.Entities.Point point)
{
Points.Add(new PointF((float)point.Position.X, (float)point.Position.Y));
}
else if (item is Arc arc)
{
Points.Add(new PointF((float)arc.Center.X, (float)arc.Center.Y));
}
}
}
else
{
sb.AppendLine("未选择任何图形");
}
TxtSelectedInfo.Text = sb.ToString();
}
private string GetEntityInfo(EntityObject entity)
{
switch (entity)
{
case DxfLine line:
return $"直线: ({line.StartPoint.X:F2}, {line.StartPoint.Y:F2}) → ({line.EndPoint.X:F2}, {line.EndPoint.Y:F2})";
case Circle circle:
return $"圆: 中心({circle.Center.X:F2}, {circle.Center.Y:F2}), 半径={circle.Radius:F2}";
case Arc arc:
return $"圆弧: 中心({arc.Center.X:F2}, {arc.Center.Y:F2}), 半径={arc.Radius:F2}, 角度={arc.StartAngle:F1}°~{arc.EndAngle:F1}°";
case Text text:
return $"文本: \"{text.Value}\" 位置({text.Position.X:F2}, {text.Position.Y:F2})";
case MText mtext:
return $"多行文本: \"{mtext.Value}\" 位置({mtext.Position.X:F2}, {mtext.Position.Y:F2})";
case DxfLwPolyline lwPoly:
return $"轻量多段线: {lwPoly.Vertexes.Count} 个顶点{(lwPoly.IsClosed ? " (闭合)" : "")}";
case Spline spline:
// return $"样条曲线: {spline.ControlPoints.Count} 个控制点";
default:
return $"{entity.Type}: {entity.CodeName}";
}
}
private void BtnClearSelection_Click(object sender, RoutedEventArgs e)
{
Points?.Clear();
_interactionController?.ClearSelection();
}
private void BtnExtractSelected_Click(object sender, RoutedEventArgs e)
{
var selectedEntities = _interactionController?.GetSelectedEntities();
if (selectedEntities == null || selectedEntities.Count == 0)
{
MessageBox.Show("请先选择要提取的图形。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var sb = new StringBuilder();
sb.AppendLine($"提取的图形信息 (共 {selectedEntities.Count} 个):\n");
var grouped = selectedEntities.GroupBy(p => p.Type.ToString());
foreach (var group in grouped)
{
sb.AppendLine($"\n{group.Key} ({group.Count()} 个):");
sb.AppendLine(new string('-', 50));
foreach (var entity in group)
{
sb.AppendLine(GetEntityInfo(entity));
}
}
MessageBox.Show(sb.ToString(), "提取的图形信息", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void BtnZoomIn_Click(object sender, RoutedEventArgs e)
{
// Smooth zoom centered at viewport center
var sv = ScrollViewer;
if (sv != null)
{
double prevScale = CanvasScaleTransform.ScaleX;
double target = prevScale * _zoomFactor;
target = Math.Max(0.05, Math.Min(target, 50.0));
AnimateScale(target, null);
}
else
{
Zoom(_zoomFactor);
}
}
private void BtnZoomOut_Click(object sender, RoutedEventArgs e)
{
var sv = ScrollViewer;
if (sv != null)
{
double prevScale = CanvasScaleTransform.ScaleX;
double target = prevScale / _zoomFactor;
target = Math.Max(0.05, Math.Min(target, 50.0));
AnimateScale(target, null);
}
else
{
Zoom(1.0 / _zoomFactor);
}
}
private void BtnFitToScreen_Click(object sender, RoutedEventArgs e)
{
var sv = ScrollViewer;
if (sv == null)
{
CanvasScaleTransform.ScaleX = 1.0;
CanvasScaleTransform.ScaleY = 1.0;
UpdateZoomDisplay();
return;
}
// Compute scale to fit the canvas into viewport with some margin
double marginFactor = 0.95;
double targetScaleX = (sv.ViewportWidth * marginFactor) / DrawingCanvas.Width;
double targetScaleY = (sv.ViewportHeight * marginFactor) / DrawingCanvas.Height;
double target = Math.Min(targetScaleX, targetScaleY);
if (double.IsNaN(target) || target <= 0)
target = 1.0;
target = Math.Max(0.05, Math.Min(target, 50.0));
// Animate to target and center
AnimateScale(target, null);
}
private void DrawingCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
// Zoom centered at current mouse position inside the ScrollViewer viewport
var sv = ScrollViewer;
if (sv == null) // fallback to previous behavior
{
double zoom1 = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
Zoom(zoom1);
return;
}
// Mouse position relative to the ScrollViewer (viewport)
System.Windows.Point mousePosInViewport = e.GetPosition(sv);
// Mouse position relative to the content (unscaled content coordinates)
System.Windows.Point mousePosInContent = e.GetPosition(DrawingCanvas);
double prevScale = CanvasScaleTransform.ScaleX;
double zoom = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
double newScale = prevScale * zoom;
// Clamp scale to reasonable range
newScale = Math.Max(0.05, Math.Min(newScale, 50.0));
// Absolute position of the content point after scaling
double absX = mousePosInContent.X * newScale;
double absY = mousePosInContent.Y * newScale;
// Apply scale
CanvasScaleTransform.ScaleX = newScale;
CanvasScaleTransform.ScaleY = newScale;
// Ensure layout updated so ScrollViewer extents are refreshed
sv.UpdateLayout();
// Calculate target offsets so that the content point stays under the mouse cursor
double targetOffsetX = absX - mousePosInViewport.X;
double targetOffsetY = absY - mousePosInViewport.Y;
// Clamp offsets to valid scrollable range
double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX));
targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY));
sv.ScrollToHorizontalOffset(targetOffsetX);
sv.ScrollToVerticalOffset(targetOffsetY);
UpdateZoomDisplay();
e.Handled = true;
}
///
/// Animate scale transform to targetScale. If anchorInContent is null, use viewport center as anchor.
/// anchorInContent, when provided, is in unscaled canvas coordinates (content space).
///
private void AnimateScale(double targetScale, System.Windows.Point? anchorInContent)
{
var sv = ScrollViewer;
if (sv == null)
{
CanvasScaleTransform.ScaleX = targetScale;
CanvasScaleTransform.ScaleY = targetScale;
UpdateZoomDisplay();
return;
}
double prevScale = CanvasScaleTransform.ScaleX;
if (Math.Abs(prevScale - targetScale) < 1e-6)
return;
// Determine anchor in content space and its position in viewport
System.Windows.Point anchorContent;
System.Windows.Point anchorViewport;
if (anchorInContent.HasValue)
{
anchorContent = anchorInContent.Value;
anchorViewport = new System.Windows.Point(anchorContent.X * prevScale - sv.HorizontalOffset, anchorContent.Y * prevScale - sv.VerticalOffset);
}
else
{
// use viewport center
anchorViewport = new System.Windows.Point(sv.ViewportWidth / 2.0, sv.ViewportHeight / 2.0);
anchorContent = new System.Windows.Point((sv.HorizontalOffset + anchorViewport.X) / prevScale, (sv.VerticalOffset + anchorViewport.Y) / prevScale);
}
// Calculate target offsets so that the anchor stays in the same viewport position after scaling
double targetOffsetX = anchorContent.X * targetScale - anchorViewport.X;
double targetOffsetY = anchorContent.Y * targetScale - anchorViewport.Y;
double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX));
targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY));
var duration = TimeSpan.FromMilliseconds(200);
var animX = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() };
var animY = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() };
int completed = 0;
EventHandler whenDone = (s, e) =>
{
completed++;
if (completed >= 2)
{
// Ensure final values
CanvasScaleTransform.ScaleX = targetScale;
CanvasScaleTransform.ScaleY = targetScale;
// Update layout so extents reflect final scale
sv.UpdateLayout();
// Recalculate clamped offsets based on final extents
double maxX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
double maxY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
double finalOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxX));
double finalOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxY));
sv.ScrollToHorizontalOffset(finalOffsetX);
sv.ScrollToVerticalOffset(finalOffsetY);
UpdateZoomDisplay();
}
};
animX.Completed += whenDone;
animY.Completed += whenDone;
CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, animX);
CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, animY);
}
private void Zoom(double factor)
{
CanvasScaleTransform.ScaleX *= factor;
CanvasScaleTransform.ScaleY *= factor;
UpdateZoomDisplay();
}
private void UpdateZoomDisplay()
{
TxtZoom.Text = $"缩放: {CanvasScaleTransform.ScaleX * 100:F0}%";
}
private void TxtStartIndex_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
StartIndex= e.NewValue;
}
}
}