WPF 可选曲线
WPF selectable curve
我目前正在开发类似图形的编辑器应用程序。到目前为止,我得到了节点和连接显示,我可以移动它们等。
我希望能够 select 将一个节点连接到另一个节点(橙色节点)的曲线。
选择曲线本身并不是真正的问题,它只是有点棘手,因为它是一个相当薄的对象(原始笔画粗细为 2)。我不得不增加笔划以使其更容易 select。我想保持笔触较小,因为我认为它看起来更好。
我的想法是绘制两条曲线,其中一条具有高描边粗细但透明,最重要的是实际的彩色曲线。这将使我基本上可以容忍我需要点击实际曲线到 select 的距离。
现在曲线 (Connection) 是一个 class 继承自 Shape 的曲线,为了拥有其中的两个,我将它们包装在一个自定义控件中。它成功了,我不喜欢它的事实是我必须将所有属性包装在可能的自定义形状上以便将数据传播到我的两个连接,例如我必须包装开始和结束点属性。
我可能也可以通过绑定来实现,但这基本上是将问题从代码隐藏转移到 XAML.
是否有更好的方法来实现我想要做的事情?我不是 WPF 的专家,所以我可能忽略了一些简单的解决方案。任何关于此事的反馈将不胜感激。
男.
好吧,首先我认为你的方法是最简单的。但是你让我感兴趣,我探索了另一种 select 元素的方法。当然可能它需要大量的工作来调整它,但也许会让你走上另一种方式。
我的想法是基于VisualTreeHelper.HitTest
。它在您指定的点执行命中测试,returns 在该点找到的依赖对象。所以我所做的是监听 MouseRightButtonDown 事件(在我的示例中 Window),并且从按下鼠标右键的那一点开始,我计算出一个由点组成的网格,形成一个圆形网格。然后我 HitTest 每个点,如果我找到一个已知的命名路径,我可以安全地 select 它。
在这个(太长的)解释之后,这里有一个示例代码:
List<DependencyObject> hitResultsList = new List<DependencyObject>();
private void WrapPanel_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
Window wp = sender as Window;
// Retrieve the coordinate of the mouse position.
Point pt = e.GetPosition((UIElement)sender);
bool elementFound = false;
foreach (Point point in GetPointsInCircle(pt, 8, pt, new Point(2, 2)))
{
// Clear the contents of the list used for hit test results.
Debug.Print(point.ToString());
hitResultsList.Clear();
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest(wp, null,
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(point));
// Perform actions on the hit test results list.
foreach (DependencyObject d in hitResultsList)
{
if (d is Path)
{
Path p = d as Path;
if (p.Name == "link1")
{
elementFound = true; //Here we found the Path with name link1, we could then select it
break;
}
}
}
if (elementFound) break;
}
}
MyHitTestResult:
// Return the result of the hit test to the callback.
public HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
// Add the hit test result to the list that will be processed after the enumeration.
hitResultsList.Add(result.VisualHit);
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
GetPointsInCircle(获取点的圆形网格):
private static IEnumerable<Point> GetPointsInCircle(Point circleCenter, float radius, Point gridCenter, Point gridStep)
{
if (radius <= 0)
{
throw new ArgumentOutOfRangeException("radius", "Argument must be positive.");
}
if (gridStep.X <= 0 || gridStep.Y <= 0)
{
throw new ArgumentOutOfRangeException("gridStep", "Argument must contain positive components only.");
}
// Loop bounds for X dimension:
int i1 = (int)Math.Ceiling((circleCenter.X - gridCenter.X - radius) / gridStep.X);
int i2 = (int)Math.Floor((circleCenter.X - gridCenter.X + radius) / gridStep.X);
// Constant square of the radius:
float radius2 = radius * radius;
for (int i = i1; i <= i2; i++)
{
// X-coordinate for the points of the i-th circle segment:
double x = gridCenter.X + i * gridStep.X;
// Local radius of the circle segment (half-length of chord) calulated in 3 steps.
// Step 1. Offset of the (x, *) from the (circleCenter.x, *):
double localRadius = circleCenter.X - x;
// Step 2. Square of it:
localRadius *= localRadius;
// Step 3. Local radius of the circle segment:
localRadius = (float)Math.Sqrt(radius2 - localRadius);
// Loop bounds for Y dimension:
int j1 = (int)Math.Ceiling((circleCenter.Y - gridCenter.Y - localRadius) / gridStep.Y);
int j2 = (int)Math.Floor((circleCenter.Y - gridCenter.Y + localRadius) / gridStep.Y);
for (int j = j1; j <= j2; j++)
{
yield return new Point(x, gridCenter.Y + j * gridStep.Y);
}
}
}
参考文献:
HitTest on MSDN
Get all points on a uniform discrete grid inside a circle's radius (Adapted)
一个快速的想法可能是在现有曲线下方渲染一条透明的粗曲线。 WPF 的命中测试系统可能仍会拾取对透明度的点击,并为您提供一些不可见的效果 'glow' 围绕视觉呈现的内容,用户仍然可以单击以与之交互。
我目前正在开发类似图形的编辑器应用程序。到目前为止,我得到了节点和连接显示,我可以移动它们等。 我希望能够 select 将一个节点连接到另一个节点(橙色节点)的曲线。
选择曲线本身并不是真正的问题,它只是有点棘手,因为它是一个相当薄的对象(原始笔画粗细为 2)。我不得不增加笔划以使其更容易 select。我想保持笔触较小,因为我认为它看起来更好。 我的想法是绘制两条曲线,其中一条具有高描边粗细但透明,最重要的是实际的彩色曲线。这将使我基本上可以容忍我需要点击实际曲线到 select 的距离。
现在曲线 (Connection) 是一个 class 继承自 Shape 的曲线,为了拥有其中的两个,我将它们包装在一个自定义控件中。它成功了,我不喜欢它的事实是我必须将所有属性包装在可能的自定义形状上以便将数据传播到我的两个连接,例如我必须包装开始和结束点属性。 我可能也可以通过绑定来实现,但这基本上是将问题从代码隐藏转移到 XAML.
是否有更好的方法来实现我想要做的事情?我不是 WPF 的专家,所以我可能忽略了一些简单的解决方案。任何关于此事的反馈将不胜感激。
男.
好吧,首先我认为你的方法是最简单的。但是你让我感兴趣,我探索了另一种 select 元素的方法。当然可能它需要大量的工作来调整它,但也许会让你走上另一种方式。
我的想法是基于VisualTreeHelper.HitTest
。它在您指定的点执行命中测试,returns 在该点找到的依赖对象。所以我所做的是监听 MouseRightButtonDown 事件(在我的示例中 Window),并且从按下鼠标右键的那一点开始,我计算出一个由点组成的网格,形成一个圆形网格。然后我 HitTest 每个点,如果我找到一个已知的命名路径,我可以安全地 select 它。
在这个(太长的)解释之后,这里有一个示例代码:
List<DependencyObject> hitResultsList = new List<DependencyObject>();
private void WrapPanel_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
Window wp = sender as Window;
// Retrieve the coordinate of the mouse position.
Point pt = e.GetPosition((UIElement)sender);
bool elementFound = false;
foreach (Point point in GetPointsInCircle(pt, 8, pt, new Point(2, 2)))
{
// Clear the contents of the list used for hit test results.
Debug.Print(point.ToString());
hitResultsList.Clear();
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest(wp, null,
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(point));
// Perform actions on the hit test results list.
foreach (DependencyObject d in hitResultsList)
{
if (d is Path)
{
Path p = d as Path;
if (p.Name == "link1")
{
elementFound = true; //Here we found the Path with name link1, we could then select it
break;
}
}
}
if (elementFound) break;
}
}
MyHitTestResult:
// Return the result of the hit test to the callback.
public HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
// Add the hit test result to the list that will be processed after the enumeration.
hitResultsList.Add(result.VisualHit);
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
GetPointsInCircle(获取点的圆形网格):
private static IEnumerable<Point> GetPointsInCircle(Point circleCenter, float radius, Point gridCenter, Point gridStep)
{
if (radius <= 0)
{
throw new ArgumentOutOfRangeException("radius", "Argument must be positive.");
}
if (gridStep.X <= 0 || gridStep.Y <= 0)
{
throw new ArgumentOutOfRangeException("gridStep", "Argument must contain positive components only.");
}
// Loop bounds for X dimension:
int i1 = (int)Math.Ceiling((circleCenter.X - gridCenter.X - radius) / gridStep.X);
int i2 = (int)Math.Floor((circleCenter.X - gridCenter.X + radius) / gridStep.X);
// Constant square of the radius:
float radius2 = radius * radius;
for (int i = i1; i <= i2; i++)
{
// X-coordinate for the points of the i-th circle segment:
double x = gridCenter.X + i * gridStep.X;
// Local radius of the circle segment (half-length of chord) calulated in 3 steps.
// Step 1. Offset of the (x, *) from the (circleCenter.x, *):
double localRadius = circleCenter.X - x;
// Step 2. Square of it:
localRadius *= localRadius;
// Step 3. Local radius of the circle segment:
localRadius = (float)Math.Sqrt(radius2 - localRadius);
// Loop bounds for Y dimension:
int j1 = (int)Math.Ceiling((circleCenter.Y - gridCenter.Y - localRadius) / gridStep.Y);
int j2 = (int)Math.Floor((circleCenter.Y - gridCenter.Y + localRadius) / gridStep.Y);
for (int j = j1; j <= j2; j++)
{
yield return new Point(x, gridCenter.Y + j * gridStep.Y);
}
}
}
参考文献:
HitTest on MSDN
Get all points on a uniform discrete grid inside a circle's radius (Adapted)
一个快速的想法可能是在现有曲线下方渲染一条透明的粗曲线。 WPF 的命中测试系统可能仍会拾取对透明度的点击,并为您提供一些不可见的效果 'glow' 围绕视觉呈现的内容,用户仍然可以单击以与之交互。