I'm attempting to emulate the behavior of Windows 10's Virtual Touchpad, ie when a user touches a control inside the app and moves their finger around, the system cursor will mirror their movement. However, I notice that there seems to be some conflict between the system processing touch and mouse inputs simultaneously, where the touch input wants to hide the cursor, and the mouse input wants to show the cursor.
I started by building a boilerplate UWP App in VS-2019, here's my MainPage.xaml, where the only thing I'm changing is giving my Grid the Name="Touchpad" property so I can track pointer events within it:
<Page
x:Class="Playground.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Playground"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Background="Black" Name="Touchpad">
</Grid>
</Page>
I'm using Windows.UI.Input.Preview.Injection to move the mouse cursor. Because this is a Restricted Capability I made changes to my project as defined here: https://learn.microsoft.com/en-us/uwp/api/windows.ui.input.preview.injection#remarks
public sealed partial class MainPage : Page
{
private Point lastPosition;
private InputInjector inputInjector = InputInjector.TryCreate();
public MainPage()
{
this.InitializeComponent();
ApplicationView.PreferredLaunchViewSize = new Size(480, 480);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
Touchpad.PointerPressed += new PointerEventHandler(Touchpad_PointerPressed);
Touchpad.PointerReleased += new PointerEventHandler(Touchpad_PointerReleased);
Touchpad.PointerMoved += new PointerEventHandler(Touchpad_PointerMoved);
}
private void Touchpad_PointerMoved(object sender, PointerRoutedEventArgs e)
{
e.Handled = true;
PointerPoint pointer = e.GetCurrentPoint(Touchpad);
Point currentPosition = pointer.Position;
if (pointer.PointerDevice.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch &&
lastPosition != currentPosition &&
pointer.Properties.IsPrimary == true)
{
Point delta = new Point(currentPosition.X - lastPosition.X, currentPosition.Y - lastPosition.Y);
InjectedInputMouseInfo mouseInfo = new InjectedInputMouseInfo();
mouseInfo.MouseOptions = InjectedInputMouseOptions.Move;
mouseInfo.DeltaX = (int)delta.X;
mouseInfo.DeltaY = (int)delta.Y;
if (inputInjector != null)
{
inputInjector.InjectMouseInput(new[] { mouseInfo });
}
lastPosition = currentPosition;
}
}
private void Touchpad_PointerReleased(object sender, PointerRoutedEventArgs e)
{
e.Handled = true;
lastPosition = new Point(0,0);
}
private void Touchpad_PointerPressed(object sender, PointerRoutedEventArgs e)
{
e.Handled = true;
PointerPoint pointer = e.GetCurrentPoint(Touchpad);
if (pointer.PointerDevice.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch &&
pointer.Properties.IsPrimary == true)
{
lastPosition = pointer.Position;
Window.Current.CoreWindow.PointerCursor = null;
}
}
}
I noticed that while moving my finger around, there seems to be two cursors (or one that's bouncing back and forth really fast) - one in the app itself, and one in the system/desktop. I added Window.Current.CoreWindow.PointerCursor = null; which successfully hides the cursor inside the app, but the one that's being driven by the injection is blinking/flickering.
I feel like I'm missing something, as the feedback on the Virtual Touchpad in Win10 is buttery smooth and is also a UWP app, as far as I know...
Related
I have the following code:
<Window x:Class="Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Canvas Name="Canvas_Main" />
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(Object sender, RoutedEventArgs e)
{
Rectangle lastRectangle = null;
Random random = new Random(0);
for (Int32 counter = 0; counter < 5; counter++)
{
Rectangle rectangle = new Rectangle();
rectangle.Fill = Brushes.Blue;
rectangle.Width = random.Next(100, 200);
rectangle.Height = counter * 100;
Canvas_Main.Children.Add(rectangle);
if (lastRectangle == null) {
Canvas.SetLeft(rectangle, 0);
Canvas.SetTop(rectangle, 0);
}
else
{
Canvas.SetLeft(rectangle, lastRectangle.ActualWidth);
Canvas.SetTop(rectangle, 0);
}
lastRectangle = rectangle;
}
}
}
This isn't working as expected (laying each rectangle diagonally next to each other), as lastRectangle.ActualWidth is 0. As I understand things from this answer, it is because lastRectangle has not been measured and arranged.
I am curious, at what point would the measuring and arranging be done, if not when added to a container that is already visible and loaded?
The Framework.Loaded event of an element is raised after the measure and arrange layout pass, but before the rendering of the element.
The complete layout pass is initialized when UIElement.InvalidateMeasure for an asynchronous layout pass or UIElement.UpdateLayout for a synchronous layout pass was invoked on the element.
In your scenario the Window.Loadedevent handler is invoked, which means that the Window and the Canvas are both loaded (but not rendered).
Now you start to add new UIElement elements to the Canvas.
Canvas.Children.Add should invoke the InvalidateMeasure method. Because InvalidateMeasure triggers an asynchronous layout pass, the Canvas and therefore the current child element will be enqueued into the layout queue and the context continues execution (adding more rectangles).
Because there is already a pending layout pass due to the freshly added element, you should avoid calling UIElement.Measure manually (this are recursive calls and quite expensive when considering performance).
Once the context has completed, the elements in the queue, that are waiting for their layout pass and final rendering, will be handled and MeasureOverride and ArrangeOverride are invoked recursively on those elements (Canvas and its children).
As a result, UIElement.RenderSize can be calculated by the layout system.
At this moment, the new FrameworkElement.ActualWidth will be available.
This is the moment the FrameworkElement.Loaded event of the added elements (the rectangles) is finally raised.
To solve your problem you have to either use Rectangle.Width instead
or wait for each Rectangle.Laoded event before adding the next:
private int ShapeCount { get; set; }
private const int MaxShapes = 5;
private Point ShapeBPosition { get; set; }
private void MainWindow_Loaded(Object sender, RoutedEventArgs e)
{
this.ShapePosition = new Point();
AddRectangle(this.ShapePosition);
}
private void AddRectangle(Point position)
{
Random random = new Random();
Rectangle rectangle = new Rectangle();
rectangle.Fill = Brushes.Blue;
rectangle.Width = random.Next(100, 200);
rectangle.Height = ++this.ShapeCount * 100;
Canvas_Main.Children.Add(rectangle);
Canvas.SetLeft(rectangle, position.X);
Canvas.SetTop(rectangle, position.Y);
rectangle.Loaded += OnRectangleLoaded;
}
private void OnRectangleLoaded(object sender, RoutedEventArgs e)
{
var rectangle = sender as Rectangle;
rectangle.Loaded -= OnRectangleLoaded;
if (this.ShapeCount == MainWindow.MaxShapes)
{
return;
}
// this.ShapePosition is struct => modify copy
Point position = this.ShapePosition;
position.Offset(rectangle.ActualWidth, 0);
this.ShapePosition = position;
AddRectangle(this.ShapePosition);
}
The measuring and arranging has been completed for the window. But you are now creating new child controls, which will ordinarily not be measured/arranged until the window's next layout pass.
You can force this to happen immediately by calling the Measure method of each rectangle at the end of the loop.
I am struggling with making both touch events and manipulation work properly in a WPF project. I have a ScrollViewer which contains a picture and I would like to scroll both horizontally and vertically using a swipe gestures. Additionally, I would like to zoom in/out in the center of the pinch gesture. The code below achieves what I wish, but it has the following problems:
Sometimes the scrolling is laggy;
The scrolling does not work on the first try, only when attempting the same gesture a second time;
The zoom in/out does not work on the first try, only when attempting the same gesture a second time.
I enabled the IsManipulationEnabled and I implemented the code for zoom in/out functionality. However, I was not able to combine it with the scrolling functionality (by setting the PanningMode in the ScrollViewer only). Therefore, I created a custom control which inherits from Image control and I overwritten the OnTouchDown and OnTouchUp event handlers. Basically, what I am doing in these overwritten handlers is counting the number of touches on the screen and enabling/disabling manipulation. I also tried setting the PanningMode for the ScrollViewer, but it did not do the trick.
Below is the XAML:
<Grid>
<ScrollViewer
x:Name="ScrollViewerParent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both">
<local:CustomImage
x:Name="MainImage"
Source="{Binding Source={x:Static local:Constants.ImagePath}}"
IsManipulationEnabled="True"
ManipulationStarting="MainImage_ManipulationStarting"
ManipulationDelta="MainImage_ManipulationDelta">
</local:CustomImage>
</ScrollViewer>
</Grid>
Here is the code-behind:
public partial class MainWindow : Window
{
private void MainImage_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = ScrollViewerParent;
e.Handled = true;
}
private void MainImage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var matrix = MainImage.LayoutTransform.Value;
Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
if (centerOfPinch == null)
{
return;
}
var deltaManipulation = e.DeltaManipulation;
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
MainImage.LayoutTransform = new MatrixTransform(matrix);
Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
e.Handled = true;
}
}
The XAML for the custom control:
<Style TargetType="{x:Type local:CustomImage}" />
Here is where I override the OnTouchDown and OnTouchUp event handlers:
public class CustomImage : Image
{
private volatile int nrOfTouchPoints;
private volatile bool isManipulationReset;
private object mutex = new object();
static CustomImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomImage), new FrameworkPropertyMetadata(typeof(CustomImage)));
}
protected override void OnTouchDown(TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints++;
if (nrOfTouchPoints >= 2)
{
IsManipulationEnabled = true;
isManipulationReset = false;
}
}
base.OnTouchDown(e);
}
protected override void OnTouchUp(TouchEventArgs e)
{
lock (mutex)
{
if (!isManipulationReset)
{
IsManipulationEnabled = false;
isManipulationReset = true;
nrOfTouchPoints = 0;
}
}
base.OnTouchUp(e);
}
}
What I expect from this code is the following:
When using one finger to swipe horizontally or vertically across the touchscreen, the image should be scrolled accordingly;
When I use a pinch gesture on the touch screen, the image should be zoomed in/out in the center of the pinch.
Fortunately, I managed to find the perfect solution. Therefore, I am going to post the answer in the case that someone is working on a similar problem and needs some help.
What I did:
Got rid of the custom control as it was not necessary;
Create a field which counts the number of the touch points;
Implemented the TouchDown event handler, which increases the number of touch points by 1 (this method is called each time there is a touch down gesture on the device);
Implemented the TouchUp event handler, which decreases the number of touch points by 1 (this method is called each time there is a touch up gesture on the device);
In the Image_ManipulationDelta event handler, I check the number of touch points:
if the number of touch points < 2, then the translation value is added to the current offset of the scrollbars, thus achieving scrolling;
otherwise, the center of the pinch is calculated and a scale gesture is applied.
Here is the full XAML:
<Grid
x:Name="GridParent">
<ScrollViewer
x:Name="ScrollViewerParent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both">
<Image
x:Name="MainImage"
Source="{Binding Source={x:Static local:Constants.ImagePath}}"
IsManipulationEnabled="True"
TouchDown="MainImage_TouchDown"
TouchUp="MainImage_TouchUp"
ManipulationDelta="Image_ManipulationDelta"
ManipulationStarting="Image_ManipulationStarting"/>
</ScrollViewer>
</Grid>
Here is the entire code discussed above:
public partial class MainWindow : Window
{
private volatile int nrOfTouchPoints;
private object mutex = new object();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = ScrollViewerParent;
e.Handled = true;
}
private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
int nrOfPoints = 0;
lock (mutex)
{
nrOfPoints = nrOfTouchPoints;
}
if (nrOfPoints >= 2)
{
DataLogger.LogActionDescription($"Executed {nameof(Image_ManipulationDelta)}");
var matrix = MainImage.LayoutTransform.Value;
Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
if (centerOfPinch == null)
{
return;
}
var deltaManipulation = e.DeltaManipulation;
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
MainImage.LayoutTransform = new MatrixTransform(matrix);
Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
e.Handled = true;
}
else
{
ScrollViewerParent.ScrollToHorizontalOffset(ScrollViewerParent.HorizontalOffset - e.DeltaManipulation.Translation.X);
ScrollViewerParent.ScrollToVerticalOffset(ScrollViewerParent.VerticalOffset - e.DeltaManipulation.Translation.Y);
}
}
private void MainImage_TouchDown(object sender, TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints++;
}
}
private void MainImage_TouchUp(object sender, TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints--;
}
}
}
}
I have a canvas which has a zooming function. There are a lot of elements inside of it so, for selection I was going to use a selection box which I create dynamically when selection box is clicked. On clicking the button, I add the rectangle to the canvas and again, on clicking the button, I remove it.
I have the following xaml code:
<Viewbox x:Name="vbCanvas">
<Grid x:Name="theGrid"
MouseDown="Grid_MouseDown"
MouseUp="Grid_MouseUp"
MouseMove="Grid_MouseMove"
Background="Transparent">
<Canvas Name="canvasWaSNA" Margin="0,10,10,10" Height="720" Width="1280">
</Canvas>
</Grid>
</Viewbox>
mouse events of theGrid draws the rectangle on runtime on the canvas. The codes for those events are:
bool mouseDown = false;
Point mouseDownPos;
Point mouseUpPos;
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
mouseDown = true;
mouseDownPos = e.GetPosition(theGrid);
theGrid.CaptureMouse();
// Initial placement of the drag selection box.
Canvas.SetLeft(sBox, mouseDownPos.X);
Canvas.SetTop(sBox, mouseDownPos.Y);
sBox.Width = 0;
sBox.Height = 0;
// Make the drag selection box visible.
sBox.Visibility = Visibility.Visible;
}
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// Release the mouse capture and stop tracking it.
mouseDown = false;
mouseUpPos = e.GetPosition(theGrid);
theGrid.ReleaseMouseCapture();
// Show the drag selection box.
sBox.Visibility = Visibility.Visible;
MessageBox.Show(mouseDownPos.ToString() + " " + mouseUpPos.ToString());
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
// When the mouse is held down, reposition the drag selection box.
Point mousePos = e.GetPosition(theGrid);
if (mouseDownPos.X < mousePos.X)
{
Canvas.SetLeft(sBox, mouseDownPos.X);
sBox.Width = mousePos.X - mouseDownPos.X;
}
else
{
Canvas.SetLeft(sBox, mousePos.X);
sBox.Width = mouseDownPos.X - mousePos.X;
}
if (mouseDownPos.Y < mousePos.Y)
{
Canvas.SetTop(sBox, mouseDownPos.Y);
sBox.Height = mousePos.Y - mouseDownPos.Y;
}
else
{
Canvas.SetTop(sBox, mousePos.Y);
sBox.Height = mouseDownPos.Y - mousePos.Y;
}
}
}
To create a Rectangle at runtime, I have to click a button. The event of that button is as follows:
private void select_Click_1(object sender, RoutedEventArgs e)
{
if (!canvasWaSNA.Children.Contains(sBox))
{
sBox.Name = "selectionBox";
sBox.StrokeThickness = 1.5 / zoomfactor;
sBox.StrokeDashArray = new DoubleCollection { 1, 2 };
sBox.Visibility = System.Windows.Visibility.Collapsed;
sBox.Stroke = Brushes.Gray;
canvasWaSNA.Children.Add(sBox);
}
else
{
sBox.Visibility = System.Windows.Visibility.Collapsed;
canvasWaSNA.Children.Remove(sBox);
}
}
I am using the following code to zoom into the canvas:
double zoomfactor = 1.0;
void window_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(canvasWaSNA); //gets the location of the canvas at which the mouse is pointed
Matrix m = canvasWaSNA.RenderTransform.Value;
if (e.Delta > 0)
{ //the amount of wheel of mouse changed. e.Delta holds int value.. +ve for uproll and -ve for downroll
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
zoomfactor *= 1.1;
}
else
{
m.ScaleAtPrepend(1 / 1.1, 1 / 1.1, p.X, p.Y);
zoomfactor /= 1.1;
}
canvasWaSNA.RenderTransform = new MatrixTransform(m);
}
When my canvas is on original size, The rectangle is drawn perfectly but as I zoom in or zoom out, rectangle is drawn abnormally. It starts to draw from other points. What might be the problem? Please help
Well, I wasnot supposed to capture the mouse position with respect to theGrid, as I had to create the rectangle with respect to canvas. So, I have to get the position as e.GetPosition(canvasWaSNA) and the intended result was shown. It captured the mouse position on the canvas. Now, the rectangle is drawn perfectly even when zoomed in or zoomed out.
Also, I improved the StrokeThickness of the rectangle drawn by referencing it with the zoomfactor of the canvas.
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{mouseDown = true;
mouseDownPos = e.GetPosition(canvasWaSNA);
theGrid.CaptureMouse();
sBox.StrokeThickness = 1.5 / zoomfactor;
// Initial placement of the drag selection box.
Canvas.SetLeft(sBox, mouseDownPos.X);
Canvas.SetTop(sBox, mouseDownPos.Y);
sBox.Width = 0;
sBox.Height = 0;
// Make the drag selection box visible.
sBox.Visibility = Visibility.Visible;
}
I'm still learning WPF but what I'm trying to do is make it so when the image is clicked it follows the mouse around. So far I have the events firing properly, but I don't think I'm relocating the image properly.
<Image x:Name="LetterBlock" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" MouseDown="ImageOnMouseDown" MouseMove="ImageOnMouseMove">
<Image.Source>
<ImageSource>pack://application:,,,/Resources/A_LetterBlock.jpg</ImageSource>
</Image.Source>
<Image.Margin >
<Thickness>145,104,0,0</Thickness>
</Image.Margin>
</Image>
And this is my event handlers. I probably have some irrelevant code in there, but I can clean that up once I get it working.
public void ImageOnMouseDown(Object sender, MouseEventArgs e) {
IsClicked = true;
}
public void ImageOnMouseMove(Object sender, MouseEventArgs e)
{
if (!IsClicked)
return;
Point position = GetMousePositionWindowsForms();
Thickness nMargin = new Thickness(position.X, position.Y, 0, 0);
LetterBlock.Margin = nMargin;
}
private Point GetMousePositionWindowsForms()
{
System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
return new Point(point.X, point.Y);
}
The problem is that when I click on the image, it disappears...
//note I just took out InvalidateVisual and it still doesn't work, but the image jumps off the screen now.
//note2 I added some code to maximize the window and that helped. I learned that the image is running away from my mouse. It seems the mousemove event is only firing when the mouse is ontop of the image and immediately stops after its off of the image so it's kinda like it's running away from the mouse...
Try this. Use the Mouse.GetPosition(this) to get the position of the mouse pointer relative to the Window with the upper-left corner of the Window being the point of origin..
using System;
using System.Windows;
using System.Windows.Input;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void LetterBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
IsClicked = !IsClicked;
if (IsClicked)
{
LetterBlock.CaptureMouse();
}
else
{
LetterBlock.ReleaseMouseCapture();
}
}
private void LetterBlock_MouseMove(object sender, MouseEventArgs e)
{
if (!IsClicked)
return;
Point position = Mouse.GetPosition(this);
Thickness nMargin = new Thickness(position.X, position.Y, 0, 0);
LetterBlock.Margin = nMargin;
//InvalidateVisual();
}
public bool IsClicked { get; set; }
}
When I clicked on a control,
How to get cursor position relative to upper left corner of a (winforms) control ?
C#, VS 2005
PS:
I'm asking on context of tooltip "show" method which need that coordinates ..
This is my code to set tooltips on a composite control, might give you a clue (LED derivers from UserControl):
public LED()
{
InitializeComponent();
m_Image = global::AdvAdmittance.Controls.Properties.Resources.ledgray_small;
m_ToolTip = new ToolTip();
m_ToolTip.AutoPopDelay = 5000;
m_ToolTip.InitialDelay = 1000;
m_ToolTip.ReshowDelay = 500;
m_ToolTip.ShowAlways = true;
m_LedPictureBox.MouseHover += new EventHandler(m_LedPictureBox_MouseHover);
m_LedPictureBox.MouseLeave += new EventHandler(m_LedPictureBox_MouseLeave);
m_LedPictureBox.Click += new EventHandler(m_LedPictureBox_Click);
}
void m_LedPictureBox_MouseHover(object sender, EventArgs e)
{
if (m_ToolTipText != string.Empty)
{
Point toolTipPoint = this.Parent.PointToClient(Cursor.Position);
toolTipPoint.Y -= 20;
m_ToolTip.Show(m_ToolTipText, this.Parent, toolTipPoint);
}
}
void m_LedPictureBox_MouseLeave(object sender, EventArgs e)
{
m_ToolTip.Hide(this.m_LedPictureBox);
}
Ahh, Thanks for an answer.
All I need is a PointToClient method.
I hope (maybe) it will be useful for other people, here "my" code.
I took almost all code from http://support.microsoft.com/kb/322634 and modified three lines:
void treeView1_MouseMove(object sender, MouseEventArgs e)
{
// Get the node at the current mouse pointer location.
TreeNode theNode = this.treeView1.GetNodeAt(e.X, e.Y);
// Set a ToolTip only if the mouse pointer is actually paused on a node.
if ((theNode != null))
{
// Verify that the tag property is not "null".
if (theNode.Tag != null)
{
// Change the ToolTip only if the pointer moved to a new node.
if (theNode.Tag.ToString() != this.toolTip1.GetToolTip(this.treeView1))
{
//this.toolTip1.SetToolTip(this.treeView1, theNode.Tag.ToString());
Point c = System.Windows.Forms.Cursor.Position;
Point p = treeView1.PointToClient(c);
this.toolTip1.Show(theNode.Tag.ToString(), treeView1, p);
}
}
else
{
this.toolTip1.SetToolTip(this.treeView1, "");
}
}
else // Pointer is not over a node so clear the ToolTip.
{
this.toolTip1.SetToolTip(this.treeView1, "");
}
}
Have a look at
Windows Forms Coordinates
Control.PointToClient Method
C# Get a control’s position on a
form
Control PointToClient() vs
PointToScreen()