Silverlight LineDataPoint Increase Size on MouseOver - silverlight

I am using a custom style in a resource dictionary for the LineDataPoint available with the toolkit.
I want the datapoint to increase in size on the mouseover event and the revert back to its original size once the mouse leaves. What's the best way to implement this?

I haven't found a better solution then using an extended class with necessary functionality:
public class ExtendedLineSeries : LineSeries
{
//I assume that all points have the same size
private double _originalDataPointWidth;
private double _originalDataPointHeight;
protected override DataPoint CreateDataPoint()
{
var dp = base.CreateDataPoint();
if (this.IncreaseDataPointSizeTo != null)
{
dp.MouseEnter += new MouseEventHandler(OnDataPointMouseEnter);
dp.MouseLeave += new MouseEventHandler(OnDataPointMouseLeave);
}
return dp;
}
/// <summary>
/// The width and height to which the point is increased in size
/// </summary>
public double? IncreaseDataPointSizeTo { get; set; }
void OnDataPointMouseLeave(object sender, MouseEventArgs e)
{
var dp = sender as DataPoint;
if (dp != null)
{
//return to the original size
dp.Width = _originalDataPointWidth;
dp.Height = _originalDataPointHeight;
dp.UpdateLayout();
base.UpdateDataPoint(dp);
}
}
void OnDataPointMouseEnter(object sender, MouseEventArgs e)
{
var dp = sender as DataPoint;
if (dp != null)
{
//remember the original size and enlarge the data point
_originalDataPointWidth = dp.ActualWidth;
_originalDataPointHeight = dp.ActualHeight;
dp.Width = dp.Height = IncreaseDataPointSizeTo.Value;
dp.UpdateLayout();
base.UpdateDataPoint(dp);
}
}
}
This class can be used everywhere where you use the common LineSeries class. It has the additional property IncreaseDataPointSizeTo which contains the final size in width and height of the hovered datapoint.
Example of xaml code:
<charting:Chart>
<charting:Chart.Series>
<ext:ExtendedLineSeries IsSelectionEnabled="True"
IncreaseDataPointSizeTo="16" ...

Related

change main form controls by trigger event inside canvas

I am currently working on a project that required me to use a canvas in order to draw rectangles around specific places in a picture (to mark places)
Each rectangle (actually "rectangle" since it is also a custom class that I created by inheriting from the Grid class and contain a rectangle object) contains properties and data about the marked place inside the picture.
my main form contains controls such as TextBox ,DropDownLists and etc.
Now what I am trying to do is that for each time I am clicking on the "rectangle" object the main form controls will be filled with the object data.
I do not have access to those controls from the canvas class.
this code is inside the costume canvas class to add the object into the canvas:
protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e)
{
if(e.ClickCount==2)
{
testTi = new TiTest();
base.OnMouseLeftButtonDown(e);
startPoint = e.GetPosition(this);
testTi.MouseLeftButtonDown += testTi_MouseLeftButtonDown;
Canvas.SetLeft(testTi, e.GetPosition(this).X);
Canvas.SetTop(testTi, e.GetPosition(this).X);
this.Children.Add(testTi);
}
}
and by clicking an object that is placed inside the canvas i want to get the information.
for now just want to make sure i am getting the right object with a simple messagebox
void testTi_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(sender.GetType().ToString());
}
this is my costume "Rectangle" class
class TiTest:Grid
{
private Label tiNameLabel;
private Rectangle tiRectangle;
private String SomeText = string.Empty;
private String version = "1.0";
private String application = "CRM";
private String CRID = "NNN";
public String SomeText1
{
get { return SomeText; }
set { SomeText = value; }
}
public Rectangle TiRectangle
{
get { return tiRectangle; }
set { tiRectangle = value; }
}
public Label TiNameLabel
{
get { return tiNameLabel; }
set { tiNameLabel = value; }
}
public TiTest()
{
this.SomeText = "Hello World!!";
this.TiNameLabel = new Label
{
Content = "Test Item",
VerticalAlignment = System.Windows.VerticalAlignment.Top,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left
};
TiRectangle = new Rectangle
{
Stroke = Brushes.Red,
StrokeDashArray = new DoubleCollection() { 3 },//Brushes.LightBlue,
StrokeThickness = 2,
Cursor = Cursors.Hand,
Fill = new SolidColorBrush(Color.FromArgb(0, 0, 111, 0))
};
Background= Brushes.Aqua;
Opacity = 0.5;
this.Children.Add(this.tiNameLabel);
this.Children.Add(this.tiRectangle);
}
}
is there any way to access the main form controls from the costume canvas class or by the costume rectangle class?
Thanks in advance
You can have your main window be binded to a singletone ViewModel holding the properties of the rectangles.
ViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Singletone
private static MainWindowViewModel _instance;
private MainWindowViewModel()
{
}
public static MainWindowViewModel Instance
{
get
{
if (_instance == null)
_instance = new MainWindowViewModel();
return _instance;
}
}
#endregion
#region Properties
private string _someInfo;
public string SomeInfo
{
get
{
return _someInfo;
}
set
{
if (_someInfo != value)
{
_someInfo = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SomeInfo"));
}
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
In main window xaml
<TextBox Text="{Binding SomeInfo}"/>
Also set the view model as your main window data context (in main window constructor for exmaple)
this.DataContext = MainWindowViewModel.Instance;
Finally, from where you handle the click event of the rectangles (testTi_MouseLeftButtonDown), access the MainWindowViewModel instance and set it's properties accordingly.
MainWindowViewModel.Instance.SomeInfo = myRectangle.SomeInfo;
This will trigger the PropertyChanged event, which will update your control's on the main window.
If you are not familiar with the MVVM (Model, View. View Model) pattern you can read about it here
Hope this helps

Expanding square control [duplicate]

Does anyone have an idea how to keep the Height/Width Ratio 1:1 of a UserControl?
E.g. if Height > Width, Width & Height will have the same size and vice versa.
I'm not sure this will work, but if you register a handler for the SizeChanged event and in there put in your code keep the aspect ratio 1:1.
The SizeChangedEventArgs argument has the old size and the new size so you can check which has changed and update the other accordingly.
You might need to introduce a guard variable so that you don't get a cascade of SizeChanged events as a result of updating the Height or Width.
Another alternative:
<local:MyControl Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"/>
Try using a ViewBox and setting its Stretch property to Uniform
i used this code for keeping aspect ratio
inside usercontrol globally define org_width, org_height, org_ratio :
private static double org_width = 77.6;//desired width
private static double org_height = 81.4;//desired height
private static double org_ratio = org_width / org_height;
use this code inside usercontrol in SizeChanged event:
FrameworkElement UCborder = this;
UCborder.Width = UCborder.Height*org_ratio;
and finally your user control code should looks like this:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace yournamespace
{
public partial class YourUserControl : UserControl
{
private static double org_width = 77.6;//desired width
private static double org_height = 81.4;//desired height
private static double org_ratio = org_width / org_height; // width/height
public YourUserControl()
{
InitializeComponent();
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement UCborder = this;
UCborder.Width = UCborder.Height*org_ratio;
}
}
}
good luck
private bool isSizeChangeDefered;
private void uiElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
//Keep Acpect Ratio
const double factor = 1.8;
if(isSizeChangeDefered)
return;
isSizeChangeDefered = true;
try
{
if (e.WidthChanged)
{
driverPan.Height = e.NewSize.Width * factor;
}
if (e.HeightChanged)
{
driverPan.Height = e.NewSize.Width / factor;
}
}
finally
{
// e.Handled = true;
isSizeChangeDefered = false;
}
}
maybe this helps... cheers

WPF: mouse leave event doesn't trigger with mouse down

I'm having an issue with mouse enter / leave events. When mouse button is pressed and hold with cursor inside the control and then cursor is moved out of the control fast enough this events don't trigger.
Could you please advice me why it happens? Is there any way to obtain these events properly?
Please check the sample project to see it in action: https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip
Update. I have found the same problem here without an answer. Started bounty there.
EDIT: After Sisyphe correctly noted that the behavior did not work for elements with mouse interaction, I have rewritten the code.
The behavior can be attached to a window or any other FrameworkElement. By default, all contained elements will be monitored for MouseLeave while the left mouse button is down, and the handlers executed. The behavior can also be applied just to its associated element by setting MonitorSubControls="False".
What the behavior does, basically (refer to the comments in the code for more detail):
Is only "active" if left mouse button is pressed
Watches for mouse position changes from in- to outside of an element. In this case, executes the event handlers.
Known limitations (could all be resolved with some more effort, I reckon, but don't seem too important to me):
Does not execute handlers for transitions to a contained element ("inner" boundaries)
Does not guarantee correct order of execution of the handlers
Does not resolve that for slow transitions to the outside of the window, e.LeftButton is reported as released (bug?).
I decided not to use the Win32 hook and instead using a timer, who will not fire more than about every 0.15 seconds (despite a smaller interval set, clock drift?). For fast mouse movements, the evaluated points could be too far apart and miss an element that is just flitted across.
This script produces the output below: With the behavior attached to the window, moving inside the orangeBorder (leaves blueBorder by inner boundary with mouse button released: 0), pressing left mousebutton inside the orange border and moving (fast) outside the window executes the leave handlers (1 - 4). Releasing the mouse button outside the window, moving back in over the goldTextBox (5), pressing left mousebutton in the textbox, leaving (fast or slow) outside the window again executes the correct handlers (6 - 9).
Xaml (example):
<Window x:Class="WpfApplication1.MouseLeaveControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MouseLeaveControlWindow" Height="300" Width="300" x:Name="window" MouseLeave="OnMouseLeave">
<i:Interaction.Behaviors>
<beh:MonitorMouseLeaveBehavior />
</i:Interaction.Behaviors>
<Grid x:Name="grid" MouseLeave="OnMouseLeave" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="blueBorder" MouseLeave="OnMouseLeave" Background="SteelBlue" Margin="50" Grid.RowSpan="2" />
<Border x:Name="orangeBorder" MouseLeave="OnMouseLeave" Background="DarkOrange" Margin="70, 70, 70, 20" />
<TextBox x:Name="goldTextBox" MouseLeave="OnMouseLeave" Background="Gold" Margin="70, 20, 70, 70" Grid.Row="1" Text="I'm a TextBox" />
</Grid>
</Window>
Code behind (just for debug purpose):
public partial class MouseLeaveControlWindow : Window
{
public MouseLeaveControlWindow()
{
InitializeComponent();
}
private int i = 0;
private void OnMouseLeave(object sender, MouseEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
if (e.LeftButton == MouseButtonState.Pressed)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1}.", i, fe.Name)); i++;
}
else
{
System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1} (Released).", i, fe.Name)); i++;
}
}
}
MonitorMouseLeaveBehavior:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.ComponentModel;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class MonitorMouseLeaveBehavior : Behavior<FrameworkElement>
{
[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;
};
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(UInt16 virtualKeyCode);
private enum VK
{
LBUTTON = 0x01
}
private bool _tracking;
private const int _interval = 1;
private Timer _checkPosTimer = new Timer(_interval);
private Dictionary<FrameworkElement, RoutedEventHandlerInfo[]> _leaveHandlersForElement = new Dictionary<FrameworkElement, RoutedEventHandlerInfo[]>();
private Window _window;
private Dictionary<FrameworkElement, Rect> _boundsByElement = new Dictionary<FrameworkElement, Rect>();
private Dictionary<FrameworkElement, bool> _wasInside = new Dictionary<FrameworkElement, bool>();
private List<FrameworkElement> _elements = new List<FrameworkElement>();
/// <summary>
/// If true, all subcontrols are monitored for the mouseleave event when left mousebutton is down.
/// True by default.
/// </summary>
public bool MonitorSubControls { get { return (bool)GetValue(MonitorSubControlsProperty); } set { SetValue(MonitorSubControlsProperty, value); } }
public static readonly DependencyProperty MonitorSubControlsProperty = DependencyProperty.Register("MonitorSubControls", typeof(bool), typeof(MonitorMouseLeaveBehavior), new PropertyMetadata(true, OnMonitorSubControlsChanged));
private static void OnMonitorSubControlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MonitorMouseLeaveBehavior beh = (MonitorMouseLeaveBehavior)d;
beh.AddOrRemoveLogicalChildren((bool)e.NewValue);
}
/// <summary>
/// Initial actions
/// </summary>
protected override void OnAttached()
{
_window = this.AssociatedObject is Window ? (Window)this.AssociatedObject : Window.GetWindow(this.AssociatedObject); // get window
_window.SourceInitialized += (s, e) =>
{
this.AddOrRemoveLogicalChildren(this.MonitorSubControls); // get all monitored elements
this.AttachHandlers(true); // attach mousedown and sizechanged handlers
this.GetAllBounds(); // determine bounds of all elements
_checkPosTimer.Elapsed += (s1, e1) => Dispatcher.BeginInvoke((Action)(() => { CheckPosition(); }));
};
base.OnAttached();
}
protected override void OnDetaching()
{
this.AttachHandlers(false);
base.OnDetaching();
}
/// <summary>
/// Starts or stops monitoring of the AssociatedObject's logical children.
/// </summary>
/// <param name="add"></param>
private void AddOrRemoveLogicalChildren(bool add)
{
if (_window != null && _window.IsInitialized)
{
AddOrRemoveSizeChangedHandlers(false);
_elements.Clear();
if (add)
_elements.AddRange(VisualHelper.FindLogicalChildren<FrameworkElement>(this.AssociatedObject));
_elements.Add(this.AssociatedObject);
AddOrRemoveSizeChangedHandlers(true);
}
}
/// <summary>
/// Attaches/detaches size changed handlers to the monitored elements
/// </summary>
/// <param name="add"></param>
private void AddOrRemoveSizeChangedHandlers(bool add)
{
foreach (var element in _elements)
{
element.SizeChanged -= element_SizeChanged;
if (add) element.SizeChanged += element_SizeChanged;
}
}
/// <summary>
/// Adjusts the stored bounds to the changed size
/// </summary>
void element_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
GetBounds(fe);
}
/// <summary>
/// Attaches/Detaches MouseLeftButtonDown and SizeChanged handlers
/// </summary>
/// <param name="attach">true: attach, false: detach</param>
private void AttachHandlers(bool attach)
{
AddOrRemoveSizeChangedHandlers(attach);
if (attach)
_window.PreviewMouseLeftButtonDown += window_PreviewMouseLeftButtonDown;
else // detach
_window.PreviewMouseLeftButtonDown -= window_PreviewMouseLeftButtonDown;
}
/// <summary>
/// Gets the bounds for all monitored elements
/// </summary>
private void GetAllBounds()
{
_boundsByElement.Clear();
foreach (var element in _elements)
GetBounds(element);
}
/// <summary>
/// Gets the bounds of the control, which are used to check if the mouse position
/// is located within. Note that this only covers rectangular control shapes.
/// </summary>
private void GetBounds(FrameworkElement element)
{
Point p1 = new Point(0, 0);
Point p2 = new Point(element.ActualWidth, element.ActualHeight);
p1 = element.TransformToVisual(_window).Transform(p1);
p2 = element.TransformToVisual(_window).Transform(p2);
if (element == _window) // window bounds need to account for the border
{
var titleHeight = SystemParameters.WindowCaptionHeight + 2 * SystemParameters.ResizeFrameHorizontalBorderHeight; // not sure about that one
var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
p1.Offset(-verticalBorderWidth, -titleHeight);
p2.Offset(-verticalBorderWidth, -titleHeight);
}
Rect bounds = new Rect(p1, p2);
if (_boundsByElement.ContainsKey(element))
_boundsByElement[element] = bounds;
else
_boundsByElement.Add(element, bounds);
}
/// <summary>
/// For all monitored elements, detach the MouseLeave event handlers and store them locally,
/// to be executed manually.
/// </summary>
private void RerouteLeaveHandlers()
{
foreach (var element in _elements)
{
if (!_leaveHandlersForElement.ContainsKey(element))
{
var handlers = ReflectionHelper.GetRoutedEventHandlers(element, UIElement.MouseLeaveEvent);
if (handlers != null)
{
_leaveHandlersForElement.Add(element, handlers);
foreach (var handler in handlers)
element.MouseLeave -= (MouseEventHandler)handler.Handler; // detach handlers
}
}
}
}
/// <summary>
/// Reattach all leave handlers that were detached in window_PreviewMouseLeftButtonDown.
/// </summary>
private void ReattachLeaveHandlers()
{
foreach (var kvp in _leaveHandlersForElement)
{
FrameworkElement fe = kvp.Key;
foreach (var handler in kvp.Value)
{
if (handler.Handler is MouseEventHandler)
fe.MouseLeave += (MouseEventHandler)handler.Handler;
}
}
_leaveHandlersForElement.Clear();
}
/// <summary>
/// Checks if the mouse position is inside the bounds of the elements
/// If there is a transition from inside to outside, the leave event handlers are executed
/// </summary>
private void DetermineIsInside()
{
Point p = _window.PointFromScreen(GetMousePosition());
foreach (var element in _elements)
{
if (_boundsByElement.ContainsKey(element))
{
bool isInside = _boundsByElement[element].Contains(p);
bool wasInside = _wasInside.ContainsKey(element) && _wasInside[element];
if (wasInside && !isInside)
ExecuteLeaveHandlers(element);
if (_wasInside.ContainsKey(element))
_wasInside[element] = isInside;
else
_wasInside.Add(element, isInside);
}
}
}
/// <summary>
/// Gets the mouse position relative to the screen
/// </summary>
public static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
/// <summary>
/// Gets the mouse button state. MouseEventArgs.LeftButton is notoriously unreliable.
/// </summary>
private bool IsMouseLeftButtonPressed()
{
short leftMouseKeyState = GetAsyncKeyState((ushort)VK.LBUTTON);
bool ispressed = leftMouseKeyState < 0;
return ispressed;
}
/// <summary>
/// Executes the leave handlers that were attached to the controls.
/// They have been detached previously by this behavior (see window_PreviewMouseLeftButtonDown), to prevent double execution.
/// After mouseup, they are reattached (see CheckPosition)
/// </summary>
private void ExecuteLeaveHandlers(FrameworkElement fe)
{
MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
MouseEventArgs mouseEvent = new MouseEventArgs(mouseDev, 0) { RoutedEvent = Control.MouseLeaveEvent };
if (_leaveHandlersForElement.ContainsKey(fe))
{
foreach (var handler in _leaveHandlersForElement[fe])
{
if (handler.Handler is MouseEventHandler)
((MouseEventHandler)handler.Handler).Invoke(fe, mouseEvent);
}
}
}
/// <summary>
/// Sets the mouse capture (events outside the window are still directed to it),
/// and tells the behavior to watch out for a missed leave event
/// </summary>
private void window_PreviewMouseLeftButtonDown(object sender, MouseEventArgs e)
{
System.Diagnostics.Debug.WriteLine("--- left mousebutton down ---"); // todo remove
this.RerouteLeaveHandlers();
_tracking = true;
_checkPosTimer.Start();
}
/// <summary>
/// Uses the _tracking field as well as left mouse button state to determine if either
/// leave event handlers should be executed, or monitoring should be stopped.
/// </summary>
private void CheckPosition()
{
if (_tracking)
{
if (IsMouseLeftButtonPressed())
{
this.DetermineIsInside();
}
else
{
_wasInside.Clear();
_tracking = false;
_checkPosTimer.Stop();
System.Diagnostics.Debug.WriteLine("--- left mousebutton up ---"); // todo remove
// invoking ReattachLeaveHandlers() immediately would rethrow MouseLeave for top grid/window
// if both a) mouse is outside window and b) mouse moves. Wait with reattach until mouse is inside window again and moves.
_window.MouseMove += ReattachHandler;
}
}
}
/// <summary>
/// Handles the first _window.MouseMove event after left mouse button was released,
/// and reattaches the MouseLeaveHandlers. Detaches itself to be executed only once.
/// </summary>
private void ReattachHandler(object sender, MouseEventArgs e)
{
ReattachLeaveHandlers();
_window.MouseMove -= ReattachHandler; // only once
}
}
}
VisualHelper.FindLogicalChildren, ReflectionHelper.GetRoutedEventHandlers:
public static List<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
foreach (var child in LogicalTreeHelper.GetChildren(obj))
{
if (child != null)
{
if (child is T)
children.Add((T)child);
if (child is DependencyObject)
children.AddRange(FindLogicalChildren<T>((DependencyObject)child)); // recursive
}
}
return children;
}
/// <summary>
/// Gets the list of routed event handlers subscribed to the specified routed event.
/// </summary>
/// <param name="element">The UI element on which the event is defined.</param>
/// <param name="routedEvent">The routed event for which to retrieve the event handlers.</param>
/// <returns>The list of subscribed routed event handlers.</returns>
public static RoutedEventHandlerInfo[] GetRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
{
var routedEventHandlers = default(RoutedEventHandlerInfo[]);
// Get the EventHandlersStore instance which holds event handlers for the specified element.
// The EventHandlersStore class is declared as internal.
var eventHandlersStoreProperty = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);
if (eventHandlersStore != null)
{
// Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance
// for getting an array of the subscribed event handlers.
var getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
routedEventHandlers = (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(eventHandlersStore, new object[] { routedEvent });
}
return routedEventHandlers;
}
Approach #1 - is still a valid one (as a pure managed solution) if you work out the specifics.
(capture could be given to a specific control to avoid issues, but I haven't tried)
This should help you get the events ('fixed' events).
Key is to track the mouse move when outside window (and only when mouse is down).
For that you'd need to do the capture (but slightly different than suggested as that won't work - on down/up instead).
private void Window_MouseDown(object sender, MouseEventArgs e)
{
this.CaptureMouse();
}
private void Window_MouseUp(object sender, MouseEventArgs e)
{
this.ReleaseMouseCapture();
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
test1.Content = "Mouse left";
}
private void Window_MouseEnter(object sender, MouseEventArgs e)
{
test1.Content = "Mouse entered";
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this)
{
if (!this.IsMouseInBounds(e))
Window_MouseLeave(sender, e);
else
Window_MouseEnter(sender, e);
}
test2.Content = e.GetPosition(this).ToString();
}
private bool IsMouseInBounds(MouseEventArgs e)
{
var client = ((FrameworkElement)this.Content);
Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
return bounds.Contains(e.GetPosition(this));
}
private Point GetRealPosition(Point mousePoint)
{
return Application.Current.MainWindow.PointFromScreen(mousePoint);
}
Note:
You'd need to finish this according to your situation. I have just 'dummy wired' the mouse move to Enter and Leave and w/o any smart algorithm there (i.e. generated enter/leave will keep on firing). I.e. add some flag to actually save the state of the enter/leave properly.
Also I'm measuring whether mouse is within the 'client bounds' of the Window. You'd need to adjust that if you need that in respect of borders etc.
Also I forgot to add the obvious - wire up the new events MouseDown="Window_MouseDown" MouseUp="Window_MouseUp"
That's "normal" behaviour. Capture the mouse inside MouseEnter handler.
Mouse.Capture(yourUIElement);
and later release it in MouseLeave,
Mouse.Capture(null);
Edited: More explanation.
WPF does not track mouse movement precisely. You can deduce that from the fact that if you capture MouseMove event, you can see that the it reports you event every 20milliseconds intervals and not by pixel precision.. more like 8 pixels per event.
Now this is not that horrible, but WPF also does not report mouse movement outside the window, if you happen to move your mouse. This is default behaviour. You can change it throuh Mouse.Capture as said.
Now, you can imagine why this problem happens. If you can move your mouse outside the window faster than mouse move report happens, then WPF still thinks that it's inside the application.
EDIT
In case you need it - I edited in a simplified wrapper for ease of use (just add commands in your
view-model)
Approach #2 - using Global Mouse Hook to track mouse move - the rest is similar to #1.
Actually, this is more of an example on how to do a global hook from C#.
In XAML you can hook up all 3 or just one, two events
my:Hooks.EnterCommand="{Binding EnterCommand}"
my:Hooks.LeaveCommand="{Binding LeaveCommand}"
my:Hooks.MouseMoveCommand="{Binding MoveCommand}"
In your view-model define commands
RelayCommand _enterCommand;
public RelayCommand EnterCommand
{
get
{
return _enterCommand ?? (_enterCommand = new RelayCommand(param =>
{
var point = (Point)param;
test1.Content = "Mouse entered";
// test2.Content = point.ToString();
},
param => true));
}
}
And the attached properties (the 'nice' wrapper)...
public static class Hooks
{
private static Dictionary<ContentControl, Action> _hash = new Dictionary<ContentControl, Action>();
#region MouseMoveCommand
public static ICommand GetMouseMoveCommand(ContentControl control) { return (ICommand)control.GetValue(MouseMoveCommandProperty); }
public static void SetMouseMoveCommand(ContentControl control, ICommand value) { control.SetValue(MouseMoveCommandProperty, value); }
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnMouseMoveCommandChanged));
static void OnMouseMoveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
static void Instance_MouseMoveLL(object sender, WinHook.MouseLLMessageArgs e)
{
}
static void OnAutoGeneratingColumn(ICommand command, object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (command.CanExecute(e)) command.Execute(e);
}
#endregion
#region EnterCommand
public static ICommand GetEnterCommand(ContentControl control) { return (ICommand)control.GetValue(EnterCommandProperty); }
public static void SetEnterCommand(ContentControl control, ICommand value) { control.SetValue(EnterCommandProperty, value); }
public static readonly DependencyProperty EnterCommandProperty =
DependencyProperty.RegisterAttached("EnterCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnEnterCommandChanged));
static void OnEnterCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
#endregion
#region LeaveCommand
public static ICommand GetLeaveCommand(ContentControl control) { return (ICommand)control.GetValue(LeaveCommandProperty); }
public static void SetLeaveCommand(ContentControl control, ICommand value) { control.SetValue(LeaveCommandProperty, value); }
public static readonly DependencyProperty LeaveCommandProperty =
DependencyProperty.RegisterAttached("LeaveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnLeaveCommandChanged));
static void OnLeaveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
#endregion
static void SetupMouseMove(ContentControl control)
{
Action onmove;
if (_hash.TryGetValue(control, out onmove) == false)
{
onmove = () =>
{
var entered = false;
var moveCommand = control.GetValue(Hooks.MouseMoveCommandProperty) as ICommand;
var enterCommand = control.GetValue(Hooks.EnterCommandProperty) as ICommand;
var leaveCommand = control.GetValue(Hooks.LeaveCommandProperty) as ICommand;
// hook is invoked on the 'caller thread' (i.e. your GUI one) so it's safe
// don't forget to unhook and dispose / release it, handle unsubscribe for events
WinHook.Instance.MouseMoveLL += (s, e) =>
{
Point point = control.PointFromScreen(new Point(e.Message.Pt.X, e.Message.Pt.Y));
if (moveCommand != null && moveCommand.CanExecute(point))
moveCommand.Execute(point);
var newEntered = control.IsMouseInBounds(point); // don't use 'IsMouseOver'
if (newEntered != entered)
{
entered = newEntered;
if (entered)
{
if (enterCommand != null && enterCommand.CanExecute(point))
enterCommand.Execute(point);
}
else
{
if (leaveCommand != null && leaveCommand.CanExecute(point))
leaveCommand.Execute(point);
}
}
};
};
control.Loaded += (s, e) => onmove();
_hash[control] = onmove;
}
}
private static bool IsMouseInBounds(this ContentControl control, Point point)
{
var client = ((FrameworkElement)control.Content);
Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
return bounds.Contains(point);
}
}
And you could use HookManager from the article.
Or the minimal hook code (mind that proper IDisoposable is required, exception handling etc.):
public sealed class WinHook : IDisposable
{
public static readonly WinHook Instance = new WinHook();
public const int WH_MOUSE_LL = 14;
public const uint WM_MOUSEMOVE = 0x0200;
public delegate void MouseLLMessageHandler(object sender, MouseLLMessageArgs e);
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetCurrentThreadId();
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public class MouseLLHookStruct
{
public POINT Pt;
public uint mouseData;
public uint flags;
public uint time;
public uint dwExtraInfo;
}
public class MouseLLMessageArgs : EventArgs
{
public bool IsProcessed { get; set; }
public MouseLLHookStruct Message { get; private set; }
public MouseLLMessageArgs(MouseLLHookStruct message) { this.Message = message; }
}
static IntPtr GetModuleHandle()
{
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
return GetModuleHandle(module.ModuleName);
}
public event MouseLLMessageHandler MouseMoveLL;
int _hLLMouseHook = 0;
HookProc LLMouseHook;
private WinHook()
{
IntPtr hModule = GetModuleHandle();
LLMouseHook = LowLevelMouseProc;
_hLLMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LLMouseHook, hModule, 0);
if (_hLLMouseHook == 0) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
}
public void Release()
{
if (_hLLMouseHook == 0) return;
int hhook = _hLLMouseHook;
_hLLMouseHook = 0;
bool ret = UnhookWindowsHookEx(hhook);
if (ret == false) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
}
public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && lParam.ToInt32() > 0
&& wParam.ToInt32() == (int)WM_MOUSEMOVE)
{
MouseLLHookStruct msg = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
MouseLLMessageArgs args = new MouseLLMessageArgs(msg);
if (MouseMoveLL != null)
MouseMoveLL(this, args);
if (args.IsProcessed)
return -1; // return 1;
}
return CallNextHookEx(_hLLMouseHook, nCode, wParam, lParam);
}
// implement IDisposable properly and call `Release` for unmanaged resources / hook
public void Dispose() { }
}
Note: global mouse hooks are notorious for performance issues. And you cannot use the local one (recommended but most of the time useless) - as it doesn't get outside mouse moves.
Also avoid putting anything 'heavy' inside the event - or anything that 'spawns off' from it. There is a limit actually on the time you can spend processing the event - or your hook will get removed, i.e. stop working. If you need to do some processing from the event, pop up a new thread and Invoke back.
My favorite solution is actually to give the hook its own thread and then events need to be invoked - but that's out of scope and a bit more complex (you need a 'pump' in there etc.).
As to 'why' all this is necessary:
I don't like speculating, but seems that events are throttled - and the critical 'one' is missed when 'crossing the border', something like that. Anyway, it's all about the mouse moves no matter how you look at it.

How can I find UIElements in a rectangle in WPF?

I need to find UIElements in (rectangle/area/bounds).
MainWindow I'm doing the following:
I register the mouse down as the start position.
I regsiter the mouse up position.
Now I need to find ll (buttons, textboxes, etc) in the rectangle between start
postion and the end position.
I found in the msdn the HitTest approach but it is only for one point. I think, walking through all points in the founded
rectangle it is a performance disaster.
http://msdn.microsoft.com/en-us/library/ms752097.aspx
My code based on MVVM pattern:
private ObservableCollection<UIElementViewModel> wells;
private Point stratPoint; // Mouse down
public ICommand MouseUpRightCommand
{
get
{
if (this.mouseUpRightCommand == null)
{
this.mouseUpRightCommand = new RelayCommands(
param =>
{
if (param is MouseButtonEventArgs)
{
var e = (param as MouseButtonEventArgs);
//Set the end point
endPosition = e.GetPosition(((ItemsControl)e.Source));
// for example, here I want to find all controls(UIElements) in the
// founded rectangle of stratPoint and endPosition.
}
});
}
return this.mouseUpRightCommand;
}
}
Any other idea or a better approach?
Thanks
I would use FrameworkElement (which extends UIElement) instead of UIElement, in order to use ActualWidth and ActualHeight properties
Then create a static class which does some math MouseUtils
with those static fields
private static double _dContainerTop;
private static double _dContainerBottom;
private static double _dContainerLeft;
private static double _dContainerRight;
private static double _dCursorTop;
private static double _dCursorLeft;
private static double _dCursorRight;
private static double _dCursorBottom;
and those static methods
private static void FindValues(FrameworkElement element, Visual rootVisual)
{
var containerTopLeft = container.TransformToAncestor(rootVisual).Transform(new Point(0, 0));
_dContainerTop = containerTopLeft.Y;
_dContainerBottom = _dContainerTop + container.ActualHeight;
_dContainerLeft = containerTopLeft.X;
_dContainerRight = _dContainerLeft + container.ActualWidth;
}
and
public static bool IsElementUnderRectCursor(FrameworkElement element, Point startPoint, Point endPoint, Visual rootVisual)
{
_dCursorTop=Math.Min(startPoint.Y, endPoint.Y);
_dCursorBottom=Math.Max(startPoint.Y, endPoint.Y);
_dCursorLeft=Math.Min(startPoint.X, endPoint.X);
_dCursorRight=Math.Max(startPoint.X, endPoint.X);
FindValues(container, rootVisual);
if (_dContainerTop < _dCursorTop|| _dCursorBottom< _dContainerBottom )
{
return false;
}
if (_dContainerLeft < _dCursorLeft|| _dContainerRight < _dCursorRight)
{
return false;
}
return true;
}
Rootvisual being your window for example;
Then loop over ObservableCollection<FrameworkElement> wells and call that function IsElementUnderRectCursor.
This is inspired from:
Kinecting the Dots
Astreal thanks again for your answer. It's done. I just moved the selection code from modelView to view. The selection done only in the UI.
private void SelectWells(RectangleGeometry selectionRectangle, FrameworkElement frameworkElement)
{
var items = GetItemsControl(frameworkElement);
foreach (var item in items.Items)
{
var viusalItem = (ContentPresenter)items.ItemContainerGenerator.ContainerFromItem(item);
var wellControl = this.GetWellControl(viusalItem);
var relativePoint = wellControl.TransformToAncestor(items).Transform(new Point(0, 0));
var controlRectangle =
new RectangleGeometry(
new Rect(relativePoint.X, relativePoint.Y, wellControl.ActualWidth, wellControl.ActualHeight));
var intersectionGeometry = Geometry.Combine(
selectionRectangle, controlRectangle, GeometryCombineMode.Intersect, null);
if (intersectionGeometry.GetArea() > 0)
{
wellControl.Command.Execute(this);
}
}
}
usefull link for u:
http://www.codeproject.com/Articles/354853/WPF-Organization-Chart-Hierarchy-MVVM-Application
When an user clicks on a node in the tree we need to let the ViewModel node know that the selection has changed. We like to route the event as a command to the ViewModel

Drag & Drop in Treeview

I'm trying to drag and drop files in my treeview but I have no idea why it's breaking down if I run it and try dragging a file.
The code below is what I tried. Please help.
private void TreeViewItem_Drop( object sender, DragEventArgs e)
{
TreeViewItem treeViewItem = e.Source as TreeViewItem;
TreeViewItem obj = e.Data.GetData(typeof(TreeViewItem)) as TreeViewItem;
if ((obj.Parent as TreeViewItem) != null)
{
(obj.Parent as TreeViewItem).Items.Remove(obj);
}
else
{
treeViewItem.Items.Remove(obj);
treeViewItem.Items.Insert(0, obj);
e.Handled = true;
}
}
private void TreeViewItem_MouseLeftButtonDown( object sender,MouseButtonEventArgs e)
{
DependencyObject dependencyObject = _treeview.InputHitTest(e.GetPosition(_treeview)) as DependencyObject;
Debug.Write(e.Source.GetType().ToString());
if (dependencyObject is TextBlock)
{
TreeViewItem treeviewItem = e.Source as TreeViewItem;
DragDrop.DoDragDrop(_treeview, _treeview.SelectedValue, DragDropEffects.Move);
e.Handled = true;
}
}
This article is very helpful. Drag drop wpf
This code may be of use to you as well.
Point _startPoint;
bool _IsDragging = false;
void TemplateTreeView_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed ||
e.RightButton == MouseButtonState.Pressed && !_IsDragging)
{
Point position = e.GetPosition(null);
if (Math.Abs(position.X - _startPoint.X) >
SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(position.Y - _startPoint.Y) >
SystemParameters.MinimumVerticalDragDistance)
{
StartDrag(e);
}
}
}
void TemplateTreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(null);
}
private void StartDrag(MouseEventArgs e)
{
_IsDragging = true;
object temp = this.TemplateTreeView.SelectedItem;
DataObject data = null;
data = new DataObject("inadt", temp);
if (data != null)
{
DragDropEffects dde = DragDropEffects.Move;
if (e.RightButton == MouseButtonState.Pressed)
{
dde = DragDropEffects.All;
}
DragDropEffects de = DragDrop.DoDragDrop(this.TemplateTreeView, data, dde);
}
_IsDragging = false;
}
The following code can be used as is, if you can add an IDragDrop interface to your objects that represent the data being dragged. If not, then this should still serve as a concise starting point.
Drag and drop functionality is actually implemented by Windows as opposed to Wpf or .Net, allowing for dragging and dropping across applications. As a result, initiating the drag and drop operation, and handling the potential drop are two completely separate matters. This code breaks things down into two helper classes, one for the source from which something is dragged, and the other for the potential destination where some things can be dropped.
First, the IDragDrop interface and all other relevant code aside from the helper classes (e.g., the using statements at the beginning of the file).
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Input;
namespace YourNamespaceHere;
public interface IDragDrop
{
bool CanDrag { get; }
bool CanDrop( IDragDrop draggedObject );
bool Drop( IDragDrop draggedObject );
}
The CanDrag property allows for a heterogenous tree view in which all items derive from a base class which implements IDragDrop, but subclasses that do not represent draggable data objects use public bool CanDrag => false;. In a file browser, for example, the data type used to represent a drive might define CanDrag this way, while folders and files that can be dragged would use public bool CanDrag => true;.
CanDrop answers whether or not a given IDragDrop object can be dropped onto the instance on which the method is called. For example, a drive or folder might return true if the IDragDrop is a file that is small enough to fit in the remaining space on the drive, or false otherwise. A file might always return false. The Drop method executes the move.
The following helper class handles initiating a drag and drop operation.
public class DragDropSourceHelper
{
private Rectangle dragBoxFromMouseDown = Rectangle.Empty;
private object mouseDownOriginalSource;
public DependencyObject DragSource { get; init; }
public Func<object, IDragDrop> GetDraggedObject { get; init; }
public void DragSource_MouseDown( object sender, MouseEventArgs e )
{
if( e.LeftButton != MouseButtonState.Pressed )
return;
var position = e.GetPosition( null );
mouseDownOriginalSource = e.OriginalSource;
dragBoxFromMouseDown = new Rectangle(
(int)(position.X - SystemParameters.MinimumHorizontalDragDistance),
(int)(position.Y - SystemParameters.MinimumVerticalDragDistance),
(int)(SystemParameters.MinimumHorizontalDragDistance * 2),
(int)(SystemParameters.MinimumVerticalDragDistance * 2) );
}
public void DragSource_MouseUp( object sender, MouseEventArgs e )
{
if( e.LeftButton != MouseButtonState.Released )
return;
dragBoxFromMouseDown = Rectangle.Empty;
}
public void DragSource_MouseMove( object sender, MouseEventArgs e )
{
if( e.LeftButton != MouseButtonState.Pressed )
return;
var position = e.GetPosition( null );
if( dragBoxFromMouseDown == Rectangle.Empty
|| dragBoxFromMouseDown.Contains( (int)position.X, (int)position.Y )
)
return;
var draggedObject = GetDraggedObject.Invoke( mouseDownOriginalSource );
if( draggedObject is null )
return;
DragDropEffects finalDropEffect = DragDrop.DoDragDrop(
DragSource,
new DataObject( typeof(IDragDrop), draggedObject),
DragDropEffects.Move );
//If the source needed to act on a completed drop action,
//it would use finalDropEffect to do so.
}
}
The public methods need to be subscribed to the events on the source control (e.g., the tree view). The DragSource object needs to be the dependency object representing the source control. The func receives the element on which the user pressed down the left mouse button, and needs to produce the IDragDrop object representing the data object to be dragged. In a tree view, this is usually accomplished by searching the visual tree up from the object parameter until finding a TreeViewItem object, and then using its DataContext as the IDragDrop return value.
One task that the above helper class handles is wrapping the dragged object in a DataObject (MS Docs), which is the wrapper that allows for data to be dragged back and forth between applications. The helper class below unwraps the dragged object, so you don't need to worry about it in this simple example. The DoDragDrop( ) method's signature allows for any object and attempts to wrap other types of objects in a DataObject itself, but this automatic wrapping did not work when I tried it here. Finding information about the DoDragDrop( ) method (MS Docs) can be difficult because it is a static method on a class that shares a name with the DragDrop event from Windows Forms.
The following helper class handles enabling the control to have some items dropped on it, except that you must still set the AllowDrop property on the target control to true yourself.
public class DragDropTargetHelper
{
public Func<object, IDragDrop, bool> CanDrop { get; init; }
public Func<object, IDragDrop, bool> Drop { get; init; }
public void DragTarget_DragOver( object sender, DragEventArgs e )
{
var draggedObject = e.Data.GetData( typeof(IDragDrop) ) as IDragDrop;
if( draggedObject is null || !CanDrop( e.OriginalSource, draggedObject ) )
e.Effects = DragDropEffects.None;
else
e.Effects = DragDropEffects.Move;
e.Handled = true;
}
public void DragTarget_Drop( object sender, DragEventArgs e )
{
var draggedObject = e.Data.GetData( typeof(IDragDrop) ) as IDragDrop;
if( Drop( e.OriginalSource, draggedObject ) )
e.Handled = true;
}
}
The CanDrop func takes as input the object over which a drop is being considered and the IDragDrop object being dragged, and determines whether the IDragDrop can be dropped on that particular object. Drop is the same, except that it actually executes the drop operation. It returns true when the drop was successfully completed, or false when the draggable object should still be considered "being dragged" because it was not successfully dropped. As with the func in the source helper, if you are using a tree view you probably want to cast the source objects to dependency objects and search up the visual tree until finding a TreeViewItem.
As with the source helper, connect the public methods to the events on the target control, and do not forget to set its AllowDrop property to true (e.g., treeview.AllowDrop = true;). When moving tree view items around within a tree view, both the source and target controls are the Wpf TreeView control.
Done.
String the above three code blocks together into one file, and you have a self-contained set of drag and drop helper classes. You'll need to expand on this work using the e.Effects and DragDropEffects enum if you want to distinguish between different kinds of drag and drop operations, such as move versus copy.
The following code shows my application's use of these helper classes. Do.ValidateAs<Type>( object ) is essentially a cast.
using Wpf = System.Windows.Controls;
private void ConfigureDragDrop( Wpf.TreeView treeView )
{
var dragHelper = new DragDropSourceHelper
{
DragSource = treeView,
GetDraggedObject = source =>
{
var sourceDependency = Do.ValidateAs<DependencyObject>( source );
var treeViewItem = VisualUpwardSearch<Wpf.TreeViewItem>( sourceDependency );
return treeViewItem.DataContext as IDragDrop;
},
};
treeView.PreviewMouseDown += dragHelper.DragSource_MouseDown;
treeView.MouseMove += dragHelper.DragSource_MouseMove;
treeView.MouseUp += dragHelper.DragSource_MouseUp;
var dropHelper = new DragDropTargetHelper
{
CanDrop = ( source, draggedObject ) =>
{
var sourceDependency = Do.ValidateAs<DependencyObject>( source );
var treeViewItem = VisualUpwardSearch<Wpf.TreeViewItem>( sourceDependency );
if( treeViewItem?.DataContext is IDragDrop dragDrop )
return dragDrop.CanDrop( draggedObject );
else
return false;
},
Drop = ( source, draggedObject ) =>
{
var sourceDependency = Do.ValidateAs<DependencyObject>( source );
var treeViewItem = VisualUpwardSearch<Wpf.TreeViewItem>( sourceDependency );
if( treeViewItem?.DataContext is IDragDrop dragDrop )
return dragDrop.Drop( draggedObject );
else
return false;
},
};
treeView.DragOver += dropHelper.DragTarget_DragOver;
treeView.Drop += dropHelper.DragTarget_Drop;
treeView.AllowDrop = true;
}
The method you are more likely to actually want to copy that not part of the helper classes, which handles the visual tree search that I mentioned above, is as follows:
private static TFind VisualUpwardSearch<TFind>( DependencyObject source )
where TFind : class
{
while ( source != null && !(source is TFind) )
source = VisualTreeHelper.GetParent( source );
return source as TFind;
}

Resources