Treeview ContainerFromItem always returns null - c#

I've read a few threads on this subject but couldn't find anything to do what I'm trying to do. I have a treeview that is bound to a hierarchical set of objects. Each of these objects represents an icon on a map. When the user clicks one of the icons on the map, I want to select the item in the tree view, focus on it, and scroll it into view. The map object has a list of the objects that are bound to the treeview. In the example, Thing is the type of object bound to the tree.
public void ScrollIntoView(Thing t)
{
if (t != null)
{
t.IsSelected = true;
t.IsExpanded = true;
TreeViewItem container = (TreeViewItem)(masterTreeView
.ItemContainerGenerator.ContainerFromItem(t));
if (container != null)
{
container.Focus();
LogicalTreeHelper.BringIntoView(container);
}
}
}
So far, no matter what I've tried, container is always null. Any ideas?

Is the item actually a child of the masterTreeView?
This might actually be quite difficult since TreeViewItems are ItemsControls with their own ItemContainerGenerator which means you should only be able to get the container from the immediate parent's ItemContainerGenerator and not from the root.
Some recursive function which first goes up the hierarchy to the root and then reverses this route on the ui level always getting the container of the items might work out but your data-items need a reference to their logical parent data object.

The issue is that each TreeViewItem is itself an ItemsControl so they each manage their own containers for their children.

You have 3 choices:
You disable the virtualization of items: <TreeView VirtualizingStackPanel.IsVirtualizing="False">, but this could have impact on performance
You manage ItemContainerGenerator status for each item (some code provided as examples). Pretty complex.
You add a hierarchical view model to your hierarchy and implement a IsExpanded property to it for each node level. The best solution.
Disable virtualization:
<TreeView VirtualizingStackPanel.IsVirtualizing="False">
Good Luck...
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using HQ.Util.General;
namespace HQ.Util.Wpf.WpfUtil
{
public static class TreeViewExtensions
{
// ******************************************************************
public delegate void OnTreeViewVisible(TreeViewItem tvi);
public delegate void OnItemExpanded(TreeViewItem tvi, object item);
public delegate void OnAllItemExpanded();
// ******************************************************************
private static void SetItemHierarchyVisible(ItemContainerGenerator icg, IList listOfRootToNodeItemPath, OnTreeViewVisible onTreeViewVisible = null)
{
Debug.Assert(icg != null);
if (icg != null)
{
if (listOfRootToNodeItemPath.Count == 0) // nothing to do
return;
TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, always better to verify
{
listOfRootToNodeItemPath.RemoveAt(0);
if (listOfRootToNodeItemPath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodeItemPath, onTreeViewVisible);
}
}
else
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
tvi = icgSender.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, it is always better to verify
{
SetItemHierarchyVisible(icg, listOfRootToNodeItemPath, onTreeViewVisible);
actionHolder.Execute();
}
};
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
return;
}
}
}
// ******************************************************************
/// <summary>
/// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem
/// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
/// This method should work for Virtualized and non virtualized tree.
/// The difference with ExpandItem is that this one open up the tree up to the target but will not expand the target itself,
/// while ExpandItem expand the target itself.
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="listOfRootToNodePath">Any collectionic List. The collection should have every objet of the path to the targeted item from the root
/// to the target. For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2 (index 3)</param>
/// <param name="onTreeViewVisible">Optionnal</param>
public static void SetItemHierarchyVisible(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
SetItemHierarchyVisible(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
}
// ******************************************************************
private static void ExpandItem(ItemContainerGenerator icg, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
Debug.Assert(icg != null);
if (icg != null)
{
if (listOfRootToNodePath.Count == 0) // nothing to do
return;
TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, always better to verify
{
listOfRootToNodePath.RemoveAt(0);
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
if (listOfRootToNodePath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodePath, onTreeViewVisible);
}
}
else
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
tvi = icgSender.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, it is always better to verify
{
SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);
actionHolder.Execute();
}
};
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
return;
}
}
}
// ******************************************************************
/// <summary>
/// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem
/// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
/// This method should work for Virtualized and non virtualized tree.
/// The difference with SetItemHierarchyVisible is that this one open the target while SetItemHierarchyVisible does not try to expand the target.
/// (SetItemHierarchyVisible just ensure the target will be visible)
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="listOfRootToNodePath">The collection should have every objet of the path, from the root to the targeted item.
/// For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2</param>
/// <param name="onTreeViewVisible">Optionnal</param>
public static void ExpandItem(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
ExpandItem(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
}
// ******************************************************************
private static void ExpandSubWithContainersGenerated(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
{
ItemContainerGenerator icg = ic.ItemContainerGenerator;
foreach (object item in ic.Items)
{
var tvi = icg.ContainerFromItem(item) as TreeViewItem;
actionItemExpanded(tvi, item);
tvi.IsExpanded = true;
ExpandSubContainers(tvi, actionItemExpanded, referenceCounterTracker);
}
}
// ******************************************************************
/// <summary>
/// Expand any ItemsControl (TreeView, TreeViewItem, ListBox, ComboBox, ...) and their childs if any (TreeView)
/// </summary>
/// <param name="ic"></param>
/// <param name="actionItemExpanded"></param>
/// <param name="referenceCounterTracker"></param>
public static void ExpandSubContainers(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
{
ItemContainerGenerator icg = ic.ItemContainerGenerator;
{
if (icg.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
}
else if (icg.Status == GeneratorStatus.NotStarted)
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
if (icgSender.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
// Never use the following method in BeginInvoke due to ICG recycling. The same icg could be
// used and will keep more than one subscribers which is far from being intended
// ic.Dispatcher.BeginInvoke(actionHolder.Action, DispatcherPriority.Background);
// Very important to unsubscribe as soon we've done due to ICG recycling.
actionHolder.Execute();
referenceCounterTracker.ReleaseRef();
}
};
referenceCounterTracker.AddRef();
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
// Next block is only intended to protect against any race condition (I don't know if it is possible ? How Microsoft implemented it)
// I mean the status changed before I subscribe to StatusChanged but after I made the check about its state.
if (icg.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
}
}
}
}
// ******************************************************************
/// <summary>
/// This method is asynchronous.
/// Expand all items and subs recursively if any. Does support virtualization (item recycling).
/// But honestly, make you a favor, make your life easier en create a model view around your hierarchy with
/// a IsExpanded property for each node level and bind it to each TreeView node level.
/// </summary>
/// <param name="treeView"></param>
/// <param name="actionItemExpanded"></param>
/// <param name="actionAllItemExpanded"></param>
public static void ExpandAll(this TreeView treeView, Action<TreeViewItem, object> actionItemExpanded = null, Action actionAllItemExpanded = null)
{
var referenceCounterTracker = new ReferenceCounterTracker(actionAllItemExpanded);
referenceCounterTracker.AddRef();
treeView.Dispatcher.BeginInvoke(new Action(() => ExpandSubContainers(treeView, actionItemExpanded, referenceCounterTracker)), DispatcherPriority.Background);
referenceCounterTracker.ReleaseRef();
}
// ******************************************************************
}
}
And
using System;
using System.Threading;
namespace HQ.Util.General
{
public delegate void CountToZeroAction();
public class ReferenceCounterTracker
{
private Action _actionOnCountReachZero = null;
private int _count = 0;
public ReferenceCounterTracker(Action actionOnCountReachZero)
{
_actionOnCountReachZero = actionOnCountReachZero;
}
public void AddRef()
{
Interlocked.Increment(ref _count);
}
public void ReleaseRef()
{
int count = Interlocked.Decrement(ref _count);
if (count == 0)
{
if (_actionOnCountReachZero != null)
{
_actionOnCountReachZero();
}
}
}
}
}

Related

How to Dispose subscriptions in a UserControl if you can change the VisualParent

I have a FooUserControl which subscribes on it's LoadedEvent. This UserControl can be placed else where on your gui (on any Window or inside of any Control). To avoid leaks, I have implemented some kind of disposing.
The problem with this solution:
If you put the FooUserControl on a TabItem of a TabControl and change the tabs, the OnVisualParentChanged() is called and the subscription is disposed. If I wouldn't add this method, and you close the TabItem the subscription is still alive in background, although the UserControl can be disposed. The same problem will occur with a page
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
public FooUserControl()
{
Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
var parentWindow = Window.GetWindow(this);
if(parentWindow != null)
parentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
// check if the parent visual has been changed
// can happen if you use the control on a page
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (oldParent != null)
{
_Dispose();
}
base.OnVisualParentChanged(oldParent);
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
I finally found a solution. In the UnLoaded event, I scan the Logical/VisualTree if there is still an instance present or not.
Since there is no real disposing mechanism in wpf, I have adopted this solution. I'm open for a better solution!
FooUserControl
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
private Window _ParentWindow;
public FooUserControl()
{
Loaded += _OnLoaded;
Unloaded += _OnUnloaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
_ParentWindow = Window.GetWindow(this);
_ParentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _OnUnloaded(object sender, RoutedEventArgs e)
{
// look in logical and visual tree if the control has been removed
if (_ParentWindow.FindChildByUid<NLogViewer>(Uid) == null)
{
_Dispose();
}
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
DependencyObjectExtensions
public static class DependencyObjectExtensions
{
/// <summary>
/// Analyzes both visual and logical tree in order to find all elements of a given
/// type that are descendants of the <paramref name="source"/> item.
/// </summary>
/// <typeparam name="T">The type of the queried items.</typeparam>
/// <param name="source">The root element that marks the source of the search. If the
/// source is already of the requested type, it will not be included in the result.</param>
/// <param name="uid">The UID of the <see cref="UIElement"/></param>
/// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
public static T FindChildByUid<T>(this DependencyObject source, string uid) where T : UIElement
{
if (source != null)
{
var childs = GetChildObjects(source);
foreach (DependencyObject child in childs)
{
//analyze if children match the requested type
if (child != null && child is T dependencyObject && dependencyObject.Uid.Equals(uid))
{
return dependencyObject;
}
var descendant = FindChildByUid<T>(child, uid);
if (descendant != null)
return descendant;
}
}
return null;
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
/// supports content elements. Keep in mind that for content elements,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="parent">The item to be processed.</param>
/// <returns>The submitted item's child elements, if available.</returns>
public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
{
if (parent == null) yield break;
if (parent is ContentElement || parent is FrameworkElement)
{
//use the logical tree for content / framework elements
foreach (object obj in LogicalTreeHelper.GetChildren(parent))
{
var depObj = obj as DependencyObject;
if (depObj != null) yield return (DependencyObject) obj;
}
}
else
{
//use the visual tree per default
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(parent, i);
}
}
}
}

How to disable Togglebutton from extended combobox class

I have a class that extends Combobox. In this class I can ONLY disable the togglebutton. I still want users to click into the combobox and type something in to filter its contents.
If you know any other way to destroy the togglebutton please let me know (as long as it doesn't involve recreating the template - I've tried and failed and the generated combobox template code is to large to dump in stackoverflow so I can't solve it that way. )
In the beginning I wanted to be able to toggle the togglebutton (if there less than 10 item left in the filter allow a user to click the togglebutton to show the list of items if there are more the togglebutton would disappear) . At this point I'll take any solution that shrinks, hides, moves, intentionally breaks, removes, buries alive, replaces either the togglebutton or the mouse on click event that tells the combobox that it should show its popup or anything in between that will stop this action.
This is my third question trying to find something that will work. Honestly any suggestions would be helpful (just no copy and edit the template please go to my other question where I ask help on how to implement that solution)
using Analytics_Module.Models;
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Analytics_Module.UI_Components
{
class MultiselectFilteredComboBox : ComboBox
{
////
// Public Fields
////
/// <summary>
/// The search string treshold length.
/// </summary>
/// <remarks>
/// It's implemented as a Dependency Property, so you can set it in a XAML template
/// </remarks>
public static readonly DependencyProperty MinimumSearchLengthProperty =
DependencyProperty.Register(
"MinimumSearchLength",
typeof(int),
typeof(MultiselectFilteredComboBox),
new UIPropertyMetadata(3));
////
// Private Fields
////
/// <summary>
/// Caches the previous value of the filter.
/// </summary>
private string oldFilter = string.Empty;
/// <summary>
/// Holds the current value of the filter.
/// </summary>
private string currentFilter = string.Empty;
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
////
// Constructors
////
/// <summary>
/// Initializes a new instance of the FilteredComboBox class.
/// </summary>
/// <remarks>
/// You could set 'IsTextSearchEnabled' to 'false' here,
/// to avoid non-intuitive behavior of the control
/// </remarks>
public MultiselectFilteredComboBox()
{
}
////
// Properties
////
/// <summary>
/// Gets or sets the search string treshold length.
/// </summary>
/// <value>The minimum length of the search string that triggers filtering.</value>
[Description("Length of the search string that triggers filtering.")]
[Category("Filtered ComboBox")]
[DefaultValue(3)]
public int MinimumSearchLength
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (int)this.GetValue(MinimumSearchLengthProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(MinimumSearchLengthProperty, value);
}
}
/// <summary>
/// Gets a reference to the internal editable textbox.
/// </summary>
/// <value>A reference to the internal editable textbox.</value>
/// <remarks>
/// We need this to get access to the Selection.
/// </remarks>
protected TextBox EditableTextBox
{
get
{
return this.GetTemplateChild("PART_EditableTextBox") as TextBox;
}
}
////
// Event Raiser Overrides
////
/// <summary>
/// Keep the filter if the ItemsSource is explicitly changed.
/// </summary>
/// <param name="oldValue">The previous value of the filter.</param>
/// <param name="newValue">The current value of the filter.</param>
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
/// <summary>
/// Confirm or cancel the selection when Tab, Enter, or Escape are hit.
/// Open the DropDown when the Down Arrow is hit.
/// </summary>
/// <param name="e">Key Event Args.</param>
/// <remarks>
/// The 'KeyDown' event is not raised for Arrows, Tab and Enter keys.
/// It is swallowed by the DropDown if it's open.
/// So use the Preview instead.
/// </remarks>
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Tab || e.Key == Key.Enter)
{
// Explicit Selection -> Close ItemsPanel
this.IsDropDownOpen = false;
}
else if (e.Key == Key.Escape)
{
// Escape -> Close DropDown and redisplay Filter
this.IsDropDownOpen = false;
this.SelectedIndex = -1;
this.Text = this.currentFilter;
}
else
{
if (e.Key == Key.Down)
{
// Arrow Down -> Open DropDown
this.IsDropDownOpen = true;
}
base.OnPreviewKeyDown(e);
}
// Cache text
this.oldFilter = this.Text;
}
/// <summary>
/// Modify and apply the filter.
/// </summary>
/// <param name="e">Key Event Args.</param>
/// <remarks>
/// Alternatively, you could react on 'OnTextChanged', but navigating through
/// the DropDown will also change the text.
/// </remarks>
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Up || e.Key == Key.Down)
{
// Navigation keys are ignored
}
else if (e.Key == Key.Tab || e.Key == Key.Enter)
{
// Explicit Select -> Clear Filter
this.ClearFilter();
}
else
{
// The text was changed
if (this.Text != this.oldFilter)
{
// Clear the filter if the text is empty,
// apply the filter if the text is long enough
if (this.Text.Length == 0 || this.Text.Length >= this.MinimumSearchLength)
{
this.RefreshFilter();
this.IsDropDownOpen = true;
// Unselect
this.EditableTextBox.SelectionStart = int.MaxValue;
}
}
base.OnKeyUp(e);
// Update Filter Value
this.currentFilter = this.Text;
}
}
/// <summary>
/// Make sure the text corresponds to the selection when leaving the control.
/// </summary>
/// <param name="e">A KeyBoardFocusChangedEventArgs.</param>
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
//this.ClearFilter();
//int temp = this.SelectedIndex;
//this.SelectedIndex = -1;
//this.Text = string.Empty;
//this.SelectedIndex = temp;
//base.OnPreviewLostKeyboardFocus(e);
}
////
// Helpers
////
/// <summary>
/// Re-apply the Filter.
/// </summary>
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
}
}
/// <summary>
/// Clear the Filter.
/// </summary>
private void ClearFilter()
{
this.currentFilter = string.Empty;
this.RefreshFilter();
}
/// <summary>
/// The Filter predicate that will be applied to each row in the ItemsSource.
/// </summary>
/// <param name="value">A row in the ItemsSource.</param>
/// <returns>Whether or not the item will appear in the DropDown.</returns>
private bool FilterPredicate(object value)
{
MultiSelectDropDownListEntry tmp = (MultiSelectDropDownListEntry)value;
// No filter, no text
if (value == null)
{
return false;
}
// No text, no filter
if (this.Text.Length == 0)
{
return true;
}
// Case insensitive search
return tmp.Name.ToString().ToLower().Contains(this.Text.ToLower());
}
}
}
Found a way.
I added this to the class
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
(((this.GetVisualChild(0) as Grid).Children)[1] as System.Windows.Controls.Primitives.ToggleButton).IsEnabled = false;
}

WPF Tab Control Attached Property

I am having difficulty getting an attached property working on a WPF Tab Control. I have implemented the class defined in the CodeProject tutorial
http://www.codeproject.com/Articles/349140/WPF-TabControl-focus-behavior-with-invisible-tabs
defined below.
namespace MyNamespace
{
public static class TabControlBehavior
{
public static readonly DependencyProperty FocusFirstVisibleTabProperty =
DependencyProperty.RegisterAttached("FocusFirstVisibleTab",
typeof(bool),
typeof(TabControlBehavior),
new FrameworkPropertyMetadata(OnFocusFirstVisibleTabPropertyChanged));
/// <summary>Gets the focus first visible tab value of the given element.
/// </summary>
/// <param name="element">The element.</param>
/// <returns></returns>
public static bool GetFocusFirstVisibleTab(TabControl element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(FocusFirstVisibleTabProperty);
}
/// <summary>Sets the focus first visible tab value of the given element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="value">if set to <c>true</c> [value].</param>
public static void SetFocusFirstVisibleTab(TabControl element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(FocusFirstVisibleTabProperty, value);
}
/// <summary>Determines whether the value of the dependency property <c>IsFocused</c> has change.
/// </summary>
/// <param name="d">The dependency object.</param>
/// <param name="e">The <see
/// cref="System.Windows.DependencyPropertyChangedEventArgs"/>
/// instance containing the event data.</param>
private static void OnFocusFirstVisibleTabPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tabControl = d as TabControl;
if (tabControl != null)
{
// Attach or detach the event handlers.
if ((bool)e.NewValue)
{
// Enable the attached behavior.
tabControl.Items.CurrentChanged += new EventHandler(TabControl_Items_CurrentChanged);
var collection = tabControl.Items as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged +=
new NotifyCollectionChangedEventHandler(TabControl_Items_CollectionChanged);
}
}
else
{
// Disable the attached behavior.
tabControl.Items.CurrentChanged -= new EventHandler(TabControl_Items_CurrentChanged);
var collection = tabControl.Items as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged -=
new NotifyCollectionChangedEventHandler(TabControl_Items_CollectionChanged);
}
// Detach handlers from the tab items.
foreach (var item in tabControl.Items)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged -=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
}
}
}
/// <summary>Handles the CollectionChanged event of the TabControl.Items collection.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
/// cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/>
/// instance containing the event data.</param>
static void TabControl_Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Attach event handlers to each tab so that when the Visibility property changes of the selected tab,
// the focus can be shifted to the next (or previous, if not next tab available) tab.
var collection = sender as ItemCollection;
if (collection != null)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
// Attach event handlers to the Visibility and IsEnabled properties.
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged +=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
}
// Detach event handlers from old items.
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged -=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
// Attach event handlers to the Visibility and IsEnabled properties.
foreach (var item in collection)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged +=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
break;
case NotifyCollectionChangedAction.Move:
default:
break;
}
// Select the first element if necessary.
if (collection.Count > 0 && collection.CurrentItem == null)
{
collection.MoveCurrentToFirst();
}
}
}
/// <summary>Handles the CurrentChanged event of the TabControl.Items collection.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/>
/// instance containing the event data.</param>
static void TabControl_Items_CurrentChanged(object sender, EventArgs e)
{
var collection = sender as ItemCollection;
if (collection != null)
{
UIElement element = collection.CurrentItem as UIElement;
if (element != null && element.Visibility != Visibility.Visible)
{
element.Dispatcher.BeginInvoke(new Action(() => collection.MoveCurrentToNext()),
System.Windows.Threading.DispatcherPriority.Input);
}
}
}
/// <summary>Handles the IsVisibleChanged event of the tab item.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
/// cref="System.Windows.DependencyPropertyChangedEventArgs"/>
/// instance containing the event data.</param>
static void TabItem_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TabItem tab = sender as TabItem;
if (tab != null && tab.IsSelected && tab.Visibility != Visibility.Visible)
{
// Move to the next tab item.
TabControl tabControl = tab.Parent as TabControl;
if (tabControl != null)
{
if (!tabControl.Items.MoveCurrentToNext())
{
// Could not move to next, try previous.
tabControl.Items.MoveCurrentToPrevious();
}
}
}
}
}
}
I then try to set the attached dependency property in my xaml code as follows :
<Window x:Class="MyNamespace.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:MyNamespace">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type TabControl}">
<Setter Property="local:TabControlBehavior.FocusFirstVisibleTab" Value="True" />
</Style>
</ResourceDictionary>
</Window.Resources>
</Window>
However I can't compile due to the following error
MC4003: Cannot resolve the Style Property 'FocusFirstVisibleTab'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property.
Thanks in advance for any help.
The problem was resolved by renaming MyNamespace to something other than the namespace containing my application.

Drag Drop Row behavior on WPF DataGrid

I am trying to make an attached behavior to reorder rows bby doing Drag & Drop
I found some solution (On Stackoverflow and by googling) and using them i am trying to make the behavior... I took the example from Hordcodenet website (i dont have the link now)
Code
public static class DragDropRowBehavior
{
private static DataGrid dataGrid;
private static Popup popup;
private static bool enable;
private static object draggedItem;
public static object DraggedItem
{
get { return DragDropRowBehavior.draggedItem; }
set { DragDropRowBehavior.draggedItem = value; }
}
public static Popup GetPopupControl(DependencyObject obj)
{
return (Popup)obj.GetValue(PopupControlProperty);
}
public static void SetPopupControl(DependencyObject obj, Popup value)
{
obj.SetValue(PopupControlProperty, value);
}
// Using a DependencyProperty as the backing store for PopupControl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupControlProperty =
DependencyProperty.RegisterAttached("PopupControl", typeof(Popup), typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged));
private static void OnPopupControlChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || !(e.NewValue is Popup))
{
throw new ArgumentException("Popup Control should be set", "PopupControl");
}
popup = e.NewValue as Popup;
dataGrid = depObject as DataGrid;
// Check if DataGrid
if (dataGrid == null)
return;
if (enable && popup != null)
{
dataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove += new MouseEventHandler(OnMouseMove);
}
else
{
dataGrid.BeginningEdit -= new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding -= new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove -= new MouseEventHandler(OnMouseMove);
dataGrid = null;
popup = null;
draggedItem = null;
IsEditing = false;
IsDragging = false;
}
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
// Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(DragDropRowBehavior), new UIPropertyMetadata(false,OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject depObject,DependencyPropertyChangedEventArgs e)
{
//Check if value is a Boolean Type
if (e.NewValue is bool == false)
throw new ArgumentException("Value should be of bool type", "Enabled");
enable = (bool)e.NewValue;
}
public static bool IsEditing { get; set; }
public static bool IsDragging { get; set; }
private static void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
IsEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (IsDragging) ResetDragDrop();
}
private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
IsEditing = false;
}
/// <summary>
/// Initiates a drag action if the grid is not in edit mode.
/// </summary>
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (IsEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(dataGrid));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
IsDragging = true;
DraggedItem = row.Item;
}
/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging || IsEditing)
{
return;
}
//get the target item
var targetItem = dataGrid.SelectedItem;
if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
{
//remove the source from the list
((dataGrid).ItemsSource as IList).Remove(DraggedItem);
//get target index
var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem);
//move source at the target's location
((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem);
//select the dropped item
dataGrid.SelectedItem = DraggedItem;
}
//reset
ResetDragDrop();
}
/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private static void ResetDragDrop()
{
IsDragging = false;
popup.IsOpen = false;
dataGrid.IsReadOnly = false;
}
/// <summary>
/// Updates the popup's position in case of a drag/drop operation.
/// </summary>
private static void OnMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return;
//display the popup if it hasn't been opened yet
if (!popup.IsOpen)
{
//switch to read-only mode
dataGrid.IsReadOnly = true;
//make sure the popup is visible
popup.IsOpen = true;
}
Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight);
popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize);
//make sure the row under the grid is being selected
Point position = e.GetPosition(dataGrid);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position);
if (row != null) dataGrid.SelectedItem = row.Item;
}
}
Helper Class
///
/// Common UI related helper methods.
///
public static class UIHelpers
{
#region find parent
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">A direct or indirect child of the
/// queried item.</param>
/// <returns>The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.</returns>
public static T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
//get parent item
DependencyObject parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
{
return parent;
}
else
{
//use recursion to proceed with next level
return TryFindParent<T>(parentObject);
}
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent"/> method, which also
/// supports content elements. Do note, that for content element,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>The submitted item's parent, if available. Otherwise
/// null.</returns>
public static DependencyObject GetParentObject(DependencyObject child)
{
if (child == null) return null;
ContentElement contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
FrameworkContentElement fce = contentElement as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
//if it's not a ContentElement, rely on VisualTreeHelper
return VisualTreeHelper.GetParent(child);
}
#endregion
#region update binding sources
/// <summary>
/// Recursively processes a given dependency object and all its
/// children, and updates sources of all objects that use a
/// binding expression on a given property.
/// </summary>
/// <param name="obj">The dependency object that marks a starting
/// point. This could be a dialog window or a panel control that
/// hosts bound controls.</param>
/// <param name="properties">The properties to be updated if
/// <paramref name="obj"/> or one of its childs provide it along
/// with a binding expression.</param>
public static void UpdateBindingSources(DependencyObject obj,
params DependencyProperty[] properties)
{
foreach (DependencyProperty depProperty in properties)
{
//check whether the submitted object provides a bound property
//that matches the property parameters
BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty);
if (be != null) be.UpdateSource();
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
//process child items recursively
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
UpdateBindingSources(childObject, properties);
}
}
#endregion
/// <summary>
/// Tries to locate a given item within the visual tree,
/// starting with the dependency object at a given position.
/// </summary>
/// <typeparam name="T">The type of the element to be found
/// on the visual tree of the element at the given location.</typeparam>
/// <param name="reference">The main element which is used to perform
/// hit testing.</param>
/// <param name="point">The position to be evaluated on the origin.</param>
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T : DependencyObject
{
DependencyObject element = reference.InputHitTest(point)
as DependencyObject;
if (element == null) return null;
else if (element is T) return (T)element;
else return TryFindParent<T>(element);
}
}
Problem is that the Event OnMouseLeftButtonDown is not called when i press it over a row to drag it... but OnMouseLeftButtonUp is called after that....
Is there ne way to do this....
I cant seem to find a way
Finally i got the Problem and also made some changes for this to work properly
I used this example to make the Drag Drop Logic amd made this behavior may be its use ful to others .... please suggest improvements i would be happy to change ...
Behavior
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Microsoft.Windows.Controls;
using System.Windows.Input;
using System.Collections;
namespace DataGridDragAndDrop
{
public static class DragDropRowBehavior
{
private static DataGrid dataGrid;
private static Popup popup;
private static bool enable;
private static object draggedItem;
public static object DraggedItem
{
get { return DragDropRowBehavior.draggedItem; }
set { DragDropRowBehavior.draggedItem = value; }
}
public static Popup GetPopupControl(DependencyObject obj)
{
return (Popup)obj.GetValue(PopupControlProperty);
}
public static void SetPopupControl(DependencyObject obj, Popup value)
{
obj.SetValue(PopupControlProperty, value);
}
// Using a DependencyProperty as the backing store for PopupControl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupControlProperty =
DependencyProperty.RegisterAttached("PopupControl", typeof(Popup), typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged));
private static void OnPopupControlChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || !(e.NewValue is Popup))
{
throw new ArgumentException("Popup Control should be set", "PopupControl");
}
popup = e.NewValue as Popup;
dataGrid = depObject as DataGrid;
// Check if DataGrid
if (dataGrid == null)
return;
if (enable && popup != null)
{
dataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove += new MouseEventHandler(OnMouseMove);
}
else
{
dataGrid.BeginningEdit -= new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding -= new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove -= new MouseEventHandler(OnMouseMove);
dataGrid = null;
popup = null;
draggedItem = null;
IsEditing = false;
IsDragging = false;
}
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
// Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(DragDropRowBehavior), new UIPropertyMetadata(false,OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject depObject,DependencyPropertyChangedEventArgs e)
{
//Check if value is a Boolean Type
if (e.NewValue is bool == false)
throw new ArgumentException("Value should be of bool type", "Enabled");
enable = (bool)e.NewValue;
}
public static bool IsEditing { get; set; }
public static bool IsDragging { get; set; }
private static void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
IsEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (IsDragging) ResetDragDrop();
}
private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
IsEditing = false;
}
/// <summary>
/// Initiates a drag action if the grid is not in edit mode.
/// </summary>
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (IsEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(dataGrid));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
IsDragging = true;
DraggedItem = row.Item;
}
/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging || IsEditing)
{
return;
}
//get the target item
var targetItem = dataGrid.SelectedItem;
if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
{
//get target index
var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem);
//remove the source from the list
((dataGrid).ItemsSource as IList).Remove(DraggedItem);
//move source at the target's location
((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem);
//select the dropped item
dataGrid.SelectedItem = DraggedItem;
}
//reset
ResetDragDrop();
}
/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private static void ResetDragDrop()
{
IsDragging = false;
popup.IsOpen = false;
dataGrid.IsReadOnly = false;
}
/// <summary>
/// Updates the popup's position in case of a drag/drop operation.
/// </summary>
private static void OnMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return;
popup.DataContext = DraggedItem;
//display the popup if it hasn't been opened yet
if (!popup.IsOpen)
{
//switch to read-only mode
dataGrid.IsReadOnly = true;
//make sure the popup is visible
popup.IsOpen = true;
}
Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight);
popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize);
//make sure the row under the grid is being selected
Point position = e.GetPosition(dataGrid);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position);
if (row != null) dataGrid.SelectedItem = row.Item;
}
}
}
UIHelper Class
public static class UIHelpers
{
#region find parent
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">A direct or indirect child of the
/// queried item.</param>
/// <returns>The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.</returns>
public static T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
//get parent item
DependencyObject parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
{
return parent;
}
else
{
//use recursion to proceed with next level
return TryFindParent<T>(parentObject);
}
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent"/> method, which also
/// supports content elements. Do note, that for content element,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>The submitted item's parent, if available. Otherwise
/// null.</returns>
public static DependencyObject GetParentObject(DependencyObject child)
{
if (child == null) return null;
ContentElement contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
FrameworkContentElement fce = contentElement as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
//if it's not a ContentElement, rely on VisualTreeHelper
return VisualTreeHelper.GetParent(child);
}
#endregion
#region update binding sources
/// <summary>
/// Recursively processes a given dependency object and all its
/// children, and updates sources of all objects that use a
/// binding expression on a given property.
/// </summary>
/// <param name="obj">The dependency object that marks a starting
/// point. This could be a dialog window or a panel control that
/// hosts bound controls.</param>
/// <param name="properties">The properties to be updated if
/// <paramref name="obj"/> or one of its childs provide it along
/// with a binding expression.</param>
public static void UpdateBindingSources(DependencyObject obj,
params DependencyProperty[] properties)
{
foreach (DependencyProperty depProperty in properties)
{
//check whether the submitted object provides a bound property
//that matches the property parameters
BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty);
if (be != null) be.UpdateSource();
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
//process child items recursively
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
UpdateBindingSources(childObject, properties);
}
}
#endregion
/// <summary>
/// Tries to locate a given item within the visual tree,
/// starting with the dependency object at a given position.
/// </summary>
/// <typeparam name="T">The type of the element to be found
/// on the visual tree of the element at the given location.</typeparam>
/// <param name="reference">The main element which is used to perform
/// hit testing.</param>
/// <param name="point">The position to be evaluated on the origin.</param>
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T : DependencyObject
{
DependencyObject element = reference.InputHitTest(point)
as DependencyObject;
if (element == null) return null;
else if (element is T) return (T)element;
else return TryFindParent<T>(element);
}
}
Usage
<!-- Drag and Drop Popup -->
<Popup x:Name="popup1"
AllowsTransparency="True"
IsHitTestVisible="False"
Placement="RelativePoint"
PlacementTarget="{Binding ElementName=shareGrid}">
<!-- Your own Popup construction Use properties of DraggedObject inside for Binding -->
<TextBlock Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold"
<!-- I used name property of in my Dragged row -->
Text="{Binding Path=Name}" />
</Popup>
<DataGrid x:Name="myDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="False"
ItemsSource="{Binding}"
SelectionMode="Single"
this:DragDropRowBehavior.Enabled="True"
this:DragDropRowBehavior.PopupControl="{Binding ElementName=popup1}"></DataGrid >
I've created a behavior out of #Dave solution:
public class DragDropRowBehavior : Behavior<DataGrid>
{
private object draggedItem;
private bool isEditing;
private bool isDragging;
#region DragEnded
public static readonly RoutedEvent DragEndedEvent =
EventManager.RegisterRoutedEvent("DragEnded", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DragDropRowBehavior));
public static void AddDragEndedHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
uie.AddHandler(DragDropRowBehavior.DragEndedEvent, handler);
}
public static void RemoveDragEndedHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
uie.RemoveHandler(DragDropRowBehavior.DragEndedEvent, handler);
}
private void RaiseDragEndedEvent()
{
var args = new RoutedEventArgs(DragDropRowBehavior.DragEndedEvent);
AssociatedObject.RaiseEvent(args);
}
#endregion
#region Popup
public static readonly DependencyProperty PopupProperty =
DependencyProperty.Register("Popup", typeof(System.Windows.Controls.Primitives.Popup), typeof(DragDropRowBehavior));
public System.Windows.Controls.Primitives.Popup Popup
{
get { return (System.Windows.Controls.Primitives.Popup)GetValue(PopupProperty); }
set { SetValue(PopupProperty, value); }
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.BeginningEdit += OnBeginEdit;
AssociatedObject.CellEditEnding += OnEndEdit;
AssociatedObject.MouseLeftButtonUp += OnMouseLeftButtonUp;
AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
AssociatedObject.MouseMove += OnMouseMove;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.BeginningEdit -= OnBeginEdit;
AssociatedObject.CellEditEnding -= OnEndEdit;
AssociatedObject.MouseLeftButtonUp -= OnMouseLeftButtonUp;
AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
AssociatedObject.MouseMove -= OnMouseMove;
Popup = null;
draggedItem = null;
isEditing = false;
isDragging = false;
}
private void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
isEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (isDragging) ResetDragDrop();
}
private void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
isEditing = false;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(AssociatedObject));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
isDragging = true;
draggedItem = row.Item;
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!isDragging || isEditing)
return;
//get the target item
var targetItem = AssociatedObject.SelectedItem;
if (targetItem == null || !ReferenceEquals(draggedItem, targetItem))
{
//get target index
var targetIndex = ((AssociatedObject).ItemsSource as IList).IndexOf(targetItem);
//remove the source from the list
((AssociatedObject).ItemsSource as IList).Remove(draggedItem);
//move source at the target's location
((AssociatedObject).ItemsSource as IList).Insert(targetIndex, draggedItem);
//select the dropped item
AssociatedObject.SelectedItem = draggedItem;
RaiseDragEndedEvent();
}
//reset
ResetDragDrop();
}
private void ResetDragDrop()
{
isDragging = false;
Popup.IsOpen = false;
AssociatedObject.IsReadOnly = false;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (!isDragging || e.LeftButton != MouseButtonState.Pressed)
return;
Popup.DataContext = draggedItem;
//display the popup if it hasn't been opened yet
if (!Popup.IsOpen)
{
//switch to read-only mode
AssociatedObject.IsReadOnly = true;
//make sure the popup is visible
Popup.IsOpen = true;
}
var popupSize = new Size(Popup.ActualWidth, Popup.ActualHeight);
Popup.PlacementRectangle = new Rect(e.GetPosition(AssociatedObject), popupSize);
//make sure the row under the grid is being selected
var position = e.GetPosition(AssociatedObject);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(AssociatedObject, position);
if (row != null) AssociatedObject.SelectedItem = row.Item;
}
}
Hope it helps the future users :)
if the event is not fired at the desired object, it is because some other control is "swallowing" it. Try to use OnPreviewMouseLeftButtonDown instead. If preview is not showing the event, try to move that preview method to a parent class.

How to bind a Command to the ListView's ItemSelectionChanged event?

Is it possible to do bind a Command to the ListView's ItemSelectionChanged? How? If not possible, is there another way to call a command on when the list view's selection changes?
Yes -- the MVVM-light framework has support to databind Commands to Routed Events. See http://blog.galasoft.ch/archive/2009/11/05/mvvm-light-toolkit-v3-alpha-2-eventtocommand-behavior.aspx
<Rectangle Fill="White"
Stroke="Black"
Width="200"
Height="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding TestCommand,
Mode=OneWay}"
CommandParameter="{Binding Text,
ElementName=MyTextBox,
Mode=OneWay}"
MustToggleIsEnabledValue="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
at first, i use in my project a command where the selectedItems a passed as parameter. downside of this approach the user has to klick the command button to start the action, but this fulfill my requirements in most cases.
on way to achieve what you want is something like erash posted - to use a kind of EventToCommand behavior like mvvm-light has.
another way is to create a attached behavior to bind to the selecteditems in your viewmodel. you have to adept the following code to ListView or create something more general :)
<DataGrid AttachedProperties:DatagridService.SelectedItemsSource="{Binding SelectedItems, Mode=OneWay}" />
ViewModel:
public ObservableCollection<object> SelectedItems//init collection just ONCE!
{
get
{
if (this.selectedItems == null)
{
this.selectedItems = new ObservableCollection<object>();
this.selectedItems.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
}
return this.selectedItems;
}
}
private void SelectedItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//this is called when ever the selecteditems changed
}
Attached Behavior:
public static class DatagridService
{
#region SelectedItemSource
#region Attached Property Declaration
/// <summary>
/// Identifies the ListBoxExtension.SelectedItemsSource attached property.
/// </summary>
public static readonly DependencyProperty SelectedItemsSourceProperty =
DependencyProperty.RegisterAttached(
"SelectedItemsSource",
typeof(IList),
typeof(DatagridService),
new PropertyMetadata(
null,
new PropertyChangedCallback(OnSelectedItemsSourceChanged)));
#endregion
#region Attached Property Accessors
/// <summary>
/// Gets the IList that contains the values that should be selected.
/// </summary>
/// <param name="element">The ListBox to check.</param>
public static IList GetSelectedItemsSource(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (IList)element.GetValue(SelectedItemsSourceProperty);
}
/// <summary>
/// Sets the IList that contains the values that should be selected.
/// </summary>
/// <param name="element">The ListBox being set.</param>
/// <param name="value">The value of the property.</param>
public static void SetSelectedItemsSource(DependencyObject element, IList value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(SelectedItemsSourceProperty, value);
}
#endregion
#region IsResynchingProperty
// Used to set a flag on the ListBox to avoid reentry of SelectionChanged due to
// a full syncronisation pass
private static readonly DependencyPropertyKey IsResynchingPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
"IsResynching",
typeof(bool),
typeof(DatagridService),
new PropertyMetadata(false));
#endregion
#region Private Static Methods
private static void OnSelectedItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGrid grd = d as DataGrid;
if (grd == null)
throw new InvalidOperationException("The DataGridExtension.SelectedItemsSource attached " +
"property can only be applied to DataGrid controls.");
grd.SelectionChanged -= new SelectionChangedEventHandler(OnDataGridSelectionChanged);
if (e.NewValue != null)
{
ListenForChanges(grd);
}
BindingExpression bexp = grd.GetBindingExpression(SelectedItemsSourceProperty);
if (bexp != null)
bexp.UpdateSource();
}
private static void ListenForChanges(DataGrid grd)
{
// Wait until the element is initialised
if (!grd.IsInitialized)
{
EventHandler callback = null;
callback = delegate
{
grd.Initialized -= callback;
ListenForChanges(grd);
};
grd.Initialized += callback;
return;
}
grd.SelectionChanged += new SelectionChangedEventHandler(OnDataGridSelectionChanged);
ResynchList(grd);
}
private static void OnDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid grd = sender as DataGrid;
if (grd != null)
{
bool isResynching = (bool)grd.GetValue(IsResynchingPropertyKey.DependencyProperty);
if (isResynching)
return;
IList list = GetSelectedItemsSource(grd);
if (list != null)
{
foreach (object obj in e.RemovedItems)
{
if (list.Contains(obj))
list.Remove(obj);
}
foreach (object obj in e.AddedItems)
{
if (!list.Contains(obj))
list.Add(obj);
}
}
}
}
private static void ResynchList(DataGrid grd)
{
if (grd != null)
{
grd.SetValue(IsResynchingPropertyKey, true);
IList list = GetSelectedItemsSource(grd);
if (grd.SelectionMode == DataGridSelectionMode.Single)
{
grd.SelectedItem = null;
if (list != null)
{
if (list.Count > 1)
{
// There is more than one item selected, but the listbox is in Single selection mode
throw new InvalidOperationException("DataGrid is in Single selection mode, but was given more than one selected value.");
}
if (list.Count == 1)
grd.SelectedItem = list[0];
}
}
else
{
grd.SelectedItems.Clear();
if (list != null)
{
foreach (object obj in grd.Items)
if (list.Contains(obj))
grd.SelectedItems.Add(obj);
}
}
grd.SetValue(IsResynchingPropertyKey, false);
}
}
#endregion
#endregion
}

Categories