Making a moveable control in WPF - wpf

I have a panel, within that panel are several rectangular controls (the number of controls vaires) I want the user to be able to move the controls around within the panel so that they can arrange the controls in the way that suits them best. does anyone have any resources i could read or simple tips which would get me headed down the right road?
thanks

I figured out a possible, simple method of moving a control in a drag/move style... Here are the steps.
Select an element in your control which you wish to be the movement area. This is the area in which, if me user holds the mouse down, the control will move. In my case it was a rectangular border at the top of the control.
Use the OnMouseDown event to set a boolean (in my case IsMoving) to true and the MouseUp event to set it to false
On the first MouseDown event, set some Point property (InitialPosition) using the following code
if (FirstClick)
{
GeneralTransform transform = this.TransformToAncestor(this.Parent as Visual);
Point StartPoint = transform.Transform(new Point(0, 0));
StartX = StartPoint.X;
StartY = StartPoint.Y;
FirstClick = false;
}
Now that you have the starting position, you need to get the position of the mouse relative to your movement control. This is so you dont end up clicking the middle of your header to move it and it instantly moves the top left of the control to the mouse pointer location. To do this, place this code in the MouseDown event:
Point RelativeMousePoint = Mouse.GetPosition(Header);
RelativeX = RelativeMousePoint.X;
RelativeY = RelativeMousePoint.Y;
Now you have the point the control originated at (startX and STartY), the position of the mouse within your movement control (RelativeX, RelativeY), we just need to move the control to a new location! There are a few steps involved in doing this. Firstly your control needs to have a RenderTransform which is a TranslateTransform. If you dont want to set this in XAML, feel free to set it using this.RenderTransform = new TranslateTransform.
Now we need to set the X and Y coordinates on the RenderTransform so that the control will move to a new location. The following code accomplishes this
private void Header_MouseMove(object sender, MouseEventArgs e)
{
if (IsMoving)
{
//Get the position of the mouse relative to the controls parent
Point MousePoint = Mouse.GetPosition(this.Parent as IInputElement );
//set the distance from the original position
this.DistanceFromStartX= MousePoint.X - StartX - RelativeX ;
this.DistanceFromStartY= MousePoint.Y - StartY - RelativeY;
//Set the X and Y coordinates of the RenderTransform to be the Distance from original position. This will move the control
TranslateTransform MoveTransform = base.RenderTransform as TranslateTransform;
MoveTransform.X = this.DistanceFromStartX;
MoveTransform.Y = this.DistanceFromStartY;
}
}
As you can guess, there is a bit of code left off(variable declarations etc) but this should be all you need to get you started :) happy coding.
EDIT:
One problem you may encounter is that this allows you to move the control out of the area of its parent control. Here is some quick and dirty code to fix that issue...
if ((MousePoint.X + this.Width - RelativeX > Parent.ActualWidth) ||
MousePoint.Y + this.Height - RelativeY > Parent.ActualHeight ||
MousePoint.X - RelativeX < 0 ||
MousePoint.Y - RelativeY < 0)
{
IsMoving = false;
return;
}
Place this code in your MouseMove event before the actual movement takes place. This will check if the control is trying to move outside the bounds of the parent control. The IsMoving = false command will cause the control to exit movement mode. This means that the user will need to click the movement area again to try to move the control as it will have stopped at the boundary. If you want the control to automatically continue movement, just take that line out and the control will jump back onto the cursor as soon as it is back in a legal area.

You can find a lot of inspiration here:
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

Related

WPF TouchDevice get absolute coordinates

I try to create a window, that can be moves or resized via multi-touch gestures. I tried it this way. I captures the TouchDown-Event of the window and saved all active TouchDevices in a List, to know which TouchDevices are active. I catch updated and deactivated event of the TouchDevices to know when they are moved and when they are deactivated. I save the Left and Top Property of the Window and the position where the TouchDevice started and everytime the Updated event is called I move the Window to the new Position relative to the new position of the TouchDevice. This works if I move the finger. But if I don't move the finger (or just very little) the window suddenly began shaking (moving chaotically) and then soon disappears to a position outside of the screen.
I think the problem here is, that the function "GetTouchPoint" of the TouchDevice only give relative coordinates related to the window (even if I set the parameter null instead if the window reference). And because the Window moves the relative position of the TouchDevice (that doesn't move) changes too. So I did a research but wasn't able to find a way to determine the screen coordinates of the touch device.
So I hope anyone can help me how to get the absolute coordinates of the TouchDevice. Or can help me find another way to "DragMove" the window with touch.(I tried DragMove, but that only works for mouse clicks, not TouchDowns) Also I like to resize the window when two Touch Devices are active and therefor I also need absolute coordinates because otherwise same effect happens.
I ran into this issue because my taskbar is on the right edge of the screen, effectively pushing the maximized window to the right. This issue also arises when the application's window is not maximized, and floating somewhere on the screen.
Here is an extension method that fixes the coordinates based on the application window's position.
public static Point FixCoordinates(this Point point)
{
var left = Application.Current.MainWindow.Left;
var top = Application.Current.MainWindow.Top;
return new Point(point.X + left, point.Y + top);
}
You may want to pass in a window that hosts your touched control as a parameter. In my case it is application's main window.
Also, since you tagged the question with "multi-touch", here is a method which averages multiple touch coordinates:
public static Point GetAverage(this IEnumerable<Point> points)
{
var averageX = points.Average(p => p.X);
var averageY = points.Average(p => p.Y);
return new Point(averageX, averageY);
}
And I use it in the code like this:
private void TouchAdornment_TouchDown(object sender, TouchEventArgs e)
{
var touchPosition = (sender as UIElement).TouchesOver.Select(t => t.GetTouchPoint(null).Position).GetAverage().FixCoordinates();
}

How to dynamically resize a label inside a WPF application?

I need to know how I could dynamically resize a label in a WPF application.
I've already found a sample in this article which has already achieved both dragging and resizing a label at the same time.
I dug into the code, and to make it short, I found out that inside OnMouseMove event of the label, it checks the mouse cursor shape and if it was Hand it will do the dragging and if it was either of the resizing arrows it will do the resizing correspondingly.
Check it out. you'll see.
In this certain example I couldn't manage to find out how the cursor shape changes to resizing arrows when the mouse is hovered on the label's border.
So
I either need to find out 'how I could change the mouse cursor shape to resizing arrows when hovered on a label's border', OR to find a new approach to resize a label, dynamically.
Changing the cursor is done via the this.Cursor property.
I opened the code in the article and saw how they do it...
In the OnMouseMove the cursor is changed if the left mouse button is NOT clicked:
Point currentLocation = e.MouseDevice.GetPosition(wnd);
......
......
const int dragHandleWidth = 3;
var bottomHandle = new Rect(0, height - dragHandleWidth, width, dragHandleWidth);
var rightHandle = new Rect(width - dragHandleWidth, 0, dragHandleWidth, height);
Point relativeLocation = wnd.TranslatePoint(currentLocation, this);
if (rightHandle.Contains(relativeLocation))
{
this.Cursor = Cursors.SizeWE;
}
else if (bottomHandle.Contains(relativeLocation))
{
this.Cursor = Cursors.SizeNS;
}
else
{
this.Cursor = Cursors.Hand;
}
In other words, they check if the current mouse location is within 3 px of the bottom or right border, if it is, they change the cursor accordingly...
You can easily change this logic to suite your needs....

WPF Thumb DragDelta moving across monitors

I have a Popup control that I added a thumb to so I can drag it around the screen. The thumb's DragDelta event was overloaded with this:
private static void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Thumb thumb = (Thumb)sender;
Popup popup = thumb.Tag as Popup;
if (popup != null)
{
popup.HorizontalOffset += e.HorizontalChange;
popup.VerticalOffset += e.VerticalChange;
}
}
The Dragging works perfectly (I used the Dragging example from here: http://www.codeproject.com/Articles/43636/WPF-A-search), except for when the popup reaches the end of the monitor and crosses over to the other (dual monitor setup). For instance if I have the popup open on the Left monitor and start dragging it right, when the right border of it touches the edge of the monitor it's movement is erratic and starts moving all around until I move further right and it displays on the other monitor.
I debugged through this scenario, and this is a numerical example of basically what happens:
At edge of screen:
HorizontalOffset = 600
HorizontalChange = 1
Move Right:
HorizontalOffset = 601
HorizontalChange = -800
HorizontalOffset = -199
HorizontalChange = 401
HorizontalOffset = 200
HorizontalChange = -150
Which gives this weird strobe effect of the popup while it moves to the other monitor; Is there something I need to do to get it to transition smoothly across monitors?
I still haven't figured out how to un-bind pop-ups to a screen, but I was able to accomplish what I needed by using the Window control instead. I made WindowStyle.None so there was no border and overloaded the MouseLeftButtonDown event with a delegate to the DragMove() method so they can be dragged around the screen. This allowed me to have very similar look and feel as the pop-ups, but able to drag it around the screen with no flicker.

Should I be using AdornerLayer to avoid clipping my adorner off-screen?

I'm writing some WPF code involving Adorners. I'm using Josh Smith's UIElementAdorner.cs (found in the project on his Infragistics Blog). I'm adorning with a blurb of information text. I need to place my adorner smartly, so that it does not clip off the screen.
What's the best way to find out if I'm going to clip?
I'm using the following code to create and place my adorner. I have a funny feeling that basing whether or not I'll clip on the AdornerLayer isn't the best option.
var infoBubble = new InfoBubble {InformationText = #"I like cheese."};
var adornedElementRect = new Rect(Target.DesiredSize);
var layer = AdornerLayer.GetAdornerLayer(Target);
var adorner = new UiElementAdorner<Control>(Target) { Child = infoBubble };
adorner.Measure(new Size(layer.ActualWidth, layer.ActualHeight));
var adornerRect = new Rect(adorner.DesiredSize);
var top = -1*(adornerRect.Height);
var left = adornedElementRect.Width/2;
// Using layer to judge where to place the adorner
var upperLeftPoint = Target.TranslatePoint(new Point(left, top), layer);
var lowerRightPoint = Target.TranslatePoint(new Point(left + adornerRect.Width,
top + adornerRect.Height), layer);
if (upperLeftPoint.Y < 0) top -= upperLeftPoint.Y; // shift down by Y
if (lowerRightPoint.X > layer.ActualWidth)
left -= (lowerRightPoint.X - layer.ActualWidth); // shift left
Keep in mind that this code is contained in a TargetedTriggerAction that designers (aka users of Blend) are expected to use when they want information blurbs above certain UI elements. Thus, this code will know very little about the element to be adorned or its environment.
Yes, is the best answer I can discern.
According to further reading and some experimentation, when calling GetAdornerLayer we receive the lowest layer above the target control in the visual tree. This means we could get a layer below the AdornerDecorator's layer defined in a Window's template. That lower AdornerDecorator could have ClipToBounds="True" (I have no idea why, but it could).
Knowing this information, I can be relatively certain that the AdorneLayer I'm drawing into is the best bounding box for whatever I'm drawing. I could have the ability to draw outside this box (for example if ClipToBounds were False on a AdornerDecorator lower than the Window's), but I shouldn't count on that ability.

Silverlight. Fix needed. Dragging stack panel item to the right moves it underneath other items

I have stack panel with custom controls in it. I attach standard MouseDragElementBehavior to each item.
When I drag to the right the item moves underneath the other items.
What would be a viable solution to create better user experience - to show better visual cue - how the item moves and where is it going to be dropped.
After a bit of tinkering I realised that nothing can be dragged within stack panel to the right not being coverd by other elements .. unless you drag the very right item..
What I did to resolve it:
Created a visual cue (half transparent shape of a generic item to represnt it during the drag operation)
Made the cue invisible (width=0) and keep it always as the very last element of the stack panel children
Subscribed the stack panel to mouse left button up, down, move
Emulated drag/drop with code
Once the drag initated I turn the cue to visible and set its translate transform to the current mouse coordinates
Adjust translate transform on every mouse move event
On drop I hide the cue again and rearrange items in a way I want.
To stress again - whatever way you do - you have to manipulate with the last element in StackPanel.Children collection....
If the MouseDragElementBehavior doesn't work the way you need it to, you could always descend from the class and customize it to your needs. For example:
public class DragBehvaior : MouseDragElementBehavior
{
public DragBehvaior()
{
this.DragBegun += new MouseEventHandler(DragBehvaior_DragBegun);
}
void DragBehvaior_DragBegun(object sender, MouseEventArgs e)
{
var element = this.AssociatedObject as UIElement;
// Calculate the correct ZIndex here.
Canvas.SetZIndex(element, 100);
}
}

Resources