Di Chuyển Điểm Trên Đường Thẳng Trong WINUI

Yêu Cầu Kỹ Thuật

Cần di chuyển một điểm dọc theo đường thẳng AB với điều kiện điểm không rời khỏi đường thẳng. Hai điểm đầu cuối A(X1,Y1) và B(X2,Y2) có thể thay đổi động, cần xử lý các trường hợp đặc biệt khi đường thẳng thẳng đứng hoặc nằm ngang.

Phân Tích Giải Pháp

Sử dụng Line để biểu diễn đường thẳng và Ellipse làm điểm di động đặt trong Canvas. Khi di chuyển chuột, chuyển đổi tọa độ con trỏ thành điểm nằm trên đường thẳng bằng công thức toán học:

  1. Xác định khoảng giới hạn tọa độ: X ∈ [min(X1,X2), max(X1,X2)], Y ∈ [min(Y1,Y2), max(Y1,Y2)]
  2. Tính hệ số góc:
    • Độ dốc chính: doDoc = (Y2-Y1)/(X2-X1)
    • Độ dốc nghịch: doDocNguoc = (X2-X1)/(Y2-Y1)
  3. Chọn phương án tính toán dựa trên độ dốc để tránh sai số lớn

Triển Khai Giao Diện

<Canvas x:Name="canvasBase">
  <Line
    x:Name="duongThang"
    Stroke="Black"
    StrokeThickness="2"
    X1="10"
    X2="400"
    Y1="10"
    Y2="400"/>
  
  <Ellipse
    x:Name="diemDiDong"
    Canvas.Left="0"
    Canvas.Top="0"
    Width="20"
    Height="20"
    Fill="Blue"
    Opacity="0.6"/>
</Canvas>

Xử Lý Sự Kiện WINUI

diemDiDong.PointerPressed += (phat, thamSo) => {
  var ellipse = phat as Ellipse;
  ellipse.CapturePointer(thamSo.Pointer);
  
  PointerEventHandler xuLyDiChuyen = null;
  PointerEventHandler xuLyNha = null;
  
  xuLyDiChuyen = (s, e) => {
    if (!ellipse.PointerCaptures.Any()) return;
    
    var viTriHienTai = e.GetCurrentPoint(canvasBase).Position;
    double xGioiHan = GioiHan(viTriHienTai.X, duongThang.X1, duongThang.X2);
    double yGioiHan = GioiHan(viTriHienTai.Y, duongThang.Y1, duongThang.Y2);
    
    if (KiemTraDiem(duongThang)) return;
    
    if (duongThang.X1 == duongThang.X2) {
      xGioiHan = duongThang.X1;
    }
    else if (duongThang.Y1 == duongThang.Y2) {
      yGioiHan = duongThang.Y1;
    }
    else {
      double doDoc = (duongThang.Y2 - duongThang.Y1) / (duongThang.X2 - duongThang.X1);
      if (Math.Abs(doDoc) > 1) {
        xGioiHan = duongThang.X1 + (1/doDoc) * (yGioiHan - duongThang.Y1);
      }
      else {
        yGioiHan = duongThang.Y1 + doDoc * (xGioiHan - duongThang.X1);
      }
    }
    
    double banKinh = ellipse.Width / 2;
    Canvas.SetLeft(ellipse, xGioiHan - banKinh);
    Canvas.SetTop(ellipse, yGioiHan - banKinh);
  };
  
  xuLyNha = (s, e) => {
    ellipse.ReleasePointerCapture(thamSo.Pointer);
    ellipse.PointerMoved -= xuLyDiChuyen;
    ellipse.PointerReleased -= xuLyNha;
  };
  
  ellipse.PointerMoved += xuLyDiChuyen;
  ellipse.PointerReleased += xuLyNha;
};

private double GioiHan(double giaTri, double min, double max) {
  return Math.Max(Math.Min(min, max), Math.Min(Math.Max(min, max), giaTri));
}

private bool KiemTraDiem(Line duong) {
  return duong.X1 == duong.X2 && duong.Y1 == duong.Y2;
}

Lưu ý Quan Trọng

  • Luôn giải phóng con trỏ bằng ReleasePointerCapture
  • Hủy đăng ký sự kiện sau khi hoàn thành
  • Kiểm tra điều kiện đường thẳng là điểm (X1=X2 và Y1=Y2)
  • Xử lý các trường hợp đặc biệt khi đường thẳng đứng hoặc ngang
  • Đảm bảo tâm Ellipse nằm trên đường thẳng

Thẻ: WINUI WPF Canvas Ellipse PointerEvent

Đăng vào ngày 8 tháng 6 lúc 22:51