A tooltip or something similar move with cursor in WPF - c#

Is it possible to move a Tooltip or something like that with cursor when mouse goes on a specific control?
I tried TextBlock, but Margin property not work.
private TextBlock tooltip = new TextBlock();
private void imgRoom_MouseEnter(object sender, MouseEventArgs e)
{
Point position = e.GetPosition((IInputElement)sender);
tooltip.Visibility = System.Windows.Visibility.Visible;
tooltip.Margin = new Thickness(position.X, position.Y, 0, 0);
tooltip.Width = 100;
tooltip.Height = 100;
tooltip.Background = new SolidColorBrush(Colors.Red);
}
private void imgRoom_MouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition((IInputElement)sender);
tooltip.Margin = new Thickness(position.X, position.Y, 0, 0);
}

You can achieve the effect using a Popup and some simple properties upon it. From window code...
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Rectangle Name="rect" Margin="50,50,0,0" Width="100" Height="100" Fill="LightBlue" MouseMove="Rectangle_MouseMove" MouseLeave="Rectangle_MouseLeave" />
<Popup Name="floatingTip" AllowsTransparency="True" Placement="Relative" PlacementTarget="{Binding ElementName=rect}">
<TextBlock>Look At Me</TextBlock>
</Popup>
</Grid>
</Window>
And this is what the codebehind would look like.
...
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
if (!floatingTip.IsOpen) { floatingTip.IsOpen = true; }
Point currentPos = e.GetPosition(rect);
// The + 20 part is so your mouse pointer doesn't overlap.
floatingTip.HorizontalOffset = currentPos.X + 20;
floatingTip.VerticalOffset = currentPos.Y;
}
private void Rectangle_MouseLeave(object sender, MouseEventArgs e)
{
floatingTip.IsOpen = false;
}
...
So from the XAML you can see that the popup placement is relative to the rectangle. When you go mousing over the rectangle, it becomes visible, and its position is updated as the mouse moves. Naturally this is a very basic solution, but with some minor tweaks, handling events like 'MouseEnter' and property adjustment you can come up with some really neat effects.

I don't know if this is a best practice, or if it performs well, but you could use an Adorner.
I've created a proof of concept before, but haven't used it in a production scenario (yet). The adorner can be used to create something like this (tooltip), or a custom mouse cursor or drop target icons.
Make sure you set IsHitTestVisible = false if the adorner doesn't need to be hit tested and might appear directly under the mouse location.
UPDATE
Just fully read the description of adorners:
Common applications for adorners include:
Adding functional handles to a UIElement that enable a user to manipulate the element in some way (resize, rotate, reposition, etc.).
Provide visual feedback to indicate various states, or in response to various events.
Overlay visual decorations on a UIElement.
Visually mask or override part or all of a UIElement.

This moves the tooltip around with the mouse cursor.
private void OnMouseMoveHandler(object sender, MouseEventArgs args)
{
if ((sender as FrameworkElement).ToolTip == null)
(sender as FrameworkElement).ToolTip = new ToolTip() { Placement = PlacementMode.Relative };
double x = args.GetPosition((sender as FrameworkElement)).X;
double y = args.GetPosition((sender as FrameworkElement)).Y;
var tip = ((sender as FrameworkElement).ToolTip as ToolTip);
tip.Content = tooltip_text;
tip.HorizontalOffset = x + 10;
tip.VerticalOffset = y + 10;
}

Related

When changing call order of UIElement.CaptureMouse() on Canvas containing two Image, I get different result

I'm now just trying to create a simple image viewer with panning and zooming, for studying WPF.
I wrote my XAML code like this:
<Window x:Class="PanningImageTest.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"
xmlns:local="clr-namespace:PanningImageTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border Name="panBorder" ClipToBounds="True">
<Canvas Name="panImage" MouseLeftButtonDown="Image_MouseLeftButtonDown"
MouseMove="Image_MouseMove"
MouseLeftButtonUp="Image_MouseLeftButtonUp">
<!-- Two Image is intended, as in real situation,
they will use different sources -->
<Image Source="Wallpaper.jpg" Stretch="None"/>
<Image Source="Wallpaper.jpg" Stretch="None"/>
</Canvas>
</Border>
</Grid>
</Window>
And wrote my code like this:
using System.Windows;
using System.Windows.Media;
namespace PanningImageTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Point _start;
private Point _origin;
private void Image_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.panImage.CaptureMouse();
_start = e.GetPosition(this.panBorder);
_origin.X = this.panImage.RenderTransform.Value.OffsetX;
_origin.Y = this.panImage.RenderTransform.Value.OffsetY;
}
private void Image_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!this.panImage.IsMouseCaptured)
{
return;
}
Point mousePos = e.GetPosition(this.panBorder);
Matrix matrix = this.panImage.RenderTransform.Value;
matrix.OffsetX = _origin.X + (mousePos.X - _start.X);
matrix.OffsetY = _origin.Y + (mousePos.Y - _start.Y);
this.panImage.RenderTransform = new MatrixTransform(matrix);
}
private void Image_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.panImage.ReleaseMouseCapture();
}
}
}
If I call this.panImage.CaptureMouse() BEFORE the _start = ... etc ... lines, All the Images just teleports to the position of the mouse as soon as I clicked somewhere on the image.
Like this:
Call before
But if I call the same function after the lines, like the following, it just works as intended:
_start = e.GetPosition(this.panBorder);
_origin.X = this.panImage.RenderTransform.Value.OffsetX;
_origin.Y = this.panImage.RenderTransform.Value.OffsetY;
this.panImage.CaptureMouse();
Like this:
Call After
If I use only one Image tag in Canvas, it works nicely in both cases.
I tried changing .NET versions, moving events to Border instead of Canvas, but everything just failed to explain these results.
I have no idea why this happens. Can anyone give some explanations?
MouseMove event has started to be fired even the MouseLeftButtonDown method has not yet finished.
So you call CaptureMouse() method as the last line, and use the following check as a guard to prevent the image from being dragged when you have not yet gotten the value of _origin.
if (!this.panImage.IsMouseCaptured)
{
return;
}
You need to set the start position and the coordinates before you start to calculate the offsets. Otherwise you get the offsets wrong. So it makes no sense to capture the mouse before you have done this.
Note that the MouseMove event will be raised as soon as you move the mouse(all the time basically) but until you have captured it, the event handler will just return without moving the image by setting its RenderTransform property.
So you should set the start position and the X and Y coordinates and then move the image based on these. Not the other way around.

How to change the location of wpf grid at run time?

I have created a WPF Grid. That grid contains Textbox and Button controls.Here i want to relocate the grid at run time .How its possible please answer this if you know.Like a movable window.
I don't know if you want to relocate after mouse events or in code after some different event? If second you can change grid margins in event/method where you want to do it and call UpdateLayout() method after it. If first here is my answer for similar question:
Check time after a mousebuttondown before the mousebuttonup
I found this solution on stackoveflow
<Grid x:Name="grid" Background="Blue"
Width="100" Height="100"
MouseDown="Grid_MouseDown" MouseMove="Grid_MouseMove" MouseUp="Grid_MouseUp">
<Grid.RenderTransform>
<TranslateTransform x:Name="tt"/>
</Grid.RenderTransform>
Place it in a window
<Window x:Name="window" ...>
<Grid x:Name="grid"...
And code:
Point m_start;
Vector m_startOffset;
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
m_start = e.GetPosition(window);
m_startOffset = new Vector(tt.X, tt.Y);
grid.CaptureMouse();
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (grid.IsMouseCaptured)
{
Vector offset = Point.Subtract(e.GetPosition(window), m_start);
tt.X = m_startOffset.X + offset.X;
tt.Y = m_startOffset.Y + offset.Y;
}
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
grid.ReleaseMouseCapture();
}

MouseMove event is called even when cursor is outside of canvas. `Border`around canvas did not work

short discription of the to-be situation:
As soon as the mouse moves over my canvas I want an ellipse to be displayed on the canvas. The ellipse is supposed to stick to the mouse movement until the user hits the left mouse button. At the time the user hits the left mouse button, the program is expected to place the ellipse on the canvas.
Therefore I used the following xaml code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="700" Width="1200">
<Grid>
<Border ClipToBounds="true">
<Canvas x:Name="canvasss" Background="AntiqueWhite" Width="524" Height="368" MouseMove="Canvasss_MouseMove" MouseDown="Canvasss_MouseDown">
</Canvas>
</Border>
</Grid>
And the c# code:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication1
{
public class Item
{
private readonly Ellipse shape;
public Item(Canvas canvas)
{
shape = new Ellipse { Width = 50, Height = 50, Fill = Brushes.Black };
canvas.Children.Add(shape);
SetPosition(0.0, 0.0);
}
public void SetPosition(double x, double y)
{
Canvas.SetLeft(shape, x);
Canvas.SetTop(shape, y);
}
}
public partial class MainWindow : Window
{
private readonly IList<Item> shapes;
private Item currentMovingShape;
public MainWindow()
{
InitializeComponent();
shapes = new List<Item>();
InitMovingShape();
}
private void InitMovingShape()
{
currentMovingShape = new Item(canvasss);
}
private void SetMovingShapePosition(MouseEventArgs e)
{
var pos = e.GetPosition(canvasss);
currentMovingShape.SetPosition(pos.X - 25, pos.Y - 25);
}
private void Canvasss_MouseMove(object sender, MouseEventArgs e)
{
var pos = e.GetPosition(canvasss);
currentMovingShape.SetPosition(pos.X - 25, pos.Y - 25);
}
private void Canvasss_MouseDown(object sender, MouseButtonEventArgs e)
{
shapes.Add(currentMovingShape);
InitMovingShape();
}
}
}
The problem is, that as soon as the mouse leaves the canvas, the ellipse still sticks to the mouse and it is even possible to place the ellipse outside the canvas.
First I used the code (code is from stijn: C# - WPF - Mousemove event on canvas will overload the mouse events so click event is not fired) without the Borderaround the canvas in the xaml code. Then I read it might help to place a Borderarround the canvas (MouseMove event is called even when cursor is outside of canvas) and implemented it to the xaml code as you can see. Unfortunetely it did not help.
Can someone help me with a solution? A code solution wold be much appreciated, since I am a total beginner in c#.
Looks like you want to implement Adorners in your control. Do read about them as they will come handy to perform operations like these easily in wpf.
However, to fix your problem. You should be implementing MouseEnter and MouseLeave events to your code. Add the shape on MouseEnter and remove the shape on MouseLeave or MouseDown. So when the Mouse ReEnters the canvas the MouseEnter logic will kick in and will add the shape and your MouseMove logic will move the shape with the Mouse. Hope I am being clear here. Let me know if you are still struggling with the code.
EDIT
xaml
<Grid Background="Transparent">
<Canvas x:Name="canvasss" Background="AntiqueWhite" Width="300" Height="300" MouseEnter="canvasss_MouseEnter" MouseLeave="canvasss_MouseLeave" MouseMove="Canvasss_MouseMove" MouseDown="Canvasss_MouseDown" Margin="50" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
Code
public partial class MainWindow : Window
{
private readonly IList<Ellipse> shapes;
private Ellipse currentMovingShape;
public MainWindow()
{
InitializeComponent();
shapes = new List<Ellipse>();
}
private void canvasss_MouseEnter(object sender, MouseEventArgs e)
{
AddEllipse();
}
private void AddEllipse()
{
currentMovingShape = new Ellipse { Width = 50, Height = 50, Fill = Brushes.Black };
currentMovingShape.IsHitTestVisible = false;
canvasss.Children.Add(currentMovingShape);
Canvas.SetLeft(currentMovingShape, Mouse.GetPosition(canvasss).X - 25);
Canvas.SetTop(currentMovingShape, Mouse.GetPosition(canvasss).Y - 25);
}
private void canvasss_MouseLeave(object sender, MouseEventArgs e)
{
if (currentMovingShape != null)
{
canvasss.Children.Remove(currentMovingShape);
currentMovingShape = null;
}
}
private void Canvasss_MouseMove(object sender, MouseEventArgs e)
{
Canvas.SetLeft(currentMovingShape, e.GetPosition(canvasss).X - 25);
Canvas.SetTop(currentMovingShape, e.GetPosition(canvasss).Y - 25);
}
private void Canvasss_MouseDown(object sender, MouseButtonEventArgs e)
{
if (currentMovingShape != null)
{
currentMovingShape.IsHitTestVisible = true;
shapes.Add(currentMovingShape);
AddEllipse();
}
}
}

Handling horizontal swipe on control in WinRT

So let's say I have stackpanel on the left edge with width 200px. Now I want to handle horizontal swipe from left to right on this panel and show additional panel. Then handle swipe from right to left to hide it.
I tried handling page's ManipulationStarted and ManipulationDelta events, but it doesn't seem to have any effect at least with mouse. Any ideas, what could be an easy way to implement that?
What I've tried:
Handle page's swipe events and on the beginning of swipe check, if it was started in bounds of stackpanel, else I ignore it.
If swipe's delta was more then positive 40, looks like swipe was from left to right.
My XAML file:
// standard stuff of page
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical" Width="200" HorizontalAlignment="Left" Background="White" x:Name="Panel"/>
</Grid>
// continue standard stuff
C# file:
public MainPage()
{
this.InitializeComponent();
this.ManipulationDelta += MainPage_ManipulationDelta;
this.ManipulationStarted += MainPage_ManipulationStarted;
}
private void MainPage_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("heh");
if (e.Position.X < 200)
{
initialPoint = e.Position;
isSwiping = true;
}
}
private void MainPage_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if (e.IsInertial && isSwiping)
{
Point currentPoint = e.Position;
if (currentPoint.X - initialPoint.X >= 40)
{
isSwiping = false;
e.Complete();
System.Diagnostics.Debug.WriteLine("finished swipe :)");
}
}
}
private Point initialPoint;
private Boolean isSwiping;
(Again omitted default empty page)
You need to set ManipulationMode on a control e.g. ManipulationMode="TranslateX" and have the control respond to hit-testing (i.e. if it does not have a background - set the Background to Transparent) to receive manipulation events.
Then again - why not just use a ListView that has built-in support for swipes?

Click and drag selection box in WPF

Is it possible to implement mouse click and drag selection box in WPF. Should it be done through simply drawing a rectangle, calculating coordinates of its points and evaluating position of other objects inside this box? Or are there some other ways?
Could you give a bit of sample code or a link?
Here is sample code for a simple technique that I have used in the past to draw a drag selection box.
XAML:
<Window x:Class="DragSelectionBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Grid
x:Name="theGrid"
MouseDown="Grid_MouseDown"
MouseUp="Grid_MouseUp"
MouseMove="Grid_MouseMove"
Background="Transparent"
>
<Canvas>
<!-- This canvas contains elements that are to be selected -->
</Canvas>
<Canvas>
<!-- This canvas is overlaid over the previous canvas and is used to
place the rectangle that implements the drag selection box. -->
<Rectangle
x:Name="selectionBox"
Visibility="Collapsed"
Stroke="Black"
StrokeThickness="1"
/>
</Canvas>
</Grid>
</Window>
C#:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
bool mouseDown = false; // Set to 'true' when mouse is held down.
Point mouseDownPos; // The point where the mouse button was clicked down.
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
// Capture and track the mouse.
mouseDown = true;
mouseDownPos = e.GetPosition(theGrid);
theGrid.CaptureMouse();
// Initial placement of the drag selection box.
Canvas.SetLeft(selectionBox, mouseDownPos.X);
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Width = 0;
selectionBox.Height = 0;
// Make the drag selection box visible.
selectionBox.Visibility = Visibility.Visible;
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// Release the mouse capture and stop tracking it.
mouseDown = false;
theGrid.ReleaseMouseCapture();
// Hide the drag selection box.
selectionBox.Visibility = Visibility.Collapsed;
Point mouseUpPos = e.GetPosition(theGrid);
// TODO:
//
// The mouse has been released, check to see if any of the items
// in the other canvas are contained within mouseDownPos and
// mouseUpPos, for any that are, select them!
//
}
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(selectionBox, mouseDownPos.X);
selectionBox.Width = mousePos.X - mouseDownPos.X;
}
else
{
Canvas.SetLeft(selectionBox, mousePos.X);
selectionBox.Width = mouseDownPos.X - mousePos.X;
}
if (mouseDownPos.Y < mousePos.Y)
{
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Height = mousePos.Y - mouseDownPos.Y;
}
else
{
Canvas.SetTop(selectionBox, mousePos.Y);
selectionBox.Height = mouseDownPos.Y - mousePos.Y;
}
}
}
}
I wrote an article about this:
https://www.codeproject.com/Articles/148503/Simple-Drag-Selection-in-WPF
You can get this functionality pretty easily by adding an InkCanvas and set its EditingMode to Select. Although it's primarily intended for Tablet PC ink collection and rendering, it's very easy to use it as a basic designer surface.
<Window Width="640" Height="480" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<InkCanvas EditingMode="Select">
<Button Content="Button" Width="75" Height="25"/>
<Button Content="Button" Width="75" Height="25"/>
</InkCanvas>
</Window>
This project created a custom MultiSelector which supports several selection methods including a rectangular "lasso" style:
Developing a MultiSelector by Teofil Cobzaru
It is far too long to reproduce here. The key elements of the design, IIRC, were to create a custom ItemContainer which knows how to interact with its MultiSelector parent. This is analagous to ListBoxItem / ListBox.
This is probably not the simplest possible approach, however if you are already using some type of ItemsControl to host the items which may need to be selected, it could fit into that design pretty easily.
MouseDown logic:
MouseRect.X = mousePos.X >= MouseStart.X ? MouseStart.X : mousePos.X;
MouseRect.Y = mousePos.Y >= MouseStart.Y ? MouseStart.Y : mousePos.Y;
MouseRect.Width = Math.Abs(mousePos.X - MouseStart.X);
MouseRect.Height = Math.Abs(mousePos.Y - MouseStart.Y);

Categories