Is it possible to transalte/rotate/scale items without a ScatterView? I want to manipulate items which can be on top of other elements like a button, list or a custom control which should be static. When I add them to a ScatterView, they all become ScatterViewItems which is not the desired effect.
Expanding a bit on Mark's answer...
Yes, you can use the manipulation and inertia API's to accomplish this, see this overview page.
A while back I created my own very basic scatterview control that essentially did what scatterview does, but with the following limitations:
Only one child, so it works more like a Border
No default visual appearance or special behavior of the child item like SV does
One problem that hit me while developing this is that you have to make your container control occupy a larger area than the actual child. Otherwise, you will not be able to reliably capture the contact events when the fingers are outside your manipulated object. What I did was making my container fullscreen (1024x768) and be transparent and it works ok for my case.
For the manipulation itself, you use an instance of the Affine2DManipulationProcessor class. This class requires you to start the manipulation and then it will constantly feed you with delta event (Affine2DManipulationDelta).
If you want your manipulation to have a more real behavior after the user releases their fingers, you will use the Affine2DInertiaProcessor class which works in a similar way to the manipulation processor. The basic setup is that as soon as the manipulation processor is done (user released fingers) you tell the inertia processor to start.
Let's look at some code :) Here's my setup method in my container class:
private void InitializeManipulationInertiaProcessors()
{
manipulationProcessor = new Affine2DManipulationProcessor(
Affine2DManipulations.TranslateY | Affine2DManipulations.TranslateX |
Affine2DManipulations.Rotate | Affine2DManipulations.Scale,
this);
manipulationProcessor.Affine2DManipulationCompleted += new EventHandler<Affine2DOperationCompletedEventArgs>(processor_Affine2DManipulationCompleted);
manipulationProcessor.Affine2DManipulationDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(processor_Affine2DManipulationDelta);
inertiaProcessor = new Affine2DInertiaProcessor();
inertiaProcessor.Affine2DInertiaDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(processor_Affine2DManipulationDelta);
}
To start it all, I trap ContactDown in my container class:
protected override void OnContactDown(ContactEventArgs e)
{
base.OnContactDown(e);
e.Contact.Capture(this);
// Tell the manipulation processor what contact to track and it will
// start sending manipulation delta events based on user motion.
manipulationProcessor.BeginTrack(e.Contact);
e.Handled = true;
}
That's all, now sit back and let the manipulation processor do its work. Whenever it has new data it will raise the delta event (happens several times / second while user moves the fingers). Note that it is up to you as a consumer of the processor to do something with the values. It will only tell you things like "user has applied a rotation of X degrees" or "user moved finger X,Y pixels". What you typically do then is to forward these values to rendertransforms to actually show the user what has happened.
In my case, my child object has three hard coded rendertransforms: "translate", "rotate" and "scale" which I update with the values from the processor. I also do some boundary checking to make sure the object isn't moved outside the surface or scaled too large or too small:
/// <summary>
/// This is called whenever the manipulator or the inertia processor has calculated a new position
/// </summary>
/// <param name="sender">The processor who caused the change</param>
/// <param name="e">Event arguments containing the calculations</param>
void processor_Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{
var x = translate.X + e.Delta.X;
var y = translate.Y + e.Delta.Y;
if (sender is Affine2DManipulationProcessor)
{
// Make sure we don't move outside the screen
// Inertia processor does this automatically so only adjust if sender is manipulation processor
y = Math.Max(0, Math.Min(y, this.ActualHeight - box.RenderSize.Height));
x = Math.Max(0, Math.Min(x, this.ActualWidth - box.RenderSize.Width));
}
translate.X = x;
translate.Y = y;
rotate.Angle += e.RotationDelta;
var newScale = scale.ScaleX * e.ScaleDelta;
Console.WriteLine("Scale delta: " + e.ScaleDelta + " Rotate delta: " + e.RotationDelta);
newScale = Math.Max(0.3, Math.Min(newScale, 3));
scale.ScaleY = scale.ScaleX = newScale;
}
One thing to notice here is that both the manipulation and the inertia processor uses the same callback for delta events.
The final piece of the puzzle is when the user releases the finger and I want to start the inertia processor:
/// <summary>
/// This is called when the manipulator has completed (i.e. user released contacts) and we let inertia take over movement to make a natural slow down
/// </summary>
void processor_Affine2DManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
{
inertiaProcessor.InitialOrigin = e.ManipulationOrigin;
// Set the deceleration rates. Smaller number means less friction (i.e. longer time before it stops)
inertiaProcessor.DesiredAngularDeceleration = .0010;
inertiaProcessor.DesiredDeceleration = .0010;
inertiaProcessor.DesiredExpansionDeceleration = .0010;
inertiaProcessor.Bounds = new Thickness(0, 0, this.ActualWidth, this.ActualHeight);
inertiaProcessor.ElasticMargin = new Thickness(20);
// Set the initial values.
inertiaProcessor.InitialVelocity = e.Velocity;
inertiaProcessor.InitialExpansionVelocity = e.ExpansionVelocity;
inertiaProcessor.InitialAngularVelocity = e.AngularVelocity;
// Start the inertia.
inertiaProcessor.Begin();
}
yes, you can use the ManipulationProcessor's that come with the API
Related
When a WPF item is frozen, the docs says it cannot be changed. I'm just not very sure what "change" mean by in this context.
For example, if I create an instance of a shape and then freeze it, is it possible to do things like rotate or translate it even though it is frozen?
Short answer:
Once an object is frozen, you cannot modify any properties on it. This applies recursively.
Longer answer:
First of all, the Shape class (and thus Path, Ellipse, Rectangle etc.) are not freezable.
But assuming you are talking about Geometry, which is freezable, then the general answer is no, because attempting to modify properties of a frozen object is not possible. For example, the following code will throw an exception
var geo = new LineGeometry();
geo.Freeze();
// InvalidOperationException:
geo.Transform = new TranslateTransform(10, 10);
And freezing is recursive, so its not possible to cheat the system like this:
var tx = new TranslateTransform(10, 10);
var geo = new LineGeometry();
geo.Transform = tx;
geo.Freeze();
// InvalidOperationException:
tx.X = 20;
But, back to your original question about shapes, which are constructed out of geometries (but doesn't derive from them).
You can freeze the geometry of your shape, and still apply transformations to that shape. This works because the transform is on the shape object, and not on the freezable:
var geo = new LineGeometry(new Point(0,0), new Point(10,10));
geo.Freeze();
var myShape = new Path { Data = geo };
// This is fine, even though the geometry is frozen
myShape.RenderTransform = new TranslateTransform(10, 10);
I'm developing a WPF application for the Surface, and have run into a bug which neither I nor my colleagues know the answer to.
I have a situation when the user can drag things from a box. The box is just an image, and when touching it, another image is spawned and is the object that gets the interaction, i.e. gets dragged and dropped. The whole dragging is performed with SurfaceDragDrop.BeginDragDrop(...). Upon drop, the object simply disappears.
Now the problem is that occasionally (seems to be when input comes rapidly, just hammering the screen and not actually dragging the objects) the spawned item never despawns, i.e. it remains on screen even when input has been removed. It is possible to intereact with it by touching it to continue dragging it, but it's movement and rotation is off because it appears to think there already is another finger interacting with it.
However, through debugging I have certified that there are no touches registered to the object. It's AreAnyTouchesCaptured property is false. Manipulation is however active. So what seems to happen is that it goes into some sort of manipulation which never ends.
In order to get rid of it, I've implemented a temporary solution which simply does SurfaceDragDrop.CancelDragDrop(..) on such objects that have been on screen a certain period of time without any touches captured.
I hope my description of the problem is somewhat clear, if not please ask. Below is the code used to spawn the objects. I have however not written it (an ex-colleague did), I'm just stuck with the debugging:
private void item_PreviewTouchDown(object sender, TouchEventArgs e)
{
var box = sender as Canvas;
var imgpath = String.Format(#"[someimgpath].png");
var bmp = new BitmapImage(new Uri(imgpath, UriKind.RelativeOrAbsolute));
Image draggedItem = new Image { Source = bmp, Stretch = Stretch.None, Width = bmp.Width, Height = bmp.Height };
draggedItem.UpdateLayout();
var touchPoint = e.TouchDevice.GetTouchPoint(box);
var itemX = touchPoint.Position.X - draggedItem.Width / 2.0;
var itemY = touchPoint.Position.Y - draggedItem.Height / 2.0;
box.Children.Add(draggedItem);
draggedItem.SetValue(Canvas.LeftProperty, itemX);
draggedItem.SetValue(Canvas.TopProperty, itemY);
draggedItem.Visibility = System.Windows.Visibility.Hidden;
//We should perfom drag-and-drop when the size of the draggedItem is updated, since
//BeginDragDrop uses ActualWidth and ActualHeight property
draggedItem.SizeChanged += (o, ev) =>
{
List<InputDevice> devices = new List<InputDevice>();
devices.Add(e.TouchDevice);
foreach (TouchDevice touch in box.TouchesCapturedWithin)
{
if (touch != e.TouchDevice)
{
devices.Add(touch);
}
}
var img = new Image { Source = draggedItem.Source, Stretch = Stretch.None, Width = bmp.Width, Height = bmp.Height };
img.SetValue(Canvas.LeftProperty, itemX);
img.SetValue(Canvas.TopProperty, itemY);
var cursor = SurfaceDragDrop.BeginDragDrop(box, draggedItem, img, draggedItem, devices, DragDropEffects.Move);
};
e.Handled = true;
}
I asked this question in a similar post but there have been significant updates since then, but still no results so I will try to re-ask the question with the updated information.
Basically I have a pivot view with 4 pivot items. If I create the scenario where I hit the windows key then rapidly press the back key my application will reopen without reconstructing (this is the expected outcome). The functionality of the application is there. I can press application bar buttons etc.
What doesn't work is the pivot items are frozen. If I was on Pivot item A and I press the start and back button quickly I come back to Pivot Item A. If I try to switch Pivot Items, the screen does not update, its "frozen" on Pivot Item A BUT the functionality of Pivot Item B is there. (I know this because the application bar Icons for Pivot Item B are now showing).
I have read many articles on proper tombstoning scenarios and how to approach this problem. My data IS being tombstoned correctly, and upon reactivation the tombstoned data works. No objects are null so I don't have any exceptions being thrown at me.
I check to see if I need to reload the Main ViewModel (I don't need to in this case so the UI elements being created initially are not being re created).
What does fix the problem however is if the application is reconstructed. Lets say I go to the marketplace from my app, let it finish loading and press back, My application will be refreshed and working fine since it properly deactivated and reconstructed istelf. I don't rely on constructors doing all the work so I am not missing any key elements not being set when they aren't fired in the windows/back button scenario.
Does anyone have any idea why my screen would not be updating?
constructor/loaded event/on navigated to event
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (App.firstTimeLoading == true)
{
App.firstTimeLoading = false;
}
BuildApplicationBar();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = App.ViewModel;
App.viewIdentifier = StringResource.MainPageView;
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
String bookTitle;
App.Parser.appBookInfoDict.TryGetValue(CPlayerInventoryKeys.kInventoryKeyTitleShortTitle, out bookTitle);
PivotBackground.Title = bookTitle.ToUpper();
CreatePivotItems();
}
if (App.playerController.chapterPlayer.Source == null)
App.restoreStateClass.RestoreState();
//applies the proper background image
if (App.isDarkTheme)
{
BitmapImage bitmapImage = new BitmapImage(new Uri(StringResource.PanoramaBlackImage, UriKind.Relative));
BackgroundImage.ImageSource = bitmapImage;
BackgroundImage.Opacity = .85;
}
else
{
BitmapImage bitmapImage = new BitmapImage(new Uri(StringResource.PanoramaWhiteImage, UriKind.Relative));
BackgroundImage.ImageSource = bitmapImage;
BackgroundImage.Opacity = .5;
}
if (App.firstTimeLoading == false && PivotBackground.SelectedItem != SuggestedPivotItem)
BuildApplicationBar();
else if (PivotBackground.SelectedItem == SuggestedPivotItem)
{
BuildMarketPlaceApplicationBar();
}
base.OnNavigatedTo(e);
}
I found the answer. Since I had a media element open (play/paused) and I was implementing the "non tombstoned" method of hitting windows key and back button very quickly, the media element source was corrupt. Even though I reset this source, apparently it can be ignored and not function properly. All I had to do was add a line of code to the Application Deactivated handler.
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
App.MainAudioPlayer.Source = null; //(only showing line added)
}
The behavior you are describing seems to be solely related to the way you are manipulating data internally and constructing your layout. I tested this both in the emulator and on a couple of physical devices, both producing normal output (even when bound to a view model).
Try creating a new Pivot-based application (without all your data - just using the default template) and see if the problem persists. Also worth mentioning - are you testing on a device or in the emulator?
Are you using transitions from the toolkit?
Are they defined in XAML?
If so that could be the issue. There's a bug which is fixed in the next version.
The solution for now is to remove the transitions or define them in code.
I'm measuring the time between frames in a simple WPF animation. Perforator says the app performs at ~60fps, so I expected the time between frames to be ~16.6ms with little deviation.
public MainWindow()
{
...
CompositionTarget.Rendering += Rendering;
}
List<long> FrameDurations = new List<long>();
private long PreviousFrameTime = 0;
private void Rendering(object o, EventArgs args)
{
FrameDurations.Add(DateTime.Now.Ticks - PreviousFrameTime);
PreviousFrameTime = DateTime.Now.Ticks;
}
Two things surprised me:
Time between frames is fairly irregular
Time between frames is ~8ms. I had expected that the monitor's refresh rate would set a lower bound on time between frames (ie. 60Hz = 16.6ms between each frame, and anything faster is pointless).
Y - Time between frames in ticks (10,000 ticks = 1ms)
X - Frame count
Possible confounding factors
Timer inaccuracy
If CompositionTarget.Rendering doesn't actually correlate to the drawing of a single frame
The project I'm using: SimpleWindow.zip
===Edit
Markus pointed out I could be using RenderingEventArgs.RenderingTime.Ticks instead of DateTime.Now.Ticks. I repeated the run and got very different results. The only difference is timing method:
DateTime.Now.Ticks
RenderingEventArgs.RenderingTime.Ticks
Data from RenderingEventArgs produced data much closer the expected 16.6ms/frame, and it is consistent.
I'm not sure why DateTime.Now and RenderingEventArgs would produce such very different data.
Assuming RenderingEventArgs is producing correct times, it's still a bit disconcerting that those times are not the expected 16.6ms.
If the display is updating every 16.6ms and WPF is updating every 14.9ms, we can expect a race condition that would result in tearing. That is to say, roughly every 10th frame WPF will be trying to write its image while the display is trying to read the image.
I raised this question with the WPF team and here is a summary of the response I was given:
Calculating the framerate from the UI
thread is difficult. WPF decouples
the UI thread from the render thread.
The UI thread will render:
Whenever something is marked as dirty and we drain down to Render
priority. This can happen more often
than the refresh rate.
If an animation is pending (or if someone hooked the
CompositionTarget.Rendering event) we
will render on the UI thread after
every present from the render thread.
This involves advancing the timing
tree so animations calculate their new
values.
Because of this, the
CompositionTarget.Rendering event can
be raised multiple times per “frame”.
We report the intended “frame time” in
the RenderingEventArgs, and
applications should only do
“per-frame” work when the reported
frame time changes.
Note that the UI thread is doing many
things, so it is not reliable to
assume the CompositionTarget.Rendering
event handler runs at a reliable
cadence. The model we use (decoupling
the two threads) means that the UI
thread can be a little behind, since
it is calculating animations for a
future frame time.
Special thanks to Dwayne Need for explaining this to me.
First - 'Christopher Bennage's-Answer has a good explanation and provides a hint to a solution:
"Only do “per-frame” work when the reported frame time changes"
This is a little bit hard, since the RenderingEventArgs are hidden as a normal EventArgs and a cast has to be done.
To make this a little bit easyer, a convinient solution can be found in "EVAN'S CODE CLUNKERS"
http://evanl.wordpress.com/2009/12/06/efficient-optimal-per-frame-eventing-in-wpf/
I took his code and modified it a bit. Now just take my snipped, add the class to your Project, use CompositionTargetEx were you used CompositionTarget and you are fine :)
public static class CompositionTargetEx {
private static TimeSpan _last = TimeSpan.Zero;
private static event EventHandler<RenderingEventArgs> _FrameUpdating;
public static event EventHandler<RenderingEventArgs> Rendering {
add {
if (_FrameUpdating == null)
CompositionTarget.Rendering += CompositionTarget_Rendering;
_FrameUpdating += value;
}
remove {
_FrameUpdating -= value;
if (_FrameUpdating == null)
CompositionTarget.Rendering -= CompositionTarget_Rendering;
}
}
static void CompositionTarget_Rendering(object sender, EventArgs e) {
RenderingEventArgs args = (RenderingEventArgs)e;
if (args.RenderingTime == _last)
return;
_last = args.RenderingTime; _FrameUpdating(sender, args);
}
}
WPF is not designed to be a constant frame rate rendering system. WPF renders to screen when the elements in the screen are marked as changed. The rendering system runs as a message loop so there is no way to ensure that a frame will be rendered at specific intervals.
I'm trying to implement a chart that can handle real time data, which comes in every 1 ms. I poll for 50ms of data so that I'm not trying to redraw the screen every single ms. I am using a PathGeometry on a Canvas to add line segments. I always see the framerate steadily tick downwards, as the redraws grow slower and slower. I did not think that a Line with roughly 10,000 points would be so difficult for my computer to render. Is there something I'm doing wrong? Or is there some other design philosophy that may be more adept at handling real time data in WPF?
In the ViewModel I have:
public PointCollection LinePoints;
In the View I listen to this collection being changed and add line segments:
_viewModel.LinePoints.Changed += LinePoints_Changed;
void LinePoints_Changed(object sender, System.EventArgs e)
{
while (_viewModel.LinePoints.Count - 1 > pathFigure.Segments.Count)
{
// pathFigure is part of the PathGeometry on my canvas
pathFigure.Segments.Add(new LineSegment(_viewModel.LinePoints[pathFigure.Segments.Count], true));
}
}
For simulation purposes I am injecting points using a BackgroundWorker:
void addPointsWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
DateTime startTime = DateTime.Now;
int numPointsAdded = 0;
while (!bw.CancellationPending && (DateTime.Now - startTime).Seconds < 10)
{
List<Point> points = new List<Point>();
for (int i = 0; i < 50; i++)
{
Math.Sin(numPointsAdded++/(500*Math.PI))));
}
System.Threading.Thread.Sleep(50);
bw.ReportProgress(0, points);
}
}
public void addPointsWorker_ProgressChanged(List<Point> pointsToAdd)
{
foreach(Point point in pointsToAdd)
{
// triggers CollectionChanged which will add the line segments
ViewModel.LinePoints.Add(point);
}
}
Along with slowdown I also experience UI unresponsiveness. I figured it's because I'm making too many ReportProgress calls and filling up the message pump, but if I could resolve the slow rendering I think this other issue would go away as well. I am open to any suggestions!
I have two suggestions:
Make sure your collection only contains the points that will be rendered to the screen. An easy way to do this would be to use a queue and remove from the front as more points are added. I doubt that all 10,000 points need to be rendered at once.
If this is still not getting you the performance you need, you may want to use a lower-level drawing mechanism. StreamGeometry would be the first thing to try. After that, WriteableBitmap would provide the best performance possible in WPF.
After a bit of time tweaking things, I've found that Charlie's is the best solution. I also discovered that instead of using a LineSegment every time a point is added, using a PolyLineSegment with the 50 or so points of data I'm already queuing up does wonders for performance.
However, it doesn't delay the inevitable slowdown that occurs. But using a combination of PolyLineSegment with only the last several hundred or even thousand points has given me the best performance while showing the most amount of data possible.