OnScrollChanged event for my ScrollViewer in MVVM WPF - wpf

I wish to use an attached property to handle the ScrollChanged event of my ScrollViewer. At the minute the event and logic are handled in the UI code behind, which obviously goes against MVVM somewhat. I was wondering if anyone has any ideas about how you do this with attached properties? The only resources I've found seem very long winded. Here is the code I have at the minute:
XAML:
<ScrollViewer x:Name="MyScroller" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible" ScrollChanged="MyScroller_OnScrollChanged">
UI Codebehind:
private void MyScroller_OnScrollChanged(object sender, ScrollChangedEventArgs e) {
var scrollViewer = sender as ScrollViewer;
if ((Math.Abs(e.ExtentHeightChange) < 0) && (Math.Abs(e.ExtentWidthChange) < 0)) return;
var xMousePositionOnScrollViewer = Mouse.GetPosition(scrollViewer).X;
var yMousePositionOnScrollViewer = Mouse.GetPosition(scrollViewer).Y;
var offsetX = e.HorizontalOffset + xMousePositionOnScrollViewer;
var offsetY = e.VerticalOffset + yMousePositionOnScrollViewer;
var oldExtentWidth = e.ExtentWidth - e.ExtentWidthChange;
var oldExtentHeight = e.ExtentHeight - e.ExtentHeightChange;
var relx = offsetX / oldExtentWidth;
var rely = offsetY / oldExtentHeight;
offsetX = Math.Max(relx * e.ExtentWidth - xMousePositionOnScrollViewer, 0);
offsetY = Math.Max(rely * e.ExtentHeight - yMousePositionOnScrollViewer, 0);
scrollViewer.ScrollToHorizontalOffset(offsetX);
scrollViewer.ScrollToVerticalOffset(offsetY);
}

Related

WPF Blur effect causing high CPU even when collapsed

We have to show a legal pop-up in our WPF app. When the pop-up is shown, we use blur effect on the view below.
Recently we recognized that this is causing high GPU usage. Because of the spinner control in the background. The more active content, the more GPU usage.
We collapse this spinner when the pop-up is shown based on a property. But this doesn't help. Only when we set it to collapsed in MainWindow.xaml it works.
We tried multiple things e.g. BitmapCache and other techniques but with no success so far.
Here an example:
https://github.com/rmoergeli/BlurEffectTest.git
I investigated that the problem in demo code hides in your animation: it doesn't stop on changing it visibility from visible to collapsed.
So I found a solution to stop animation on being collapsed with the help of MSDN resource.
public partial class Spinner : UserControl
{
private Canvas _content;
private Storyboard _rotationStoryboard;
public Spinner()
{
// Create a name scope for the page.
NameScope.SetNameScope(this, new NameScope());
DefineContent();
SizeChanged += Spinner_SizeChanged;
IsVisibleChanged += Spinner_IsVisibleChanged;
Loaded += Spinner_Loaded;
}
private void Spinner_Loaded(object sender, RoutedEventArgs e)
{
_rotationStoryboard.Begin(this, isControllable: true);
}
private void Spinner_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool isVisible && isVisible)
_rotationStoryboard.Resume(this);
else
_rotationStoryboard.Pause(this);
}
private void DefineContent()
{
_content = new Canvas();
//set content render transform origin point to center
_content.RenderTransformOrigin = new Point(0.5 , 0.5);
_content.RenderTransform = new RotateTransform(angle: 0);
// Assign the canvas a name by
// registering it with the page, so that
// it can be targeted by storyboard
// animations.
RegisterName("animatableCanvas", _content);
Content = _content;
DefineAnimatableContent();
// Create an animation and a storyboard to animate the
// canvas.
DoubleAnimation doubleAnimation = new DoubleAnimation
{
To = 360,
Duration = new Duration(TimeSpan.FromSeconds(3)),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetName(doubleAnimation, "animatableCanvas");
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("RenderTransform.Angle"));
_rotationStoryboard = new Storyboard();
_rotationStoryboard.Children.Add(doubleAnimation);
}
private void DefineAnimatableContent() //renamed Refresh method
{
int n = Balls;
double size = BallSize;
_content.Children.Clear();
double x = ActualWidth / 2;
double y = ActualHeight / 2;
double r = Math.Min(x, y) - size / 2;
double doubleN = Convert.ToDouble(n);
for (int i = 1; i <= n; i++)
{
double doubleI = Convert.ToDouble(i);
double x1 = x + Math.Cos(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
double y1 = y + Math.Sin(doubleI / doubleN * 2d * Math.PI) * r - size / 2;
var e = new Ellipse
{
Fill = BallBrush,
Opacity = doubleI / doubleN,
Height = size,
Width = size
};
Canvas.SetLeft(e, x1);
Canvas.SetTop(e, y1);
_content.Children.Add(e);
};
}
#region Event Handlers
private void Spinner_SizeChanged(object sender, SizeChangedEventArgs e)
{
//we dont need this anymore as we set content render transform origin point to content center
//Transform.CenterX = ActualWidth / 2;
//Transform.CenterY = ActualHeight / 2;
DefineAnimatableContent();
}
#endregion
//other logic
}
This code stops animation on changing Spiner control visibility. But sure you can pause and resume animation with any trigger you need.
N.B! Using this approach you define all the content (not only animarable parts) in code behind so you don't need Spinner.xaml resource anymore.

Draw fast graphics in WPF directly, instead of using CompositionTarget.Rendering

I have a control that throws events and I need to draw immediately after said events fire. The event fires around 15 times per second with a regular interval, and are handled as expected. I tried the following scenarios:
Scenario 1
In XAML, I created a canvas.
Whenever events from custom control fire, I update a counter. When the CompositionTarget event fires and the counter has changed, the canvas gets redrawn (based on the counter). However, this is too slow. The CompositionTarget event fires at a low speed and is irregular. See:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/how-to-render-on-a-per-frame-interval-using-compositiontarget?view=netframeworkdesktop-4.8
Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?
Scenario 2
I installed the SkiaSharp WPF Nuget packages. Tried basically the same thing as in Scenario 1, this time using the OnPaintSurface event. But I get more-or-less the same results, it seems that OnPaintService is called in a similar way as CompositionTarget.
Demo (notice the irregular updates, sometimes frames hanging or dropping):
Code from demo:
<Window x:Class="TMCVisualizer.AlphaWindow" 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:TMCVisualizer" xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" xmlns:skiasharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" mc:Ignorable="d"
Title="AlphaWindow" Width="1000" Height="480" WindowStyle="SingleBorderWindow" AllowsTransparency="False" ContentRendered="Handle_ContentRendered">
<Grid Background="Beige" x:Name="grid">
<skia:SKElement PaintSurface="OnPaintSurface" IgnorePixelScaling="True" Height="210" VerticalAlignment="Bottom" x:Name="board"></skia:SKElement>
</Grid>
</Window>
//Removed attaching event handlers and such for clarity
private float _index = -1;
private void OnCustomControlEvent(float counter, DateTime dateTime)
{
_index = counter;
this.Dispatcher.Invoke(() =>
{
board.InvalidateVisual();
//Will trigger redraw => OnPaintSurface
});
}
private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Transparent);
var paint = new SKPaint
{
Color = SKColors.Black,
IsAntialias = true,
Style = SKPaintStyle.Fill,
TextAlign = SKTextAlign.Center,
TextSize = 24
};
var coord = new SKPoint(e.Info.Width / 2, (e.Info.Height + paint.TextSize) / 2);
canvas.DrawText(_index.ToString(), coord, paint);
}
Also, I tried to directly draw on a writeable bitmap using SkiaSharp, again with the same results. The code I wrote looks similar to this:
The most efficient way to draw in SkiaSharp without using PaintSurface event
Is there any way to draw directly in WPF? Maybe I'm missing something, or maybe I don't fully understand SkiaSharp yet? Is there an alternative package I can use, such as SkiaSharp? I'd hate to say goodbye to WPF, because there are some other WPF components that I need to use in my app.
Edit : What I am trying to achieve:
My "component" that fires the events is a mod music player (SharpMik). I want to draw the notes that are being played in the player on screen (like a music tracker). When using the OnPaintSurface and a regular canvas, I couldn't get a better result than this (when the music is playing at the same time you see that the notes are not properly updated with the beat (slight delays)):
Code for the above result:
<Window x:Class="TMCVisualizer.AlphaWindow" 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:TMCVisualizer" mc:Ignorable="d"
Title="AlphaWindow" Width="1000" Height="480" WindowStyle="SingleBorderWindow" AllowsTransparency="False" ContentRendered="Handle_ContentRendered">
<Canvas x:Name="canvas" Height="210" VerticalAlignment="Bottom" />
</Window>
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using SharpMik;
using SharpMik.Drivers;
using SharpMik.Player;
using SharpMik.Player.Events.EventArgs;
namespace TMCVisualizer
{
public partial class AlphaWindow : Window
{
public Module ModModule;
public MikMod ModPlayer;
private Dictionary<int, List<TextBlock>> _dict = new Dictionary<int, List<TextBlock>>();
public List<Row> Rows;
private string _track = #"C:\Users\Wroah\Documents\MEKX-RMB.S3M";
private float _index = -1F;
public AlphaWindow()
{
InitializeComponent();
((App)Application.Current).WindowPlace.Register(this);
this.Loaded += Handle_AlphaWindow_Loaded;
}
private void Handle_AlphaWindow_Loaded(object sender, RoutedEventArgs e)
{
ModPlayer = new MikMod();
ModPlayer.Init<NaudioDriver>("");
}
private void SetupAndPlay()
{
CompositionTarget.Rendering -= Handle_Tick;
ModModule = ModPlayer.LoadModule(_track);
Rows = ModPlayer.Export(ModModule);
DrawGrid();
//Load again
ModModule = ModPlayer.LoadModule(_track);
this.KeyUp += Handle_AlphaWindow_KeyUp;
CompositionTarget.Rendering += Handle_Tick;
}
private void Handle_AlphaWindow_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
Task.Run(() =>
{
ModPlayer.Play(ModModule);
});
}
}
private void Handle_Tick(object sender, EventArgs e)
{
var index = ModPlayer.GetCurrentIndex();
if (index != _index)
{
_index = index;
UpdateGrid();
}
}
private void Handle_ContentRendered(object sender, EventArgs e)
{
SetupAndPlay();
}
private void UpdateGrid()
{
for (var i = 0; i < 13; i++)
{
var dictIndex = (int)_index;
dictIndex -= 7;
dictIndex += i;
var hasData = dictIndex >= 0;
var list = _dict[i];
var cols = ModModule.numchn;
Row row = null;
if (hasData)
{
row = Rows[dictIndex];
}
for (var j = 0; j <= cols; j++)
{
if (j == 0)
{
//Draw pattern position counter
if (hasData)
list[0].Text = row.Patpos.ToString("D2");
else
list[0].Text = "";
}
else
{
if (hasData)
list[j].Text = row.Cols[j - 1].note;
else
list[j].Text = ".";
}
}
}
}
private void DrawGrid()
{
var xPos = 0;
var yPos = 1;
var width = canvas.ActualWidth;
canvas.Children.Clear();
canvas.Background = Brushes.Black;
for (var i = 0; i < 13; i++)
{
var line = new Line();
line.X1 = xPos;
line.X2 = width;
line.Y1 = yPos;
line.Y2 = yPos;
line.StrokeThickness = (int)1;
line.SnapsToDevicePixels = true;
line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
line.Stroke = Brushes.Gray;
if (i == 6)
{
var brush = new SolidColorBrush(Colors.White);
brush.Opacity = .7;
var rect = new Rectangle();
rect.Width = width;
rect.Height = 15;
rect.Fill = brush;
rect.SnapsToDevicePixels = true;
Canvas.SetLeft(rect, 0);
Canvas.SetTop(rect, yPos);
canvas.Children.Add(rect);
}
canvas.Children.Add(line);
var list = new List<TextBlock>();
for (var j = 0; j < 64; j++)
{
var txt = new TextBlock();
txt.FontFamily = new FontFamily("Consolas");
txt.Width = 30;
txt.Foreground = Brushes.White;
if (i == 6)
txt.Foreground = Brushes.Black;
txt.TextAlignment = TextAlignment.Center;
txt.SnapsToDevicePixels = true;
txt.IsEnabled = false;
Canvas.SetTop(txt, yPos);
Canvas.SetLeft(txt, j * 30);
canvas.Children.Add(txt);
list.Add(txt);
}
_dict.Add(i, list);
yPos += 15;
}
}
}
}
What I learned, with trial and error and the feedback from user Clemens (many thanks!) is the following.
If you want to draw fast, don't use the CompositionTarget event. Draw directly instead. Needless to say, try to keep the amount of drawing needed to a minimum.
When the timing of drawing is critical (e.g. animation), I'd say, don't rely on events too much. My event invoking program wasn't too precise.
For now, WPF drawing speed is fast enough. If I run into serious problems, I might check out if MonoGame / SkiaSharp are tools that can improve performance.
What I did in order to fix my problem, was calculating the amount of elapsed ticks between each "frame" beforehand. Then, when the music starts playing, I start updating the screen in a loop. I draw a frame, and wait for the amount of ticks until the next frame. To keep everything in sync, I compare the amount of music elapsed ticks with the animation elapsed ticks and change the frame speed accordingly, if that makes any sense ;)
EDIT:
Though I got great speed improvements, it wasn't enough. I switched to MonoGame. I also learned that classic WinForms has slightly better performance than WPF.

Attach to Event of Sibling Control in Custom WPF Control Initialization

I am trying to create a WPF custom slider control that acts as a scrollbar for a Listview. I'm doing this by putting the name of the listview in the Tag attribute of my custom slider and then using the slider's OnValueChange event to scroll the listview. This works great, however, when I scroll in the listview with my mousewheel the slider doesn't move. What I need is a way to attach a method to the listview's MouseWheel event when my custom slider initializes. Here is what I've tried:
Custom slider class:
public class LabeledScrollbar : Slider
{
public override void EndInit()
{
var listbox = (ListBox)this.FindName(this.Tag.ToString());
if (listbox != null)
{
listbox.MouseWheel += new MouseWheelEventHandler(this.OnMouseWheel);
}
base.EndInit();
}
protected void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
this.Value += 5;
}
protected override void OnValueChanged(double oldValue, double newValue)
{
var listBox = (ListBox)this.FindName(this.Tag.ToString());
var collection = (CollectionView)CollectionViewSource.GetDefaultView(listBox.ItemsSource);
if (newValue == this.Maximum)
{
if (VisualTreeHelper.GetChildrenCount(listBox) > 0)
{
var chrome = VisualTreeHelper.GetChild(listBox, 0);
var scrollView = (ScrollViewer)VisualTreeHelper.GetChild(chrome, 0);
scrollView.ScrollToTop();
}
}
else
{
var index = (collection.Count - 1) - (int)Math.Floor(newValue);
var selectedItem = collection.GetItemAt(index);
listBox.ScrollIntoView(selectedItem);
}
}
}
XAML:
<ListView x:Name="listViewCategories">
...
</ListView>
<local:LabeledScrollbar x:Name="categoryScrollbar" Orientation="Vertical" TickPlacement="BottomRight" Tag="listViewCategories"></local:LabeledScrollbar>
While it seems like OnMouseWheel should fire when the I scroll in the listview, it's not happening and I haven't been able to find anything else to try. Is there a way to do what I want in WPF? I know I could put a method in the code behind of my view to make the MouseScroll event of the listview move the slider, but I was hoping to encapsulate as much of the logic for the slider in the slider class as possible.
So it seems that the trick was to use PreviewMouseWheel instead of MouseWheel. For future reference here is my current class:
/// <summary>
/// This class creates a custom control that can be used as a scrollbar for a listbox that displays the current
/// group visible in the listbox on a small label next to the slider thumb.
///
/// To use it, set the Tag value to the name of the listbox the scollbar will be controlling.
/// </summary>
public class LabeledScrollbar : Slider
{
//Tracks control initialization to ensure it only gets loaded once
private bool initialized = false;
//The listview this slider will control
private ListView listView;
public LabeledScrollbar(): base()
{
this.Loaded += LabeledScrollbar_Loaded;
}
void LabeledScrollbar_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
//The tag must be set to the name of a listbox
listView = (ListView)this.FindName(this.Tag.ToString());
if (listView != null && !this.initialized)
{
//Make sure that the mouse wheel event in the linked listbox is handled
listView.PreviewMouseWheel += (s, ev) =>
{
if (ev.Delta > 0)
this.Value += 3;
else
this.Value -= 3;
};
//Move scrollbar and list to the top if the collection changes
((INotifyCollectionChanged)listView.Items).CollectionChanged += (s, ev) =>
{
this.Maximum = ((ItemCollection)listView.Items).Count - 1;
this.Value = this.Maximum;
};
//Get the max value of the slider by checking the tag value and looking up the associated listbox
this.Maximum = ((ItemCollection)listView.Items).Count - 1;
this.Value = this.Maximum;
this.initialized = true;
}
}
protected override void OnValueChanged(double oldValue, double newValue)
{
//Refresh the tickbar so that it will render for a new value
InvalidateTickbar();
//Scroll the list box to the correct location
ScrollToIndex(newValue);
}
private void ScrollToIndex(double newValue)
{
if (newValue == this.Maximum)
{
//ScrollIntoView method does not scroll to the top so
//we need to access the scrollview to move the slider to the top
if (VisualTreeHelper.GetChildrenCount(listView) > 0)
{
var chrome = VisualTreeHelper.GetChild(listView, 0);
var scrollView = (ScrollViewer)VisualTreeHelper.GetChild(chrome, 0);
scrollView.ScrollToTop();
}
}
else
{
var collection = (CollectionView)CollectionViewSource.GetDefaultView(listView.ItemsSource);
var index = (collection.Count - 1) - (int)Math.Floor(newValue);
var selectedItem = collection.GetItemAt(index);
listView.ScrollIntoView(selectedItem);
}
}
private void InvalidateTickbar()
{
//update the tickbar for the new position
if (VisualTreeHelper.GetChildrenCount(this) > 0)
{
var firstChild = VisualTreeHelper.GetChild(this, 0);
if (VisualTreeHelper.GetChildrenCount(firstChild) > 0)
{
var secondChild = (CustomTickBar)VisualTreeHelper.GetChild(firstChild, 0);
secondChild.InvalidateVisual();
}
}
}
}

Strange act of canvas when Scale/ Zoom in Silverlight

I'm working on Zooming with Silverlight 5,
My Idea is to zoom according to mouse position on the Canvas, and the ability to Drag that Canvas,
The problem I have is when scale is less than 1, about 0.6 or 0.5, point to the corner of the canvas and wheel up, the canvas will change its position or "jump", any help please?
these two photos describe the status before Then after:
I have the following XAML:
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer x:Name="sv" Margin="0,0,0,76" ScrollViewer.VerticalScrollBarVisibility="Disabled" Background="#FFE3E7F1">
<Canvas x:Name="grd" Height="394" Width="630">
<Canvas x:Name="cvs" Background="White" MouseWheel="cvs_MouseWheel" MouseLeftButtonDown="cvs_MouseLeftButtonDown" MouseLeftButtonUp="cvs_MouseLeftButtonUp" MouseMove="cvs_MouseMove" Height="391" Canvas.Left="2" Canvas.Top="1" Width="625">
<Canvas.Effect>
<DropShadowEffect ShadowDepth="0"/>
</Canvas.Effect>
<Rectangle Height="70" Canvas.Left="155" Canvas.Top="58" Width="79" Fill="#FFFFBFBF"/>
<Rectangle Height="70" Canvas.Left="544" Canvas.Top="126" Width="79" Fill="#FF8B92FF"/>
</Canvas>
</Canvas>
</ScrollViewer>
</Grid>
and here's the C#:
public partial class MainPage : UserControl
{
CompositeTransform canvasTransform = new CompositeTransform();
bool canDragCanvas;
double mouseRelatedPositionX = 0;
double mouseRelatedPositionY = 0;
public MainPage()
{
// Required to initialize variables
InitializeComponent();
}
private void cvs_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var scaleFactor = 0.2*(e.Delta < 0?-1:1);
var centerX = e.GetPosition(cvs).X;
var centerY = e.GetPosition(cvs).Y;
if (centerX > cvs.ActualWidth * canvasTransform.ScaleX || centerX < 0 || centerY > cvs.ActualHeight * canvasTransform.ScaleY || centerY < 0)
{
centerX = cvs.ActualWidth/2;
centerY = cvs.ActualHeight/2;
}
canvasTransform.CenterX = centerX;
canvasTransform.CenterY = centerY;
canvasTransform.ScaleX += scaleFactor;
canvasTransform.ScaleY += scaleFactor;
cvs.RenderTransform = canvasTransform;
}
private void cvs_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
canDragCanvas = true;
mouseRelatedPositionX = e.GetPosition(cvs).X;
mouseRelatedPositionY = e.GetPosition(cvs).Y;
}
private void cvs_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
canDragCanvas = false;
}
private void cvs_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if(!canDragCanvas) return;
var leftValueToAdd = e.GetPosition(cvs).X - mouseRelatedPositionX;
var topValueToAdd = e.GetPosition(cvs).Y - mouseRelatedPositionY;
UpdateCanvasPosition(leftValueToAdd*canvasTransform.ScaleX, topValueToAdd*canvasTransform.ScaleX);
}
void UpdateCanvasPosition(double leftValueToAdd,double topValueToAdd)
{
var leftOffset = canvasTransform.CenterX - canvasTransform.CenterX * canvasTransform.ScaleX;
var rightOffset = (cvs.ActualWidth - canvasTransform.CenterX) - (cvs.ActualWidth - canvasTransform.CenterX) * canvasTransform.ScaleX;
var topOffset = canvasTransform.CenterY - canvasTransform.CenterY * canvasTransform.ScaleY;
var bottomOffset = (cvs.ActualHeight - canvasTransform.CenterY) - (cvs.ActualHeight - canvasTransform.CenterY) * canvasTransform.ScaleY;
var canvasLeftInBorders = Canvas.GetLeft(cvs)+ leftValueToAdd + leftOffset > 0;
var canvasRightInBorders = Canvas.GetLeft(cvs) + cvs.ActualWidth * canvasTransform.ScaleX + leftValueToAdd + leftOffset < grd.ActualWidth;
var canvasTopInBorders = Canvas.GetTop(cvs) + topValueToAdd + topOffset > 0;
var canvasBottomInBorders = Canvas.GetTop(cvs) + cvs.ActualHeight * canvasTransform.ScaleY + topValueToAdd + topOffset < grd.ActualHeight;
if (leftValueToAdd > 0)
{
if (canvasLeftInBorders)
leftValueToAdd = 0;
}
else if (leftValueToAdd < 0)
if (canvasRightInBorders)
leftValueToAdd = 0;
if (topValueToAdd > 0)
{
if (canvasTopInBorders)
topValueToAdd = 0;
}
else if (topValueToAdd < 0)
if (canvasBottomInBorders)
topValueToAdd = 0;
Canvas.SetLeft(cvs, Canvas.GetLeft(cvs) + leftValueToAdd);
Canvas.SetTop(cvs,Canvas.GetTop(cvs)+topValueToAdd);
}
}
Basically you are attaching mouse events to the surface that is zooming and the mouse coordinates get altered too. Silverlight is designed to still be interactive when you rotate, zoom and tilt.
You want to put a transparent layer over the top, that is not zoomed, and attached your mouse methods that that layer.
If you leave that layer turned on you will have to calculate collisions, but you can make it so that the layer only appears on mouse down and goes away on mouse up.

Make Object Follow Mouse On MouseDown and "Stick" On MouseUp

I'm working with a project that is WPF and VB.net. I want to visually simulate "dragging" an object (though I do not want to use standard drag and drop for reason of purpose).
Basically, I have a label object that, on its MouseDown event, I want it to follow the mouse cursor inside a 640x480 solid-size grid (but not outside of it!). Mind you, this grid is centered inside a full-screen window. Again, the object should not follow the mouse outside of the grid (I'm guessing a "ClipToBounds = True" here)
Then, on the label's MouseUp event, I want it to either stay in its current position or return to its original position, as determined by the value of a boolean variable set by another object's MouseEnter property.
Note, if it would be easier to work with, I can change the grid to a canvas in a cinch. I'm guessing that would be desirable.
So, after that long-winded explanation, here is my question (two-fold):
How do I make the object (label) follow the mouse cursor inside the grid/canvas, but not outside of it? This needs to happen on the MouseDown event of the label.
How do I make the object "stick" in its current position? (From this, I can probably figure out how to make it return to its original position on my own. :D )
My upvote to whoever can help me accomplish this goal the most efficiently! Thank you all very much.
How about something like this :
XAML :
<Canvas x:Name="canv" ToolTip="tt one" Width="400" Height="400" Background="Blue">
<Rectangle x:Name="rec" Fill="Red" Height="50" Width="50" MouseDown="Rectangle_MouseDown" MouseMove="Rectangle_MouseMove" MouseUp="Rectangle_MouseUp" />
</Canvas>
CODE-BEHIND :
private bool isDragging;
private void Rectangle_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
rec.CaptureMouse();
isDragging = true;
}
private void Rectangle_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (isDragging)
{
Point canvPosToWindow = canv.TransformToAncestor(this).Transform(new Point(0, 0));
Rectangle r = sender as Rectangle;
var upperlimit = canvPosToWindow.Y + (r.Height / 2);
var lowerlimit = canvPosToWindow.Y + canv.ActualHeight - (r.Height / 2);
var leftlimit = canvPosToWindow.X + (r.Width / 2);
var rightlimit = canvPosToWindow.X + canv.ActualWidth - (r.Width / 2);
var absmouseXpos = e.GetPosition(this).X;
var absmouseYpos = e.GetPosition(this).Y;
if ((absmouseXpos > leftlimit && absmouseXpos < rightlimit)
&& (absmouseYpos > upperlimit && absmouseYpos < lowerlimit))
{
Canvas.SetLeft(r, e.GetPosition(canv).X - (r.Width / 2));
Canvas.SetTop(r, e.GetPosition(canv).Y - (r.Height / 2));
}
}
}
private void Rectangle_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
rec.ReleaseMouseCapture();
isDragging = false;
}
This code could be enhanced, but I think you got the idea ;)
Based on #Bruno's, this is my solution:
double maxX;
double maxY;
private void OnRectMouseDown(object sender, MouseButtonEventArgs e)
{
maxX = canv.ActualWidth - rect.Width;
maxY = canv.ActualHeight - rect.Height;
rect.CaptureMouse();
rect.MouseMove += OnRectMouseMove;
rect.MouseUp += OnRectMouseUp;
}
private void OnRectMouseMove(object sender, MouseEventArgs e)
{
var pos = e.GetPosition(canv);
var newX = pos.X - (rect.Width / 2);
var newY = pos.Y - (rect.Height / 2);
if (newX < 0) newX = 0;
if (newX > maxX) newX = maxX;
if (newY < 0) newY = 0;
if (newY > maxY) newY = maxY;
rect.SetValue(Canvas.LeftProperty, newX);
rect.SetValue(Canvas.TopProperty, newY);
xVal.Content = (newX / maxX).ToString("F3");
yVal.Content = (newY / maxY).ToString("F3");
}
private void OnRectMouseUp(object sender, MouseButtonEventArgs e)
{
rect.ReleaseMouseCapture();
rect.MouseMove -= OnRectMouseMove;
rect.MouseUp -= OnRectMouseUp;
}
try this:
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
(name).Location = new Point(e.X,e.Y);
}
so that will make it so if you click the object will appear there

Resources