Pinch-Zoom: Get Touch Position relative to Object - wpf

I want to make a Touch-Viewer where I want to zoom into the point between my finger.
I have a viewbox with content-presenter inside a grid:
<Grid IsManipulationEnabled="True">
<Viewbox x:Name="PART_Viewbox">
/// Content
</Viewbox>
</Grid>
Zoom in with the scrollwheel of my mouse into the point where the mouse is, i solved within the following methods:
var position = e.GetPosition(PART_Viewbox);
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
The method e.GetPosition ist only available in MouseEventsArgs and not in ManipulationDeltaEventArgs.
So how can i get the relative position in touch-mode?
When i take the e.ManipulationOrigin from ManipulationDeltaEventArgs and the grid is bigger then the Viewbox it translates the content of the viewbox somewhere else while zooming in or out.
The most examples showing zooming into the middle of the content (image). Thats not what I want.

The class FrameworkElement derives from UIElement. There is a method TranslatePoint which translates a point relative to a element.
In ManipulationDeltaEventArgs is a Property ManipulationOrigin which represents the point between the two fingers in a pinch zoom. An other important property is the ManipulationContainer that contains the element on that the manipulation is executed.
So in my case i can do following:
// Typecast ManipulationContainer to FrameworkElement and get point around the finger
Point position =((FrameworkElement)e.ManipulationContainer)
.TranslatePoint(e.ManipulationOrigin, _Viewbox);
// Center the new point in account to previous manipulations
position = matrix.Transform(position);
// do the transformation
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, position.X, position.Y)
I found this hint in the sourcecode of https://multitouchtransformbehavior.codeplex.com/

Related

How to get a one-to-one match of an Adorner with its adorned element within a ScrollViewer?

wpf
I have a control A, (an inkcanvas), within a Grid within a ScrollViewer. Conrol A is taller then the physical window, so the ScrollViewer correctly adds a vertical scroll bar and the entire control can be viewed by scrolling down. When attaching an Adorner with a control B, (another inkcanvas), to control A, scrolling downward shows the Adorner to be cut off at the bottom of the screen. That is, the Adorner is not completely covering the adorned element and/or is not extended downward when scrolling.
How do I get the Adorner (the control) to completely cover the adorned element and respect the ScrollViewer. (I need a one-to-one match between the pixels of the Adorner control and the adorned element within the ScrollViewer).
TIA
Edit#1: The key line in the Adorner that sets the background of the InkCanvas is
_inkcanvas.Background = CreateGrid();
public InkCanvasTextAdorner(InkCanvas element)
: base(element)
{
_element = element;
_visuals = new VisualCollection(this);
_inkcanvas = new InkCanvas();
_inkcanvas.Background = CreateGrid();
_visuals.Add(_inkcanvas);
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(element);
adornerLayer.Add(this);
}
If an InkCanvas is used (as the above) the Adorner's background is clipped at the bottom. The adorner control, however, does continue to the bottom of the adorned element.
However, if a Canvas is used instead of the InkCanvas, the Adorner's background does extend to the bottom of the adorned element.
What's wrong?
I'm guessing that the difference in the Background property expanding past the physical screen with the Canvas and not the InkCanvas may be because the Canvas inherits from Panel whereas the InkCanvas does not. Based on the finding that the Canvas background does do what I need, I find the below code does accomplish what I want--the canvas allows images from layers beneath it to be seen yet posts a grid of lines overwhich the InkCanvas will accept strokes. All is well :)
public InkCanvasTextAdorner(InkCanvas element)
: base(element)
{
_element = element;
// The VisualCollection has only one visual parent. I.e. InkCanvasTextAdorner is the parent to the VisualCollection.
// By overriding default rendering behavior of the VisualCollection, any kind of control and its children can be placed in the Adorner.
_visuals = new VisualCollection(this);
_inkcanvas = new InkCanvas();
_inkcanvas.Background = Brushes.Transparent;
_canvas = new Canvas();
_canvas.Background = CreateGrid();
_grid = new Grid();
_grid.Children.Add(_canvas);
_grid.Children.Add(_inkcanvas);
// The _grid is a logical child of the VisualCollection of the Adorner. The ArrangeOverride and MeasureOverride will set up the Grid control.
_visuals.Add(_grid); // Adding a single control for display.
// AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(element);
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(element);
adornerLayer.Add(this);
}
Which looks like:

How to Update UserControl (Chart) in MVVM

I have a chart embedded in a usercontrol
PlotControl.xaml (PlotControl.xaml.cs)
This plot control is used on a View in MVVM.
NOW! when I update the UI of PlotControl (e.g. I draw vertical and horizontal markers on chart), these updates are not visible on View (unless I do double click on the View or do a Minimize-Maximize window).
Is there a way the updated UI is updated automatically on View?
View code looks like:
<Grid Margin="4">
<nms:PlotControl x:Name="PlotControl" Margin="10,10" DockPanel.Dock="Right" />
</nms:PlotControl>
Snippet from PlotControl.xaml.cs code looks like:
ChartPanel cpnl = new ChartPanel();
chart.View.Layers.Add(cpnl);
ChartMarker rightVerticalMarker = new ChartMarker(chart, MarketType.RightVertical);
rightVerticalMarker.DataPoint = new Point(30, double.NaN);
cpnl.Children.Add(rightVerticalMarker);
cpnl.UpdateLayout();
chart.UpdateLayout();
ChartMarker is simply a line Horizontal (or Vertical) defined by enum MarketType.
NOTE: I have been searching for this problem since two days in SO, but nowhere could I find the solution.
Updates can be forced by Arrange methods. Here is what I have found, which solved my problem.
Size userControlSize = this.RenderSize; //original size of userControl
Size chartSize = chart.RenderSize; // original size of chart
chart.Arrange(chart.View.PlotRect); // forcing layout elements on chart
chart.Arrange(new Rect(chartSize)); // setting the chart size to original value
Arrange(new Rect(userControlSize)); // setting the userControl size to original value

WPF ContextMenu placement adjusted event

Does anyone know of how I can determine when the ContextMenu get its placement automatically adjusted due to being too close to the edge of the screen?
My scenario is that I have a ContextMenu that has 2 rounded corners and 2 square corners. When the menu opens down I round the bottom 2, and if the menu is opening upwards then I round the top 2. The problem is that I haven't found an event or property to bind to that tells me when the menu gets its direction automatically changed.
Here's some simplified sample code to try out. If you click when the window is at top of screen then menu goes down. If you move window to bottom of screen then the menu will go up.
<Window x:Class="menuRedirection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="100" Width="200">
<DockPanel Name="panel" ContextMenuOpening="DockPanel_ContextMenuOpening">
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
</ContextMenu>
</DockPanel.ContextMenu>
<Rectangle DockPanel.Dock="Bottom" Name="menuTarget" Fill="Red" Height="10"/>
<TextBlock DockPanel.Dock="Top" Text="right click for context menu"/>
</DockPanel>
</Window>
private void DockPanel_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
ContextMenuService.SetPlacement(panel, PlacementMode.Bottom);
ContextMenuService.SetPlacementTarget(panel, menuTarget);
}
Here's what the real application looks like so you can see my problem with needing to know to adjust my rounded corners.
As far as I can tell, this is not possible.
Using JustDecompile, I traced this functionality to the UpdatePosition method in the Popup class. The final location seems to be set here:
this._positionInfo.X = num4;
this._positionInfo.Y = num5;
this._secHelper.SetPopupPos(true, num4, num5, false, 0, 0);
_secHelper is a helper class of type PopupSecurityHelper, and seems to just be an internal helper...And, none of these result in an event or even a public property being changed.
Here is an MSDN article explaining how popup positioning is determined in general (The 'When the popup encounters the edge of a screen' describes your scenario).
However, this article explains how you can use the CustomPopupPlacementCallback to override these behaviors somewhat. However, this still uses a PopupPrimaryAxis, which should flip the menu as necessary, and will result in the same problem.
The only other thing I could think of is that you could look into the PlacementRectangle and maybe poll the size and location similar to how UpdatePosition does things...or just check the popup itself just like UpdatePosition does.
This is a private method, though. So, any logic you try to mimic could change in a future version of the framework.
UPDATE
Also, you could possibly try bastardizing PointToScreen or PointFromScreen, but that would be very convoluted code if it worked...
I was unable to find a true WPF solution but Justin's comment lead me down the path of experimenting with comparing the menu's location with the PlacementTarget's location.
First step was to subscribe to the contextMenu.Loaded event (this fires after layout has been processed but before it's fully visible on the screen).
<ContextMenu ContextMenu.Loaded="ContextMenu_Loaded">
And then when that fires I can figure out if the menu was internally switched to the alternate placement for my requested placementMode. If it was reversed then I go ahead and adjust my rounded corners accordingly.
NOTE: i initially had used getWindowRect and compared the menu Rect with the target's Rect, but found that the menu Rect was always returning the prior instance's location. To avoid this problem I now get the relevant screen's workingArea and manually see if the menu fits.
NOTE2: be sure your menu's template results in the same window height for both inverted and regular display. Otherwise, your calculation could be off since getWindowRect returns the last menu's size.
void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
bool reversed = isMenuDirectionReversed(this.ContextMenu);
//existing styles are read-only so we have to make a clone to change a property
if (reversed)
{//round the top corners if the menu is travelling upward
Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(10, 10, 0, 0) });
this.ContextMenu.Style = newStyle;
}
else
{ //since we may have overwritten the style in a previous evaluation,
//we also need to set the downward corners again
Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(0, 0, 10, 10) });
this.ContextMenu.Style = newStyle;
}
}
Evaluation method:
private bool isMenuDirectionReversed(ContextMenu menu)
{
//get the window handles for the popup' placement target
IntPtr targetHwnd = (HwndSource.FromVisual(menu.PlacementTarget) as HwndSource).Handle;
//get the relevant screen
winFormsScreen screen = winFormsScreen.FromHandle(targetHwnd);
//get the actual point on screen (workingarea not taken into account)
FrameworkElement targetCtrl = menu.PlacementTarget as FrameworkElement;
Point targetLoc = targetCtrl.PointToScreen(new Point(0, 0));
//compute the location for the bottom of the target control
double targetBottom = targetLoc.Y + targetCtrl.ActualHeight;
if (menu.Placement != PlacementMode.Bottom)
throw new NotImplementedException("you need to implement your own logic for other modes");
return screen.WorkingArea.Bottom < targetBottom + menu.ActualHeight;
}
Final result:

Connector Lines with textbox, binding issue

I want to draw a line and when I double click it I want to put a text on it. I was thinking of putting it in a contentcontrol and drawing a line, put a collapsed textbox on top, detect a double click, show the textbox set it to a textblock, etc. The only problem I run into I don't know what to set the coordinates of the line since it is inside the contentcontrol, so an actual line gets drawn. i've been stuck for hours, any help would be appreciated.
Basically I need an object with a start and end point properties which has the shape of a line , with a content presenter. But I don't know how to go about doing this. Any pointers would be appreciated.
We do the same for labeling our connections. If you draw your connection via a path you can use
LineGeometry.GetPointAtFractionLength(0.5, out midPoint, out tangetMidPoint);
That way you would have the center position on your geometry. Now you could store this into a dependency property which you use to position the label. Of course this must be called everytime your shape/geometry changes its position or size.
a small example for a control combining this.
public class LabeledLine : ContentControl
{
public static readonly DependencyProperty LabelPosition ...
public static readonly DependencyProperty LineGeometry ...
// call me everytime the LineGeometry gets changed.
public void UpdatePath()
{
LineGeometry.GetPointAtFractionLength(0.5, out midPoint, out tangetMidPoint);
LabelPosition = midPoint;
}
}
Your ControlTemplate would look something like that
<ControlTemplate TargetType="{x:Type local:LabeledLine}">
<Canvas x:Name="canvas">
<Path Data="{TemplateBinding LineGeometry}"/>
<TextBox Canvas.Left="{TemplateBinding LabelPosition.X}" Canvas.Top="{TemplateBinding LabelPosition.Y}"/>
</Canvas>
<ControlTemplate/>
Now to add the ContentControl functionality you could add the ContentPresenter in place of the TextBox.
Basically I need an object with a start and end point properties which
has the shape of a line
For that just add 2 dp properties for your 2 positions. Make sure to add a dependency property changed handler to call the UpdatePath method.

problem with ContainerVisual.Transform

in my custom control i have a ContainerVisual object and a DrawingVisual under it.
I override ArrangeOverride and calculate the rectangle that i want to draw in based on the given size and the control's padding.
after that i set my ContainerVisual object's transform to the upper left corner of the rectangle so that the methods that render the drawing would not have to take account of the rectangle and assume that the drawing origin is at point 0,0.
this does not work, and the drawing is displaced. if instead i set transform of the DrawingVisual object it works and the rectangle is displayed the way it is supposed to be.
i thought that if i set transform on the container, it will automatically be applied to the visuals under it. is that so?
thanks for any help
EDIT: Updated the source code to show complete code.
class MyControl : Control
{
private readonly ContainerVisual container = new ContainerVisual();
private readonly DrawingVisual drawing = new DrawingVisual();
private Rect rect;
private void RenderDrawing()
{
using (var c = drawing.RenderOpen())
{
var p = new Pen(new SolidColorBrush(Colors.Black), 1);
c.DrawRectangle(null, p, new Rect(0, 0, rect.Width, rect.Height));
}
}
protected override Size ArrangeOverride(Size s)
{
var h = Math.Max(0, s.Height - Padding.Top - Padding.Bottom);
var w = Math.Max(0, s.Width - Padding.Left - Padding.Right);
var r = new Rect(Padding.Left, Padding.Top, w, h);
if (rect != r)
{
rect = r;
container.Clip = new RectangleGeometry(rect);
container.Transform = new TranslateTransform(rect.Left, rect.Top);
// replace the line above with the following line to make it work
// drawing.Transform = new TranslateTransform(rect.Left, rect.Top);
RenderDrawing();
}
return s;
}
protected override Visual GetVisualChild(int index)
{
return container;
}
protected override Size MeasureOverride(Size s)
{
return new Size();
}
protected override int VisualChildrenCount
{
get { return 1; }
}
public MyControl()
{
container.Children.Add(drawing);
AddVisualChild(container);
}
}
<Window x:Class="MyApp.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:c="clr-namespace:MyApp"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<c:MyControl Padding="20" />
</Grid>
</Window>
Explanation of strange clipping behavior
Now that you have posted your full source code I was finally able to see what you were seeing. Your problem isn't in the transform at all: It is in the clip!
If you comment out the container.Clip assignment statement, you get identical results no matter whether you put the transform on container or drawing
If you uncommented container.Clip assignment statement, the clipping region is perfectly centered on when the drawing is transformed, but when the container is transformed the clipping area is offset, so that only the lower and right lines of the rectangle were visible (and not all of those)
The reason this occurs is that the geometry specified for container.Clip is part of the container, so it is affected by container.Transform but not drawing.Transform:
This can be better understood by looking at the upper-left corners of the container, drawing, rectangle, and clip area relative to the upper-left corner of the window:
When you set the transform on the drawing:
Container is at (0,0) relative to window (null transform)
Clip area is at (20,20) relative to window (null transform + RectangleGeometry)
Drawing is at (20,20) relative to window (null transform + TranslateTransform)
Rectangle is at (20,20) relative to window (null transform + TranslateTransform + 0,0)
When you set the transform on the container:
Container is at (20,20) relative to window (TranslateTransform)
Clip area is at (40,40) relative to window (TranslateTransform + RectangleGeometry)
Drawing is at (20,20) relative to window (TranslateTransform + null transform)
Rectangle is at (20,20) relative to window (TranslateTransform + null transform + 0,0)
So your problem isn't that the transform isn't happening: It is that the transform is moving the clip area too, so the clip area no longer coincides with the rectangle and you can only see two sides of the rectangle.
Answer given for original code (retained because it has some useful explanation)
In fact, the code you posted never uses "container" so all you will see is a blank screen.
In your actual code you are using "container" incorrectly, preventing the events from occurring in the correct sequence to cause its Transform to be picked up and passed to the MIL layer.
Remember that when a Visual has a Transform set, it is not the visual itself but that Visual's visual parent that actually handles that transform. For example, if you render a page to XPS using ReachFramework or do hit testing, the Transform on the outermost Visual is ignored.
Your understanding is correct: If your visual tree is built following all the rules, it doesn't matter whether your transform is on your "container" or your "drawing".
Since you are using Control anyway, I'm curious why you don't just let the normal UIElement-based layout system handle your layout needs.
First update (retained for the same reason)
Thanks for the code correction. It is as I suspected: You are building your visual tree incorrectly. If you are using AddVisualChild you also must also override GetVisualChild and VisuaChildrenCount. This is because Visual does not store a list of children: It is up to the subclass (your class) to do this. What is happening is:
When you call AddVisualChild the container's transform is null so that is what is passed down to MILCore.
Later when you change the container's transform, it uses its parent pointer (that was set in AddVisualChild) to signal that its transform data must be refreshed. This update requires part of the visual tree to be scanned using GetVisualChild and VisualChildrenCount.
Since you didn't implement these methods this part of the update fails.
You say you are "new to WPF." Are you aware that you are playing with some of WPF's most low-level and esoteric features, ones that would never be used in a most ordinary WPF applications? It is equivalent to starting to learn programming using machine language. Normally you would use templates with Path, Rectangle, etc for this purpose. Sometimes you might go lower level and use a DrawingBrush with a DrawingGroup containing GeometryDrawings, etc. But you would almost never go all the way down to DrawingVisual and RenderOpen! The only time you would do that is when you have huge drawings consisting of millions of individual items and so you want to bypass all the layout and structure overhead of the higher layers for absolute maximum performance.
Manipulating the visual tree yourself (AddVisualChild, etc) is also an advanced feature. I always recommend people new to WPF stick with UIElement and above for the first few months, using Control with templates. I recommend they use Path and other shape subclasses for their drawings, and use VisualBrushes when advanced drawing effects are needed.
Hope this helps.
the problem is with the container.Clip. it should be
container.Clip = new RectangleGeometry(new Rect(0, 0, w, h));

Resources