일부 시나리오에서는 캔버스의 경계를 제어해야 하며, 이때 캔버스의 네 방향 경계와 극값을 계산해야 합니다.
먼저 효과를 확인해보세요:
캔버스는 RenderTransform을 통해 변환됩니다. 변환 과정에서 실제 너비와 높이는 변경되지 않으므로 Transform을 통해 변환 상태를 실시간으로 기록해야 합니다.
- 이 예제에서는 Canvas에 대해 RenderTransform 변환을 사용하여 확대/축소 및 이동을 지원합니다.
- 가장 바깥쪽은 Border로 감싸서 캔버스의 실제 너비와 높이를 제한합니다. Canvas가 변환될 때 실제 너비와 높이는 변경되지 않으므로, 캔버스 이동 오프셋은 Canvas와 Border의 너비/높이 상대 위치를 계산하여 결정됩니다.
- 코드 예제:
<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(TransformGroup group, Point point, double delta)
{
if ((scale.ScaleX + delta) <= 1)
{
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;
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;
var scaleWidth = writeBorad.ActualWidth * scaleSize;
var scaleHeight = writeBorad.ActualHeight * scaleSize;
var gridWidth = canvasGrid.ActualWidth;
var gridHeight = canvasGrid.ActualHeight;
var minX = -(scaleWidth - gridWidth);
var minY = -(scaleHeight - gridHeight);
double resetX = 0;
double resetY = 0;
if (targetX > 0)
{
targetX = 0;
resetX = targetX;
isNeedResetX = true;
}
if (targetX < minX)
{
targetX = minX;
resetX = targetX;
isNeedResetX = true;
}
if (targetY > 0)
{
targetY = 0;
resetY = targetY;
isNeedResetY = true;
}
if (targetY < minY)
{
targetY = minY;
resetY = targetY;
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();
}
}
요약:
- Border를 통해 캔버스의 실제 너비와 높이를 제한합니다. Canvas는 Transform을 사용하여 이동 및 확대/축소를 수행하며, 실시간 너비/높이 계산을 통해 오프셋을 결정하여 경계 초과를 감지합니다.
- 이 데모에서는 Canvas의 ScaleTransform과 TranslateTransform을 사용했지만, 이후 MatrixTransform으로 전환하면 더 높은 유연성을 제공하며 다중 터치 Manipulation 작업을 지원할 수 있습니다.
- 향후 경계 초과 시 바운스 효과와 애니메이션 처리를 추가할 예정입니다.