WPF moving border around on screen - c#

I'm making a game inventory system where I have a grid and borders that take up a grid space have a background image of the icon. For example in grid position 0,0 I have a border with a background of a health potion. I want to be able to drag this border to any other grid location. Right now I'm working on just clicking and dragging the border to follow the mouse.
What I have so far sort of works but not really. If sort of freaks out when I left click on the icon and move slowly. However I can move out of the icon fast and it doesn't keep up so it then stops moving until I'm back over it. Any ideas on how to get this kind of functionality?
public partial class MainWindow : Window
{
TranslateTransform trans = new TranslateTransform();
Border border;
public MainWindow()
{
InitializeComponent();
// create a broder and set it's background image
border = new Border();
border.Visibility = System.Windows.Visibility.Visible;
var img = (Image)MasterGrid.FindResource("notepad");
var imgBrush = new ImageBrush(img.Source);
border.Background = imgBrush;
border.Margin = new Thickness(2.0);
// add the border to the grid
Grid.SetRow(border, 0);
Grid.SetColumn(border, 1);
Grid.SetRowSpan(border, 2);
Grid.SetColumnSpan(border, 1);
InvGrid.Children.Add(border);
// hook up events
_00.MouseUp += new System.Windows.Input.MouseButtonEventHandler(_00_MouseUp);
border.MouseDown += border_MouseDown;
border.MouseMove += border_MouseMove;
}
void border_MouseMove(object sender, MouseEventArgs e)
{
// make the border follow the mouse position
trans.X = e.GetPosition(border).X;
trans.Y = e.GetPosition(border).Y;
}
void border_MouseDown(object sender, MouseButtonEventArgs e)
{
border.RenderTransform = trans;
}
private void _00_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
MessageBox.Show("test");
}
}

It's not that easy, so I will give you some steps:
You should know sizes of cells, then you are able to calculate position of each one.
Prepare a container (a Grid for example), which will be only for moving purpose.
When you start dragging put the item in the container (2.), set its position under your cursor and hide the cell's content.
When you drop the item, calculate over which cell you are (maybe there is even a function to get a control under the cursor) and do the rest of logic.
I'm not sure if dragging is possible in case of regular controls, maybe you will have to replace your Grid with Canvas.
Try something like this:
<Grid>
<Border x:Name="DragContainer" Width="40" Height="40" />
<!-- OTHER STUFF - YOUR GRID WITH ITEMS -->
</Grid>
and modify only Border's Margin.
Another way to do it is to do dragging inside Canvas, then you will be able to set Canvas.Left, Canvas.Top - it should work properly.
<Canvas>
<Border x:Name="DragContainer" Width="40" Height="40" />
<!-- OTHER STUFF - YOUR GRID WITH ITEMS -->
</Canvas>

Related

Creating a draggable map pin on UWP

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.

Slide Border onto screen

I need to make a border slide from the bottom of the screen into view, however i'm having issues getting the ActualHeight of my border control. Because this is animation code i'm putting it in the code-behind as it's the Views responsibility.
My Border has got it's Loaded event tied to this:
private void NotifcationWindow_Loaded(object sender, RoutedEventArgs e)
{
SlideFromBottom(sender);
}
The Sender is the Border object, so the SlideFromBottom method SHOULD be able to use this object and get it's height as it has already been rendered.
public void SlideFromBottom(object sender)
{
//The Notification container
Border notification = (sender as Border);
//The screen size
var workingArea = SystemParameters.WorkArea;
Storyboard sb = new Storyboard();
var animation = new DoubleAnimation()
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(5),
// Get the height and turn it to a negative value, and add screen height.
From = (notification.ActualHeight * -1),
//Slide the border into view
To = notification.ActualHeight
};
Storyboard.SetTarget(animation, notification);
Storyboard.SetTargetProperty(animation, new PropertyPath("(Margin.Bottom)"));
sb.Begin();
}
I'm not receiving any errors, but the animation isn't playing, have I got something wrong? The Border is Vertically Aligned to the bottom, so the negative margin should take it off the screen.
The reason why you're not seeing anything is that you haven't added the animation to the storyboard:
sb.Children.Add(animation);
Then there are some more problems. Such that a part of the margin cannot be animated separately. You would need a ThicknessAnimation.
But there is an easier solution. Use the RenderTransform. If you give your border the following render transform:
<Border>
<Border.RenderTransform>
<TranslateTransform/>
</Border.RenderTransform>
</Border>
, then you can animate it as follows:
// ...
var animation = new DoubleAnimation()
{
BeginTime = TimeSpan.FromSeconds(0),
Duration = TimeSpan.FromSeconds(5),
From = notification.ActualHeight,
To = 0
};
Storyboard.SetTarget(animation, notification);
Storyboard.SetTargetProperty(animation, new PropertyPath("RenderTransform.Y"));
sb.Children.Add(animation);
// ...

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?

Delay in drag/drop of UIElement in Windows Phone 8.1

I'm using the ManipulationDelta event handler to drag and drop a simple ellipse in a canvas across the screen. I'm using the standard approach posted online in several places. Following is the code in my event handler:
Ellipse dragableItem = sender as Ellipse;
TranslateTransform translateTransform = dragableItem.RenderTransform as TranslateTransform;
double newPosX = Canvas.GetLeft(dragableItem) + translateTransform.X + e.Delta.Translation.X;
double newPosY = Canvas.GetTop(dragableItem) + translateTransform.Y + e.Delta.Translation.Y;
if (!isCanvasBoundary(newPosX, TestCanvas.ActualWidth - dragableItem.ActualWidth, 0))
translateTransform.X += e.Delta.Translation.X;
if (!isCanvasBoundary(newPosY, TestCanvas.ActualHeight - dragableItem.ActualHeight, 0))
translateTransform.Y += e.Delta.Translation.Y;
The drag and drop operation works fine, but there's a nasty delay of around 1 second between when the user begins dragging to when the ellipse actually changes its position. I can see by printing to the Debugger that the event handler itself finishes executing almost instantly, so I'm guessing it has something to do a pre-programmed refresh rate for all UIElements on the screen that's causing this delay?
Is there anyway around this issue?
I had the same problem some time ago. I guess that the delay is to decide whether the gesture is drag or tap. It's hard to touch a screen without any accidental drag.
To eliminate this lag, you can use PointerMove and PointerPressed events. Here's my example. You have canvas with two ellipses which can be dragged without any delay.
XAML
<Grid>
<Canvas x:Name="Board" PointerMoved="Canvas_PointerMoved" Background="Black">
<Ellipse Width="64" Height="64" Fill="Red"
Canvas.Left="32" Canvas.Top="128" PointerPressed="Ellipse_PointerPressed"/>
<Ellipse Width="96" Height=" 96" Fill="Blue"
Canvas.Left="128" Canvas.Top="16" PointerPressed="Ellipse_PointerPressed"/>
</Canvas>
</Grid>
As you can see, I'm handling PointerMoved event in canvas and PointerPressed event in ellipses. It's important that the background of the canvas is not transparent to handle touch events.
C#
public sealed partial class MainPage : Page
{
UIElement draggedItem = null;
Point offset;
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Ellipse_PointerPressed(object sender, PointerRoutedEventArgs e)
{
draggedItem = sender as UIElement;
offset = e.GetCurrentPoint(draggedItem).Position;
}
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (draggedItem == null)
return;
Point dragPoint = e.GetCurrentPoint(Board).Position;
Canvas.SetLeft(draggedItem, dragPoint.X - offset.X);
Canvas.SetTop(draggedItem, dragPoint.Y - offset.Y);
}
}
I think the code is quite simple and understandable. I use PointerPressed to decide which object is dragged. I'm also calculating some offset, because we want to move the object relative to the point where user touches.

Dynamic Margin on Window Drag

Ok so complete rewrite of the question due to lack of replies. I want a window that is drag-able but as it's being dragged, alter the margin to extend as far as the old position of the window. I.e. Window moves right X, extend margin left X. Now I've hit a few snags such as the window having it's edges cut off for some reason. Here's my code, let me know if you can spot anything!
private void Window_LocationChanged(object sender, EventArgs e)
{
double TmpLeft = Math.Abs(this.Left - WinLeft);
double TmpTop = Math.Abs(this.Top - WinTop);
if (this.IsLoaded)
{//depending on whether the window is moved left, right
if (this.Left > WinLeft)
{//depending on whether the window is moved up, down
if (this.Top > WinTop)
bdr.Margin = new Thickness(TmpLeft, TmpTop, 0, 0);
else
bdr.Margin = new Thickness(TmpLeft, 0, 0, TmpTop);
}
else
{
if (this.Top > WinTop)
bdr.Margin = new Thickness(0, TmpTop, TmpLeft+ 40, 0);
else
bdr.Margin = new Thickness(0, 0, TmpLeft, TmpTop);
}
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
WinLeft = this.Left;
WinTop = this.Top;
bdr.Height = this.ActualHeight;//I set these because they are auto
bdr.Width = this.ActualWidth; //before the window opens
}
At the moment the whole window (set window background to yellow so I can see the margin) is moving, where as I want the top left corner of the margin to remain in place. I've also noticed that the area I click on to drag the window (a border) tends to move around as well so is no longer under my click as I move the window. Hope that's clear, comment any further questions.
OLD - ONLY READ TO UNDERSTAND WHAT I'M TRYING TO DO
So I'm trying to create an application that has a pop up window with a pointer/line coming from the child window to a particular place in the parent window. I achieved this like so;
<Border Name="brdr" Margin="40,0,0,0" >
//Content
</Border>
<Line
Name="Pointer"
X1="0"
X2="40"
Y1="55"
Y2="50"
StrokeThickness="2"
Stroke="Black"
></Line>
Notice the 40 left Margin on the border that makes the window larger than it appears so that the Polygon sticks out to the left and points to the parent window (If there's a better/cooler/more elegant way of doing this, I'm all ears).
So that worked fine but now I want the pointer to be dynamic. As in, if the child window gets dragged the pointer must scale relatively to the parent window's location. I.e. it must appear as if the two windows are attached via the line. Now my plan was to record the point the child window opens on (because it opens relative to the parent window, it's correct when it initialises) and then use the difference between this and the new location point (after dragging) to find the new points that the line should be going to. My code probably says it better than I ever could...
private void Window_LocationChanged(object sender, EventArgs e)
{
if (this.IsLoaded)
{
brdr.Margin = new Thickness(Math.Abs(this.Left - WinLeft) + 40, Math.Abs(this.Top - WinTop), 0, 0);
Pointer.X1 = Math.Abs(this.Left - WinLeft);
Pointer.Y1 = Math.Abs(this.Top - WinTop) + 55;
Pointer.X2 = Math.Abs(this.Left - WinLeft) + 40;
Pointer.Y2 = Math.Abs(this.Top - WinTop) + 50;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
WinLeft = this.Left;
WinTop = this.Top;
}
As you can see I have to set the window margin so that it extends to the old position. Then I reset the Line coords to the new values. All these values are calculated, like I said, by comparing the opening window coords to the current coords.
My problem is, this isn't right. Would be very impressed to see someone able to figure this out.
i m surprised there's no code to switch from in-window coordinates to screen coordinates.
In a project of mine, i had to place a window right under a Control. I used this to get the screen coordinates of the point at the middle of the control:
Point point = MyControl.PointToScreen(new Point((MyControl.ActualWidth / 2)
, MyControl.ActualHeight));
PresentationSource source = PresentationSource.FromVisual(MyControl);
double dpiX = (96 * source.CompositionTarget.TransformToDevice.M11);
double dpiY = (96 * source.CompositionTarget.TransformToDevice.M22);
XInScreenCoordinates = ((point.X * 96 / dpiX));
YInScreenCoordinates = ((point.Y * 96 / dpiY));
If i didn't do this, there would be an error in placement, the more to the right the more error.
ok a small app i did :
there's MainWindow, the main window with very simple content, a line
and a Button :
<Grid>
<Line x:Name="MyLine" Stroke="Black" StrokeThickness="4" />
<Button Name="MyButton" Margin="248,101,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top"
Content="Button" />
</Grid>
then i did another window, of small size (300*300) named TestWindow.
TestWindow has no content or code behind.
the code behind for MainWindow :
public partial class MainWindow : Window
{
public TestWindow MyTestWindow;
public MainWindow()
{
InitializeComponent();
MyTestWindow = new TestWindow();
MyTestWindow.Show();
MyTestWindow.LocationChanged += new EventHandler(WinLocationChanged);
MyTestWindow.SizeChanged += new SizeChangedEventHandler ( WinSizeChanged);
this.LocationChanged += new EventHandler(WinLocationChanged);
this.SizeChanged += new SizeChangedEventHandler(WinSizeChanged);
}
void WinSizeChanged(object sender, SizeChangedEventArgs e) {
UpdateLine(); }
void WinLocationChanged(object sender, EventArgs e) {
UpdateLine(); }
void UpdateLine()
{
// 1. get Window's center in in this window coordinates
double CX_sc =0.0, CY_sc = 0.0;
GetWindowCoordinatesInCurrentWindow(MyTestWindow, ref CX_sc, ref CY_sc);
// 2. Get Center of target Control coordinates in this window coordinates
Point CenterButtonPoint = MyButton.TransformToAncestor(this).Transform(new Point(MyButton.ActualWidth / 2.0, MyButton.ActualHeight / 2.0));
//3. Change line's coord.
MyLine.X1 = CX_sc;
MyLine.Y1 = CY_sc;
MyLine.X2 = CenterButtonPoint.X;
MyLine.Y2 = CenterButtonPoint.Y;
}
void GetWindowCoordinatesInCurrentWindow(Window ChildWindow, ref double X, ref double Y)
{
Point CenterOfChildWindow = ChildWindow.PointToScreen(new Point((ChildWindow.ActualWidth / 2)
, ChildWindow.ActualHeight/2));
Point UpperLeftOfCurrentWindow = this.PointToScreen(new Point(0, 0));
PresentationSource source = PresentationSource.FromVisual(this);
double dpiX = ( source.CompositionTarget.TransformToDevice.M11);
double dpiY = (source.CompositionTarget.TransformToDevice.M22);
X = (( CenterOfChildWindow.X -UpperLeftOfCurrentWindow.X ) /dpiX);
Y = ( (CenterOfChildWindow.Y-UpperLeftOfCurrentWindow.Y ) / dpiY );
}
}
What it does is that whenever one of the window is moved or resized, it will draw a
line into MainWindow that 'links' the Button to the middle of the TestWindow.
OK OK i know this is not what you want :=), since the line cannot go outside of MainWindow. But the idea is to have a transparent Popup which covers the whole screen in which you do the same thing.
Actually found a great way of doing this. What I did was I set the child window's WindowState="Maximized" then placed everything except the Line in a Canvas and by following http://denismorozov.blogspot.ie/2008/01/drag-controls-in-wpf-using.html (great article) I could make it so that I could move around my window. I had hard coded the line so that when the window opens it's in the correct position. Since one of the points of the Line shouldn't move, it was just a matter of updating the other point in the Canvas_MouseMove event like so;
Point p = border.TransformToAncestor(this).Transform(new Point(0, 0));
Line.X2 = p.X;
Line.Y2 = p.Y + 50;
Very simple way of getting your window to display relative to the parent window if you want a line joining the two. It gives the appearance of moving the window, but your actually moving a control around a maximised transparent window.

Categories