Creating a draggable map pin on UWP - c#

After following the instructions in the link here How to create draggable pin in windows 10 to give pick location functionality?
I have a half working dragging pin.
Currently the map pin (Grid) starts in the correct place on the map, however, upon dragging the map pin the pin initially pops up to the top left of the screen (0,0) and starts dragging from here.
Upon dropping the pin it appears to have disconnected from the map element itself as you can then scroll the map and the pin stays in place on the screen.
I am using Xamarin.Forms with a custom map renderer.
Following the original example the map would still pan along with the pin dragging, I corrected this by disabling panning upon Grid_ManipuationStarted.
The issue seems to be with the Grid_ManipulationDelta function as this is what removes the Grid from the MapControl children.
I've uploaded a video of the issue onto YouTube. Found here: https://youtu.be/uUkB5Pi5MnA
My code is as follows:
void IMapControls.startDraggable(Location l) {
// Create the XAML
var grid = new Windows.UI.Xaml.Controls.Grid {
Height=50,
Width=32,
Background = new ImageBrush() { ImageSource= new BitmapImage(new Uri("ms-appx:///pin.png",UriKind.RelativeOrAbsolute)),Stretch = Stretch.Uniform },
};
grid.ManipulationMode = ManipulationModes.TranslateX|ManipulationModes.TranslateY;
grid.ManipulationCompleted += Grid_ManipulationCompleted;
grid.ManipulationStarted += Grid_ManipuationStarted;
grid.ManipulationDelta += Grid_ManipulationDelta;
// Set RenderTransform so not null later
CompositeTransform tran = new CompositeTransform();
grid.RenderTransform = tran;
// Add XAML to the map.
nativeMap.Children.Add(grid);
Geopoint snPoint = new Geopoint(new BasicGeoposition() { Latitude = l.lat,Longitude = l.lng });
MapControl.SetLocation(grid,snPoint);
MapControl.SetNormalizedAnchorPoint(grid,new Windows.Foundation.Point(0.5,1.0));
}
private void Grid_ManipuationStarted(Object sender,ManipulationStartedRoutedEventArgs e) {
nativeMap.PanInteractionMode=MapPanInteractionMode.Disabled;
}
private void Grid_ManipulationDelta(object sender,ManipulationDeltaRoutedEventArgs e) {
var grid = sender as Windows.UI.Xaml.Controls.Grid;
CompositeTransform xform = grid.RenderTransform as CompositeTransform;
xform.TranslateX += e.Delta.Translation.X;
xform.TranslateY += e.Delta.Translation.Y;
e.Handled = true;
}
private void Grid_ManipulationCompleted(object sender,ManipulationCompletedRoutedEventArgs e) {
nativeMap.PanInteractionMode=MapPanInteractionMode.Auto;
var grid = sender as Windows.UI.Xaml.Controls.Grid;
Rect point = grid.TransformToVisual(nativeMap).TransformBounds(new Rect(0,0,grid.Width,grid.Height));
Geopoint gPoint;
nativeMap.GetLocationFromOffset(new Windows.Foundation.Point(point.X,point.Y),out gPoint);
Debug.WriteLine(gPoint.Position.Latitude);
Debug.WriteLine(gPoint.Position.Longitude);
}

Solved this by creating a Grid and adding it to the map, then creating an Image and adding this to the Grid. You drag the Image using Delta.Translation as above and upon dropping the pin it moves the Grid using MapControl.SetLocation (Not TranslateX/Y) to where the pin drops, then TranslateX/Y the pin Image to 0,0 (relative to the parent Grid).
This works as the Grid only moves using SetLocation which seems to work fine and doesn't disconnect it from the map like before. The pin moves relative to the Grid and has no issues as before, ie. Moving to 0,0 upon start and disconnecting from the map object.
Cheers!
Can post code if anyone is interested but I think it is pretty self explanatory.

Related

Position a Winforms dialog directly over a DataGridView cell

I have a DataGridView with 2 columns of file-names. I would like to emulate the Windows File Explorer 'Rename' context menu on these file-names. To that end, I created a simple WinForms dialog with no header and a textbox entry for renaming. I display it when a grid's file-name cell is right-clicked. I am trying to position it directly over the cell, but have been unable to get it to display in the correct location. It's down by several rows and to the right by a few character widths. I'm positioning the dialog thusly:
Point location;
void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) {
var cellRect = dataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
// Point location = dataGridView.Location;
location = dataGridView.Bounds.Location;
location.Offset(cellRect.Location);
location = dataGridView.PointToScreen(location);
}
async void renameToolStripMenuItem_Click(object sender, EventArgs e) {
using (var rfd = new RenameFileDialog(fi)) {
// Lifted from designer
rfd.ControlBox = false;
rfd.Text = string.Empty;
rfd.formBorderStyle = FormBorderStyle.SizableToolWindow;
// Actually in method
rfd.StartPosition = FormStartPosition.Manual;
rfd.Location = location;
rfd.ShowDialog(dataGridView);
}
}
I suspect I'm getting tripped up by Location vs ClientRectangle vs Control Bounds or margins or padding, but I haven't been able to identify where the undesired offsets are coming from. Can someone tell me how to position the dialog, or otherwise suggest a way to emulate Explorer's "Rename' in a dataGridView?
The original sin is here:
location = dataGridView.Bounds.Location;
to translate to Screen coordinates the origin point of a Control, using the Control itself as the relative reference, you have to consider its own origin, which is always (0, 0) (Point.Empty).
If you use its Location property, you instead consider the Control's offset in relation to its Parent.
If you then use this measure and call Control.PointToScreen(), you retrieve a position inside the Control's Client area
The offset of a location inside its ClientRectangle, added to this measure, is then of course moved right and down (since the Control's origin is not at (0, 0))
In other words, the Screen coordinates of the origin point of a Control are:
[Control].PointToScreen(Point.Empty);
As described in Open a Form under a DataGridView, you just need to consider the bounds of the Cell that raised the CellMouseDown event:
Point location = Point.Empty;
private void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
var dgv = sender as DataGridView;
var cellRect = dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
location = dgv.RectangleToScreen(cellRect).Location;
}
As a note, in normal conditions, the coordinates that GetCellDisplayRectangle() returns are moved 7 pixels to the right and 1 pixel down, in relation to a Cell's grid, since it considers the internal bounds
If you want to position your Form over the Cell's grid, you could add:
location.Offset(-7, -1);

Is it possible to apply RenderTransform to the actual UIElements?

Im creating Path objects with curvy edges on a Canvas. I want to be able to rotate them and move them around the canvas. But when i try to apply several transform to and object it only visually displays the last one. I assume its because the transforms are applied to the coordinates of the Path object and are only displayed but not saved afterwards.
So if i would run something like:
my_canvas.Children[0].RenderTransform = new TranslateTransform(0, 100);
my_canvas.Children[0].RenderTransform = new TranslateTransform(0, 150);
it would move my Path 150 pixels down.
Is there a way i can save the transform progress of RenderTransform or do i have to recreate my Path with different parameters/write a method to displace the pixels manually?
Edit
Another example:
my_canvas.Children[0].MouseDown += (object sender, MouseButtonEventArgs e) =>
{
if (e.LeftButton == MouseButtonState.Pressed)
{
MouseDownLocation = e.GetPosition(my_canvas);
}
};
my_canvas.Children[0].MouseMove += (object sender, MouseEventArgs e) =>
{
if (e.LeftButton == MouseButtonState.Pressed)
{
my_canvas.Children[0].RenderTransform = new TranslateTransform(-(MouseDownLocation.X - e.GetPosition(my_canvas).X), -(MouseDownLocation.Y - e.GetPosition(my_canvas).Y));
}
};
This code allows me to move my element and it works fine: i can pick it up, visually move it, and let it go. But only once. If try to do it again it tries to do the transformation based on the elements' previous position. And as I am typing this i realize that i can probably solve this by keeping track of the offsets the transformations are causing.
Just add the next translation to the X and Y values of the existing RenderTransform:
my_canvas.Children[0].RenderTransform = new TranslateTransform(0, 100);
((TranslateTransform)my_canvas.Children[0].RenderTransform).Y += 150;
Or use Canvas.Left and Canvas.Top instead of a TranslateTransform:
UIElement element = my_canvas.Children[0];
Canvas.SetTop(element, 100);
Canvas.SetTop(element, Canvas.GetTop(element) + 150);

Draw point where mouse clicked

I'm using HelixToolkit to see and interact with STL files. I need to draw or mark a point clicked by user on the window. I have the coordinates, I know where to draw that point, but I don't know how to draw it, can someone help me? I post some code to explain what I have right now:
private void vierport3d_MouseRightClick(object sender, MouseButtonEventArgs e)
{
Point mousePos = e.GetPosition(viewPort3d);
PointHitTestParameters hitParams = new PointHitTestParameters(mousePos);
VisualTreeHelper.HitTest(viewPort3d, null, ResultCallback, hitParams);
}
public HitTestResultBehavior ResultCallback(HitTestResult result)
{
RayHitTestResult rayResult = result as RayHitTestResult;
if (rayResult != null)
{
RayMeshGeometry3DHitTestResult rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;
//HERE I HAVE THE LOCATION TO DRAW
MessageBox.Show(rayMeshResult.PointHit.X + " " + rayMeshResult.PointHit.Y + " " + rayMeshResult.PointHit.Z);
if (rayMeshResult != null)
{
// I THINK I HAVE TO DRAW THE POINT HERE
}
}
return HitTestResultBehavior.Continue;
}
PD: I show the stl on a viewport3d.
We had same scenario in our project and used a sphere to visually indicate the point.
<ht:SphereVisual3D Radius="0.75" Fill="Red" Center="{Binding ContactPoint}" />
ContactPoint is a Point3D type.
This might help, but its probably not the most effecient.
Try the following:
This will create a 3D sphere that can be rendered at the given coordinates.
var sphereSize = 0.025;
/* keep these values low, the higher the values the more detailed the sphere which may impact your rendering perfomance.*/
var phi = 12;
var theta = 12;
MeshBuilder meshBuilder = new MeshBuilder();
Pass in your x,y,z to the first parameter. i.e. the click 3D location.
meshBuilder.AddSphere( new Point3D(x,y,z), sphereSize , theta, phi);
GeometryModel3D sphereModel = new GeometryModel3D(meshBuilder.ToMesh(),MaterialHelper.CreateMaterial(Brushes.Green,null,null,1,0));
Rendering the Point in your viewport
You will need a ModelVisual3D component as a child of the HelixViewport. ( This can be implemented in C# or in XAML) its up to you, ill show both ways.
C# version
NB: You need a reference to the helixviewport if its defined in xaml. Set the x:Name:"" to something appropriate. e.g x:Name="helixViewPort"
ModelVisual3D visualizer = new ModelVisual3D();
visualizer.Content = sphereModel;
helixViewPort.Children.Add(visualizer);
XAML version
I'll assume your xaml code has at least a helix view port so you'll have to add a ModelVisual3D child to the helix viewport if there's none.
<h:HelixViewport3D x:Name="HelixPlotViewPort" >
<h:DefaultLights/>
<ModelVisual3D x:Name="Visualizer">
</ModelVisual3D>
</h:HelixViewport3D>
//Then in C# add the following
Visualizer.Content = sphereModel;
That should do it, hope it helps, do inform us if you find a better solution. :)

Activating mousedown on dynamically created object

I'm working on a project where I want to "drag" data from a listview to a panel. The goal is that people can select the information on the listview and then position a panel with that information on it on a sort of flowchart on another panel.
Pretty much all of the code is already working but there's one thing which I'm still struggeling with. When I perform a Mouse Down on the listview, a panel is being generated with a few labels on it to be able to drag. The code below is a simplified version for readability.
private void createPlayerPanel()
{
var coordinates = this.PointToClient(Cursor.Position);
// CREATE THE PANEL
Panel panPlayer = new Panel();
// SETUP THE PANEL
panPlayer.Name = "panPlayer1";
panPlayer.Width = 200;
panPlayer.Height = 50;
panPlayer.BackColor = Color.Gainsboro;
panPlayer.Click += new EventHandler(this.panClick);
panPlayer.MouseDown += new MouseEventHandler(panMouseDown);
// ADD THE PANEL TO THE FORM
this.Controls.Add(panPlayer);
// BRING THE PANEL TO THE FRONT OF THE FORM
panPlayer.BringToFront();
panPlayer.Location = new Point(coordinates.X - 14, coordinates.Y - 12);
}
public bool Dragging = false;
Point location;
Panel panSelected;
private void panMouseDown(object sender, MouseEventArgs e)
{
panSelected = (Panel)sender;
Dragging = true;
location = e.Location;
}
However with the same Mouse Down I would like to drag the panel to the flowchart. So in one smooth movement, click on the information in the listview and drag it to the second panel.
When I generate the panel, I also include a Mouse Down mouseeventhandler but which is not active yet when I perform the mousedown. So when I release the mouse and then hold down the mouse again, it works fine and I can drag the panel to it's new position. But I would like to do it in one action. So MOUSE DOWN > GENERATE PANEL > ACTIVATE MOUSE EVENT > DRAG PANEL
Is there a way I can activate the mousedown-event of the panel when I've finished creating it?

Is there a way to move adjust a pushpin position manualy on windows phone 8 using bing maps?

I am working on a windows phone 8.0 application wich uses bing maps. I wnat to add a push pin with my current location. So far soo good. What I do not manage to do is to move that pin. More precisely, I want to tap that pin and to mo move it otherwhere on the map.
I tried with events like ManipulationStarted, ManipulationDelta, ManipulationCompleted and other resources on the web, but no results yet.
My C# code:
public partial class MainPage : PhoneApplicationPage
{
Pushpin pin = new Pushpin();
// Constructor
public MainPage()
{
InitializeComponent();
GetCurrentPosition();
}
async void GetCurrentPosition()
{
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracyInMeters = 50;
Geoposition geoposition = await geolocator.GetGeopositionAsync();
Grid MyGrid = new Grid();
MyGrid.RowDefinitions.Add(new RowDefinition());
MyGrid.RowDefinitions.Add(new RowDefinition());
MyGrid.Background = new SolidColorBrush(Colors.Transparent);
pin.Content = "I'm a pin";
pin.Foreground = new SolidColorBrush(Colors.Purple);
pin.ManipulationDelta += pin_ManipulationDelta;
pin.ManipulationStarted += pin_ManipulationStarted;
pin.ManipulationCompleted += pin_ManipulationCompleted;
MyGrid.Children.Add(pin);
//Creating a MapOverlay and adding the Grid to it.
MapOverlay MyOverlay = new MapOverlay();
MyOverlay.Content = MyGrid;
MyOverlay.GeoCoordinate = new GeoCoordinate(geoposition.Coordinate.Latitude, geoposition.Coordinate.Longitude);
MyOverlay.PositionOrigin = new Point(0, 0.5);
MyMap.Center = new System.Device.Location.GeoCoordinate(geoposition.Coordinate.Latitude, geoposition.Coordinate.Longitude);
MyMap.ZoomLevel = 16;
MyMap.CartographicMode = Microsoft.Phone.Maps.Controls.MapCartographicMode.Road;
MapLayer MyLayer = new MapLayer();
MyLayer.Add(MyOverlay);
MyMap.Layers.Add(MyLayer);
translateTransform = new TranslateTransform();
}
void pin_ManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e)
{
//MessageBox.Show("started");
}
void pin_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
MessageBox.Show("moving");
//how do a get the coordonites while I'm moving the pin?
}
void pin_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
MessageBox.Show("completed");
}
}
Aby ideas how to make the pin draggeble? If I set pin.AllowDrop=true it throws NotImplementedException.
Thank you in advance!
Just thinking a little differently...
The solution I used for NZ Topo Map was to overlay a cross-hair over the map so the user could position the pin precisely by dragging the map instead of dragging a pin. I would then use an app bar with a tick and a cross for "create pin" and "cancel".
Just an alternative UI idea for you to mull over that might be easier to implement and maybe easier for the user.
You have to set the move events on the mouse events on the map. This will let you be able to track where the user has moved to. Essentially you would add a mouse down event to the pushpin which would set a flag to capture to allow dragging the pin. Then you would use the mouse move event on the map to reposition the pushpin and the mouse up event on the map to turn the dragging flag off. You will also likely want to display panning of the map. Try capturing the center value when the mouse down event fires, then set the center of the map to this value constantly on the mouse move event when the drag flag it turned on.

Categories