Draw curved path between two objects on a <Canvas/> in Silverlight - silverlight

I am playing around with some stuff in Silverlight, and I am trying to dynamically draw a curved line between two other objects on a <Canvas/>. I tried doing something like this:
public partial class MainNodeConnection : UserControl
{
private MainNode _sourceNode;
public MainNode SourceNode
{
get { return _sourceNode; }
set { _sourceNode = value; }
}
private ChildNode _targetNode;
public ChildNode TargetNode
{
get { return _targetNode; }
set { _targetNode = value; }
}
private double _sourceX;
private double _sourceY;
private double _targetX;
private double _targetY;
private Path _connection;
public MainNodeConnection()
{
InitializeComponent();
_connection = new Path();
this.Content = _connection;
}
public void UpdateLocations()
{
_sourceX = Canvas.GetLeft(_sourceNode) + (SourceNode.Width/2);
_sourceY = Canvas.GetTop(_sourceNode) + (SourceNode.Height/2);
_targetX = Canvas.GetLeft(_targetNode);
_targetY = Canvas.GetTop(_targetNode);
string pathData = String.Format("M {0},{1} C {2},{3} {4},{5}", _sourceX, _sourceY, _targetX - _sourceX, _targetY - _sourceX, _targetX, _targetY);
PathGeometry geoData = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = new Point(_sourceX, _sourceY);
BezierSegment pseg = new BezierSegment();
pseg.Point1 = new Point(_targetX - _sourceX, _targetY - _sourceY);
pFigure.Segments.Add(pseg);
geoData.Figures.Add(pFigure);
_connection.Stroke = new SolidColorBrush(Colors.Black);
_connection.StrokeThickness = 1;
_connection.Data = geoData;
this.Content = _connection;
}
}
and I built the objects on the <Canvas/> like this:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
MainNodeConnection mnc = new MainNodeConnection();
mnc.Width = 300;
mnc.Height = 300;
Canvas.SetLeft(mnc, Canvas.GetLeft(mainNode1));
Canvas.SetTop(mnc, Canvas.GetTop(mainNode1));
mnc.SourceNode = mainNode1;
mnc.TargetNode = childNode1;
nodeCanvas.Children.Add(mnc);
mnc.UpdateLocations();
}
}
the problem I have is I can't get the line to show up. Can anyone spot what I'm doing wrong, or is there a different/better way to do this?

You're missing Point2 and Point3 in the Bezier curve.
From the help file:
A cubic Bezier curve is defined by
four points: a start point, an end
point (Point3), and two control points
(Point1 and Point2). The BezierSegment
class does not contain a property for
the starting point of the curve; it
only defines the end point. The
beginning point of the curve is the
current point of the PathFigure to
which the BezierSegment is added.
The two control points of a cubic
Bezier curve behave like magnets,
attracting portions of what would
otherwise be a straight line toward
themselves and producing a curve. The
first control point, Point1, affects
the beginning portion of the curve;
the second control point, Point2,
affects the ending portion of the
curve. Note that the curve doesn't
necessarily pass through either of the
control points; each control point
moves its portion of the line toward
itself, but not through itself.

Related

WPF, app looses ability to create or manipulate GUI objects

i need some help with WPF
I'm getting started with WPF and wanted to make a simple snake game with the following classes:
GameBoard : Window
{
private Game game;
public GameBoard(){ this.game = new Game(this); }
public Rectangle rectangleFactory(int x, int y)
{
Rectangle rect = new Rectangle();
rect.Width = 10;
rect.Height = 10;
GameArea.Children.Add(rect); /// My Canvas is called GameArea
Canvas.SetLeft(rect, 10 * x);
Canvas.SetTop(rect, 10 * y);
return rect;
}
public void rectangleBin(Rectangle)
{
GameArea.Children.Remove(rect);
}
}
Thing{
private GameBoard Board;
private System.Windows.Shapes.Rectangle rect;
private int[] coordinaters;
public Thing(GameBoard board, int[] coordinates)
{
this.Board = board;
Draw();
}
public Draw(){ rect = Board.rectangleFactory(coordinates);}
public Dispose(){ Board.rectangleBin(rect)}
}
SnakeSegment : Thing
{
SnakeSegment next;
public SnakeSegment(GameBoard board, int[] coordinates) : base(board, coordinates) { }
}
Apple : Thing()
{
public Apple(GameBoard board, int[] coordinates) : base(board, coordinates) { }
public void newApple()
{
Dispose();
getNewCoordinate();
Draw();
}
}
Game
{
private GameBoard Board;
private Apple apple;
Private Snake snake;
private static System.timers.timer timer
public Game(GameBoard board)
{
this.Board = board;
this.apple = new Apple(board, int start_coordinates);
this.snake = new snake(this);
this.timer = new Timer(100);
timer.Elapsed += new ElapsedEventHandler(update_game);
}
public void update_game(object o, ElapsedEventArgs s){ snake.move_forward(); }
}
Snake
{
private SnakeSegmet Head;
Private SnakeSegment Tail
private GameBoard Board;
public Snake(Game game)
{
this.Board = game.Board;
this.Head = new SnakeSegment(Board, start_cooridantse)
this.Tail = Head;
}
private void AddSegment()
{
Head.Next = new SnakeSegment(Board, newCoordinates);
Head = Head.Next;
}
}
There is of course more but i left it out in the interest of readability.
When i start the game it draws the game area and adds an apple and an initial rectangle for the snake, so far so good
But then i update the game and things go south
my next call to SnakeSegment constructor in Head.Next = new SnakeSegment()
ends in rectangleFactory when calling new Rectangle()
the program doesn't crash, but it topples the current stack and nothing more is executed untinl the timer raises an other event
ive tried to troubleshoot and reroute the flow a bit and if i instead call Apple::newApple() the same thing happens when i get to GameArea.Children.Remove(rect);
Im completely new to WPF and this really gives me a headache, if anybody could explain why it happens id be very grateful.
Regards, Tobias
If you want to write your program like that, use WinForms. If you want to use WPF, you'll need to pretty much start again from scratch. WPF is a data-centric language... we manipulate data objects, not UI objects.
Using WPF, you'd need to think about your game objects as a collection of data objects. The board would then be the UI container control that you data bind your collection to. You would then define what each object should look like in a DataTemplate in the XAML. After binding the location properties of the data objects to the location properties of the UI objects you could then move the objects in the UI by changing the location properties of the data objects from code.
Having said all that, I think that you'll find that WPF is not a good Framework for making games. Its main strengths are its data binding capabilities and rich UI possibilities, but it's not very efficient. You'll need a good graphics card to get the most out of it.

Animate WPF Drag Adorner movement

in our application we use a adorner for some fake drag & drop. The adorner doesn't follow to mouse but is set to some specific coordinates on the screen when the mouse moves.
double xPosLocation = (int)(dividerDistanceXAxis * virtualPosition) + YAxisData.SpacingLeft;
double yPosLocation = CalculateValueToYPosition(VirtualPriceOfVehicle);
DragAdorner.UpdatePosition(xPosLocation - offsetX, yPosLocation + offsetY);
This works fine so far. But with a code like this the adorner jumps from one location to another. This is how it was supposed to work but it would be nice to have some smooth movement transitions when the adorner it set to a new position.
Can it be done? Since I see no position property I guess it is not possible to do a double-animation or something like that.
You have to use a DependencyProperty for animation
public class DragAdorner
{
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
// Using a DependencyProperty as the backing store for PositionProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(DragAdorner), new UIPropertyMetadata(PositionChanged));
private static void PositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UpdatePosition(Position);
}
}
Then you could do something like this (not tested)
double xPosLocation = (int)(dividerDistanceXAxis * virtualPosition) + YAxisData.SpacingLeft;
double yPosLocation = CalculateValueToYPosition(VirtualPriceOfVehicle);
Point newLocation = new Point(xPosLocation, yPosLocation);
PointAnimation myPointAnimation = new PointAnimation();
myPointAnimation.From = DragAdorner.Position;
myPointAnimation.To = newLocation;
myPointAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myPointAnimation);
Storyboard.SetTargetName(myPointAnimation, DragAdorner.Position);
myStoryboard.Begin();

How do I get the current mouse screen coordinates in WPF?

How to get current mouse coordination on the screen?
I know only Mouse.GetPosition() which get mousePosition of element, but I want to get the coordination without using element.
Or in pure WPF use PointToScreen.
Sample helper method:
// Gets the absolute mouse position, relative to screen
Point GetMousePos() => _window.PointToScreen(Mouse.GetPosition(_window));
To follow up on Rachel's answer.
Here's two ways in which you can get Mouse Screen Coordinates in WPF.
1.Using Windows Forms. Add a reference to System.Windows.Forms
public static Point GetMousePositionWindowsForms()
{
var point = Control.MousePosition;
return new Point(point.X, point.Y);
}
2.Using Win32
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
var w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
Do you want coordinates relative to the screen or the application?
If it's within the application just use:
Mouse.GetPosition(Application.Current.MainWindow);
If not, I believe you can add a reference to System.Windows.Forms and use:
System.Windows.Forms.Control.MousePosition;
If you try a lot of these answers out on different resolutions, computers with multiple monitors, etc. you may find that they don't work reliably. This is because you need to use a transform to get the mouse position relative to the current screen, not the entire viewing area which consists of all your monitors. Something like this...(where "this" is a WPF window).
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
public System.Windows.Point GetMousePosition()
{
var point = Forms.Control.MousePosition;
return new Point(point.X, point.Y);
}
This works without having to use forms or import any DLLs:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Gets the current mouse position on screen
/// </summary>
private Point GetMousePosition()
{
// Position of the mouse relative to the window
var position = Mouse.GetPosition(Window);
// Add the window position
return new Point(position.X + Window.Left, position.Y + Window.Top);
}
You may use combination of TimerDispatcher (WPF Timer analog) and Windows "Hooks" to catch cursor position from operational system.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT pPoint);
Point is a light struct. It contains only X, Y fields.
public MainWindow()
{
InitializeComponent();
DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += new EventHandler(timer_tick);
dt.Interval = new TimeSpan(0,0,0,0, 50);
dt.Start();
}
private void timer_tick(object sender, EventArgs e)
{
POINT pnt;
GetCursorPos(out pnt);
current_x_box.Text = (pnt.X).ToString();
current_y_box.Text = (pnt.Y).ToString();
}
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
This solution is also resolving the problem with too often or too infrequent parameter reading so you can adjust it by yourself. But remember about WPF method overload with one arg which is representing ticks not milliseconds.
TimeSpan(50); //ticks
If you're looking for a 1 liner, this does well.
new Point(Mouse.GetPosition(mWindow).X + mWindow.Left, Mouse.GetPosition(mWindow).Y + mWindow.Top)
The + mWindow.Left and + mWindow.Top makes sure the position is in the right place even when the user drags the window around.
Mouse.GetPosition(mWindow) gives you the mouse position relative to the parameter of your choice.
mWindow.PointToScreen() convert the position to a point relative to the screen.
So mWindow.PointToScreen(Mouse.GetPosition(mWindow)) gives you the mouse position relative to the screen, assuming that mWindow is a window(actually, any class derived from System.Windows.Media.Visual will have this function), if you are using this inside a WPF window class, this should work.
I wanna use this code
Point PointA;
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e) {
PointA = e.MouseDevice.GetPosition(sender as UIElement);
}
private void Button_Click(object sender, RoutedEventArgs e) {
// use PointA Here
}

How to draw connecting lines between two controls on a grid WPF

I am creating controls (say button) on a grid. I want to create a connecting line between controls.
Say you you do mousedown on one button and release mouse over another button. This should draw a line between these two buttons.
Can some one help me or give me some ideas on how to do this?
Thanks in advance!
I'm doing something similar; here's a quick summary of what I did:
Drag & Drop
For handling the drag-and-drop between controls there's quite a bit of literature on the web (just search WPF drag-and-drop). The default drag-and-drop implementation is overly complex, IMO, and we ended up using some attached DPs to make it easier (similar to these). Basically, you want a drag method that looks something like this:
private void onMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if (element == null)
return;
DragDrop.DoDragDrop(element, new DataObject(this), DragDropEffects.Move);
}
On the target, set AllowDrop to true, then add an event to Drop:
private void onDrop(object sender, DragEventArgs args)
{
FrameworkElement elem = sender as FrameworkElement;
if (null == elem)
return;
IDataObject data = args.Data;
if (!data.GetDataPresent(typeof(GraphNode))
return;
GraphNode node = data.GetData(typeof(GraphNode)) as GraphNode;
if(null == node)
return;
// ----- Actually do your stuff here -----
}
Drawing the Line
Now for the tricky part! Each control exposes an AnchorPoint DependencyProperty. When the LayoutUpdated event is raised (i.e. when the control moves/resizes/etc), the control recalculates its AnchorPoint. When a connecting line is added, it binds to the DependencyProperties of both the source and destination's AnchorPoints. [EDIT: As Ray Burns pointed out in the comments the Canvas and grid just need to be in the same place; they don't need to be int the same hierarchy (though they may be)]
For updating the position DP:
private void onLayoutUpdated(object sender, EventArgs e)
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, isInput ? 0 : size.Height);
AnchorPoint = TransformToVisual(node.canvas).Transform(ofs);
}
For creating the line class (can be done in XAML, too):
public sealed class GraphEdge : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Source { get { return (Point) this.GetValue(SourceProperty); } set { this.SetValue(SourceProperty, value); } }
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Destination { get { return (Point) this.GetValue(DestinationProperty); } set { this.SetValue(DestinationProperty, value); } }
public GraphEdge()
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding {Source = this, Path = new PropertyPath(SourceProperty)};
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
Content = new Path
{
Data = geometry,
StrokeThickness = 5,
Stroke = Brushes.White,
MinWidth = 1,
MinHeight = 1
};
}
}
If you want to get a lot fancier, you can use a MultiValueBinding on source and destination and add a converter which creates the PathGeometry. Here's an example from GraphSharp. Using this method, you could add arrows to the end of the line, use Bezier curves to make it look more natural, route the line around other controls (though this could be harder than it sounds), etc., etc.
See also
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dd246675-bc4e-4d1f-8c04-0571ea51267b
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx
http://www.syncfusion.com/products/user-interface-edition/wpf/diagram
http://www.mindscape.co.nz/products/wpfflowdiagrams/

Creating SelectionBorder: Bit in the face by decimal rounding?

I am currently implementing a class called SelectionBorder in WPF. It's derived from the Shape class.
It basically looks like this:
public class SelectionBorder : Shape
{
public Point StartPoint {get; set;}
public PointCollection Points {get; set;}
public double StrokeLength {get; set;}
protected override Geometry DefiningGeometry{
get{
//Magic!
}
}
}
The StartPoint and Points properties determine the corners of the border. The border is a typical stroked line border (one black stroke, one invisible stroke like that: - - - -)
The problem that I have now is that since the corner points are freely choosable it's pretty common that the count of strokes (meaning black and invisible strokes) is not even (in fact not even an integer) and therefore the first stroke looks longer than the others (visible in the picture). This maybe doesn't seem to be a big deal but I later want to animate the border so that the strokes circle round the content. When doing this animation the tiny flaw in the static view becomes clearly visible and in my opinion is highly disturbing.
alt text http://img14.imageshack.us/img14/2874/selectionborder.png
The problem is that I tried to determine a StrokeLength that gets as close to the original StrokeLength as possible and creates an even number of strokes. However the problem I've run into is that WPF (obviously) can't display the whole precision of a double decimal StrokeLength and therefore the resulting stroke number is uneven once again.
Is there any workaround for this problem? Do you probably have another solution for my problem?
Thanks in advance!
EDIT: I retested and reviewed the code after a little break for fitness today and after all it happens only on very big StrokeLengths. I plan to use StrokeLengths of 2 where the little animation jumping does matter much less than I originally thought.
You could make more than one corner "un-matched" in that regard. For example, instead of having one point be the "source" and "destination" of the animated dashes, you could pick 2 points. One would be the "source", dashes appearing to march away from it in 2 directions, and another point be the "destination", where dashes converge and disappear.
GIMP, for example, animates selection dashed lines in this way and seems to pick a point closest to the lower-left for the "source" and a point closest to the upper-right for the "destination".
You could come up with some other scheme, as well.
Just remember that while it may look disturbing to you, most users will not care.
I just found a way that makes it way easier to create such an animated SelectionBorder.
Instead of creating the animation by moving an self-created AnimationPoint through animation I just animated the StrokeDashOffset property natively provided by the Shape class and setting the StrokeDashArray to define the StrokeLength.
It would look like this in XAML:
<namespace:SelectionBorder StrokeDashArray="2" AnimationDuration="0:0:1" Stroke="Black" />
The class looks like this:
public class SelectionBorder : Shape
{
private DoubleAnimation m_Animation;
private bool m_AnimationStarted;
public SelectionBorder()
{
IsVisibleChanged += OnIsVisibleChanged;
}
protected void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (Visibility == Visibility.Visible)
{
StartAnimation();
}
else
{
StopAnimation();
}
}
public void StartAnimation()
{
if (m_AnimationStarted)
return;
if (m_Animation == null)
{
m_Animation = CreateAnimation();
}
BeginAnimation(StrokeDashOffsetProperty, m_Animation);
m_AnimationStarted = true;
}
protected virtual DoubleAnimation CreateAnimation()
{
DoubleAnimation animation = new DoubleAnimation();
animation.From = 0;
if (StrokeDashArray.Count == 0)
animation.To = 4;
else
animation.To = StrokeDashArray.First() * 2;
animation.Duration = AnimationDuration;
animation.RepeatBehavior = RepeatBehavior.Forever;
return animation;
}
public void StopAnimation()
{
if (m_AnimationStarted)
{
BeginAnimation(StrokeDashOffsetProperty, null);
m_AnimationStarted = false;
}
}
#region Dependency Properties
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(SelectionBorder), new UIPropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5))));
#endregion Dependency Properties
protected override Geometry DefiningGeometry
{
get
{
double width = (double.IsNaN(Width)) ? ((Panel)Parent).ActualWidth : Width;
double height = (double.IsNaN(Height)) ? ((Panel)Parent).ActualHeight : Height;
RectangleGeometry geometry = new RectangleGeometry(new Rect(0, 0, width, height));
return geometry;
}
}
}

Resources