Animate WPF Drag Adorner movement - wpf

in our application we use a adorner for some fake drag & drop. The adorner doesn't follow to mouse but is set to some specific coordinates on the screen when the mouse moves.
double xPosLocation = (int)(dividerDistanceXAxis * virtualPosition) + YAxisData.SpacingLeft;
double yPosLocation = CalculateValueToYPosition(VirtualPriceOfVehicle);
DragAdorner.UpdatePosition(xPosLocation - offsetX, yPosLocation + offsetY);
This works fine so far. But with a code like this the adorner jumps from one location to another. This is how it was supposed to work but it would be nice to have some smooth movement transitions when the adorner it set to a new position.
Can it be done? Since I see no position property I guess it is not possible to do a double-animation or something like that.

You have to use a DependencyProperty for animation
public class DragAdorner
{
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
// Using a DependencyProperty as the backing store for PositionProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(DragAdorner), new UIPropertyMetadata(PositionChanged));
private static void PositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UpdatePosition(Position);
}
}
Then you could do something like this (not tested)
double xPosLocation = (int)(dividerDistanceXAxis * virtualPosition) + YAxisData.SpacingLeft;
double yPosLocation = CalculateValueToYPosition(VirtualPriceOfVehicle);
Point newLocation = new Point(xPosLocation, yPosLocation);
PointAnimation myPointAnimation = new PointAnimation();
myPointAnimation.From = DragAdorner.Position;
myPointAnimation.To = newLocation;
myPointAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myPointAnimation);
Storyboard.SetTargetName(myPointAnimation, DragAdorner.Position);
myStoryboard.Begin();

Related

Building custom TextBlock control in WPF

I have built custom WPF Control which unique function is displaying text. I tried using TextBlock from System.Windows.Controls namespace but it's not working for me (I have ~10000 strings with different position and too much memory loss). So I tried making my own control by inheriting FrameworkElement, overriding OnRender method which now contain single line:
drawingContext.DrawText(...);
But...
I get a little confusing result.
After comparing performance for 10000 objects, I realized that the time needed for creating and adding to Canvas is still ~10 sec, and memory usage for my application raises from ~32MB to ~60MB !!!
So no benefits at all.
Can anyone explain why this happens, and what is the other way to create simple (simple = allocate less memory, take less time to create) visual with two functions:
display text
set position (using thickness or TranslateTransform)
Thanks.
Check out AvalonEdit
Also not sure how you are storing the strings, but have you used StringBuilder before?
Here is my code (a little bit modified):
public class SimpleTextBlock : FrameworkElement
{
#region Static
private const double _fontSize = 12;
private static Point _emptyPoint;
private static Typeface _typeface;
private static LinearGradientBrush _textBrush;
public readonly static DependencyProperty TextWidthProperty;
static SimpleTextBlock()
{
_emptyPoint = new Point();
_typeface = new Typeface(new FontFamily("Sergoe UI"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
GradientStopCollection GSC = new GradientStopCollection(2);
GSC.Add(new GradientStop(Color.FromArgb(160, 255, 255, 255), 0.0));
GSC.Add(new GradientStop(Color.FromArgb(160, 180, 200, 255), 0.7));
_textBrush = new LinearGradientBrush(GSC, 90);
_textBrush.Freeze();
SimpleTextBlock.TextWidthProperty = DependencyProperty.Register(
"TextWidth",
typeof(double),
typeof(SimpleTextBlock),
new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.AffectsRender));
}
#endregion
FormattedText _formattedText;
public SimpleTextBlock(string text)
{
_formattedText = new FormattedText(text, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, _typeface, _fontSize, _textBrush);
}
public SimpleTextBlock(string text, FlowDirection FlowDirection)
{
_formattedText = new FormattedText(text, System.Globalization.CultureInfo.InvariantCulture, FlowDirection, _typeface, _fontSize, _textBrush);
}
protected override void OnRender(DrawingContext drawingContext)
{
_formattedText.MaxTextWidth = (double)GetValue(TextWidthProperty);
drawingContext.DrawText(_formattedText, _emptyPoint);
}
public double TextWidth
{
get { return (double)base.GetValue(TextWidthProperty); }
set { base.SetValue(TextWidthProperty, value); }
}
public double ActualTextWidth
{
get { return _formattedText.Width; }
}
public double ActualTextHeight
{
get { return _formattedText.Height; }
}
}
Since it sounds like we determined you should stylize a control like listbox, here are some examples of different things you can do:
Use Images as Items
Stylized and Binding
Honestly it all depends on what you want it to look like. WPF is great in how much control it gives you on how something looks.
Crazy example using a listbox to make the planet's orbits

How do I throttle a slider's value change event?

I got a slider that on value change forces a fairly serious computation, so I want to throttle it to fire actual event after for example 50ms pass when user has finished sliding it.
While I learned some various stuff about Rx its unclear how should I approach this using MVVM pattern.
In my current MVVM approach I got slider value bound to my viewModel. I would prefer to add Rx throttle with minimal possible impact on existing code (as a beginning at least).
Ive seen some other threads about MVVM and Rx and I don't think they lead me to some exact direction with my problem. I see various possible approaches and would like not to invent a bycicle.
In this case, you should bind to the PropertyChanged event of your ViewModel, something like:
Observable.FromEvent<PropertyChangedEventArgs>(x => this.PropertyChanged +=x, x => this.PropertyChanged -= x)
.Where(x => x.PropertyName == "SliderName")
.Select(_ => this.SliderName)
.Throttle(TimeSpan.FromMilliseconds(50));
Or, if you were using ReactiveUI, it'd look like this:
this.WhenAnyValue(x => x.SliderName)
.Throttle(TimeSpan.FromMilliseconds(50), RxApp.DeferredScheduler);
Lets just outline the problem. You have a View Model which has some double typed Property. When a value is assigned to this property a fairly expensive calculation takes place. Wouldn't normally be a problem but when the UI binds the value of a Slider to this property the rapid changes generated does create a problem.
First decision to be made is between the view and view-model which is responsible for dealing with this problem. It could be argued both ways the View-Model has "chosen" to make a property assignment an expensice operatione on the other hand the View has "chosen" to assign the property using a Slider.
My choice would be on view side of things because thats a better place to implement this. However rather than fiddle with the View directly I would build a new Control to add the feature. Let's call it the DelaySlider. It will derive from Silder and have two additional dependency properties Delay and DelayedValue. The DelayedValue will match the existing value of Value property but only after Delay milliseconds have elapsed since the last Value changed.
Here is the full code for the control:-
public class DelaySlider : Slider
{
private DispatcherTimer myTimer;
private bool myChanging = false;
#region public double DelayedValue
public double DelayedValue
{
get { return (double)GetValue(DelayedValueProperty); }
set { SetValue(DelayedValueProperty, value); }
}
public static readonly DependencyProperty DelayedValueProperty =
DependencyProperty.Register(
"DelayedValue",
typeof(double),
typeof(DelaySlider),
new PropertyMetadata(0.0, OnDelayedValuePropertyChanged));
private static void OnDelayedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DelaySlider source = d as DelaySlider;
if (source != null && !source.myChanging)
{
source.Value = (double)e.NewValue;
}
}
#endregion public double DelayedValue
#region public int Delay
public int Delay
{
get { return (int)GetValue(DelayProperty); }
set { SetValue(DelayProperty, value); }
}
public static readonly DependencyProperty DelayProperty =
DependencyProperty.Register(
"Delay",
typeof(int),
typeof(DelaySlider),
new PropertyMetadata(0, OnDelayPropertyChanged));
private static void OnDelayPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DelaySlider source = d as DelaySlider;
if (source != null)
{
source.OnDelayPropertyChanged((int)e.OldValue, (int)e.NewValue);
}
}
private void OnDelayPropertyChanged(int oldValue, int newValue)
{
if (myTimer != null)
{
myTimer.Stop();
myTimer = null;
}
if (newValue > 0)
{
myTimer = new DispatcherTimer();
myTimer.Tick += myTimer_Tick;
myTimer.Interval = TimeSpan.FromMilliseconds(newValue);
}
}
void myTimer_Tick(object sender, EventArgs e)
{
myTimer.Stop();
myChanging = true;
SetValue(DelayedValueProperty, Value);
myChanging = false;
}
#endregion public int Delay
protected override void OnValueChanged(double oldValue, double newValue)
{
base.OnValueChanged(oldValue, newValue);
if (myTimer != null)
{
myTimer.Start();
}
}
}
Now replace your Silder with DelaySlider and bind your View-Model property to the DelayedValue and specify your millisecond delay value in its Delay property.
You now have a useful re-usable control, you haven't messed about with nasty tricks in the View, you have no additional code in the code-behind of the view, the View-Model is unchanged and undisturbed and you haven't had to do include the Rx stuff at all.

WPF DependencyProperty Firing only once

I am having issues with one of my dependency properties not firing on change. Set up is as follows. I have a class that acts as a mediator for a ScrollViewer, in there I have a dependency property for a ScrollViewer type, and 2 doubles, VerticalOffsetNew, and VerticalScrollOffset. So I attach a previewmousewheel event to grab events coming from the mouse wheel.
In this event i grab the scrollviewers current offset and set VerticalScrollOffset to that value. Then depending on the delta I set the VerticalOffsetNew to be offset+delta.
Now the issue I am seeing is the following. the VerticalScrollOffset does not seem to get set after the first time. I have a onpropertychange event for that and the breakpoint is only hit the first time. The other property that is being set in the preview event (verticaloffsetnew) gets set everytime no problem.
any ideas?
public double VerticalScrollOffset
{
get { return (double)GetValue(VerticalScrollOffsetProperty); }
set { SetValue(VerticalScrollOffsetProperty, value); }
}
public static readonly DependencyProperty VerticalScrollOffsetProperty =
DependencyProperty.Register("VerticalScrollOffset", typeof(double),
typeof(ScrollViewerOffsetMediator), new PropertyMetadata(OnVerticalScrollOffsetChanged));
public static void OnVerticalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewerOffsetMediator mediator = (ScrollViewerOffsetMediator)d;
}
Above is my DP that is only set once in the preview code, below is the preview code.
private void CustomPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double mouseWheelChange = (double)e.Delta;
ScrollViewer thisScroller = (ScrollViewer)sender;
// not sure why the delta is so large here
//double newVOffset = this.VerticalOffset - (mouseWheelChange / 40);
// forcing scroll wheel to iterate by 8
mouseWheelChange /= Math.Abs(mouseWheelChange);
double newVOffset = thisScroller.VerticalOffset - (mouseWheelChange * 8);
// this does not trigger a change after the first time for some
// some strange reason !!!!!!!
this.VerticalScrollOffset = thisScroller.VerticalOffset;
if (newVOffset < 0)
{
this.VerticalOffset = 0;
}
else if (newVOffset > thisScroller.ScrollableHeight)
{
this.VerticalOffset = thisScroller.ScrollableHeight;
}
else
{
this.VerticalOffset = newVOffset;
}
e.Handled = true;
}
Thanks for any help.
Update
So there is something else I have noticed, the preview mouse call updates both the VerticalOffset and the VerticalScrollOffset. Both of which are DP's, now the thing that is interesting is that in the change event of the VerticalOffset there is a call to an animate method that will animate the VerticalScrollOffset DP. It seems that when I remove that call both DP's change correctly but as soon as I have that it fails. Is there some restriction or conflicts that may be arising, that I dont see or WPF silently tries to resolve?
Thanks again.
Have you tried implementing INotifyPropertyChanged?

How do I get the current mouse screen coordinates in WPF?

How to get current mouse coordination on the screen?
I know only Mouse.GetPosition() which get mousePosition of element, but I want to get the coordination without using element.
Or in pure WPF use PointToScreen.
Sample helper method:
// Gets the absolute mouse position, relative to screen
Point GetMousePos() => _window.PointToScreen(Mouse.GetPosition(_window));
To follow up on Rachel's answer.
Here's two ways in which you can get Mouse Screen Coordinates in WPF.
1.Using Windows Forms. Add a reference to System.Windows.Forms
public static Point GetMousePositionWindowsForms()
{
var point = Control.MousePosition;
return new Point(point.X, point.Y);
}
2.Using Win32
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
var w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
Do you want coordinates relative to the screen or the application?
If it's within the application just use:
Mouse.GetPosition(Application.Current.MainWindow);
If not, I believe you can add a reference to System.Windows.Forms and use:
System.Windows.Forms.Control.MousePosition;
If you try a lot of these answers out on different resolutions, computers with multiple monitors, etc. you may find that they don't work reliably. This is because you need to use a transform to get the mouse position relative to the current screen, not the entire viewing area which consists of all your monitors. Something like this...(where "this" is a WPF window).
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
public System.Windows.Point GetMousePosition()
{
var point = Forms.Control.MousePosition;
return new Point(point.X, point.Y);
}
This works without having to use forms or import any DLLs:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Gets the current mouse position on screen
/// </summary>
private Point GetMousePosition()
{
// Position of the mouse relative to the window
var position = Mouse.GetPosition(Window);
// Add the window position
return new Point(position.X + Window.Left, position.Y + Window.Top);
}
You may use combination of TimerDispatcher (WPF Timer analog) and Windows "Hooks" to catch cursor position from operational system.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT pPoint);
Point is a light struct. It contains only X, Y fields.
public MainWindow()
{
InitializeComponent();
DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += new EventHandler(timer_tick);
dt.Interval = new TimeSpan(0,0,0,0, 50);
dt.Start();
}
private void timer_tick(object sender, EventArgs e)
{
POINT pnt;
GetCursorPos(out pnt);
current_x_box.Text = (pnt.X).ToString();
current_y_box.Text = (pnt.Y).ToString();
}
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
This solution is also resolving the problem with too often or too infrequent parameter reading so you can adjust it by yourself. But remember about WPF method overload with one arg which is representing ticks not milliseconds.
TimeSpan(50); //ticks
If you're looking for a 1 liner, this does well.
new Point(Mouse.GetPosition(mWindow).X + mWindow.Left, Mouse.GetPosition(mWindow).Y + mWindow.Top)
The + mWindow.Left and + mWindow.Top makes sure the position is in the right place even when the user drags the window around.
Mouse.GetPosition(mWindow) gives you the mouse position relative to the parameter of your choice.
mWindow.PointToScreen() convert the position to a point relative to the screen.
So mWindow.PointToScreen(Mouse.GetPosition(mWindow)) gives you the mouse position relative to the screen, assuming that mWindow is a window(actually, any class derived from System.Windows.Media.Visual will have this function), if you are using this inside a WPF window class, this should work.
I wanna use this code
Point PointA;
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e) {
PointA = e.MouseDevice.GetPosition(sender as UIElement);
}
private void Button_Click(object sender, RoutedEventArgs e) {
// use PointA Here
}

How to draw connecting lines between two controls on a grid WPF

I am creating controls (say button) on a grid. I want to create a connecting line between controls.
Say you you do mousedown on one button and release mouse over another button. This should draw a line between these two buttons.
Can some one help me or give me some ideas on how to do this?
Thanks in advance!
I'm doing something similar; here's a quick summary of what I did:
Drag & Drop
For handling the drag-and-drop between controls there's quite a bit of literature on the web (just search WPF drag-and-drop). The default drag-and-drop implementation is overly complex, IMO, and we ended up using some attached DPs to make it easier (similar to these). Basically, you want a drag method that looks something like this:
private void onMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if (element == null)
return;
DragDrop.DoDragDrop(element, new DataObject(this), DragDropEffects.Move);
}
On the target, set AllowDrop to true, then add an event to Drop:
private void onDrop(object sender, DragEventArgs args)
{
FrameworkElement elem = sender as FrameworkElement;
if (null == elem)
return;
IDataObject data = args.Data;
if (!data.GetDataPresent(typeof(GraphNode))
return;
GraphNode node = data.GetData(typeof(GraphNode)) as GraphNode;
if(null == node)
return;
// ----- Actually do your stuff here -----
}
Drawing the Line
Now for the tricky part! Each control exposes an AnchorPoint DependencyProperty. When the LayoutUpdated event is raised (i.e. when the control moves/resizes/etc), the control recalculates its AnchorPoint. When a connecting line is added, it binds to the DependencyProperties of both the source and destination's AnchorPoints. [EDIT: As Ray Burns pointed out in the comments the Canvas and grid just need to be in the same place; they don't need to be int the same hierarchy (though they may be)]
For updating the position DP:
private void onLayoutUpdated(object sender, EventArgs e)
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, isInput ? 0 : size.Height);
AnchorPoint = TransformToVisual(node.canvas).Transform(ofs);
}
For creating the line class (can be done in XAML, too):
public sealed class GraphEdge : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Source { get { return (Point) this.GetValue(SourceProperty); } set { this.SetValue(SourceProperty, value); } }
public static readonly DependencyProperty DestinationProperty = DependencyProperty.Register("Destination", typeof(Point), typeof(GraphEdge), new FrameworkPropertyMetadata(default(Point)));
public Point Destination { get { return (Point) this.GetValue(DestinationProperty); } set { this.SetValue(DestinationProperty, value); } }
public GraphEdge()
{
LineSegment segment = new LineSegment(default(Point), true);
PathFigure figure = new PathFigure(default(Point), new[] { segment }, false);
PathGeometry geometry = new PathGeometry(new[] { figure });
BindingBase sourceBinding = new Binding {Source = this, Path = new PropertyPath(SourceProperty)};
BindingBase destinationBinding = new Binding { Source = this, Path = new PropertyPath(DestinationProperty) };
BindingOperations.SetBinding(figure, PathFigure.StartPointProperty, sourceBinding);
BindingOperations.SetBinding(segment, LineSegment.PointProperty, destinationBinding);
Content = new Path
{
Data = geometry,
StrokeThickness = 5,
Stroke = Brushes.White,
MinWidth = 1,
MinHeight = 1
};
}
}
If you want to get a lot fancier, you can use a MultiValueBinding on source and destination and add a converter which creates the PathGeometry. Here's an example from GraphSharp. Using this method, you could add arrows to the end of the line, use Bezier curves to make it look more natural, route the line around other controls (though this could be harder than it sounds), etc., etc.
See also
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dd246675-bc4e-4d1f-8c04-0571ea51267b
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx
http://www.syncfusion.com/products/user-interface-edition/wpf/diagram
http://www.mindscape.co.nz/products/wpfflowdiagrams/

Resources