Prevent cursor flickering when moving image with mouse position, in WPF - wpf

I have an image and I am updating its position on the canvas as the mouse moves.
<Popup Name="floatingTip" AllowsTransparency="True" Placement="Relative"
PlacementTarget="{Binding ElementName=MainCanvas}">
<Image Source="{Binding Name}"
Width="{Binding Width}"
Height="{Binding Height}"/>
</Popup>
When the mouse enters the canvas I disable the cursor:
private void Canvas_MouseEnter(object sender, MouseEventArgs e)
{
Mouse.OverrideCursor = Cursors.None;
}
While the cursor is moving inside the canvas I update the position of the popup, which contains an image:
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (!floatingTip.IsOpen && currentTool == "staticBrush")
{
floatingTip.IsOpen = true;
}
if (currentTool == "staticBrush" && lvDataBinding.SelectedIndex != -1)
{
Point currentPos = e.GetPosition(this);
floatingTip.HorizontalOffset = currentPos.X - (cursorImage.Width / 2.0);
floatingTip.VerticalOffset = currentPos.Y - (cursorImage.Height / 2.0);
cursorImage.Width = 50;
cursorImage.Height = 50;
}
}
And when the mouse leaves the canvas I set the cursor back to Cursors.Arror.
Everything works fine if the image is not underneath the cursor, that is, if I offset the images position a bit. But if I place the image directly underneath the cursor, the image begins flickering with the arrow cursor (even though the cursor is hidden).

I figured it out. I needed to set IsHitTestVisible to false on the image.

Related

Clone element to display as an image/bitmap

I'm trying to create a snapshot/image/bitmap of an element that I can display as content in another control.
It seems the suggested way to do this is with a VisualBrush, but I can't seem to get it to create a snapshot of the current value and keep that state. When you alter the original source, the changes are applied to all the "copies" that have been made too.
I have made a simple example to show what I mean.
What I want is for the items added to the stackpanel to have the opacity that was set when they were cloned. But instead, changing the opacity on the source changes all "clones".
<StackPanel Width="200" x:Name="sp">
<DockPanel>
<Button Content="Clone"
Click="OnCloneButtonClick" />
<TextBlock Text="Value" x:Name="tb" Background="Red" />
</DockPanel>
</StackPanel>
private void OnCloneButtonClick(object sender, RoutedEventArgs e)
{
tb.Opacity -= 0.1;
var brush = new VisualBrush(tb).CloneCurrentValue();
sp.Children.Add(new Border() { Background = brush, Width = tb.ActualWidth, Height = tb.ActualHeight });
}
I am afraid the visual elements aren't cloned when you call CloneCurrentValue().
You will have to clone the element yourself, for example by serializing the element to XAML and then deserialize it back using the XamlWriter.Save and XamlReader.Parse methods respectively:
private void OnCloneButtonClick(object sender, RoutedEventArgs e)
{
tb.Opacity -= 0.1;
var brush = new VisualBrush(Clone(tb));
sp.Children.Add(new Border() { Background = brush, Width = tb.ActualWidth, Height = tb.ActualHeight });
}
private static Visual Clone(Visual visual)
{
string xaml = XamlWriter.Save(visual);
return (Visual)XamlReader.Parse(xaml);
}

Mouse click on WPF (Time)Slider has no effect when slider thumb is moving bound to a timer

I use MediaElement and (Time)Slider to play and control playing of a video.
I used answer 2 to this question as a base.
In addition to the dragging capability I would also like the slider thumb to be moved to a mouse click point.
This works ok when the MediaElement and (Time)Slider are paused, but when the video is playing a mouse click has no effect
Here is my code
XAML:
<MediaElement Source="..."
Name="mediaView"
Height="450" LoadedBehavior="Manual" UnloadedBehavior="Stop" Stretch="UniformToFill"
MediaOpened="OnMediaOpened" MediaEnded="OnMediaEnded" MediaFailed="OnMediaFailed" Grid.Row="0" Grid.Column="0"/>
<Grid Name="mediaBar" VerticalAlignment="Bottom" Margin="5,10,5,0" Background="#B2282828" Grid.Row="0" Grid.Column="0">
<!-- ... -->
<Slider Name="timeSlider" Margin="5,5,5,0"
Thumb.DragStarted="OnDragStarted" Thumb.DragCompleted="OnDragCompleted" ValueChanged="OnTimeSliderValueChanged"
PreviewMouseLeftButtonUp="OnMouseLeftButtonUp" IsMoveToPointEnabled="True"
MinWidth="200" FlowDirection="LeftToRight"
Grid.Column="4" Cursor="ScrollWE" VerticalAlignment="Center"/>
<!-- ... -->
</Grid>
relevant c# part:
private void OnDragStarted(object sender, DragStartedEventArgs args)
{
isDragging = true;
ticks.Stop();
}
private void OnDragCompleted(object sender, DragCompletedEventArgs args)
{
isDragging = false;
int SliderValue = (int)timeSlider.Value;
TimeSpan ts = new TimeSpan(0, 0, 0, 0, SliderValue);
mediaView.Position = ts;
if(currentStatus == Status.PLAYING)
ticks.Start();
}
private void OnMouseLeftButtonUp(object sender, EventArgs ea)
{
if(!isDragging)
{
mediaView.Pause();
ticks.Stop();
int SliderValue = (int)timeSlider.Value; // when video is playing this not the point of the mouse click
// ...
}
}
I can understand that timeSlider.Value delivers the current point in time instead of the mouse click position when the video is playing.
Is there another way to measure the position of the mouse click and update the slider value with that ?
Or a better solution for the Mouse-click-while-slider-is-running-situation ?
Try this:
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs ea)
{
mediaView.Position = TimeSpan.FromSeconds((int)(mediaView.NaturalDuration.TimeSpan.TotalSeconds * (double)(ea.GetPosition(timeSlider).X / this.Width)));
}

WPF canvas with relative position

I have this DataGrid and this Canvas:
<DataGrid Canvas.ZIndex="1" x:Name="dgTimeline"/>
<Canvas Height="30" Width="999" Canvas.ZIndex="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="71,387,0,0">
<Line Name="time" X1="0" Y1="0" X2="0" Y2="24" Stroke="Black" StrokeThickness="2"/>
</Canvas>
Which results in:
However, when I move the horizontal scroll bar of the DataGrid the Canvas obviously stays on its position because its parent is the Window and not the DataGrid:
Is it possible to keep Canvas' position relative to the DataGrid instead of its parent in such a way that when scrolling the DataGrid the Canvas would stay stationary as it was a DataGrid's element? I tried to put the Canvas inside of the DataGrid but that didn't work.
You can add horizontal scrollbar to canvas and then try to synchronize the horizontal scrolls of canvas and datagrid. something like ...
private void dataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
canvasScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
}
private void canvasScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer dgScrollViewer = GetScrollViewerInstance();
dgScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
}
private ScrollViewer GetScrollViewerInstance()
{
var ctrl = VisualTreeHelper.GetChild(dataGrid, 0);
if (ctrl is Border)
{
var ctrl1 = VisualTreeHelper.GetChild(ctrl, 0);
if (ctrl1 is ScrollViewer)
{
dgScrollViewer = ctrl1 as ScrollViewer;
}
}
}
This code is just to give you an idea of how to do it and not a actual working code. You set the HorizontalScrollBarVisibility for Canvas to Hidden if you don't want to show it. You will not need the second event handler in that case.

Drag/Drop Button in same application throwing an exception

I have a Canvas that contains a button which I want to be able to drag and drop into another canvas. I want to copy the button to the other Canvas. Here is the code I am using:
The XAML:
<Window>
<Grid>
<Canvas
Height="300"
Width="500"
Background="Gray">
<Canvas
Name="cnvToolBox"
Canvas.Left="10"
Canvas.Top="10"
Background="AliceBlue"
Width="100"
Height="200">
<Button
Content="Drag Me!"
PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
PreviewMouseMove="Button_PreviewMouseMove"></Button>
</Canvas>
<Rectangle
Canvas.Left="119"
Canvas.Top="9"
Width="102"
Height="202"
StrokeDashArray="0.5 1.0 0.3"
Stroke="Black"
StrokeThickness="2"/>
<Canvas
Name="cnvButtonDropZone"
Canvas.Left="120"
Canvas.Top="10"
Width="100"
Height="200"
Background="LightGreen"
AllowDrop="True"
DragEnter="Canvas_DragEnter"
Drop="Canvas_Drop">
</Canvas>
</Canvas>
</Grid>
</Window>
Here's the Code Behind:
public partial class MainWindow : Window
{
private Point startPoint;
public MainWindow()
{
InitializeComponent();
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(null);
}
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point currentPosition = e.GetPosition(null);
Vector diff = startPoint - currentPosition;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
Button button = sender as Button;
DataObject dragData = new DataObject("myFormat", button);
DragDrop.DoDragDrop(button, dragData, DragDropEffects.Copy);
}
}
private void Canvas_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("myFormat") || sender == e.Source)
{
e.Effects = DragDropEffects.None;
}
}
private void Canvas_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("myFormat"))
{
Button button = e.Data.GetData("myFormat") as Button;
Canvas canvas = sender as Canvas;
canvas.Children.Add(button);
}
}
}
When I drop the button I get the following exception when I'm adding the button to the canvas:
Specified element is already the logical child of another element. Disconnect it first.
I'm just trying to learn how to drag and drop controls and not really sure what that error means and how to resolve it. I don't know where I'm going wrong. Any suggestions would be welcome.
Thanks!
The button is owned by its parent cnvToolBox. You need to remove it from cnvToolBox before adding it to the canvas.
cnvToolBox.Children.Remove(button);
var canvas = sender as Canvas;
canvas.Children.Add(button);
This moves the button from your toolbox to the canvas. If you actually want to clone the item you want something like:
if (e.Data.GetDataPresent("myFormat"))
{
var contentControl = (ContentControl)e.Data.GetData("myFormat");
var constructorInfo = contentControl.GetType().GetConstructor(new Type[] {});
if (constructorInfo != null)
{
var newElement = (UIElement)constructorInfo.Invoke(new object[]{});
var newContentControl = newElement as ContentControl;
if(newContentControl != null)
{
newContentControl.Content = contentControl.Content;
}
((Panel)sender).Children.Add(newElement);
}
}
It's because the Button already has a parent associated with it; the previous Canvas.
You can set the parent of the Button to null; which will essentially remove it from the logical relationship.
button.Parent = null;
You will then be able to add that Button to another Canvas as you have done in your code behind.
You can also remove the Button from the Children property directly if you prefer and then add it accordingly within the new Canvas.
Canvas.Children.Remove(button);

WPF Not sending MouseMove events after CaptureMouse();

I'm trying to have a WPF canvas with rounded rectangles on that I can drag round using the mouse. However once I try and capture the mouse on the canvas I don't get the move events any more.
This is a "mycanvas" user control and the rectangles are "foo" user controls. The XAML for these (minus the preamble) are:
mycanvas.xaml:
<Canvas MouseDown="CanvasMouseDown" MouseMove="CanvasMouseMove" MouseUp="CanvasMouseUp" Background="White">
<my:Foo HorizontalAlignment="Left" Canvas.Left="97" Canvas.Top="30" x:Name="m_foo" VerticalAlignment="Top" Height="87" Width="128" />
</Canvas>
foo.xaml:
<Border BorderThickness="2" BorderBrush="Black" CornerRadius="15" Background="Plum">
<Grid>
<Label Content="Foo" Height="28" HorizontalAlignment="Left" Margin="6,6,0,0" Name="label1" VerticalAlignment="Top" />
</Grid>
</Border>
And then the handlers are:
mycanvas.xaml.cs:
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is Foo)
{
m_moving = e.Source as Foo;
CaptureMouse();
e.Handled = true;
}
}
private void CanvasMouseMove(object sender, MouseEventArgs e)
{
if (m_moving != null)
{
Canvas.SetLeft(m_moving, e.GetPosition(this).X);
Canvas.SetTop(m_moving, e.GetPosition(this).Y);
}
}
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
{
ReleaseMouseCapture();
m_moving = null;
}
The MouseDown fires and so the CaptureMouse gets called (and works because I can no longer close the app or click anything else in it!) but the MouseMove never gets called anymore - so where do the MouseMove events get sent now???
If I alt-tab to another application and then go back now suddendly the MouseMove is called and the Foo moves with the mouse.
Try either:
Mouse.Capture(this, CaptureMode.SubTree);
or
m_moving.CaptureMouse();
...
if (m_moving != null)
{
m_moving.ReleaseMouseCapture();
m_moving = null;
}
The mouse events were being raised by the Foo, not by the Canvas, so when you capture the mouse with the Canvas you prevent them from being raised.
You can directly use the MouseMove event on the Window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MouseMove += MouseEventHandler;
}
private void MouseEventHandler(Object sender, MouseEventArgs e)
{
System.Windows.Point position = e.GetPosition(this);
Canvas.SetLeft(ElipseElement, position.X-5);
Canvas.SetTop(ElipseElement, position.Y-5);
}
}

Resources