create and move a point on canvas at once - c#

I got a Problem with creating and moving a point (an ellipse) in WPF MVVM.
Right now i have a RelayCommand which calls my create point handler in my vm which creates a command and executes it:
private void CreatePointHandler(MouseEventArgs e)
{
AddConnectionPointCommand addConnectionPointCommand = new AddConnectionPointCommand(this, e);
PetriNetViewModel.ExecuteCommand(addConnectionPointCommand);
}
Furthermore for an already existing point, I got a Move handler aswell (in another vm tho):
public void MovePointHandler(ConnectionPoint pointMoved, Point oldLocation, Point newLocation)
{
Vector move = new Vector(newLocation.X - oldLocation.X, newLocation.Y - oldLocation.Y);
PetriNetViewModel.ExecuteCommand(new MoveDragCanvasElementsCommand(new ConnectionPoint[] { pointMoved }, move));
}
Adding and moving a point afterwards is just working as expected.
Now i want to give the user the possibility to add and move a point in one step. In my CreatePointHandler i can figure out if the left mouse buttin it still pressed like this:
if (e.LeftButton == MouseButtonState.Pressed) {
}
but how would I move the point now? The MovePointHandler is called by an event in the codebehind (I know this shouldnt be done in mvvm, but my collegues and I think it's ok if you don't have too much code in it), which is also passing an ElementsMovedEventArgs which I dont have here.
thanks for your help in advance.

It's hard to say without seeing your codebehind that calls these handlers.
I would have thought you should have a concept of a SelectedPoint, at which you can always check if there is an intended drag happening when the mouse is moved.
i.e.
private ConnectionPoint SelectedPoint { get; set; }
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
// dragging something.. check if there is a point selected
if (SelectedPoint != null)
{
_viewModel.MovePointHandler(SelectedPoint, _oldLocation, _newLocation);
}
}
}
Then, as part of your CreatePointHandler, you immediately set the newly created instance to the SelectedPoint, until a MouseUp is detected.
private void ExecuteAddConnectionPointCommand()
{
// standard logic...
ConnectionPoint addedPoint = ...
SelectedPoint = addedPoint;
}
The specifics of the implementation will likely change depending on your architecture, but hopefully you get the point.

Related

Creating custom Label in C# and passing data in events

I had been playing around with an idea for a game, and implementation was going fairly well, but I have hit a stumbling block.
Basically, I have a form, which will show talent trees. I am just going to use labels to display the relevant details, and I want to create them programmatically. The display part is working fine, the part I am having trouble with is adding an event handler to the labels.
I want to be able to pass data during the event handling, so that I can identify which specific label was clicked, but I am hitting a brick wall. So when a particular label is clicked, the name of its associated skill (just passing a string) will be sent to the event handler. Any help would be appreciated. Here is the relevant code that I have:
public void DisplayTree()
{
int i=0;
startPoint.X = 40;
startPoint.Y = 125;
foreach(SkillNode s in tree.tier1)
{
for (i=0; i < s.labels.Count;i++ )
{
//Displays a label for each available rank for a skill
s.labels.ElementAt(i).Text = (i+1).ToString()+"/"+s.maxRank.ToString();
s.labels.ElementAt(i).Location = startPoint;
startPoint.Y += s.labels.ElementAt(i).Height + 2;
s.labels.ElementAt(i).Name = "lbl"+s.name+i.ToString();
//Only enable it to be clicked if the user is at the correct rank
if (s.rank == i)
{
s.labels.ElementAt(i).Enabled = true;
}
else
{
s.labels.ElementAt(i).Enabled = false;
}
//Add Event here
//I want to pass the name of the skill with the event
this.Controls.Add(s.labels.ElementAt(i));
}
startPoint.X += s.title.Width + 5;
startPoint.Y = 125;
}
}
public void LabelClick()
{
//Code here to pick out the name of the label
}
Try this:
public void LabelClick()
{
Console.WriteLine(((Control)sender).Name);
}
When you create an event and want to follow the official C# styleguide, you follow the following pattern:
public delegate void {YourName}EventHandler(object sender, {YourName}EventArgs args);
public event {YourName}EventHandler EventName;
Every information about what happened in the event or can be manipulated by the subscriber is stored in a class that inherits EventArgs. The delegate also contains a reference to the sender, which is the object that fires the event.
When you fire an event you do the following, regularly in a protected method that has the same name as the Event with an "On" as prefix:
EventName?.Invoke(this, new {YourName}EventArgs() { Initialize Stuff });
As you can see, you can work with the sender and identify the object. In your case you could also change object sender to UIElement sender (or similar) to make it easier to identify stuff without a cast.

Make a method wait for right click to go to next step (C# form)

well, I'm writing a bot that will use certain coordinates on screen and then will simulate 15 clicks on them (every click with different coordinates). I already made it work with coordinates I entered manually on the code but now I need a way to record those coordinates. What i wanted to do is: the users press a button, then the program shows a messagebox saying "right click the main menu", the user right clicks that and those coordinates will be recorded on an array, then the program will show a second messagebox asking to right click the next button and so... My problem is that I don't know how to make the method wait for the user to right click to continue.
I tested my program by making an event that would trigger everytime I right click and show the coordinates in a messagebox, using a UserActivityHook class with contains the event OnMouseActivity:
UserActivityHook actHook;
void MainFormLoad(object sender, System.EventArgs e)
{
actHook = new UserActivityHook();
// crate an instance with global hooks
// hang on events
actHook.OnMouseActivity+=new MouseEventHandler(MouseMoved);
}
public void MouseMoved(object sender, MouseEventArgs e)
{
if (e.Clicks > 0)
{
if (e.Button.Equals(MouseButtons.Right))
{
MessageBox.Show("X:" + e.X + " Y:" + e.Y);
}
}
}
I've trying to do something like:
private void button1_Click(object sender, EventArgs e)
{
RecordMacro(cords, 1);
}
public void RecordMacro(int coordinates[][], int slotnumber){
MessageBox.show("Right click main menu");
//saves coordinates on [0][0] and [0][1]
WaitForRightClickAndSaveCords(coordinates[][]);
MessageBox.show("Right click resupply button");
//saves coordinates on [1][0] and [1][1]
WaitForRightClickAndSaveCords(coordinates[][]);
...
}
I'm still a newbie and this is my first question in StackOverflow (I usually find an answer browsing here and don't have the need to ask myself) so I'll gladly accept any critics.
This is easiest to implement using C# 5.0's asynchrony model. We'll start out by creating a method that will generate a Task that will be completed when your conditions are met. It will do this by creating a TaskCompletionSource, adding a handler to the event, and marking the task as completed in the handler. Throw in some boilerplate code to make sure the handler is removed when done, return the Task from the completion source, and we're set:
public static Task<Point> WhenRightClicked(this UserActivityHook hook)
{
var tcs = new TaskCompletionSource<Point>();
MouseEventHandler handler = null;
handler = (s, e) =>
{
if (e.Clicks > 0 && e.Button == MouseButtons.Right)
{
tcs.TrySetResult(new Point(e.X, e.Y));
hook.OnMouseActivity -= handler;
}
};
hook.OnMouseActivity += handler;
return tcs.Task;
}
Now you can write:
public async void RecordMacro(int[][] coordinates, int slotnumber)
{
MessageBox.Show("Right click main menu");
Point mainMenuPosition = await actHook.WhenRightClicked();
MessageBox.Show("Right click resupply button");
Point resupplyButtonPosition = await actHook.WhenRightClicked();
}
There are a myriad number of ways to make this work, none of which you should remotely do. The reason is, that assuming you managed to stop execution of the thread with WaitForRightClick, you would be blocking the UI thread!
By doing that, you prevent the user from being able to click on the element you want (among lots of other reasons to never block the UI thread).
You could thread it or use asynchornous methods, as Servy suggests. This blocks the method (or executes it asynchronously) without blocking the UI thread itself.
While more complex, you could also queue up a bunch of object representing a "ClickTarget". Then, you would listen on the right-click event and record the associated coordinates with the current ClickTarget, dequeue to get the next instruction, and so on.
The complete code would be too long for StackOverflow, but to give you some ideas:
public class ClickTarget
{
Point Coordinates {get; set;}
String TargetName {get; set;}
}
Queue<ClickTarget> clickTargets;
//Obviously you instantiate/populate this somewhere
private void onRightClick(...)
{
ClickTarget target = clickTargets.Dequeue();
target.Coordinates = clickLocation;
MessageBox.Show("Please click on " + clickTargets.Peek().TargetName);
}

Silverlight UI lag - Crosshair on Graph

I've got a telerik radchart that's displaying a beautiful and incredibly-useful-to-end-user line chart.
One of the requirements for this chart was to have a x-value crosshair on the graph (e.g. when a user hovers at any point over the graph, a horizontal line appears, and where this line intersects the actual graph line, a value is displayed in another area of the screen).
In a previous iteration, I used flotJS to do my graphing & crosshair'ing, worked great, and was blazing fast. In converting this to silverlight, I've seen a tremendous amount of lag, and I'd like to know if anyone has any ideas on improving performance.
I currently expose the chart's MouseEnter/MouseLeave events to hide/show the horizontal line. This is done via a System.Windows.Visibility property sitting in my viewmodel (which my crosshair's visibility property is bound to). I then use the MouseMove method to actually calculate the x position.
Here is the code that runs this (first in my chart view's code behind)
private void rad_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
var plotAreaPanel = this.rad.DefaultView.ChartArea.ChildrenOfType<ClipPanel>().FirstOrDefault();
var position = e.GetPosition(plotAreaPanel);
var x = rad.DefaultView.ChartArea.AxisX.ConvertPhysicalUnitsToData(position.X);
(this.DataContext as ViewModels.DateCountsViewModel).XVolume = x;
}
private void rad_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
(this.DataContext as ViewModels.DateCountsViewModel).XVolumeVisibility = System.Windows.Visibility.Visible;
}
private void rad_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
(this.DataContext as ViewModels.DateCountsViewModel).XVolumeVisibility = System.Windows.Visibility.Collapsed;
}
Then in my viewmodel, in the setter for XVolume (which is being Set in the MouseMove method above), this code:
public double XVolume
{
get { return xVolume; }
set
{
this.xVolume = value;
decimal currentX = (decimal)Math.Round(value, 0);
var vol = this.VolumeCollection.Where(x => x.XValue == currentX).FirstOrDefault() as Models.ChartModel;
this.Volume = (vol == null) ? 0 : (int)vol.YValue;
RaisePropertyChanged("XVolume");
}
}
With heavy usage, the line lags up to several inches behind my pointer. I tried throttling my mouse events outlined here, but it's still really laggy.
[Note regarding link above: It uses the CompositionTarget.Rendering event, which is fired just before a frame is rendered. Therefore, to throttle the effect of the mouse event, we set a flag to indicate that we are waiting for a change to be rendered, then reset this flag just before rendering occurs... not allowing simultaneous rendering]
What can I do to mitigate this lag?

MouseDoubleClick & Touch on a ScatterViewItem

I develop a WPF4 touch aplication which use some Microsoft Surface controls. I would like to catch the MouseDoubleClick event on a ScatterViewItem. The event fires when i use the mouse, but when i use my finger, the event is never raised. Why ?
Thanks a lot.
UPDATE :
I finally wrote this code to reproduce a simple double tap with TouchLeave event on a limited rect (16px width & height) and 500ms for the required time between 2 TouchLeave events. This code is not optimized but it works, if you have some remark, don't hesitate :)
private bool _doubleHasTimeAndPos = false;
private TimeSpan? _doubleTouchTime = null;
private Point? _doubleTouchPoint = null;
private void ScatterViewItem_TouchLeave(object sender, TouchEventArgs e)
{
if (!this._doubleHasTimeAndPos)
{
this._doubleTouchTime = DateTime.Now.TimeOfDay;
this._doubleTouchPoint = e.GetTouchPoint(this.scatterView).Position;
this._doubleHasTimeAndPos = true;
}
else
{
if (DateTime.Now.TimeOfDay.Subtract(this._doubleTouchTime.Value).TotalMilliseconds <= 500)
{
Point touchPoint = e.GetTouchPoint(this.scatterView).Position;
double xDelta = Math.Abs(this._doubleTouchPoint.Value.X - touchPoint.X);
double yDelta = Math.Abs(this._doubleTouchPoint.Value.Y - touchPoint.Y);
if (xDelta <= 16 && yDelta <= 16)
{
// DOUBLE TAP here !
}
}
this._doubleTouchTime = null;
this._doubleTouchPoint = null;
this._doubleHasTimeAndPos = false;
}
}
Just a guess due to lack of touch infrastructure to test this code ...
WPF recommends using Mouse.MouseDown attached event for any UIElement and then using ((MouseButtonEventArgs)e.ClickCount) to be checked for value 2 for double click.
<ScatterViewItem Mouse.MouseDown="MouseButtonEventHandler" ... />
And then
private void MouseButtonDownHandler(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
//// Its a double click... should work for touch.
}
}
Let me know if this works....
At an API level, touch events will not generate double click events.
Furthermore, Microsoft's Surface UI design guidelines (disclaimer: I helped write them) actually advise strongly against the concept of "double tap". Touch is meant to give user a more 'natural' way of interacting with technology without the need for any traning, documentation, or memorization of 'hidden' features. Double-tap is not an interaction humans do in the physical world and is not something that there are any visual affordances for. Therefore, it is not appropriate to use in software applications. Apple concurs with this philosophy, btw - you won't see any double-tap interactions on the iPhone or iPad.

WPF: Detect Animation or Cancel Timeline.Completed Event? How?

I'm moving 3d camera like this:
Point3DAnimation pa;
// Triggered by user click
void MoveCamera(object sender, EventArgs e)
{
pa = new Point3DAnimation(myPoint3D, TimeSpan.FromMilliseconds(2000));
pa.Completed += new EventHandler(pa_Completed);
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, pa); // anim#1
}
// we're in place. do some idle animation
void pa_Completed(object sender, EventArgs e)
{
pa = new Point3DAnimation(myPoint3Ddz, TimeSpan.FromMilliseconds(5000));
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, pa); // anim#2
}
User clicks.
Camera moves to choosen position (anim#1).
When anim#1 ends anim#2 is played.
Everything is ok... until user triggers MoveCamera when previous anim#1 is not finished.
In that case:
New anim#1 is starting.
Completed event of old anim#1 is triggered.
anim#2 is started instatntly (overlapping new anim#1).
2 & 3 are wrong here. How i can avoid that?
I think that pa_Completed() should detect that new anim#1 is already playing, or MoveCamera() should unregister Complete event from old anim#1. But what the right way to do it?
If the goal is to chain two animations together, let WPF do the heavy lifting by using the Point3DAnimationUsingKeyFrames class.
First, build the key frame animation in XAML (it's a bear to do it in code):
<Window.Resources>
<Point3DAnimationUsingKeyFrames x:Key="CameraMoveAnimation" Duration="0:0:7">
<LinearPoint3DKeyFrame KeyTime="28%" />
<LinearPoint3DKeyFrame KeyTime="100%" />
</Point3DAnimationUsingKeyFrames>
</Window.Resources>
Next, consume it and set the actual Point3D values (using your code names):
private void MoveCamera(object sender, EventArgs e) {
Point3DAnimationUsingKeyFrames cameraAnimation =
(Point3DAnimationUsingKeyFrames)Resources["CameraMoveAnimation"];
cameraAnimation.KeyFrames[0].Value = myPoint3D;
cameraAnimation.KeyFrames[1].Value = myPoint3dz;
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, cameraAnimation);
}

Categories