Reputation: 79
I'm trying to create a "fake" drag animation on a chart. The goal is to allow the user to manually edit points from a graph by dragging them.
The idea is fairly simple: I put a canvas on top of the graph and upon clicking on one of the graph's point, a red circle becomes visible on the clicked mark value, as well as 2 lines coming from the previous and next graph point. I "just" have to set the opacity of each shape from 0 to 1 and specify their position (XAML code):
<Canvas Name="GraphOverlay" Grid.Row="1">
<Ellipse Canvas.Left="{Binding ElipseCanvasLeft}" Canvas.Top="{Binding ElipseCanvasTop}" Stroke="{Binding ShapesColor}" StrokeThickness="6" Width="10" Height="10" Opacity="{Binding ShapesOpacity}"/>
<Line X1="{Binding leftX1}" X2="{Binding leftX2}" Y1="{Binding leftY1}" Y2="{Binding leftY2}" Stroke="{Binding ShapesColor}" StrokeThickness="3" Opacity="{Binding ShapesOpacity}"/>
<Line X1="{Binding rightX1}" X2="{Binding rightX2}" Y1="{Binding rightY1}" Y2="{Binding rightY2}" Stroke="{Binding ShapesColor}" StrokeThickness="3" Opacity="{Binding ShapesOpacity}"/>
</Canvas>
So far I can easily get the graph values from the point I clicked on as well as the previous point and next point on the graph. However, to define the position of each shape on the cavas (1 circle and two lines), I need to convert these graph values into actual pixel values. For the circle, I just have to get the mouse position when the user clics. I am using this:
<i:Interaction.Behaviors>
<utility:MouseBehaviour MouseX="{Binding PanelX, Mode=OneWayToSource}" MouseY="{Binding PanelY, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
And the MouseBehaviour class:
public class MouseBehaviour : System.Windows.Interactivity.Behavior<FrameworkElement>
{
public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
"MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public double MouseY
{
get { return (double)GetValue(MouseYProperty); }
set { SetValue(MouseYProperty, value); }
}
public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
"MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public double MouseX
{
get { return (double)GetValue(MouseXProperty); }
set { SetValue(MouseXProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
}
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var pos = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = pos.X;
MouseY = pos.Y;
}
protected override void OnDetaching()
{
AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
}
}
And PanelX
and PanelY
are two properties from the ViewModel. Source
However I am struggling getting the pixel coordinate of the previous and next point. On livechart's website, there is an example that shows how to use both commands
and events
to access graph values. However, when using events, they have access to the ConvertToPixels
method which is associated to the chart object. I don't have access to this method from the viewmodel.
Is there a way around it, or should I manually code that method ?
Edit: Here is a picture that shows what I am aiming for and what I mean by "next point", "previous point", ... Hopefully it helps!
Upvotes: 0
Views: 304
Reputation: 169160
I found a solution, I don't really know how ugly this is, but it keeps my codebehind nice and blank.
It's (very) ugly and should never pass a code review :)
Your view model now has a dependency not only on the App
class but also on a particular control in a particular window that must be present on the screen for your code to work. This will for example never work in a unit test and breaks the MVVM pattern badly.
You should inject the view model with an interface and interact with the view using this interface. There is an example of how to do this available here if you are interested.
Upvotes: 1
Reputation: 79
I found a solution, I don't really know how ugly this is, but it keeps my codebehind nice and blank.
To get access to that UI element, you have to first name it in the XAML:
<lvc:CartesianChart Name="Chart" >
...
</lvc:CartesianChart>
An then look for it in you app windows:
var MyChart = App.Current.Windows[0].FindName("Chart") as Chart;
I am not really at peace with the Windows[0]
being hard coded, but this does the trick.
Upvotes: 0