Is there an easy way to make a ScrollViewer "bouncy"? - c#

Im using a WPF ScrollViewer to host some controls. I'd really like it to interact like on a touch device where it slowly eases back into place when you pull it too far.
It doesn't have a scrollbar - I have manual mouse scrolling with click and drag using this code:
Point currentPoint = e.GetPosition(this);
// Determine the new amount to scroll.
Point delta = new Point(scrollStartPoint.X - currentPoint.X, scrollStartPoint.Y - currentPoint.Y);
if (Math.Abs(delta.X) < PixelsToMoveToBeConsideredScroll &&
Math.Abs(delta.Y) < PixelsToMoveToBeConsideredScroll)
return;
scrollTarget.X = scrollStartOffset.X + delta.X;
scrollTarget.Y = scrollStartOffset.Y + delta.Y;
// Scroll to the new position.
sv.ScrollToHorizontalOffset(scrollTarget.X);
sv.ScrollToVerticalOffset(scrollTarget.Y);
Is there an easy way to do this? Currently it just acts like a normal scrolling textbox and will neither pull outside its normal range, nor slowly ease back.

If you are working on a Touch device, look at the Microsoft SurfaceScrollViewer (http://msdn.microsoft.com/en-us/library/microsoft.surface.presentation.controls.surfacescrollviewer.aspx). It already has this behaviour built in.
Getting Touch interaction right is tricky, easier to find someone else who's already done it. ;-)

Related

Finding the Center of a Winforms Control hosted in a Wpf WindowsFormsHost

I am in the process of converting a Winforms project to WPF, and in doing so I have hit a very particular issue. Allow me to explain. Within my project I am using OpenTK, which means I need to host the GLControl within a WindowsFormHost. The GLControl makes use of a First Person Camera, and for it to work I need to calculate the exact center of the GLControl, which is where I place the mouse when the user is "looking around". Here is an example of my application in WPF:
The function that I had previously used in WinForms was:
public static Point FindCenterOfControl(Form parent, Control control)
{
Point location = parent.PointToScreen(control.Location);
return new Point(location.X + control.Width / 2, location.Y + control.Height / 2);
}
But naturally, this does not work in WPF. I have tried various parameters, but I keep getting null crashes, mainly from my GLControl, which apparently no longer uses the "Location" property when it's hosted within a WPF application. So, quite frankly, I am at a loss of how to accomplish this.
As another note, my code works as long as I guess where the center should be. I can move the camera around and it looks just fine. The issue arises when I resize the window, because then the center is nowhere near where it should be and my first person camera goes ballistic. I need a way to replicate my previous function of calculating the absolute center of a control within my WPF application.
Does anyone have any ideas how I can convert my old function to something that supports hosted controls within a WPF environment?
Nevermind, I found a solution, or at least...a dirty hack perhaps. The trick was to not try and find the center of my GLControl, but find the center of the WindowsFormsHost instead. Here is my code so that it may help someone else in the future.
public Point FindCenter()
{
System.Windows.Point relativePoint = _host.TransformToAncestor(_parent).Transform(new System.Windows.Point(0, 0));
return new Point((int) (relativePoint.X + _host.ActualWidth / 2), (int) (relativePoint.Y + _host.ActualHeight / 2));
}
Update:
As it turns out, the above method was actually wrong. It centered my cursor at a predefined location regardless of my window size or position. This location was wrong. I have crafted a new method, however, which gives the desired result.
public Point FindCenter()
{
System.Windows.Point relativePoint = _host.PointToScreen(new System.Windows.Point(0, 0));
return new Point((int)(relativePoint.X + (this.Width/2)), (int)(relativePoint.Y + (this.Height/2)));
}
That method DOES center the cursor on the control at the correct location. If you're going to use this within your own application I would suggest trying both to make certain which one suits your needs.

Trouble updating the margin property of buttons to animate them

I have a WP8 app where I display buttons on a wheel. When clicked, I want the wheel to turn and bring the clicked button on the top of it.
I'm placing the buttons with their margin property. I figured I'd use a double while loop to move the buttons along the circle degree by degree, like this (trying to simplify it as much as I can) :
private void Select(Button selectedButton)
{
Button[] SelectedButtons = { button1, button2, button3 };
//Calculates the distance between buttons in degrees
double buttonDistance = 360 / SelectedButtons.Length;
double angleDeg;
double angleRad;
int i = 0;
int j = 0;
//While the selected button hasn't reach top position, move the wheel
while (selectedButton.Margin != new Thickness(<top position of the wheel>)
{
//Moves the buttons of a single degree
while (i < SelectedButtons.Length)
{
//Calculates the position of the current button in the array in degrees and converts it in radians
angleDeg = j + 90 + i * buttonDistance;
angleRad = Math.PI * angleDeg / 180;
//Button positioning
SelectedButtons[i].Margin = new Thickness(<calculation of the margins with the given angles>);
i++;
}
System.Threading.Thread.Sleep(5);
j++;
i = 0;
}
}
However, and even with the Sleep() function, the buttons' positions only update once the loop is over, and don't animate like I want them too.
I tried to use UpdateLayout() in the loop, both on the ContentPanel and on the Buttons themselves, I tried InvalidateArrange() and InvalidateMeasure(), or even adding a canvas in, but nothing does it : the buttons only update when the whiles are over, never before.
Can you guys please help me on this ?
Quick update, for anyone who could stumble upon the same issue :
Indeed, as steveg89 said, you can't update UIElements right from the GUI thread. The link he gave suggests using a background worker, or delegates, to get it to work. Unfortunately, WP8 doesn't allow it either.
The only solution is switching to storyboard animations : http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj206955(v=vs.105).aspx
Not extremely easy for a absolute beginner, but it works fine when you take the time to understand how these work and their different types (simple animations, keyframe animations, or path animations (however path animations are not supported in Windows Phone. Lost quite a bit of time to it.))

Windows 8 App: "Snapping" Drag and Drop for Card Game

After trying to use the following snippet to move cards (Images right now) around I was not satisfied with the result.
Card.ManipulationDelta += (o, args) => {
var dragableItem = o as Image;
if (dragableItem == null) return;
var translateTransform = dragableItem.RenderTransform as TranslateTransform;
if (translateTransform == null) return;
translateTransform.X += args.Delta.Translation.X;
translateTransform.Y += args.Delta.Translation.Y;
};
Card.RenderTransform = new TranslateTransform();
The control had a funny behavior to be accelerated and would move / slide a bit after "dropping" it. Although cool I do not want this behavior and therefore changed my mind: what I am looking for is a solution to define specific areas for one active card, a bench for a few more cards and stacks for the deck, such that one can freely drag one card but it can only be dropped if it is above these certain areas otherwise it will get back to the area designated for the hand cards.
What could I try to implement this desired behavior?
I think you and I are in the same boat, I'm working on a Metro card game application and I'm not relly happy with what I've found as far as Drag and Drop goes. My inital approach was going to be to have a grid \ stackpanel or other underlying framework that the user could drag a card image ( acually a custom control ) over and then the image would snap to that framework when the user let go. I have not yet found a suitable way to obtain this behavior however because it seems like drag-drop of controls from one parent to another is not supported in Metro.
As far as your question goes, the sliding effect that you are refering to is most likely intertia, You can disable this by not setting the TranslateInertia mode, for example
Ellipse el = new Ellipse();
el.Fill = new SolidColorBrush(Windows.UI.Colors.Magenta);
el.ManipulationMode = (ManipulationModes.All ^ MainipulationModes.TranslateInteria);
//more code to add your ManipulationDelta handler etc.
You can also gain some control over the Interia by adding a handler for MainipulationInertiaStarting, though just setting e.Handled = true in the handler does not disable interia entirely for me as others have suggested.
I'd love to hear back from you and see what approach you've come up with for snapping cards, at this point I'm considering just using a large Canvas object and writing my own customer handlers for mouse moved to get card objects to be draggable and then snap into a row or other location on the playing board.
Cheers,
James

Simulate Mouse Drag Programmatically

I have a Windows Forms application with a control. The control consists of a chart panel with a canvas on which I paint. What I would like to do is to programmatically mouse drag the panel so that I have a specific distance between the right edge of the canvas and the last item painted on the canvas. I have tried two approaches. The both work in the sense that the panel is dragged as desired BUT I cannot seem to be able to get the precision of movement I desire. I coded a mouse simulator and have tried two approaches.
Approach 1:
if(this.ChartControl.ChartPanel.CanFocus)
{
// ... Focus the chart panel to be adjusted.
this.ChartControl.ChartPanel.Focus();
// ... Move cursor to lastBarScreenCoordinates on the chart panel to be adjusted.
Cursor.Position = new Point(lastBarScreenCoordinates.X, lastBarScreenCoordinates.Y);
MouseSimulator.SetMouseDragThresholds();
// ... Move chart panel to required position.
MouseSimulator.LeftMouseButtonDown(lastBarScreenCoordinates.X, lastBarScreenCoordinates.Y);
MouseSimulator.MouseMove(lastBarScreenCoordinates.X-positionShift,
lastBarScreenCoordinates.Y);
MouseSimulator.LeftMouseButtonUp(lastBarScreenCoordinates.X-positionShift,
lastBarScreenCoordinates.Y);
MouseSimulator.ResetMouseDragThresholds(_cx_default, _cy_default);
// ... Redraw the chart panel.
this.ChartControl.ChartPanel.Refresh();
// ... Reset cursor to its starting position.
Cursor.Position = new Point(startingCursorX, startingCursorY);
}
Approach 2:
if(this.ChartControl.ChartPanel.CanFocus)
{
// ... Focus the chart panel to be adjusted.
this.ChartControl.ChartPanel.Focus();
// ... Move cursor to lastBarScreenCoordinates on the chart panel to be adjusted.
Cursor.Position = new Point(lastBarScreenCoordinates.X, lastBarScreenCoordinates.Y);
MouseSimulator.SetMouseDragThresholds();
// ... Move chart panel to required position.
MouseSimulator.LeftMouseButtonDown(lastBarScreenCoordinates.X, lastBarScreenCoordinates.Y);
Cursor.Position = new Point(lastBarScreenCoordinates.X-positionShift,
lastBarScreenCoordinates.Y);
WindowsCommunication.SendMessage(this.ChartControl.Handle, 0x200, IntPtr.Zero,IntPtr.Zero);
MouseSimulator.LeftMouseButtonUp(lastBarScreenCoordinates.X-positionShift,
lastBarScreenCoordinates.Y);
MouseSimulator.ResetMouseDragThresholds(_cx_default, _cy_default);
// ... Redraw the chart panel.
this.ChartControl.ChartPanel.Refresh();
// ... Reset cursor to its starting position.
Cursor.Position = new Point(startingCursorX, startingCursorY);
}
I am using SendInput for simulating mouse clicks. Here is sample left mouse button down code ...
public static void LeftMouseButtonDown(int x, int y)
{
INPUT mouseInput = new INPUT();
mouseInput.type = SendInputEventType.InputMouse;
mouseInput.mkhi.mi.dx = CalculateAbsoluteCoordinateX(x);
mouseInput.mkhi.mi.dy = CalculateAbsoluteCoordinateY(y);
mouseInput.mkhi.mi.mouseData = 0;
mouseInput.mkhi.mi.time = 0;
mouseInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENTF_LEFTDOWN;
SendInput(1, ref mouseInput, Marshal.SizeOf(new INPUT()));
}
And I calculate normalized absolute coordinates for the mouse as follows ...
private static int CalculateAbsoluteCoordinateX(int x)
{
return ((x * 65536) + GetSystemMetrics(SystemMetric.SM_CXSCREEN) - 1) /
GetSystemMetrics(SystemMetric.SM_CXSCREEN);
}
So here are the precision issues. If I use Approach 1 (mouse move), the measured distance between the last item painted and the right edge of the canvas is different from what I set in positionShift and the cursor position difference does not equal positionShift. I initially thought it was due to pointer ballistics issues so I tried using Approach 2. Approach 2 does give me precision in pointer positioning but I am still having difficulty in that the panel moves but the distance between the last bar painted and the right edge of the canvas does not equal the positionShift amount as it should. It always seems to be off. I have been working on this for a long time now and am at my wits end. I am not sure what is going on here. How to improve the precision in my canvas drag by simulated mouse drag?
Well what you can do is this, First of all I believe the SendInput API allows for an AbsoluteValue flag so there is no need to calculate those values which may be the issue but most likely not.
Although I am curious as to why you are using a Mouse Drag opperation for this. It seems like all you want to do is reposition the canvas on every draw by some specified amount. if this is the case why not just set it explicitly on the canvas itself. Also it is unclear if you are using pure WinForms or WPF. The unclear bit being Canvas which I am fairly certain is only usable with WPF enabled windows.
That being said
WPF fix,
Depending on the Object containing the canvas just set its margin appropriately for the situation, since I do not know the data you are working with I cant say much about that. But this is a relatively simple idea so let me know if that works, it should give you, at least close too, a pixel perfect alignment.
WinForms,
Just do the above for the "Canvas" object you were talking about, or use absolute coordinates of the object to move it around.
If you could supply a sample of what you were working on looked like roughly maybe we could have a better idea of what you mean.

Constraining windows wpf

I have a window that needs to be constrained within another window. In order to do this,
I hook into the SizeChanged event on the top level window....and in that event I need to adjust the second window so that it is aligned to the nearest edge only if there is an intersection between the two i.e if the smaller window gets outside the boundary of the bigger window.
I do a lot of math calculation to get this...and im still not near to the solution!
Im having trouble doing this because it involves a lot of messy code I was wondering if any of you guys had an easier solution to this?
Basically im dealing with 2 rectangles and I need to ensure that when the size of the bigger rectangle changes...if there is an intersection between the two, then the smaller rectangle should align itself to the edge of the bigger rectangle so that the smaller rectangle is within the bigger rectangle.
Could be a simple math problem in C# forms?
Any suggestions are welcome thanks!
For both windows you need to get the x and the y coordinates of the location of the window in systemcoordinates.
How to do this in wpf can be found here http://blogs.msdn.com/b/llobo/archive/2006/05/02/code-for-getting-screen-relative-position-in-wpf.aspx
Next you need to have the two windows react on each others sizechanged events so that the one window gets notified when the others size has changed.
Then the following math will do the job:
(assuming window 1 is currently in the bounds of window 2 and window 2 size changes and you want to actually resize the window instead of moving it when possible)
//PSEUDOCODE
//Case1 (left bound changes)
if(window2.x > window1.x)
{
window1.x = window2.x;
}
//Case2 (top bound changes)
if(window2.y > window1.y)
{
window1.y = window2.y;
}
//Case3 (right bound changes)
if(window2.x + window2.width < window1.x + window1.width)
{
window1.width = window2.x + window2.width - window1.x;
}
//Case4 (bottom bound changes)
if(window2.y + window2.height < window1.y + window1.height)
{
window1.height = window2.y + window2.height - window1.y;
}

Categories