Drag & Drop in Treeview - wpf

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;
}

Related

WFP TranslateTransform Error

I am working on a drawing project. In which I have t create a draggable window where a user can Click on any Object and move it to different positions like we do in PhotoShop with our layers.
Programme I write is working perfectly for the One object on the screen and I can move to any position I want by simply dragging it. But when the number of objects is increased then it is causing a very weird problem. When I click on the any of the objects it creates a cluster of the all the objects on the windows and starts applying the transformation to the whole cluster.
Note : All the object are the Images and Container is Canvas.
Here are some images and code I am using.
private Point _currentPoint;
private Point _ancherPoint;
private bool _isInDrag = false;
private TranslateTransform _transform = new TranslateTransform();
private Image _element;
private void DropList_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
_element = FindAnchestor<Image>( ( DependencyObject )e.OriginalSource );
if( _element != null )
{
_ancherPoint = e.GetPosition( DropList );
_isInDrag = true;
_element.CaptureMouse();
e.Handled = true;
}
}
private void DropList_MouseMove( object sender, MouseEventArgs e )
{
if( _isInDrag )
{
_currentPoint = e.GetPosition( DropList );
_transform.X += ( _currentPoint.X - _ancherPoint.X );
_transform.Y += ( _currentPoint.Y - _ancherPoint.Y );
Lbl.Content = _element.Source.ToString();
Source on which transfrom is going to happen
_element.RenderTransform = _transform;
_ancherPoint = _currentPoint;
}
}
private void DropList_MouseLeftButtonUp( object sender, MouseButtonEventArgs e )
{
if( _isInDrag )
{
_element.ReleaseMouseCapture();
_isInDrag = false;
e.Handled = true;
}
}
private static T FindAnchestor<T>( DependencyObject current ) where T : DependencyObject
{
do
{
if( current is T )
{
return ( T )current;
}
current = VisualTreeHelper.GetParent( current );
}
while( current != null );
return null;
}
This is the single object I can move it to any place I want easily without any mess up.
Here I have three objects. when I click any of them they make a cluster and start moving together.
The problem is that you are using the same TranslateTransform instance for all elements. You should instead create a new instance for each element, either when you create the element, or e.g. in the MouseDown handler:
private void DropList_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_element = FindAnchestor<Image>((DependencyObject)e.OriginalSource);
if (_element != null)
{
var point = e.GetPosition((IInputElement)sender);
_isInDrag = true;
_element.CaptureMouse();
_element.RenderTransform = new TranslateTransform(point.X, point.Y); // here
e.Handled = true;
}
}
And use it when moving:
private void DropList_MouseMove(object sender, MouseEventArgs e)
{
if (_isInDrag)
{
var point = e.GetPosition((IInputElement)sender);
var transform = (TranslateTransform)_element.RenderTransform;
transform.X = point.X;
transform.Y = point.Y;
}
}

Quickest way to hide an array of pictureboxes

I have an array of pictureboxes named from B11 (co-ords 1,1) to B55 (co-ords 5,5). I would like to hide these all on startup (and in the middle of running). I was thinking of making an array of the names manually but would it be the best solution?
If they all have a common parent control, such as a panel or groupbox (or even the form):
Parent.SuspendLayout()
For Each pbox As PictureBox in Parent.Controls.OfType(Of PictureBox)()
pbox.Visible = False
Next pbox
Parent.ResumeLayout()
The Suspend/Resume-Layout() is to avoid flickering as you modify a bunch of controls at once.
You could extend the PictureBox class and use event handling to accomplish this by:
Adding a public property to the form to tell if the picture boxes should be shown or hidden.
Adding an event to the form that is raised when the show/hide picture box property is changed.
Extending the PictureBox class so that it subscribes to the event of the parent form.
Setting the visible property of the extended PictureBox class to the show/hide property of the parent form.
When the show/hide flag is changed on the parent form all of the picture boxes will change their visibility property accordingly.
Form Code:
public partial class PictureBoxForm : Form {
public PictureBoxForm() {
InitializeComponent();
this.pictureBoxesAdd();
}
private void pictureBoxesAdd() {
MyPictureBox mp1 = new MyPictureBox();
mp1.Location = new Point(1, 1);
MyPictureBox mp2 = new MyPictureBox();
mp2.Location = new Point(200, 1);
this.Controls.Add(mp1);
this.Controls.Add(mp2);
}
public event EventHandler PictureBoxShowFlagChanged;
public bool PictureBoxShowFlag {
get { return this.pictureBoxShowFlag; }
set {
if (this.pictureBoxShowFlag != value) {
pictureBoxShowFlag = value;
if (this.PictureBoxShowFlagChanged != null) {
this.PictureBoxShowFlagChanged(this, new EventArgs());
}
}
}
}
private bool pictureBoxShowFlag = true;
private void cmdFlip_Click( object sender, EventArgs e ) {
this.PictureBoxShowFlag = !this.PictureBoxShowFlag;
}
}
Extended PictureBox Code:
public class MyPictureBox : PictureBox {
public MyPictureBox() : base() {
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.ParentChanged += new EventHandler(MyPictureBox_ParentChanged);
}
private void MyPictureBox_ParentChanged( object sender, EventArgs e ) {
try {
PictureBoxForm pbf = (PictureBoxForm)this.Parent;
this.Visible = pbf.PictureBoxShowFlag;
pbf.PictureBoxShowFlagChanged += new
EventHandler(pbf_PictureBoxShowFlagChanged);
} catch { }
}
private void pbf_PictureBoxShowFlagChanged( object sender, EventArgs e ) {
PictureBoxForm pbf = (PictureBoxForm)sender;
this.Visible = pbf.PictureBoxShowFlag;
}
}
...or just put 'em all on a Panel, and change the panel's visibility.

WPF: Handle a large number of routed events

I need your recomandations for the following problem:
Let say you have a MyView type (UserControl), which defines a routed event IsSelectedChanged. It is raised every time myView.IsSelected property value is changed.
Also, you have a MyContainer (Canvas), which contains a very (very!) large number of children of type MyView. MyContainer has routed event MyViewsSelectionChanged, that is raised whenever MyViewsSelection is changed. MyViewsSelection is a set of MyView objects that have IsSelected property set to true. MyContainer will handle MyView.IsSelectedChanged for every child and will provide its MyViewSelection status to the MyContainerParent (Panel)
MyContainerParent will handle myContainer.MyViewsSelectionChanged event
The issue I am afraid of is that my application will under-perform for a very large selection of MyView objects, resulting in a sort of 'wildfire' of events.
Any recomandations to prevent the issue, will be much appreciated!
Thanks
some code:
BatchView.IsSelectedChanged (MyView):
public static readonly RoutedEvent IsSelectedChangedEvent = EventManager.RegisterRoutedEvent(
"IsSelectedChanged",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(BatchView)
);
/// <summary>
/// Occurs when IsSelected property value is changed.
/// </summary>
public event RoutedEventHandler IsSelectedChanged {
add { AddHandler(IsSelectedChangedEvent, value); }
remove { RemoveHandler(IsSelectedChangedEvent, value); }
}
void RaiseIsSelectionChangedEvent() {
RoutedEventArgs e = new RoutedEventArgs(IsSelectedChangedEvent, this.BatchViewModel);
RaiseEvent(e);
Logger.Debug("IsSelectionChanged: {0}; IsSelected = {1}", this.BatchViewModel.Description, this.IsSelected);
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached(
"IsSelected",
typeof(bool),
typeof(BatchView),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(delegate(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
BatchView view = sender as BatchView;
bool isSelected = Convert.ToBoolean(args.NewValue);
if ( view != null ) {
view._border.BorderBrush = isSelected ? Brushes.Magenta : Brushes.Black;
view.IsPrimarySelected = view.IsFocused && isSelected;
}
})));
/// <summary>
/// Get/set whether this batch view is selected
/// </summary>
public bool IsSelected {
get { return (bool)GetValue(IsSelectedProperty); }
set {
if ( IsSelected != value ) {
SetValue(IsSelectedProperty, value);
RaiseIsSelectionChangedEvent();
}
}
}
GanttView (MyContainer):
static GanttView() {
EventManager.RegisterClassHandler(typeof(BatchView), BatchView.IsSelectedChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var batchView = sender as BatchView;
var ganttView = batchView.FindVisualParent<GanttView>();
if ( ganttView != null ) {
ganttView.RaiseBatchViewsSelectionChangedEvent();
}
args.Handled = true;
}));
}
public static readonly RoutedEvent BatchViewsSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"BatchViewsSelectionChanged",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(GanttView)
);
public event RoutedEventHandler BatchViewsSelectionChanged {
add { AddHandler(BatchViewsSelectionChangedEvent, value); }
remove { RemoveHandler(BatchViewsSelectionChangedEvent, value); }
}
void RaiseBatchViewsSelectionChangedEvent() {
RoutedEventArgs e = new RoutedEventArgs(BatchViewsSelectionChangedEvent, this);
RaiseEvent(e);
Logger.Debug("BatchViewsSelectionChanged: {0};", this.SelectedBatchViews.Count());
}
SchedulerView (MyContainerParent):
static SchedulerView() {
EventManager.RegisterClassHandler(typeof(GanttView), GanttView.BatchViewsSelectionChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var schedulerView = ((GanttView)sender).FindVisualParent<SchedulerView>();
if ( schedulerView != null ) {
if ( schedulerView.BatchesSelectionChanged != null ) {
BatchesSelectionChangedEventArgs e = new BatchesSelectionChangedEventArgs();
e.SelectedBatchesCount = schedulerView.GanttView.SelectedBatchViews.Count();
e.TotalBatchesDuration = schedulerView.GanttView.SelectedBatchViews.Sum<BatchView>(bv => bv.BatchViewModel.Model.Duration);
e.TotalBatchesQuantity = schedulerView.GanttView.SelectedBatchViews.Sum<BatchView>(bv => bv.BatchViewModel.Model.Quantity);
schedulerView.BatchesSelectionChanged(schedulerView, e);
}
}
}));
}
If you are concerned about the number of events you have to process. You should re-evaluate your approach. Is there a way to determine when the user has finished selecting the items?
If there is no way of reducing the number of events then you might want to implement a throttle, i.e. you only process an event if no event has been received for a certain amount of time.
You can implement this yourself - e.g. by using a timer - or you can use the reactive extensions' (RX) Throttle function.
Throttle "Ignores the values from an observable sequence which are
followed by another value before due time with the specified source
and dueTime"
You can find the RX at http://msdn.microsoft.com/en-us/data/gg577609.aspx and the documentation for Trottle at http://msdn.microsoft.com/en-us/library/hh229298%28v=vs.103%29. Obviously you can also install RX via NuGet.
I want to share the solution to my problem. Solution is based on recomandation given by my manager and Obalix. So, I will use a timer, to delay a bit the raising of GanttView.BacthViewsSelectionChangedEvent.
static GanttView() {
EventManager.RegisterClassHandler(typeof(BatchView), BatchView.IsSelectedChangedEvent, new RoutedEventHandler(delegate(object sender, RoutedEventArgs args) {
var batchView = sender as BatchView;
var ganttView = batchView.FindVisualParent<GanttView>();
if ( ganttView != null && !ganttView._batchViewIsSelectedChangedEventQueued ) {
ganttView._batchViewIsSelectedChangedEventQueued = true;
System.Timers.Timer eventTrigger = new System.Timers.Timer(100) { AutoReset = false };
eventTrigger.Start();
eventTrigger.Elapsed += new System.Timers.ElapsedEventHandler(delegate(object timer, System.Timers.ElapsedEventArgs e) {
ganttView._batchViewIsSelectedChangedEventQueued = false;
ganttView.Dispatcher.Invoke(new Action(delegate() { ganttView.RaiseBatchViewsSelectionChangedEvent(); }), DispatcherPriority.Normal, null);
});
}
args.Handled = true;
}));
}
It is important to use the Dispatcher property for invokation of ganttView.RaiseBatchViewsSelectionChangedEvent(), otherwise you will get an exception (" The calling thread cannot access this object because a different thread owns it."), please refer to this post http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
Dan and Obalix, thank you a lot for your time and considerations!

Silverlight LineDataPoint Increase Size on MouseOver

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" ...

WPF treeview: how to implement keyboard navigation like in Explorer?

I am using the WPF treeview for the first time and am astonished of all the basic things it does not do. One of those is keyboard navigation, implemented in any self-respecting treeview, e.g. in Windows Explorer or Regedit.
This is how it should work:
If the treeview has the focus and I type (letters/numbers) the selection should move to the first visible (aka expanded) item below the currently selected item that matches the string I typed and bring that into view. If not match is found below the current item the search should continue from the top. If no match is found, the selected item should not change.
As long as I continue typing, the search string grows and the search is refined. If I stop typing for a certain time (2-5 seconds), the search string is emptied.
I am prepared to program this "by hand" from scratch, but since this is so very basic I thought surely someone has already done exactly this.
Funny, this does not seem to be a popular topic. Anyway, in the meantime I have developed a solution to the problem that satisfies me:
I attach a behavior to the TreeViewItems. In that behavior, I handle KeyUp events. In the KeyUp event handler, I search the visual tree top to bottom as it is displayed. If I find a first matching node (whose name starts with the letter on the key pressed) I select that node.
I know that is an old topic, but I guess it is still relevant for some people. I made this solution. It is attached to the KeyUp and the TextInput event on a WPF TreeView. I'm using TextInput in addition to KeyUp as I had difficulty translating "national" chars to real chars with KeyEventArgs. That went much more smooth with TextInput.
// <TreeView Name="treeView1" KeyUp="treeView1_KeyUp" TextInput="treeView1_TextInput"/>
private bool searchdeep = true; // Searches in subitems
private bool searchstartfound = false; // true when current selected item is found. Ensures that you don't seach backwards and that you only search on the current level (if not searchdeep is true)
private string searchterm = ""; // what to search for
private DateTime LastSearch = DateTime.Now; // resets searchterm if last input is older than 1 second.
private void treeView1_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
// reset searchterm if any "special" key is pressed
if (e.Key < Key.A)
searchterm = "";
}
private void treeView1_TextInput(object sender, TextCompositionEventArgs e)
{
if ((DateTime.Now - LastSearch).Seconds > 1)
searchterm = "";
LastSearch = DateTime.Now;
searchterm += e.Text;
searchstartfound = treeView1.SelectedItem == null;
foreach (var t in treeView1.Items)
if (SearchTreeView((TreeViewItem) t, searchterm.ToLower()))
break;
}
private bool SearchTreeView(TreeViewItem node, string searchterm)
{
if (node.IsSelected)
searchstartfound = true;
// Search current level first
foreach (TreeViewItem subnode in node.Items)
{
// Search subnodes to the current node first
if (subnode.IsSelected)
{
searchstartfound = true;
if (subnode.IsExpanded)
foreach (TreeViewItem subsubnode in subnode.Items)
if (searchstartfound && subsubnode.Header.ToString().ToLower().StartsWith(searchterm))
{
subsubnode.IsSelected = true;
subsubnode.IsExpanded = true;
subsubnode.BringIntoView();
return true;
}
}
// Then search nodes on the same level
if (searchstartfound && subnode.Header.ToString().ToLower().StartsWith(searchterm))
{
subnode.IsSelected = true;
subnode.BringIntoView();
return true;
}
}
// If not found, search subnodes
foreach (TreeViewItem subnode in node.Items)
{
if (!searchstartfound || searchdeep)
if (SearchTreeView(subnode, searchterm))
{
node.IsExpanded = true;
return true;
}
}
return false;
}
I was also looking for keyboard navigation, amazing how not obvious the solution was for templated items.
Setting SelectedValuePath in ListView or TreeView gives this behavior.
If the items are templated then setting the attached property: TextSearch.TextPath to the path of the property to search on will also do the trick.
Hope this helps, it definitely worked for me.
Since this question comes up most prominently when searching, I wanted to post an answer to it.
The above post by lars doesn't work for me when I'm using a databound TreeView with a HierarchicalDataTemplate, because the Items collection returns the actual databound items, not the TreeViewItem.
I ended up solving this by using the ItemContainerGenerator for individual data items, and the VisualTreeHelper to search "up" to find the parent node (if any). I implemented this as a static helper class so that I can easily reuse it (which for me is basically every TreeView).
Here's my helper class:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace TreeViewHelpers
{
public static class TreeViewItemTextSearcher
{
private static bool checkIfMatchesText(TreeViewItem node, string searchterm, StringComparison comparison)
{
return node.Header.ToString().StartsWith(searchterm, comparison);
}
//https://stackoverflow.com/questions/26624982/get-parent-treeviewitem-of-a-selected-node-in-wpf
public static TreeViewItem getParentItem(TreeViewItem item)
{
try
{
var parent = VisualTreeHelper.GetParent(item as DependencyObject);
while ((parent as TreeViewItem) == null)
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent as TreeViewItem;
}
catch (Exception e)
{
//could not find a parent of type TreeViewItem
return null;
}
}
private static bool tryFindChild(
int startindex,
TreeViewItem node,
string searchterm,
StringComparison comparison,
out TreeViewItem foundnode
)
{
foundnode = null;
if (!node.IsExpanded) { return false; }
for (int i = startindex; i < node.Items.Count; i++)
{
object item = node.Items[i];
object tviobj = node.ItemContainerGenerator.ContainerFromItem(item);
if (tviobj is null)
{
return false;
}
TreeViewItem tvi = (TreeViewItem)tviobj;
if (checkIfMatchesText(tvi, searchterm, comparison))
{
foundnode = tvi;
return true;
}
//recurse:
if (tryFindChild(tvi, searchterm, comparison, out foundnode))
{
return true;
}
}
return false;
}
private static bool tryFindChild(TreeViewItem node, string searchterm, StringComparison comparison, out TreeViewItem foundnode)
{
return tryFindChild(0, node, searchterm, comparison, out foundnode);
}
public static bool SearchTreeView(TreeViewItem node, string searchterm, StringComparison comparison, out TreeViewItem found)
{
//search children:
if (tryFindChild(node, searchterm, comparison, out found))
{
return true;
}
//search nodes same level as this:
TreeViewItem parent = getParentItem(node);
object boundobj = node.DataContext;
if (!(parent is null || boundobj is null))
{
int startindex = parent.Items.IndexOf(boundobj);
if (tryFindChild(startindex + 1, parent, searchterm, comparison, out found))
{
return true;
}
}
found = null;
return false;
}
}
}
I also save the last selected node, as described in this post:
<TreeView ... TreeViewItem.Selected="TreeViewItemSelected" ... />
private TreeViewItem lastSelectedTreeViewItem;
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
this.lastSelectedTreeViewItem = tvi;
}
And here's the above TextInput, modified to use this class:
private void treeView_TextInput(object sender, TextCompositionEventArgs e)
{
if ((DateTime.Now - LastSearch).Seconds > 1) { searchterm = ""; }
LastSearch = DateTime.Now;
searchterm += e.Text;
if (lastSelectedTreeViewItem is null)
{
return;
}
TreeViewItem found;
if (TreeViewHelpers.TreeViewItemTextSearcher.SearchTreeView(
node: lastSelectedTreeViewItem,
searchterm: searchterm,
comparison: StringComparison.CurrentCultureIgnoreCase,
out found
))
{
found.IsSelected = true;
found.BringIntoView();
}
}
Note that this solution is a little bit different from the above, in that I only search the children of the selected node, and the nodes at the same level as the selected node.
It is not very straightforward as we expect it to be. But the best solution I have found is here:
http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode
Let me know if you need more details.

Resources