Here is the problem that has been with me for the past several days. I have 10000+ Line objects on a Canvas and a Slider that I use to scale the canvas to create a zoom effect.
Everything is OK and the performance is great.
The only thing that bothers me is that the StrokeThickness of the Line gets scaled too. I tried to Bind the StrokeThickness to the inverse of the slider's value so that I get a uniform thickness whenever I move the slider. This worked but has decreased the performance substantially.
Is there any other way that would be suitable to this situation. I want to have a zero-width line thickness no matter how far I scale the canvas (like in CAD programs).
For the effect you want to achieve, overriding the StrokeThickness doesn't really mean anything as the it's value actually remains the same even if the ScaleTransform is changed.
The approach that you tried - Bind the StrokeThickness to the inverse of the slider's value - seems to be the most plausible one. To improve performance, instead of setting the StrokeThickness directly to inverse of Slider's value, set it to another property on your view model.
Then, in the event where sliding is completed, set the value of that property to the inverse of StrokeThickness.
Example -
private void OnSlidingComplete(object sender, EventArgs e)
{
LineStrokeThickness = GetInverseOfZoomValue();
}
I know that this is not exactly a solution, rather a workaround.
Related
I want to create a plot that dynamically displays active elements as rectangles. I have achieved a first version that is actually ok using OxyPlot.Annotations.RectangleAnnotation which I add to myPlotModel.Annotations, you can see it in the image hereafter:
Example of wanted display
The thing is that after a while, the amount of drawn rectangles make the update not smooth as I update the shown timewindow (which is set to 15 seconds). I have already set a maximum of drawn elements that suffice to cover the displayed window (i.e. the rectangles get removed as they are too far in the past), but the rendering is still jerky. I draw the rectangles by allocating them to an equal fraction of the Y-axis, that is the third one from the top gets:
rowNumber= 3.0
minimumY = maximalY - maximalY / totalElements * rowNumber
maximumY = maximalY - maximalY / totalElements * (rowNumber + 1.0)
And the Y-axis is hidden.
My question:
Is there a smarter way of creating such a display that would be less computationally heavy, and therefore allow a smoother update? I do not have to stick to OxyPlot, it is simply the easiest way that I found to obtain what I wanted.
Thanks for your answers!
Technically, the answer to your question is "Yes".
There are a number of ways to do this.
You could have a vertical itemscontrol that had an itemscontrol in it's template. That could have a canvas as it's itemspresenter and you could bind canvas.top and canvas.left to properties in it's content. Template each into a rectangle and bind height and width.
And of course do something about the scale on the bottom and the column of activity labels or whatever you want to call them there.
Unless you're using an absolutely ancient machine, that'd just fly.
It's quite a lot of work but it would probably be quicker to write that than to search through a load of alternative packages and decide which was optimal.
I have two different grids, one stacked up on another. Now I am trying to reduce the size of the first grid(using visual layer animations), such that the other grid now takes up all the space.
ScalarKeyFrameAnimation animation1 = compositor.CreateScalarKeyFrameAnimation();
animation1.InsertKeyFrame(1.0f, 0);
animation1.Duration = TimeSpan.FromMilliseconds(300);
visual.StartAnimation(nameof(visual.Scale) + "." + nameof(visual.Scale.Y), animation1);
I have tried manipulating both offsets and scale property but none are able to change the actual height of the grid. As a result, the second grid doesn't move from its place.
Above is the live visual tree values, the actual height is not being affected with either scale or offset animation. And no "second grid" taking up entire space animation is happening.
ActualHeight & ActualWidth are values set by the layout engine.
VisualLayer animations and RenderTransform animations in XAML apply after the Layout passes and won't update affect ANY layout properties. They are, for lack of a better word, "Render time" properties. Any changes to them will not affect any layout other object.
If you'd really like this to work, you'd need to use either a LayoutTransform or animate the Width & Height of the object if they have one set using Storyboards. Noting however, this will cause animations to run on the UI thread because they require the Layout engine to re-run passes every frame, which will reduce performance.
I'm having a minor issue with my current app, dealing with visual states and automatic / static widths.
Depending on the visual state, a StackPanel either has a width="Auto" or width="400". Blend is telling me I can't animate between these two values (and I'm not really animating here, but simply switching between a fullscreen video and a composite view). Now I have to do an explicit test and change the width when I change my Visual State (through the VisualStateManager-framework. Is there any way I can do this in XAML (through the storyboards) instead of in the codebehind?
Some code samples of what I'm doing today:
private void Trailer_OnFullScreenToggled(object sender, EventArgs e)
{
var state = (Trailer.IsFullScreen() ? "Windowed" : "Fullscreen");
// HACK: Done to get past the auto / px issue
VisualsGrid.Width = Trailer.IsFullScreen() ? 400.0 : Double.NaN;
VisualStateManager.GoToState(this, state, true);
}
Any help would be greatly appreciated!
The "Auto" value for width is aliased to Double.NaN. This is why the animation fails -- it can't do interpolation to or from that value.
Have you tried using a keyframe animation with a discrete keyframe? Using a discrete keyframe animation should get around the problem that the animation system can't interpolate to or from Double.NaN since no interpolation will happen.
I'm not in front of a development environment at the moment, so I'm not sure if you would need to use a DoubleAnimationUsingKeyFrames with a DiscreteDoubleKeyFrame and a value of Double.NaN or a StringAnimationUsingKeyFrames with a DiscreteStringKeyFrame and a value of Auto.
That, of course, will not do a smooth animation from a fixed width to auto-sizing but instead will pop between the two.
I'm resizing a canvas with touch events as follows:
e.Handled = true;
var transformation = MyCanvas.RenderTransform as MatrixTransform;
var matrix = transformation == null ? Matrix.Identity :transformation.Matrix;
matrix.ScaleAt(e.DeltaManipulation.Scale.X,
e.DeltaManipulation.Scale.Y,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
MyCanvas.RenderTransform = new MatrixTransform(matrix);
The canvas has several child canvasses. I don't want to resize them and in fact need them to go smaller. So looked at RenderTransform.Inverse but am not having any joy.
You can create a custom canvas by inheriting from Panel with
A new dependency property: NonInheritableScale
A binding between bind the transform's scale to the NonInhertiableScale property
overrides of the MeasureOverride() and ArrangeOverride() methods,
so that the take 1.0/NonInhertiableScale.X and 1.0/NonInhertiableScale.Y into account during the layout.
Here is an article on creating custom WPF panels that might help you (a search result, haven't read it).
EDIT after reading the comment below
In that case of a chart you might want to redraw the chart with different axis ranges. A RenderTransform might be not accurate enough and indeed you will have to scale back everything else (axis, labels, gridlines,...)
previous answer, still valid
You will have to iterate through the child canvases and scale them individually. As far as I know there is no build in support for what you want.
You will have to apply both the inverse scale transformation to negate the parent's resize and a scale transformation that will make them smaller.
Post the code you are using to get more detailed help and or feedback.
I am using stylus input to draw lines in a canvas. I want to change the color of the stroke with the pen pressure. so I used:
DrawingAttributes dattribute = new DrawingAttributes();
inkcan.EditingMode = InkCanvasEditingMode.Ink;
if (stylusInput.pressureFactor < 0.5)
dattribute.Color = Colors.Red;
else
dattribute.Color = Colors.Blue;
inkcan.DefaultDrawingAttributes = dattribute;
but I have found that the color changes only when I lift off and retouch the pen to tablet surface. I am not sure how to fix that problem.
Any help would be greatly appreciated.
Look at this question: InkCanvas Eraser
In the MSDN it states:
If you change the EraserShape, the cursor rendered on the InkCanvas is
not updated until the next EditingMode change.
The effect you are experiencing might be caused by the EditingMode being changed when you pull the pen off the canvas and put it back down.
If so, you could toggle the EditingMode property as I suggested in the linked answer.
EDIT
Have a look at this a 3rd down it says:
Off course, life is never as simple as that, so there is one more
little issue to handle. Apparently, the InkCanvas uses different
renderers for the final result compared to while the strokes are being
drawn. To show a transparency based on the pressure while the
drawing action is still in progress, we need to use the protected
property called DyamicRenderer which gets/sets the object used to
render the strokes on a drawing context while the stroke is being
drawn. This rendering object must be a descendent of DynamicRenderer.
All you need to do here is override the OnDraw method and change the
brush that is used. When you assign a new value to this property, the
InkCanvas actually changes an internal 'PlugIn list' which is called
whenever data is entered using the stylus.
This might be it.
The if condition is evaluated only once, so there is no reason for the colour to change while you are drawing. Unfortunately, there seems to be no "onpressurechanged" event, so you would probably have to set up a loop that checks the pressure every x milliseconds and adjusts the colour accordingly.
Since you don't want to freeze the UI, you would either need to run it in another thread and delegate the colour change back to the UI thread, or you can queue the pressure checks onto the window dispatcher with "applicationIdle" priority. This would look something like:
void checkPressure(InkCanvas inkcan)
{
//return if touch is lifted code here
DrawingAttributes dattribute = new DrawingAttributes();
if (stylusInput.pressureFactor < 0.5)
dattribute.Color = Colors.Red;
else
dattribute.Color = Colors.Blue;
inkcan.DefaultDrawingAttributes = dattribute;
this.Dispatcher.BeginInvoke(new MyPressureDelegate(checkPressure), DispatcherPriority.ApplicationIdle, inkcan);
}
assuming your stylusInput shares scope with the function, of course. Otherwise you'd need to pass it in along with the canvas in an object array.