I have a data-bound TreeView and I want to bind SelectedItem. This attached behavior works perfectly without HierarchicalDataTemplate but with it the attached behavior only works one way (UI to data) not the other because now e.NewValue is MyViewModel not TreeViewItem.
This is a code snippet from the attached behavior:
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
This is my TreeView definition:
<Window xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<TreeView ItemsSource="{Binding MyItems}" VirtualizingStackPanel.IsVirtualizing="True">
<interactivity:Interaction.Behaviors>
<behaviors:TreeViewSelectedItemBindingBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</interactivity:Interaction.Behaviors>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyViewModel}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Window>
If I can get a reference to the TreeView in the attached behavior method OnSelectedItemChanged, maybe I can use the answers in this question to get the TreeViewItem but I don't know how to get there. Does anyone know how and is it the right way to go?
Here is an improved version of the above mentioned attached behavior. It fully supports twoway binding and also works with HeriarchicalDataTemplate and TreeViews where its items are virtualized. Please note though that to find the 'TreeViewItem' that needs to be selected, it will realize (i.e. create) the virtualized TreeViewItems until it finds the right one. This could potentially be a performance problem with big virtualized trees.
/// <summary>
/// Behavior that makes the <see cref="System.Windows.Controls.TreeView.SelectedItem" /> bindable.
/// </summary>
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
/// <summary>
/// Identifies the <see cref="SelectedItem" /> dependency property.
/// </summary>
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(BindableSelectedItemBehavior),
new UIPropertyMetadata(null, OnSelectedItemChanged));
/// <summary>
/// Gets or sets the selected item of the <see cref="TreeView" /> that this behavior is attached
/// to.
/// </summary>
public object SelectedItem
{
get
{
return this.GetValue(SelectedItemProperty);
}
set
{
this.SetValue(SelectedItemProperty, value);
}
}
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += this.OnTreeViewSelectedItemChanged;
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has
/// actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= this.OnTreeViewSelectedItemChanged;
}
}
private static Action<int> GetBringIndexIntoView(Panel itemsHostPanel)
{
var virtualizingPanel = itemsHostPanel as VirtualizingStackPanel;
if (virtualizingPanel == null)
{
return null;
}
var method = virtualizingPanel.GetType().GetMethod(
"BringIndexIntoView",
BindingFlags.Instance | BindingFlags.NonPublic,
Type.DefaultBinder,
new[] { typeof(int) },
null);
if (method == null)
{
return null;
}
return i => method.Invoke(virtualizingPanel, new object[] { i });
}
/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
if (container != null)
{
if (container.DataContext == item)
{
return container as TreeViewItem;
}
// Expand the current container
if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
{
container.SetValue(TreeViewItem.IsExpandedProperty, true);
}
// Try to generate the ItemsPresenter and the ItemsPanel.
// by calling ApplyTemplate. Note that in the
// virtualizing case even if the item is marked
// expanded we still need to do this step in order to
// regenerate the visuals because they may have been virtualized away.
container.ApplyTemplate();
var itemsPresenter =
(ItemsPresenter)container.Template.FindName("ItemsHost", container);
if (itemsPresenter != null)
{
itemsPresenter.ApplyTemplate();
}
else
{
// The Tree template has not named the ItemsPresenter,
// so walk the descendents and find the child.
itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
if (itemsPresenter == null)
{
container.UpdateLayout();
itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
}
}
var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
// Ensure that the generator for this panel has been created.
#pragma warning disable 168
var children = itemsHostPanel.Children;
#pragma warning restore 168
var bringIndexIntoView = GetBringIndexIntoView(itemsHostPanel);
for (int i = 0, count = container.Items.Count; i < count; i++)
{
TreeViewItem subContainer;
if (bringIndexIntoView != null)
{
// Bring the item into view so
// that the container will be generated.
bringIndexIntoView(i);
subContainer =
(TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
}
else
{
subContainer =
(TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
// Bring the item into view to maintain the
// same behavior as with a virtualizing panel.
subContainer.BringIntoView();
}
if (subContainer == null)
{
continue;
}
// Search the next level for the object.
var resultContainer = GetTreeViewItem(subContainer, item);
if (resultContainer != null)
{
return resultContainer;
}
// The object is not under this TreeViewItem
// so collapse it.
subContainer.IsExpanded = false;
}
}
return null;
}
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
return;
}
var behavior = (BindableSelectedItemBehavior)sender;
var treeView = behavior.AssociatedObject;
if (treeView == null)
{
// at designtime the AssociatedObject sometimes seems to be null
return;
}
item = GetTreeViewItem(treeView, e.NewValue);
if (item != null)
{
item.IsSelected = true;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
And for the sake of completeness hier is the implementation of GetVisualDescentants:
/// <summary>
/// Extension methods for the <see cref="DependencyObject" /> type.
/// </summary>
public static class DependencyObjectExtensions
{
/// <summary>
/// Gets the first child of the specified visual that is of tyoe <typeparamref name="T" />
/// in the visual tree recursively.
/// </summary>
/// <param name="visual">The visual to get the visual children for.</param>
/// <returns>
/// The first child of the specified visual that is of tyoe <typeparamref name="T" /> of the
/// specified visual in the visual tree recursively or <c>null</c> if none was found.
/// </returns>
public static T GetVisualDescendant<T>(this DependencyObject visual) where T : DependencyObject
{
return (T)visual.GetVisualDescendants().FirstOrDefault(d => d is T);
}
/// <summary>
/// Gets all children of the specified visual in the visual tree recursively.
/// </summary>
/// <param name="visual">The visual to get the visual children for.</param>
/// <returns>All children of the specified visual in the visual tree recursively.</returns>
public static IEnumerable<DependencyObject> GetVisualDescendants(this DependencyObject visual)
{
if (visual == null)
{
yield break;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
var child = VisualTreeHelper.GetChild(visual, i);
yield return child;
foreach (var subChild in GetVisualDescendants(child))
{
yield return subChild;
}
}
}
}
I know this is old question, but perhaps it will be helpful for others. I combined a code from Link
And it looks now:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Behaviors
{
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// if binded to vm collection than this way is not working
//var item = e.NewValue as TreeViewItem;
//if (item != null)
//{
// item.SetValue(TreeViewItem.IsSelectedProperty, true);
//}
var tvi = e.NewValue as TreeViewItem;
if (tvi == null)
{
var tree = ((BindableSelectedItemBehavior)sender).AssociatedObject;
tvi = GetTreeViewItem(tree, e.NewValue);
}
if (tvi != null)
{
tvi.IsSelected = true;
tvi.Focus();
}
}
#endregion
#region Private
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
if (container != null)
{
if (container.DataContext == item)
{
return container as TreeViewItem;
}
// Expand the current container
if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
{
container.SetValue(TreeViewItem.IsExpandedProperty, true);
}
// Try to generate the ItemsPresenter and the ItemsPanel.
// by calling ApplyTemplate. Note that in the
// virtualizing case even if the item is marked
// expanded we still need to do this step in order to
// regenerate the visuals because they may have been virtualized away.
container.ApplyTemplate();
var itemsPresenter =
(ItemsPresenter)container.Template.FindName("ItemsHost", container);
if (itemsPresenter != null)
{
itemsPresenter.ApplyTemplate();
}
else
{
// The Tree template has not named the ItemsPresenter,
// so walk the descendents and find the child.
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
if (itemsPresenter == null)
{
container.UpdateLayout();
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
}
}
var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
// Ensure that the generator for this panel has been created.
#pragma warning disable 168
var children = itemsHostPanel.Children;
#pragma warning restore 168
for (int i = 0, count = container.Items.Count; i < count; i++)
{
var subContainer = (TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
if (subContainer == null)
{
continue;
}
subContainer.BringIntoView();
// Search the next level for the object.
var resultContainer = GetTreeViewItem(subContainer, item);
if (resultContainer != null)
{
return resultContainer;
}
else
{
// The object is not under this TreeViewItem
// so collapse it.
//subContainer.IsExpanded = false;
}
}
}
return null;
}
/// <summary>
/// Search for an element of a certain type in the visual tree.
/// </summary>
/// <typeparam name="T">The type of element to find.</typeparam>
/// <param name="visual">The parent element.</param>
/// <returns></returns>
private static T FindVisualChild<T>(Visual visual) where T : Visual
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
T descendent = FindVisualChild<T>(child);
if (descendent != null)
{
return descendent;
}
}
}
return null;
}
#endregion
#region Protected
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
#endregion
}
}
If you find, like I did, that this answer sometimes crashes because itemPresenter is null, then this modification to that solution might work for you.
Change OnSelectedItemChanged to this (if the Tree isn't loaded yet, then it waits until the Tree is loaded and tries again):
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Action<TreeViewItem> selectTreeViewItem = tvi2 =>
{
if (tvi2 != null)
{
tvi2.IsSelected = true;
tvi2.Focus();
}
};
var tvi = e.NewValue as TreeViewItem;
if (tvi == null)
{
var tree = ((BindableTreeViewSelectedItemBehavior) sender).AssociatedObject;
if (!tree.IsLoaded)
{
RoutedEventHandler handler = null;
handler = (sender2, e2) =>
{
tvi = GetTreeViewItem(tree, e.NewValue);
selectTreeViewItem(tvi);
tree.Loaded -= handler;
};
tree.Loaded += handler;
return;
}
tvi = GetTreeViewItem(tree, e.NewValue);
}
selectTreeViewItem(tvi);
}
Related
Why does my TreeViewItem does not wrap in my sample code?
Xaml:
<Window x:Class="WpfAppTestScrollViewBehavior.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppTestScrollViewBehavior"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowModel></local:MainWindowModel>
</Window.DataContext>
<TabControl>
<TabItem Header="Test">
<TreeView ItemsSource="{Binding Level1s, Mode=OneWay}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Background="LightGoldenrodYellow">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Level1}"
ItemsSource="{Binding Path=InstalledFontCollection}">
<Grid HorizontalAlignment="Stretch" Background="LightGreen">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding Path=IsSelected}"></CheckBox>
<TextBlock Grid.Column="1" Text="{Binding Path=Name}"
TextWrapping="Wrap" Margin="5,0,0,0"></TextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</TabItem>
</TabControl>
</Window>
Code:
using System;
using System.Collections.Generic;
namespace WpfAppTestScrollViewBehavior
{
public class MainWindowModel
{
public List<Level1> Level1s { get; } = new List<Level1>();
public MainWindowModel()
{
Level1s.Add(new Level1());
}
}
}
using System;
using System.Drawing.Text;
namespace WpfAppTestScrollViewBehavior
{
public class Level1
{
public bool IsSelected { get; set; }
public string Name { get; set; } = "a very long name in order to test text wrapping functionnality";
public InstalledFontCollection InstalledFontCollection { get; } = new InstalledFontCollection();
}
}
Just to prevent quick wrong answers:
You can add this code and it works fine:
<TabItem Header="Test 2">
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
<Grid HorizontalAlignment="Stretch" Background="LightPink">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"></CheckBox>
<TextBlock Grid.Column="1" Text="long name just for testing"
TextWrapping="Wrap" Margin="5,0,0,0"></TextBlock>
</Grid>
</ScrollViewer>
</TabItem>
Results of previous working code (just as example of what i expect):
Since you have not specified Width it is giving available Width to the <Textblock>
Why does my TreeViewItem does not wrap in my sample code?
The text is being wrapped but because of unrestricted width available wrap is not seen
For example:
XAML: Width: 150 units TextWrapping = "NoWrap"
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" Width="150" Name="textblock1"
TextWrapping="NoWrap" Margin="10,0,0,0"></TextBlock>
OUTPUT:
XAML: Width: 150 units TextWrapping = "Wrap"
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" Width="150" Name="textblock1"
TextWrapping="Wrap" Margin="10,0,0,0"></TextBlock>
OR
<Grid HorizontalAlignment="Stretch" Width="150" Background="LightGreen">
public string Name { get; set; } = ;
ColumnDefinition defaults to * so the TextBlock in column 1 will take all available space.
Try experimenting with MaxWidth like this: <ColumnDefinition Width="Auto" MaxWidth="100"/>
EDIT
If you don't want to use predefined Width or MaxWidth, bind the Width of the Grid to ActualWidth of TabControl like this:
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=ActualWidth}" />
I just tested this approach and it wrapped the TextBlock fine and re-wrapped it as I resized the window... If the Text of the TextBlock has no margin relative to the right window border when Width is set this way you can fix that by setting the right margin of the TextBlock to 10 or more depending on the desired padding...
I came up with a solution that is generic and seems to works for any cases whatever content is inside TreeViewItem.
It could be applied as a behavior (I favor this in order to make it simpler to use).
One restriction of using this behavior is: Define the behavior as the last element in the TreeView xaml. By doing so it will support any additional ItemContainerStyle previously defined.
Behavior code:
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using HQ.Wpf.Util;
using HQ.Wpf.Util.Converter;
namespace WpfAppTestScrollViewBehavior
{
public class BehaviorTreeView : Behavior<TreeView>
{
public static readonly DependencyProperty ShowHorizontalScrollBarProperty = DependencyProperty.Register(
"PropertyType", typeof(bool), typeof(BehaviorTreeView), new PropertyMetadata(true));
/// <summary>
/// Settting this poroperty to false will fake a TreeView-ScrollView ViewPort finite width.
/// TreeViewItem will be advise to Calculate their width according to the width of the
/// ViewPort of the ScrollView of the TreeView.
/// </summary>
public bool ShowHorizontalScrollBar
{
get { return (bool)GetValue(ShowHorizontalScrollBarProperty); }
set { SetValue(ShowHorizontalScrollBarProperty, value); }
}
// ******************************************************************
protected override void OnAttached()
{
base.OnAttached();
if (!ShowHorizontalScrollBar)
{
Style style = AssociatedObject.ItemContainerStyle ?? new Style(typeof(TreeViewItem));
var eventSetter = new EventSetter(FrameworkElement.LoadedEvent, new RoutedEventHandler(this.TreeViewItemLoaded));
style.Setters.Add(eventSetter);
AssociatedObject.ItemContainerStyle = style;
}
}
// ******************************************************************
private void TreeViewItemLoaded(object sender, RoutedEventArgs e)
{
var tvi = sender as TreeViewItem;
var contentPresenter = tvi.FindFirstChildWithNameRecursive<ContentPresenter>("PART_Header");
var treeView = tvi.GetVisualParentRecursive<TreeView>();
double offsetX = contentPresenter.TransformToAncestor(treeView).Transform(new Point(0, 0)).X;
var scrollViewer = treeView.FindVisualChild<ScrollViewer>();
Binding binding = new Binding();
binding.Source = scrollViewer;
binding.Path = new PropertyPath(nameof(ScrollViewer.ViewportWidth));
binding.Mode = BindingMode.OneWay;
var converter = new NumericFixedNumberAddedConverter();
binding.Converter = converter;
binding.ConverterParameter = -offsetX;
BindingOperations.SetBinding(contentPresenter, FrameworkElement.WidthProperty, binding);
}
// ******************************************************************
}
}
Behavior Dependencies:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WpfAppTestScrollViewBehavior
{
class NumericFixedNumberAddedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return (double) value + double.Parse(parameter.ToString());
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace WpfAppTestScrollViewBehavior
{
public static class UiUtility
{
// ******************************************************************
public static Window GetTopLevelOwnerWindowOrMainAppWindow(Control usercontrol)
{
return GetTopLevelOwnerWindow(usercontrol) ?? Application.Current.MainWindow;
}
// ******************************************************************
public static Window GetTopLevelOwnerWindowOrMainAppWindow(DependencyObject dependencyObject)
{
return GetTopLevelOwnerWindow(dependencyObject) ?? Application.Current.MainWindow;
}
// ******************************************************************
public static Window GetTopLevelOwnerWindow(Control usercontrol)
{
return GetTopLevelOwnerWindow((DependencyObject)usercontrol);
}
// ******************************************************************
public static Window GetTopLevelOwnerWindow(DependencyObject dependencyObject)
{
while (dependencyObject != null && !(dependencyObject is Window))
{
var dependencyObjectCopy = dependencyObject;
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
if (dependencyObject == null)
{
dependencyObject = dependencyObjectCopy;
String propName = "DockSite";
PropertyInfo pi = dependencyObject.GetType().GetProperty(propName);
if (pi != null)
{
DependencyObject dependencyObjectTemp = null;
try
{
dependencyObjectTemp = dependencyObject.GetType().InvokeMember(propName,
BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public, null, dependencyObject, null) as DependencyObject;
}
catch (Exception)
{
}
if (dependencyObjectTemp != null)
{
dependencyObject = LogicalTreeHelper.GetParent(dependencyObjectTemp);
}
else
{
dependencyObject = LogicalTreeHelper.GetParent(dependencyObject);
}
}
else
{
dependencyObject = LogicalTreeHelper.GetParent(dependencyObject);
}
}
}
return dependencyObject as Window;
}
// ******************************************************************
public static T FindVisualParent<T>(DependencyObject element) where T : DependencyObject
{
var parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as DependencyObject;
}
return null;
}
// ******************************************************************
public static bool IsParentOf(DependencyObject parent, DependencyObject child)
{
if (parent == null || child == null)
{
return false;
}
DependencyObject childParent = child;
do
{
if (childParent == parent)
{
return true;
}
childParent = VisualTreeHelper.GetParent(childParent);
} while (childParent != null);
return false;
}
// ******************************************************************
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
// ******************************************************************
public static DataGridColumnHeader GetColumnHeaderFromColumn(DataGrid dataGrid, DataGridColumn column)
{
// dataGrid is the name of your DataGrid. In this case Name="dataGrid"
foreach (var columnHeader in FindVisualChildren<DataGridColumnHeader>(dataGrid))
{
if (columnHeader.Column == column)
{
return columnHeader;
}
}
return null;
}
// ******************************************************************
public static void SafeInvoke(Action action)
{
if (Application.Current.Dispatcher.CheckAccess())
{
action();
}
else
{
Application.Current.Dispatcher.Invoke(action);
}
}
// ******************************************************************
public static void SafeBeginInvoke(Action action)
{
if (Application.Current.Dispatcher.CheckAccess())
{
action();
}
else
{
Application.Current.Dispatcher.BeginInvoke(action);
}
}
// ******************************************************************
public static void BindingRefresh(DependencyObject dependencyObject, DependencyProperty dependencyProperty)
{
BindingExpressionBase b = BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty);
if (b != null)
{
b.UpdateTarget();
}
}
// ******************************************************************
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject depObj, string childName)
where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T && ((FrameworkElement)depObj).Name == childName)
return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
//DFS
T obj = FindChild<T>(child, childName);
if (obj != null)
return obj;
}
return null;
}
// ******************************************************************
public static void DebugPrintControlParentHierarchy(object frameworkElement)
{
StringBuilder hierarchy = new StringBuilder();
var fe = frameworkElement as FrameworkElement;
while (fe != null)
{
hierarchy.Append(String.Format("{0} [{1}] ==> ", fe.GetType(), fe.Name));
fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
}
hierarchy.Append("!TOP!");
Debug.Print(hierarchy.ToString());
}
// ******************************************************************
/// <summary>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static T FindVisualChild<T>(this DependencyObject obj) where T : DependencyObject
{
if (obj != null && obj is Visual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
var visualChild = child as T;
if (visualChild != null)
return visualChild;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
}
return null;
}
// ******************************************************************
public static FrameworkElement GetVisualParent(this UIElement element)
{
DependencyObject parent = VisualTreeHelper.GetParent(element);
do
{
var fe = parent as FrameworkElement;
if (fe != null)
{
return fe;
}
parent = VisualTreeHelper.GetParent(parent);
} while (parent != null);
return null;
}
// ******************************************************************
public static void BringToFront(this Panel panel, UIElement element)
{
int maxIndex = 0;
foreach (UIElement e in panel.Children)
{
maxIndex = Math.Max(maxIndex, Panel.GetZIndex(e));
}
Panel.SetZIndex(element, maxIndex + 1);
}
//// ******************************************************************
///// <summary>
///// Return the center point of an direct child of a Canvas (not yet tested)
///// </summary>
///// <param name=""></param>
///// <param name="elementRelativeTo">If elementRelativeTo == null, will use direct parent</param>
///// <returns></returns>
//public static Point GetCanvasElementCenterPoint(this FrameworkElement element)
//{
// return new Point(
// Canvas.GetLeft(element) + (element.ActualWidth / 2),
// Canvas.GetTop(element) + (element.ActualHeight / 2));
//}
// ******************************************************************
public enum PointPositionVertical
{
Top,
Center,
Bottom
}
// ******************************************************************
public enum PointPositionHorizontal
{
Left,
Center,
Right
}
// ******************************************************************
public static Point GetChildCoordinate(this UIElement elementContainer, FrameworkElement childElement,
PointPositionHorizontal pointPositionHorizontal = PointPositionHorizontal.Left,
PointPositionVertical pointPositionVertical = PointPositionVertical.Top)
{
double x;
switch (pointPositionHorizontal)
{
case PointPositionHorizontal.Center:
x = childElement.ActualWidth / 2;
break;
case PointPositionHorizontal.Right:
x = childElement.ActualWidth;
break;
default:
x = 0;
break;
}
double y;
switch (pointPositionVertical)
{
case PointPositionVertical.Center:
y = childElement.ActualHeight / 2;
break;
case PointPositionVertical.Bottom:
y = childElement.ActualHeight;
break;
default:
y = 0;
break;
}
return childElement.TranslatePoint(new Point(x, y), elementContainer);
}
// ******************************************************************
public static void ApplyToEachVisualChildRecursively(this DependencyObject obj, Action<DependencyObject> action)
{
if (obj != null && obj is Visual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null)
{
action(child);
ApplyToEachVisualChildRecursively(child, action);
}
}
}
}
// ******************************************************************
public static T GetVisualParentRecursive<T>(this DependencyObject obj) where T : class
{
var element = obj as FrameworkElement;
if (element != null)
{
var frameWorkElement = VisualTreeHelper.GetParent(element) as FrameworkElement;
if (frameWorkElement != null)
{
var t = frameWorkElement as T;
if (t != null)
{
return t;
}
return frameWorkElement.GetVisualParentRecursive<T>();
}
}
return null;
}
// ******************************************************************
public static T HitTest<T>(this Visual visual, Point pt) where T : class
{
T hitResult = null;
VisualTreeHelper.HitTest(visual, null, result =>
{
if (result.VisualHit is T)
{
hitResult = result.VisualHit as T;
return HitTestResultBehavior.Stop;
}
hitResult = result.VisualHit?.GetVisualParentRecursive<T>();
if (hitResult != null)
{
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}, new PointHitTestParameters(pt));
return hitResult;
}
// ******************************************************************
public static IEnumerable<T> GetChildrenRecursive<T>(this DependencyObject depObj) where T : class
{
int count = VisualTreeHelper.GetChildrenCount(depObj);
for (int n = 0; n < count; n++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, n);
if (child is T)
{
yield return child as T;
}
foreach (T depObjChild in child.GetChildrenRecursive<T>())
{
yield return depObjChild;
}
}
}
// ******************************************************************
/// <summary>
/// EO, 2017-05-11: New code
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static T GetVisualParentRecursive<T>(this DependencyObject obj, Predicate<T> predicate = null) where T : class
{
var element = obj as FrameworkElement;
if (element != null)
{
var frameWorkElement = VisualTreeHelper.GetParent(element) as FrameworkElement;
if (frameWorkElement != null)
{
var t = frameWorkElement as T;
if (t != null)
{
if (predicate == null || predicate(t))
{
return t;
}
}
return frameWorkElement.GetVisualParentRecursive<T>(predicate);
}
}
return null;
}
// ******************************************************************
/// <summary>
/// EO, 2017-05-11: New code
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="name"></param>
/// <returns></returns>
public static T FindFirstChildWithNameRecursive<T>(this DependencyObject parent, string name) where T : FrameworkElement
{
return FindFirstChildRecursive(parent, (T child) => child.Name == name);
}
// ******************************************************************
/// <summary>
/// Find all controls (visual or not)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static T FindFirstChildRecursive<T>(this DependencyObject parent, Predicate<T> predicate = null) where T : DependencyObject
{
if (parent == null)
{
return null;
}
//use the visual tree for Visual / Visual3D elements
if (parent is Visual || parent is Visual3D)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
var childAsT = child as T;
if (childAsT != null)
{
if (predicate == null || predicate(childAsT))
{
return childAsT;
}
}
var result = FindFirstChildRecursive(child, predicate);
if (result != null)
{
return result;
}
}
}
else //use the logical tree for content / framework elements
{
foreach (DependencyObject child in LogicalTreeHelper.GetChildren(parent))
{
var childAsT = child as T;
if (childAsT != null)
{
if (predicate == null || predicate(childAsT))
{
return childAsT;
}
}
var result = FindFirstChildRecursive(child, predicate);
if (result != null)
{
return result;
}
}
}
return null;
}
// ******************************************************************
/// <summary>
/// Non recursive
/// Based on stackoverflow: http://stackoverflow.com/questions/13248013/visualtreehelper-not-finding-children-of-dependencyobject-how-can-i-reliably-fi
/// Find all controls (visual or not)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static IEnumerable<T> FindChilds<T>(this DependencyObject parent, Predicate<T> predicate) where T : DependencyObject
{
if (parent == null) yield break;
//use the visual tree for Visual / Visual3D elements
if (parent is Visual || parent is Visual3D)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var childAsT = VisualTreeHelper.GetChild(parent, i) as T;
if (childAsT != null)
{
if (predicate(childAsT))
{
yield return childAsT;
}
}
}
}
else //use the logical tree for content / framework elements
{
foreach (DependencyObject obj in LogicalTreeHelper.GetChildren(parent))
{
var childAsT = obj as T;
if (childAsT != null)
{
if (predicate(childAsT))
{
yield return childAsT;
}
}
}
}
}
// ******************************************************************
/// <summary>
/// EO, 2017-05-11: New code
/// Find all controls (visual or not)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="name"></param>
/// <returns></returns>
public static List<T> FindChildsWithNameRecursive<T>(this DependencyObject parent, string name) where T : FrameworkElement
{
return FindChildsRecursive(parent, (T child) => child.Name == name);
}
// ******************************************************************
/// <summary>
/// Find all controls (visual or not)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static List<T> FindChildsRecursive<T>(this DependencyObject parent, Predicate<T> predicate = null) where T : DependencyObject
{
List<T> childs = new List<T>();
return FindChildsRecursiveInternal(parent, predicate, childs);
}
// ******************************************************************
private static List<T> FindChildsRecursiveInternal<T>(this DependencyObject parent, Predicate<T> predicate, List<T> childs) where T : DependencyObject
{
if (parent != null)
{
//use the visual tree for Visual / Visual3D elements
if (parent is Visual || parent is Visual3D)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
var childAsT = child as T;
if (childAsT != null)
{
if (predicate == null || predicate(childAsT))
{
childs.Add(childAsT);
}
}
FindChildsRecursiveInternal(child, predicate, childs);
}
}
else //use the logical tree for content / framework elements
{
foreach (DependencyObject child in LogicalTreeHelper.GetChildren(parent))
{
var childAsT = child as T;
if (childAsT != null)
{
if (predicate == null || predicate(childAsT))
{
childs.Add(childAsT);
}
}
FindChildsRecursiveInternal(child, predicate, childs);
}
}
}
return childs;
}
// ****************************************************************** }
}
}
Usage:
<!-- ScrollViewer.HorizontalScrollBarVisibility="Disabled" -->
<TreeView ItemsSource="{Binding Level1s, Mode=OneWay}">
...
<i:Interaction.Behaviors>
<local:BehaviorTreeView ShowHorizontalScrollBar="False"></local:BehaviorTreeView>
</i:Interaction.Behaviors>
Thanks to Dean Kuga. Its solution give me the start to what I came up with.
Results:
I am new to WPF and am trying to use the Ribbon control.
I have a single tab in the application, and wish to hide the header but still show the tab itself.
I have been experimenting with various properties and styles, but I have only been able to hide the entire tab.
I've tried: ribbontab visibility, ribbontab.header visibility, setting hidden in TabHeaderItemCollection, applying style xaml to ribbontabheader element in ribbontab, experimenting with tabheadertemplate property, and generally sifting through the api looking for anything that may be relevant.
Google only turns up how to hide the whole tab.
Any other ideas?
I managed to hide both the tab headers and the application menu by shifting the control 47 pixels upwards...
<r:Ribbon Margin="0,-47,0,0" DockPanel.Dock="Top" x:Name="ribbon">
Note: you can hide just the application menu and not the tabs by doing this...
<r:Ribbon DockPanel.Dock="Top" x:Name="ribbon">
<r:Ribbon.ApplicationMenu>
<r:RibbonApplicationMenu Visibility="Collapsed" />
</r:Ribbon.ApplicationMenu>
Hiding just the tab headers, I can't quite do. I did get pretty close by overriding the ribbon class as follows...
class RibbonNoTab : Ribbon
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var ctrl = this.GetDescendants<Grid>().FirstOrDefault();
if (ctrl != null)
{
ctrl.RowDefinitions[1].Height = new GridLength(0, System.Windows.GridUnitType.Pixel);
}
}
}
The GetDescendants extension method just searches the visual tree for something of a specified type. It was taken from here: http://blog.falafel.com/finding-controls-by-type-in-silverlight-and-wpf/
The only issue with the above method was that there looks like a 1 pixel high bar remaining. You have to look pretty closely to see it!
Piggybacking off the other answer, there are a few ways you can do it without using a custom derived class
public class RibbonBehavior
{
public static bool GetHideRibbonTabs(DependencyObject obj)
{
return (bool)obj.GetValue(HideRibbonTabsProperty);
}
public static void SetHideRibbonTabs(DependencyObject obj, bool value)
{
obj.SetValue(HideRibbonTabsProperty, value);
}
// Using a DependencyProperty as the backing store for HideRibbonTabs. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HideRibbonTabsProperty =
DependencyProperty.RegisterAttached("HideRibbonTabs", typeof(bool), typeof(RibbonBehavior), new UIPropertyMetadata(false,OnHideRibbonTabsChanged));
public static void OnHideRibbonTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d == null || d.GetType() != typeof(Ribbon)) return;
(d as Ribbon).Loaded += ctrl_Loaded;
}
static void ctrl_Loaded(object sender, RoutedEventArgs e)
{
if (sender == null || sender.GetType() != typeof(Ribbon)) return;
Ribbon _ribbon = (Ribbon)sender;
var tabGrid = _ribbon.GetDescendants<Grid>().FirstOrDefault();
tabGrid.RowDefinitions[1].Height = new GridLength(0, System.Windows.GridUnitType.Pixel);
foreach (Line line in _ribbon.GetDescendants<Line>())
line.Visibility = Visibility.Collapsed;
}
}
then in your xaml:
<Ribbon SelectedIndex="0" a:RibbonBehavior.HideRibbonTabs="true">
Which will give you a nice square ribbon box with no tab:
Leaving out the code collapsing the lines leaves a little blip where the tab used to be, which was bothering me.
You could also put that loaded code straight into the code behind if you're not using MVVM, or in an EventToCommand if you are. This way is less reusable, but it's the same results.
EDIT: Here's the code for the GetDescendant methods
public static class VisualTreeExtensions
{
/// <summary>
/// Gets children, children’s children, etc. from
/// the visual tree that match the specified type
/// </summary>
public static List<T> GetDescendants<T>(this DependencyObject parent)
where T : UIElement
{
List<T> children = new List<T>();
int count = VisualTreeHelper.GetChildrenCount(parent);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (child is T)
{
children.Add((T)child);
}
children.AddRange(child.GetDescendants<T>());
}
return children;
}
else
{
return new List<T> { };
}
}
/// <summary>
/// Gets children, children’s children, etc. from
/// the visual tree that match the specified type and elementName
/// </summary>
public static List<T> GetDescendants<T>(this DependencyObject parent, string elementName)
where T : UIElement
{
List<T> children = new List<T>();
int count = VisualTreeHelper.GetChildrenCount(parent);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (child is T && (child is FrameworkElement)
&& (child as FrameworkElement).Name == elementName)
{
children.Add((T)child);
}
children.AddRange(child.GetDescendants<T>(elementName));
}
return children;
}
else
{
return new List<T> { };
}
}
/// <summary>
/// Gets the first child, child’s child, etc.
/// from the visual tree that matches the specified type
/// </summary>
public static T GetDescendant<T>(this DependencyObject parent)
where T : UIElement
{
List<T> descendants = parent.GetDescendants<T>();
if (descendants.Count > 0)
{
return descendants[0];
}
else
{
return null;
}
}
/// <summary>
/// Gets the first child, child’s child, etc. from
/// the visual tree that matches the specified type and elementName
/// </summary>
public static T GetDescendant<T>(this DependencyObject parent, string elementName)
where T : UIElement
{
List<T> descendants = parent.GetDescendants<T>(elementName);
if (descendants.Count > 0)
{
return descendants[0];
}
else
{
return null;
}
}
/// <summary>
/// Gets the first parent, parent’s parent, etc. from the
/// visual tree that matches the specified type
/// </summary>
public static T GetAntecedent<T>(this DependencyObject root)
where T : UIElement
{
if (root == null)
{
return null;
}
if (root is T)
{
return (T)root;
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(root);
if (parent == null)
{
return null;
}
else
{
return parent.GetAntecedent<T>();
}
}
}
/// <summary>
/// Gets the first parent, parent’s parent, etc. from the
/// visual tree that matches the specified type and elementName
/// </summary>
public static T GetAntecedent<T>(this DependencyObject root, string elementName)
where T : UIElement
{
if (root == null)
{
return null;
}
if (root is T && (root is FrameworkElement)
&& (root as FrameworkElement).Name == elementName)
{
return (T)root;
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(root);
if (parent == null)
{
return null;
}
else
{
return parent.GetAntecedent<T>(elementName);
}
}
}
}
In relation to flobadob's answer:
Shifting the control up 47 pixels is quite an interesting solution.
If you want to retain the line that is normally above the ribbon menu, shift the control, and then add a line in code. The following will work:
<ribbon:XamRibbon Grid.Row="1" x:Name="Ribbon" ApplicationMenuMode="Office2010" AllowMinimize="False" Margin="0,-49,0,0">
...
</ribbon:XamRibbon>
<Rectangle Grid.Row="0" HorizontalAlignment="Stretch" Fill="White" Height="5"/>
<Separator Grid.Row="0" />
Replace the white fill with the color of your background. Note also that the ribbon is Grid row 1, whereas the later placed line is grid row 0.
A while ago i wrote an "Attached Behavior" for two way syncronisation between a XamDataGrid and a "Business Object" as ObservableCollection. The XamDataGrid is the source and the ObservableCollection as DataSource is the Target. I did not use a ListCollectionView since for specific reasons.
The problem
When the DataContext of the DataGrid is changed to another Vehicle the currently loaded DataGrid does not update the DependencyProperty of the behavior.
I cannot figure out why.
The only solution i can think of is to hook the DataContextChanged of the DataGrid and do a new BindingOperation with a Path that is then set relative to the DataContext to figure out the SelectedItems property. But in that case the DependencyProperty of the behavior should be set to a Path to the SelectedItems property and not a binding.
Having the following classes
A example model
public class Vehicle
{
public PassengerList Passengers { get; set; }
}
public class PassengerList : ObservableCollection<Passenger>
{
public PassengerList()
{
SelectedPassengers = new ObservableCollection<Passenger>();
}
public ObservableCollection<Passenger> SelectedPassengers { get; private set; }
}
public class Passenger
{
public string Name { get; set; }
}
The xaml
<igDG:XamDataGrid DataSource="{Binding Passengers}">
<i:Interaction.Behaviors>
<b:XamDataGridSelectedItemsBehavior SelectedItems="{Binding Path=Passengers.SelectedPssengers}" />
</i:Interaction.Behaviors>
</igDG:XamDataGrid>
PS: I have also tried a Element binding to the DataGrid as element but that doesn't fix it. The DependencyProperty is set only once.
For example the Models
The two way behavior
When an selected item changes in the model the grid selected item has to be updated as well. It also does not need to be detached as well, using weak events.
public class XamDataGridSelectedItemsBehavior : Behavior<XamDataGrid>, IWeakEventListener
{
#region Properties
private XamDataGrid Grid
{
get { return AssociatedObject as XamDataGrid; }
}
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(INotifyCollectionChanged),
typeof(XamDataGridSelectedItemsBehavior),
new UIPropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));
private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj != null)
{
(obj as XamDataGridSelectedItemsBehavior).SelectedItems = (e.NewValue as INotifyCollectionChanged);
}
}
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set
{
// remove old listener
if (SelectedItems != null)
CollectionChangedEventManager.RemoveListener(SelectedItems, this);
SetValue(SelectedItemsProperty, value);
// add new listener
if (SelectedItems != null)
CollectionChangedEventManager.AddListener(SelectedItems, this);
}
}
#endregion
#region Init
/// <summary>
/// Hook up event listeners to the associated object.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
SelectedItemsChangedEventManager.AddListener(Grid, this);
XamDataGridRecordActivatedEventManager.AddListener(Grid, this);
XamDataGridLoadedEventManager.AddListener(Grid, this);
}
void Grid_RecordActivated(object sender, RecordActivatedEventArgs e)
{
if (_transferingToTarget)
return;
// if the CellClickAction is EnterEditModeIfAllowed, the grid does not always select the actual record
// In our case we want it to always select the record
if (e.Record.DataPresenter.FieldSettings.CellClickAction == CellClickAction.EnterEditModeIfAllowed)
{
TransferSourceToTarget();
}
}
void Grid_Loaded(object sender, RoutedEventArgs e)
{
TransferTargetToSource(true);
}
#endregion
#region Target to Source
/// <summary>
/// When selected items in the target as model has changed, then transfer selected item to grid as the source.
/// Not when transfering from grid to selected items.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void SelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_transferingToTarget)
return;
TransferTargetToSource(false);
}
private bool _transferingToSource = false;
/// <summary>
/// Transfer selected item in the target as model to the grid as source.
/// </summary>
private void TransferTargetToSource(bool notifyTargetListeners)
{
if (SelectedItems == null)
return;
List<Record> newSelection = new List<Record>();
foreach (var item in (SelectedItems as IList))
{
var record = Grid.Records.FirstOrDefault(r => (r is DataRecord) && ((r as DataRecord).DataItem == item));
if (record != null)
{
newSelection.Add(record);
}
}
_transferingToSource = true;
try
{
Grid.SelectedItems.Records.Clear();
Grid.SelectedItems.Records.AddRange(newSelection.ToArray());
if ((newSelection.Count > 0) && !newSelection.Contains(Grid.ActiveRecord))
{
Grid.ActiveRecord = newSelection.FirstOrDefault();
Grid.ActiveRecord.IsSelected = true;
}
if (notifyTargetListeners)
{
// Hack to notify the target listeners
(SelectedItems as IList).Clear();
foreach (var record in newSelection)
{
(SelectedItems as IList).Add((record as DataRecord).DataItem);
}
}
}
finally
{
_transferingToSource = false;
}
}
#endregion
#region Source to Target
/// <summary>
/// When selected items in the source as grid has changed, then transfer selected item to model as the target.
/// Not when transfering from selected items to grid.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Grid_SelectedItemsChanged(object sender, SelectedItemsChangedEventArgs e)
{
if (_transferingToSource)
return;
TransferSourceToTarget();
}
private bool _transferingToTarget = false;
/// <summary>
/// Transfer the selected item in the grid as source to the selected item in the target as model.
/// </summary>
private void TransferSourceToTarget()
{
var target = this.SelectedItems as IList;
if (target == null)
return;
_transferingToTarget = true;
try
{
// clear the target first
target.Clear();
// When no item is selected there might still be an active record
if (Grid.SelectedItems.Count() == 0)
{
if (Grid.ActiveDataItem != null)
target.Add(Grid.ActiveDataItem);
else if (Grid.ActiveRecord != null && Grid.ActiveRecord.IsDataRecord)
target.Add((Grid.ActiveRecord as DataRecord).DataItem);
else if (Grid.ActiveCell != null && Grid.ActiveCell.Record != null && Grid.ActiveCell.Record.IsDataRecord)
target.Add((Grid.ActiveCell.Record as DataRecord).DataItem);
}
else
{
// foreach record in the source add it to the target
foreach (var r in Grid.SelectedItems.Records)
{
if (r.IsDataRecord)
{
target.Add((r as DataRecord).DataItem);
}
}
}
}
finally
{
_transferingToTarget = false;
}
}
#endregion
/// <summary>
/// Receive an event and delegate it to the correct eventhandler.
/// </summary>
/// <param name="managerType"></param>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns></returns>
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(CollectionChangedEventManager))
{
SelectedItems_CollectionChanged(sender, e as NotifyCollectionChangedEventArgs);
return true;
}
else if (managerType == typeof(SelectedItemsChangedEventManager))
{
Grid_SelectedItemsChanged(sender, e as SelectedItemsChangedEventArgs);
return true;
}
else if (managerType == typeof(XamDataGridRecordActivatedEventManager))
{
Grid_RecordActivated(sender, e as RecordActivatedEventArgs);
return true;
}
else if (managerType == typeof(XamDataGridLoadedEventManager))
{
Grid_Loaded(sender, e as RoutedEventArgs);
return true;
}
return false;
}
}
#region EventManagers
public class CollectionChangedEventManager : WeakEventManagerBase<CollectionChangedEventManager, INotifyCollectionChanged>
{
protected override void StartListeningTo(INotifyCollectionChanged source)
{
source.CollectionChanged += DeliverEvent;
}
protected override void StopListeningTo(INotifyCollectionChanged source)
{
source.CollectionChanged -= DeliverEvent;
}
}
public class XamDataGridRecordActivatedEventManager : WeakEventManagerBase<XamDataGridRecordActivatedEventManager, XamDataGrid>
{
protected override void StartListeningTo(XamDataGrid source)
{
source.RecordActivated += DeliverEvent;
}
protected override void StopListeningTo(XamDataGrid source)
{
source.RecordActivated -= DeliverEvent;
}
}
public class XamDataGridLoadedEventManager : WeakEventManagerBase<XamDataGridLoadedEventManager, XamDataGrid>
{
protected override void StartListeningTo(XamDataGrid source)
{
source.Loaded += DeliverEvent;
}
protected override void StopListeningTo(XamDataGrid source)
{
source.Loaded -= DeliverEvent;
}
}
public class SelectedItemsChangedEventManager : WeakEventManagerBase<SelectedItemsChangedEventManager, XamDataGrid>
{
protected override void StartListeningTo(XamDataGrid source)
{
source.SelectedItemsChanged += DeliverEvent;
}
protected override void StopListeningTo(XamDataGrid source)
{
source.SelectedItemsChanged -= DeliverEvent;
}
}
#endregion
#region EventManager base class
// TODO: 10-10-2011 (rdj): Deze class misschien opnemen in het frontend framework? In ieder geval zolang we nog geen .NET 4.5 gebruiken
// http://10rem.net/blog/2012/02/01/event-handler-memory-leaks-unwiring-events-and-the-weakeventmanager-in-wpf-45
/// <summary>
/// Weak event manager base class to provide easy implementation of weak event managers.
/// </summary>
/// <typeparam name="TManager">Type of the manager.</typeparam>
/// <typeparam name="TEventSource">Type of the event source.</typeparam>
public abstract class WeakEventManagerBase<TManager, TEventSource> : WeakEventManager
where TManager : WeakEventManagerBase<TManager, TEventSource>, new()
where TEventSource : class
{
/// <summary>
/// Adds a listener
/// </summary>
/// <param name="source">The source of the event, should be null if listening to static events</param>
/// <param name="listener">The listener of the event. This is the class that will recieve the ReceiveWeakEvent method call</param>
public static void AddListener(object source, IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
/// <summary>
/// Removes a listener
/// </summary>
/// <param name="source">The source of the event, should be null if listening to static events</param>
/// <param name="listener">The listener of the event. This is the class that will recieve the ReceiveWeakEvent method call</param>
public static void RemoveListener(object source, IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
/// <inheritdoc/>
protected sealed override void StartListening(object source)
{
StartListeningTo((TEventSource)source);
}
/// <inheritdoc/>
protected sealed override void StopListening(object source)
{
StopListeningTo((TEventSource)source);
}
/// <summary>
/// Attaches the event handler.
/// </summary>
protected abstract void StartListeningTo(TEventSource source);
/// <summary>
/// Detaches the event handler.
/// </summary>
protected abstract void StopListeningTo(TEventSource source);
/// <summary>
/// Gets the current manager
/// </summary>
protected static TManager CurrentManager
{
get
{
var mType = typeof(TManager);
var mgr = (TManager)GetCurrentManager(mType);
if (mgr == null)
{
mgr = new TManager();
SetCurrentManager(mType, mgr);
}
return mgr;
}
}
}
#endregion
I have it working by adding two new Dependency Properties.
DataContextProperty
public static readonly DependencyProperty DataContextProperty = DependencyProperty.Register(
"DataContext",
typeof(object),
typeof(XamDataGridSelectedItemsBehavior),
new PropertyMetadata(DataContextChanged));
private static void DataContextChanged(object obj, DependencyPropertyChangedEventArgs e)
{
var behavior = obj as XamDataGridSelectedItemsBehavior;
var binding = new Binding(behavior.Path) { Source = e.NewValue };
BindingOperations.SetBinding(behavior, XamDataGridSelectedItemsBehavior.SelectedItemsProperty, binding);
}
PathProperty to use to create a new binding on whenever the DataContext has changed
public static readonly DependencyProperty PathProperty = DependencyProperty.Register(
"Path",
typeof(string),
typeof(XamDataGridSelectedItemsBehavior),
new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnPathChanged)));
private static void OnPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var behavior = obj as XamDataGridSelectedItemsBehavior;
behavior.Path = e.NewValue as string;
}
public string Path { get; set; }
The DataContext property is set in the OnAttached, so that the DataContextChanged event is being hooked into
protected override void OnAttached()
{
base.OnAttached();
SelectedItemsChangedEventManager.AddListener(Grid, this);
XamDataGridRecordActivatedEventManager.AddListener(Grid, this);
XamDataGridLoadedEventManager.AddListener(Grid, this);
BindingOperations.SetBinding(this, XamDataGridSelectedItemsBehavior.DataContextProperty, new Binding());
}
The SelectedItems dependency property is now private and slightly modified
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(INotifyCollectionChanged),
typeof(XamDataGridSelectedItemsBehavior2),
new UIPropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));
private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var behavior = obj as XamDataGridSelectedItemsBehavior;
if (behavior.SelectedItems != null)
CollectionChangedEventManager.RemoveListener(behavior.SelectedItems, behavior);
if (e.NewValue is INotifyCollectionChanged)
{
behavior.SelectedItems = e.NewValue as INotifyCollectionChanged;
CollectionChangedEventManager.AddListener(behavior.SelectedItems, behavior);
}
}
private INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
using the behavior in xaml
<igDG:XamDataGrid DataSource="{Binding Passengers}">
<i:Interaction.Behaviors>
<b:XamDataGridSelectedItemsBehavior Path="Passengers.SelectedPassengers" />
</i:Interaction.Behaviors>
</igDG:XamDataGrid>
I've got a custom horizontal ListView with custom ScrollViewer inside it's template (created with Blend). I want it to scroll horizontally when using mouse scrolling wheel.
How can I do that?
This should be done with a Behavior for greater reusability. Also, the logic from ZSH is redundant and could be simplified. Here's my code:
/// <summary>
/// Allows an <see cref="ItemsControl"/> to scroll horizontally by listening to the
/// <see cref="PreviewMouseWheel"/> event of its internal <see cref="ScrollViewer"/>.
/// </summary>
public class HorizontalScrollBehavior : Behavior<ItemsControl>
{
/// <summary>
/// A reference to the internal ScrollViewer.
/// </summary>
private ScrollViewer ScrollViewer { get; set; }
/// <summary>
/// By default, scrolling down on the wheel translates to right, and up to left.
/// Set this to true to invert that translation.
/// </summary>
public bool IsInverted { get; set; }
/// <summary>
/// The ScrollViewer is not available in the visual tree until the control is loaded.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
AssociatedObject.Loaded -= OnLoaded;
ScrollViewer = VisualTreeHelpers.FindVisualChild<ScrollViewer>(AssociatedObject);
if (ScrollViewer != null)
{
ScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (ScrollViewer != null)
{
ScrollViewer.PreviewMouseWheel -= OnPreviewMouseWheel;
}
}
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var newOffset = IsInverted ?
ScrollViewer.HorizontalOffset + e.Delta :
ScrollViewer.HorizontalOffset - e.Delta;
ScrollViewer.ScrollToHorizontalOffset(newOffset);
}
}
You'll need to add the following references:
System.Windows, System.Windows.Controls, System.Windows.Input, and you may need to get the Blend SDK NuGet package, and find and reference the System.Windows.Interactivity DLL in the Assemblies Extensions section.
Use this for VisualTreeHelpers:
public class VisualTreeHelpers
{
/// <summary>
/// Return the first visual child of element by type.
/// </summary>
/// <typeparam name="T">The type of the Child</typeparam>
/// <param name="obj">The parent Element</param>
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
return (T)child;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
Reference: https://codereview.stackexchange.com/questions/44760/is-there-a-better-way-to-get-a-child
Note that it is NOT the same as VisualTreeHelper in Windows.System.Media.
Here's how to use it in XAML:
<ListBox>
<i:Interaction.Behaviors>
<behaviors:HorizontalScrollBehavior />
</i:Interaction.Behaviors>
</ListBox>
Where the i namespace is declared as xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and
behaviors is declared as
xmlns:behaviors="clr-namespace:MyNamespace"
where MyNamespace is the namespace that contains the HorizontalScrollBehavior class.
if you implement IScrollInfo you can override the MouseWheelUp to do MouseWheelLeft
and down\right the in same way
edit (much more simple):
add to your ScrollViewer PreviewMouseWheel
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta < 0) // wheel down
{
if (myScrollViewer.HorizontalOffset + e.Delta > 0)
{
myScrollViewer.ScrollToHorizontalOffset(myScrollViewer.HorizontalOffset + e.Delta);
}
else
{
myScrollViewer.ScrollToLeftEnd();
}
}
else //wheel up
{
if (myScrollViewer.ExtentWidth > myScrollViewer.HorizontalOffset + e.Delta)
{
myScrollViewer.ScrollToHorizontalOffset(myScrollViewer.HorizontalOffset + e.Delta);
}
else
{
myScrollViewer.ScrollToRightEnd();
}
}
}
xaml:
<ScrollViewer x:Name="myScrollViewer" HorizontalScrollBarVisibility="Visible" Mouse.PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
I was kinda looking for the most simple way to make any ScrollViewer scroll left-right instead of up-down. So here is the simplest combination of the other answers.
<ScrollViewer HorizontalScrollBarVisibility="Visible"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
and:
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = (ScrollViewer)sender;
if (e.Delta < 0)
{
scrollViewer.LineRight();
}
else
{
scrollViewer.LineLeft();
}
e.Handled = true;
}
Xaml Code:
<ScrollViewer HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
</ScrollViewer>
C# Code
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - e.Delta);
e.Handled = true;
}
}
I have a TabControl in WPF.
When I remove a certain TabItem from the collection, depending on the content displayed in the TabItem, there can be a noticeable two or three second delay, before the TabItem is removed.
Any suggestions on how to improve this so that the delay is not noticeable?
Also - there appears to be a memory leak - as the memory retained by the WPF application does not decrease when the TabItem is removed. Anybody experience similar behavior?
Thanks!
Chris
Do you get the same delay when using blank tab items? And do you get the same delay when switching tabs?
WPF unloads the VisualTree of the tabs that are not currently being displayed. If you switch tabs, it unloads the one you just had and reloads the new tab. Depending on how complex your TabItems are, this could be the cause of your delay.
I ran into the same problem and found an alternative here. It basically stores the ContextPresenter of tab items in memory, and loads those up instead of redrawing the VisualTree
Edit: The link above appears to no longer work, so I'll paste a copy of the code here. It's been modified a bit to allow dragging/dropping, but it should still work the same way.
// Extended TabControl which saves the displayed item so you don't get the performance hit of
// unloading and reloading the VisualTree when switching tabs
// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : System.Windows.Controls.TabControl
{
// Holds all items, but only marks the current tab's item as visible
private Panel _itemsHolder = null;
// Temporaily holds deleted item in case this was a drag/drop operation
private object _deletedObject = null;
public TabControlEx()
: base()
{
// this is necessary so that we get the initial databound selected item
this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
/// <summary>
/// if containers are done, generate the selected item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
UpdateSelectedItem();
}
}
/// <summary>
/// get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
}
/// <summary>
/// when the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e"></param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (_itemsHolder == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
_itemsHolder.Children.Clear();
if (base.Items.Count > 0)
{
base.SelectedItem = base.Items[0];
UpdateSelectedItem();
}
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
// Search for recently deleted items caused by a Drag/Drop operation
if (e.NewItems != null && _deletedObject != null)
{
foreach (var item in e.NewItems)
{
if (_deletedObject == item)
{
// If the new item is the same as the recently deleted one (i.e. a drag/drop event)
// then cancel the deletion and reuse the ContentPresenter so it doesn't have to be
// redrawn. We do need to link the presenter to the new item though (using the Tag)
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
int index = _itemsHolder.Children.IndexOf(cp);
(_itemsHolder.Children[index] as ContentPresenter).Tag =
(item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
}
_deletedObject = null;
}
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
_deletedObject = item;
// We want to run this at a slightly later priority in case this
// is a drag/drop operation so that we can reuse the template
this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate()
{
if (_deletedObject != null)
{
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
this._itemsHolder.Children.Remove(cp);
}
}
}
));
}
}
UpdateSelectedItem();
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
}
/// <summary>
/// update the visible child in the ItemsHolder
/// </summary>
/// <param name="e"></param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
}
/// <summary>
/// generate a ContentPresenter for the selected item
/// </summary>
void UpdateSelectedItem()
{
if (_itemsHolder == null)
{
return;
}
// generate a ContentPresenter if necessary
TabItem item = GetSelectedTabItem();
if (item != null)
{
CreateChildContentPresenter(item);
}
// show the right child
foreach (ContentPresenter child in _itemsHolder.Children)
{
child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
}
/// <summary>
/// create the child ContentPresenter for the given item (could be data or a TabItem)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
{
return null;
}
ContentPresenter cp = FindChildContentPresenter(item);
if (cp != null)
{
return cp;
}
// the actual child to be added. cp.Tag is a reference to the TabItem
cp = new ContentPresenter();
cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
cp.ContentTemplate = this.SelectedContentTemplate;
cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
cp.ContentStringFormat = this.SelectedContentStringFormat;
cp.Visibility = Visibility.Collapsed;
cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
_itemsHolder.Children.Add(cp);
return cp;
}
/// <summary>
/// Find the CP for the given object. data could be a TabItem or a piece of data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
ContentPresenter FindChildContentPresenter(object data)
{
if (data is TabItem)
{
data = (data as TabItem).Content;
}
if (data == null)
{
return null;
}
if (_itemsHolder == null)
{
return null;
}
foreach (ContentPresenter cp in _itemsHolder.Children)
{
if (cp.Content == data)
{
return cp;
}
}
return null;
}
/// <summary>
/// copied from TabControl; wish it were protected in that class instead of private
/// </summary>
/// <returns></returns>
protected TabItem GetSelectedTabItem()
{
object selectedItem = base.SelectedItem;
if (selectedItem == null)
{
return null;
}
if (_deletedObject == selectedItem)
{
}
TabItem item = selectedItem as TabItem;
if (item == null)
{
item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
}
return item;
}
}