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;
}
}
Related
I have some problems with my data grid. My project is transforming a Delphi project to .Net. The product owner want the same behaviour for the datagrids.
When positioned on the last cell and tab or enter is hit, the following should happen:
A new row is added
The first cell in the new row is selected
Other demands for the datagrid is:
The focus should remain inside the datagrid once it has the focus (ALT + key combinations is the way to leave the datagrid again).
The datagrid is databound
The datagrid is used in MVVM
We use the .net4.0 full profile
This article had the best solution I could find.
I preferred to use an attached property rather than a behavior, since this enabled me to set it easily in the default style for DataGrid. Here's the code:
namespace SampleDataGridApp
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// An attached behavior that modifies the tab behavior for a <see cref="DataGrid"/>.
/// </summary>
public static class DataGridBehavior
{
/// <summary>
/// Identifies the <c>NewLineOnTab</c> attached property.
/// </summary>
public static readonly DependencyProperty NewLineOnTabProperty = DependencyProperty.RegisterAttached(
"NewLineOnTab",
typeof(bool),
typeof(DataGridBehavior),
new PropertyMetadata(default(bool), OnNewLineOnTabChanged));
/// <summary>
/// Sets the value of the <c>NewLineOnTab</c> attached property.
/// </summary>
/// <param name="element">The <see cref="DataGrid"/>.</param>
/// <param name="value">A value indicating whether to apply the behavior.</param>
public static void SetNewLineOnTab(DataGrid element, bool value)
{
element.SetValue(NewLineOnTabProperty, value);
}
/// <summary>
/// Gets the value of the <c>NewLineOnTab</c> attached property.
/// </summary>
/// <param name="element">The <see cref="DataGrid"/>.</param>
/// <returns>A value indicating whether to apply the behavior.</returns>
public static bool GetNewLineOnTab(DataGrid element)
{
return (bool)element.GetValue(NewLineOnTabProperty);
}
/// <summary>
/// Called when the value of the <c>NewLineOnTab</c> property changes.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private static void OnNewLineOnTabChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGrid d = sender as DataGrid;
if (d == null)
{
return;
}
bool newValue = (bool)e.NewValue;
bool oldValue = (bool)e.OldValue;
if (oldValue == newValue)
{
return;
}
if (oldValue)
{
d.PreviewKeyDown -= AssociatedObjectKeyDown;
}
else
{
d.PreviewKeyDown += AssociatedObjectKeyDown;
KeyboardNavigation.SetTabNavigation(d, KeyboardNavigationMode.Contained);
}
}
/// <summary>
/// Handles the <see cref="UIElement.KeyDown"/> event for a <see cref="DataGridCell"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private static void AssociatedObjectKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Tab)
{
return;
}
DataGrid dg = e.Source as DataGrid;
if (dg == null)
{
return;
}
if (dg.CurrentColumn.DisplayIndex == dg.Columns.Count - 1)
{
var icg = dg.ItemContainerGenerator;
if (dg.SelectedIndex == icg.Items.Count - 2)
{
dg.CommitEdit(DataGridEditingUnit.Row, false);
}
}
}
}
}
My default style looks like this:
<Style TargetType="DataGrid">
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="Contained" />
<Setter Property="sampleDataGridApp:DataGridBehavior.NewLineOnTab" Value="True" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
If the last column's DataGridCell has it's IsTabStop set to false like in this example the above will not work.
Here is a buggy workaround:
private static void AssociatedObjectKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Tab)
{
return;
}
DataGrid dg = e.Source as DataGrid;
if (dg == null)
{
return;
}
int offSet = 1;
var columnsReversed = dg.Columns.Reverse();
foreach (var dataGridColumn in columnsReversed)
{
// Bug: This makes the grand assumption that a readonly column's "DataGridCell" has IsTabStop == false;
if (dataGridColumn.IsReadOnly)
{
offSet++;
}
else
{
break;
}
}
if (dg.CurrentColumn.DisplayIndex == (dg.Columns.Count - offSet))
{
var icg = dg.ItemContainerGenerator;
if (dg.SelectedIndex == icg.Items.Count - 2)
{
dg.CommitEdit(DataGridEditingUnit.Row, false);
}
}
}
Ok, I have been fighting this problem for many hours now. I have tried nearly every proposed solution out there and here is what I found that works for me...
private void grid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
if (grid.SelectedIndex == grid.Items.Count - 2 && grid.CurrentColumn.DisplayIndex == grid.Columns.Count - 1)
{
grid.CommitEdit(DataGridEditingUnit.Row, false);
e.Handled = true;
}
}
}
private void DataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (grid.SelectedIndex == grid.Items.Count - 2)
{
grid.SelectedIndex = grid.Items.Count - 1;
grid.CurrentCell = new DataGridCellInfo(grid.Items[grid.Items.Count - 1], grid.Columns[0]);
}
}
What this code does is when you tab to the last cell of the last row and press tab it will move focus to the first cell of the new row. Which is what you would expect but this is not default behavior. The default behavior is to move focus to the next control and not commit the current row edit. This is clearly a bug in the DataGrid I believe which is why all the proposed solutions have a whiff of kluge. My solution doesn't smell that good I admit, but if you agree that this is bug, I prefer this to the ridiculous default behavior.
This solution works even if the grid is sorted. The newly entered row will sort to the proper place but the focus will be put on the first column of the new row.
The only unsolved issue is that when tabbing down from the top to the last cell before the new row, tab must be entered twice before focus is moved to the new row. I looked into this quirk for a bit and finally gave up on it.
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 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);
}
I have a custom button control that does not derive from Button. Is it possible for me to implement the equivalent of IsDefault so that the command associated with my control will be invoked. I was hoping that this was an attached property that I could add to any control but as far as I can tell it doesn't seem to be. Am I out of luck if my control does not derive from Button or is there at least a reasonable workaround?
UPDATE:
I just took a peek with reflector at how this is being done underneath for Button and I must say it isn't the most self explanitory code I've seen. It appears that there are at least 3 dependency properties a few custom types just for the purpose of handling the concept of a Button being default. Since there doesn't seem to be an existing way to borrow the IsDefault functionality I suppose I'll have to narrow down what I'm trying to achieve so that I can at least get default focus and access key handling to work and just ignore the complexity invloved in the Button.IsDefault implementation.
UPDATE:
Added the following code example showing my uncessful attempt at trying itowlson's suggestions.
MyButton.xaml
<UserControl x:Class="IsDefault.MyButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Height="28"
Width="117">
<Grid>
<Button Click="Button_Click">
<Button.Template>
<ControlTemplate>
<Border BorderThickness="2"
CornerRadius="12"
Background="DarkSlateBlue">
<TextBlock Foreground="WhiteSmoke"
HorizontalAlignment="Center"
VerticalAlignment="Center">Some Text</TextBlock>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</UserControl>
MyButton.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace IsDefault
{
/// <summary>
/// Interaction logic for MyButton.xaml
/// </summary>
public partial class MyButton : UserControl
{
// Provide CLR accessors for the event
public event RoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
// Using a RoutedEvent
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
"Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButton));
public bool IsDefault
{
get { return (bool)GetValue(IsDefaultProperty); }
set { SetValue(IsDefaultProperty, value); }
}
public static readonly DependencyProperty IsDefaultProperty =
DependencyProperty.Register(
"IsDefault",
typeof(bool),
typeof(MyButton),
new PropertyMetadata(false, IsDefault_PropertyChangedCallback, null));
public MyButton()
{
InitializeComponent();
}
protected override void OnAccessKey(AccessKeyEventArgs e)
{
base.OnAccessKey(e);
if (e.Key == "\r")
{
if (e.IsMultiple)
{
// There are multiple controls that are currently handling the Enter key
MessageBox.Show("there are multiple controls handling the Enter key.");
}
else
{
RaiseEvent(new RoutedEventArgs(ClickEvent, this));
}
}
}
private static void IsDefault_PropertyChangedCallback(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var button = d as MyButton;
var isDefault = (bool)e.NewValue;
if (isDefault)
{
AccessKeyManager.Register("\r", button);
}
else
{
AccessKeyManager.Unregister("\r", button);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(ClickEvent));
}
}
}
MainWindow.xaml
<Window x:Class="IsDefault.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:my="clr-namespace:IsDefault">
<Grid>
<Button Content="Button"
Height="23"
HorizontalAlignment="Left"
Margin="224,24,0,0"
Name="button1"
VerticalAlignment="Top"
Width="75" />
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="208,94,0,0"
Name="textBox1"
VerticalAlignment="Top"
Width="120" />
<my:MyButton Height="28"
HorizontalAlignment="Left"
Margin="232,154,0,0"
x:Name="myButton1"
VerticalAlignment="Top"
Width="117"
Click="myButton1_Click"
IsDefault="True"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace IsDefault
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void myButton1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("My button was clicked, yay!");
}
}
}
All that setting Button.IsDefault does is call AccessKeyManager.Register("\r", this) (or Unregister if setting to false). (Actually, it does a little bit of extra work around focus management, but that's probably not crucial for you.)
So to achieve a similar effect yourself:
Create an IsDefault dependency property in the usual way.
In your IsDefault PropertyChangedCallback, call AccessKeyManager.Register or AccessKeyManager.Unregister according to the new value, passing "\r" (the Enter string) as the key and the control instance as the element.
Override OnAccessKey to specify how your control responds to the Enter key. (For example, ButtonBase overrides this to call OnClick. You could also handle the AccessKeyManager.AccessKeyPressed attached event, but since you are defining a custom control, overriding OnAccessKey is neater.)
Although this is an older question others may still be interested in an answer as I have been. So, here is my solution. It's based on some reverse engineering of Microsoft reference source I've found (Button and ButtonBase). I'm still newby in WPF so much code may be needless, but it works!
This code adds following features to an UserControl (they seem to be closly connected to each other):
IsDefault
IsCancel
Command
Click event
(all comments in code are made by MS)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.ComponentModel;
using System.Windows.Automation.Peers;
using System.Security;
using System.Diagnostics;
[DefaultEvent("Click")]
public partial class MyButton : UserControl, ICommandSource {
#region "Private Variables"
// Cache valid bits
private ControlBoolFlags _ControlBoolField;
#endregion
#region "Constructors"
static MyButton()
{
EventManager.RegisterClassHandler(
typeof(MyButton),
AccessKeyManager.AccessKeyPressedEvent,
new AccessKeyPressedEventHandler(
OnAccessKeyPressed));
KeyboardNavigation.AcceptsReturnProperty.OverrideMetadata(
typeof(MyButton),
new FrameworkPropertyMetadata(
true));
// Disable IME on button.
// - key typing should not be eaten by IME.
// - when the button has a focus, IME's disabled status should
// be indicated as
// grayed buttons on the language bar.
InputMethod.IsInputMethodEnabledProperty.OverrideMetadata(
typeof(MyButton),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.Inherits));
}
#endregion
#region "AccessKey"
private static void OnAccessKeyPressed(object sender,
AccessKeyPressedEventArgs e)
{
if (!e.Handled && e.Scope == null && e.Target == null) {
e.Target = sender as MyButton;
}
}
/// <summary>
/// The Access key for this control was invoked.
/// </summary>
protected override void OnAccessKey(AccessKeyEventArgs e)
{
if (e.IsMultiple) {
base.OnAccessKey(e);
} else {
// Don't call the base b/c we don't want to take focus
OnClick();
}
}
#endregion
#region "Click"
/// <summary>
/// Event correspond to left mouse button click
/// </summary>
public static readonly RoutedEvent ClickEvent =
EventManager.RegisterRoutedEvent(
"Click",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyButton));
/// <summary>
/// Add / Remove ClickEvent handler
/// </summary>
[Category("Behavior")]
public event RoutedEventHandler Click
{
add {
AddHandler(ClickEvent, value);
}
remove {
RemoveHandler(ClickEvent, value);
}
}
/// <summary>
/// This virtual method is called when button is clicked and
/// it raises the Click event
/// </summary>
private void BaseOnClick()
{
RoutedEventArgs locRoutedEventArgs = new RoutedEventArgs(
MyButton.ClickEvent,
this);
this.RaiseEvent(locRoutedEventArgs);
ExecuteCommandSource(this);
}
/// <summary>
/// This method is called when button is clicked.
/// </summary>
private void OnClick()
{
if (AutomationPeer.ListenerExists(AutomationEvents.InvokePatternOnInvoked)) {
AutomationPeer locPeer =
UIElementAutomationPeer.CreatePeerForElement(this);
if (locPeer != null) {
locPeer.RaiseAutomationEvent(AutomationEvents.InvokePatternOnInvoked);
}
}
// base.OnClick should be called first. Our default command
// for Cancel Button to close dialog should happen after
// Button's click event handler has been called.
// If there Is excption And it Then 's a Cancel button and
// RoutedCommand is null,
// we will raise Window.DialogCancelCommand.
try {
BaseOnClick();
} finally {
// When the Button RoutedCommand is null, if it's a
// Cancel Button,
// Window.DialogCancelCommand will be the default command.
// Do not assign Window.DialogCancelCommand to
// Button.Command.
// If in Button click handler user nulls the Command,
// we still want to provide the default behavior.
if (Command == null && IsCancel) {
// Can't invoke Window.DialogCancelCommand directly.
// Have to raise event.
// Filed bug 936090: Commanding perf issue: can't
// directly invoke a command.
ExecuteCommand(DialogCancelCommand, null, this);
}
}
}
#endregion
#region "ClickMode"
/// <summary>
/// The DependencyProperty for the ClickMode property.
/// Flags: None
/// Default Value: ClickMode.Release
/// </summary>
public static readonly DependencyProperty ClickModeProperty =
DependencyProperty.Register(
"ClickMode",
typeof(ClickMode),
typeof(MyButton),
new FrameworkPropertyMetadata(
ClickMode.Release),
new ValidateValueCallback(
IsValidClickMode));
/// <summary>
/// ClickMode specify when the Click event should fire
/// </summary>
[Bindable(true), Category("Behavior")]
public ClickMode ClickMode
{
get {
return (ClickMode)GetValue(ClickModeProperty);
}
set {
SetValue(ClickModeProperty, value);
}
}
private static bool IsValidClickMode(object valClickMode)
{
ClickMode locClickMode = (ClickMode)valClickMode;
return locClickMode == ClickMode.Press
|| locClickMode == ClickMode.Release
|| locClickMode == ClickMode.Hover;
}
#endregion
#region "KeyDown"
/// <summary>
/// This is the method that responds to the KeyDown event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (ClickMode == ClickMode.Hover) {
// Ignore when in hover-click mode.
return;
}
if (e.Key == Key.Space) {
// Alt+Space should bring up system menu, we shouldn't
// handle it.
if ((Keyboard.Modifiers &
(ModifierKeys.Control | ModifierKeys.Alt)) !=
ModifierKeys.Alt) {
if ((!IsMouseCaptured) &&
(object.ReferenceEquals(e.OriginalSource, this))) {
IsSpaceKeyDown = true;
CaptureMouse();
if (ClickMode == ClickMode.Press) {
OnClick();
}
e.Handled = true;
}
}
} else if (e.Key == Key.Enter
&& Convert.ToBoolean(GetValue(KeyboardNavigation.AcceptsReturnProperty))) {
if (object.ReferenceEquals(e.OriginalSource, this)) {
IsSpaceKeyDown = false;
if (IsMouseCaptured) {
ReleaseMouseCapture();
}
OnClick();
e.Handled = true;
}
} else {
// On any other key we set IsPressed to false only if
// Space key is pressed
if (IsSpaceKeyDown) {
IsSpaceKeyDown = false;
if (IsMouseCaptured) {
ReleaseMouseCapture();
}
}
}
}
private bool IsSpaceKeyDown
{
get {
return ReadControlFlag(ControlBoolFlags.IsSpaceKeyDown);
}
set {
WriteControlFlag(ControlBoolFlags.IsSpaceKeyDown, value);
}
}
#endregion
#region "Command"
/// <summary>
/// The DependencyProperty for RoutedCommand
/// </summary>
[CommonDependencyProperty()]
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(MyButton),
new FrameworkPropertyMetadata(
(ICommand)null,
new PropertyChangedCallback(
OnCommandChanged)));
/// <summary>
/// Get or set the Command property
/// </summary>
[Bindable(true), Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public ICommand Command
{
get {
return (ICommand)GetValue(CommandProperty);
}
set {
SetValue(CommandProperty, value);
}
}
private static void OnCommandChanged(
DependencyObject valTarget,
DependencyPropertyChangedEventArgs e)
{
MyButton locMyButton = valTarget as MyButton;
if (locMyButton != null) {
locMyButton.OnCommandChanged(
(ICommand)e.OldValue,
(ICommand)e.NewValue);
}
}
private void OnCommandChanged(
ICommand valOldCommand,
ICommand valNewCommand)
{
if (valOldCommand != null) {
valOldCommand.CanExecuteChanged -= OnCanExecuteChanged;
}
if (valNewCommand != null) {
valNewCommand.CanExecuteChanged += OnCanExecuteChanged;
}
UpdateCanExecute();
}
#endregion
#region "CommandParameter"
/// <summary>
/// The DependencyProperty for the CommandParameter
/// </summary>
[CommonDependencyProperty()]
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(MyButton),
new FrameworkPropertyMetadata(
(object)null));
/// <summary>
/// Reflects the parameter to pass to the CommandProperty
/// upon execution.
/// </summary>
[Bindable(true), Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public object CommandParameter
{
get {
return GetValue(CommandParameterProperty);
}
set {
SetValue(CommandParameterProperty, value);
}
}
#endregion
#region "CommandTarget"
/// <summary>
/// The DependencyProperty for Target property
/// Flags: None
/// Default Value: null
/// </summary>
[CommonDependencyProperty()]
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(MyButton),
new FrameworkPropertyMetadata(
(IInputElement)null));
/// <summary>
/// The target element on which to fire the command.
/// </summary>
[Bindable(true), Category("Action")]
public IInputElement CommandTarget
{
get {
return (IInputElement)GetValue(CommandTargetProperty);
}
set {
SetValue(CommandTargetProperty, value);
}
}
#endregion
#region "CanExecute"
private void OnCanExecuteChanged(object valTarget, EventArgs e)
{
if (valTarget != null) {
UpdateCanExecute();
}
}
private bool CanExecute
{
get {
return !ReadControlFlag(ControlBoolFlags.CommandDisabled);
}
set {
if (value != CanExecute) {
WriteControlFlag(
ControlBoolFlags.CommandDisabled,
!value);
CoerceValue(IsEnabledProperty);
}
}
}
private void UpdateCanExecute()
{
if (Command != null) {
CanExecute = CanExecuteCommandSource(this);
} else {
CanExecute = true;
}
}
#endregion
#region "IsDefault"
/// <summary>
/// The DependencyProperty for the IsDefault property.
/// Flags: None
/// Default Value: false
/// </summary>
public static readonly DependencyProperty IsDefaultProperty =
DependencyProperty.RegisterAttached(
"IsDefault",
typeof(bool),
typeof(MyButton),
new UIPropertyMetadata(
false,
new PropertyChangedCallback(
OnIsDefaultChanged)));
/// <summary>
/// Specifies whether or not this button is the default button.
/// </summary>
/// <value></value>
public bool IsDefault
{
get {
return (bool)GetValue(IsDefaultProperty);
}
set {
SetValue(IsDefaultProperty, value);
}
}
private static void OnIsDefaultChanged(
DependencyObject valTarget,
DependencyPropertyChangedEventArgs e)
{
MyButton locMyButton = valTarget as MyButton;
if (locMyButton != null) {
Window locWindow = Window.GetWindow(locMyButton);
if (locWindow == null) {
locWindow = Application.Current.MainWindow;
}
if (FocusChangedEventHandler == null) {
FocusChangedEventHandler =
new KeyboardFocusChangedEventHandler(
locMyButton.OnFocusChanged);
}
if (locWindow != null) {
if ((bool)e.NewValue) {
AccessKeyManager.Register("\x000D", locMyButton);
KeyboardNavigation.SetAcceptsReturn(
locMyButton, true);
locMyButton.UpdateIsDefaulted(
Keyboard.FocusedElement);
} else {
AccessKeyManager.Unregister("\x000D", locMyButton);
KeyboardNavigation.SetAcceptsReturn(
locMyButton, false);
locMyButton.UpdateIsDefaulted(null);
}
}
}
}
private static KeyboardFocusChangedEventHandler FocusChangedEventHandler;
private void OnFocusChanged(object valTarget, KeyboardFocusChangedEventArgs e)
{
UpdateIsDefaulted(Keyboard.FocusedElement);
}
#endregion
#region "IsDefaulted"
/// <summary>
/// The key needed set a read-only property.
/// </summary>
private static readonly DependencyPropertyKey IsDefaultedPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsDefaulted",
typeof(bool),
typeof(MyButton),
new FrameworkPropertyMetadata(
false));
/// <summary>
/// The DependencyProperty for the IsDefaulted property.
/// Flags: None
/// Default Value: false
/// </summary>
public static readonly DependencyProperty IsDefaultedProperty =
IsDefaultedPropertyKey.DependencyProperty;
/// <summary>
/// Specifies whether or not this button is the button that
/// would be invoked when Enter is pressed.
/// </summary>
/// <value></value>
public bool IsDefaulted
{
get {
return (bool)GetValue(IsDefaultedProperty);
}
}
private void UpdateIsDefaulted(IInputElement valFocusElement)
{
// If it's not a default button, or nothing is focused,
// or it's disabled
// then it's not defaulted.
if (!IsDefault || valFocusElement == null || !IsEnabled) {
SetValue(IsDefaultedPropertyKey, false);
return;
}
DependencyObject locFocusDependencyObj =
valFocusElement as DependencyObject;
object locThisScope = null;
object locFocusScope = null;
// If the focused thing is not in this scope then
// IsDefaulted = false
AccessKeyPressedEventArgs locEventArgs =
default(AccessKeyPressedEventArgs);
bool locIsDefaulted = false;
try {
// Step 1: Determine the AccessKey scope from currently
// focused element
locEventArgs = new AccessKeyPressedEventArgs();
valFocusElement.RaiseEvent(locEventArgs);
locFocusScope = locEventArgs.Scope;
// Step 2: Determine the AccessKey scope from this button
locEventArgs = new AccessKeyPressedEventArgs();
this.RaiseEvent(locEventArgs);
locThisScope = locEventArgs.Scope;
// Step 3: Compare scopes
if (object.ReferenceEquals(locThisScope, locFocusScope)
&& (locFocusDependencyObj == null
|| !(bool)locFocusDependencyObj.GetValue(KeyboardNavigation.AcceptsReturnProperty))) {
locIsDefaulted = true;
}
} finally {
SetValue(IsDefaultedPropertyKey, locIsDefaulted);
}
}
#endregion
#region "IsCancel"
/// <summary>
/// The DependencyProperty for the IsCancel property.
/// Flags: None
/// Default Value: false
/// </summary>
public static readonly DependencyProperty IsCancelProperty =
DependencyProperty.Register(
"IsCancel",
typeof(bool),
typeof(MyButton),
new FrameworkPropertyMetadata(
false,
new PropertyChangedCallback(
OnIsCancelChanged)));
/// <summary>
/// Specifies whether or not this button is the cancel button.
/// </summary>
/// <value></value>
public bool IsCancel
{
get {
return (bool)GetValue(IsCancelProperty);
}
set {
SetValue(IsCancelProperty, value);
}
}
private static void OnIsCancelChanged(
DependencyObject valTarget,
DependencyPropertyChangedEventArgs e)
{
MyButton locMyButton = valTarget as MyButton;
if (locMyButton != null) {
if ((bool)e.NewValue) {
AccessKeyManager.Register("\x001B", locMyButton);
} else {
AccessKeyManager.Unregister("\x001B", locMyButton);
}
}
}
#endregion
#region "Helper Functions"
/// <summary>
/// This allows a caller to override its ICommandSource values
//// (used by Button and ScrollBar)
/// </summary>
static internal void ExecuteCommand(
ICommand command,
object parameter,
IInputElement target)
{
RoutedCommand routed = command as RoutedCommand;
if (routed != null) {
if (routed.CanExecute(parameter, target)) {
routed.Execute(parameter, target);
}
} else if (command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
static internal bool CanExecuteCommandSource(
ICommandSource commandSource)
{
ICommand command = commandSource.Command;
if (command != null) {
object parameter = commandSource.CommandParameter;
IInputElement target = commandSource.CommandTarget;
RoutedCommand routed = command as RoutedCommand;
if (routed != null) {
if (target == null) {
target = commandSource as IInputElement;
}
return routed.CanExecute(parameter, target);
} else {
return command.CanExecute(parameter);
}
}
return false;
}
/// <summary>
/// Executes the command on the given command source.
/// </summary>
/// <SecurityNote>
/// Critical - calls critical function (ExecuteCommandSource).
/// TreatAsSafe - always passes in false for userInitiated,
//// which is safe
/// </SecurityNote>
[SecurityCritical(), SecuritySafeCritical()]
static internal void ExecuteCommandSource(
ICommandSource commandSource)
{
CriticalExecuteCommandSource(commandSource, false);
}
/// <summary>
/// Executes the command on the given command source.
/// </summary>
/// <SecurityNote>
/// Critical - sets the user initiated bit on a command,
/// which is used for security purposes later.
/// It is important to validate the callers of this,
/// and the implementation to make sure
/// that we only call MarkAsUserInitiated in the
/// correct cases.
/// </SecurityNote>
[SecurityCritical()]
static internal void CriticalExecuteCommandSource(
ICommandSource commandSource,
bool userInitiated)
{
ICommand command = commandSource.Command;
if (command != null) {
object parameter = commandSource.CommandParameter;
IInputElement target = commandSource.CommandTarget;
RoutedCommand routed = command as RoutedCommand;
if (routed != null) {
if (target == null) {
target = commandSource as IInputElement;
}
if (routed.CanExecute(parameter, target)) {
routed.Execute(parameter, target);
}
} else if (command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}
/// <summary>
/// DialogCancel Command. It closes window if it's dialog and return
/// false as the dialog value.
/// </summary>
/// <remarks>
/// Right now this is only used by Cancel Button to close the dialog.
static internal readonly RoutedCommand DialogCancelCommand =
new RoutedCommand(
"DialogCancel",
typeof(Window));
#endregion
#region "ControlFlags"
internal bool ReadControlFlag(ControlBoolFlags reqFlag)
{
return (_ControlBoolField & reqFlag) != 0;
}
internal void WriteControlFlag(ControlBoolFlags reqFlag, bool #set)
{
if (#set)
{
_ControlBoolField = _ControlBoolField | reqFlag;
}
else
{
_ControlBoolField = _ControlBoolField & (~reqFlag);
}
}
internal enum ControlBoolFlags : ushort
{
ContentIsNotLogical = 0x1,
// used in contentcontrol.cs
IsSpaceKeyDown = 0x2,
// used in ButtonBase.cs
HeaderIsNotLogical = 0x4,
// used in HeaderedContentControl.cs, HeaderedItemsControl.cs
CommandDisabled = 0x8,
// used in ButtonBase.cs, MenuItem.cs
ContentIsItem = 0x10,
// used in contentcontrol.cs
HeaderIsItem = 0x20,
// used in HeaderedContentControl.cs, HeaderedItemsControl.cs
ScrollHostValid = 0x40,
// used in ItemsControl.cs
ContainsSelection = 0x80,
// used in TreeViewItem.cs
VisualStateChangeSuspended = 0x100
// used in Control.cs
}
#endregion
}
/// <summary>
/// An attribute that indicates that a DependencyProperty
/// declaration is common
/// enough to be included in KnownTypes.cs.
/// </summary>
[Conditional("COMMONDPS")]
internal sealed class CommonDependencyPropertyAttribute : Attribute
{
}
Datagrid in silverlight 3 toolkit is not responding to scrollwheel(mousewheel). Is there any way to get support of scroll wheel?
Below is the behavior I am using. And below that is how you attach it to the datagrid in xaml.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Automation.Peers;
using System.Windows.Interactivity;
using System.Windows.Automation.Provider;
using System.Windows.Automation;
using System.Windows.Data;
namespace GLS.Gui.Helper.Behaviors
{
public class MouseWheelScrollBehavior : Behavior<Control>
{
/// <summary>
/// Gets or sets the peer.
/// </summary>
/// <value>The peer.</value>
private AutomationPeer Peer { get; set; }
/// <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()
{
this.Peer = FrameworkElementAutomationPeer.FromElement(this.AssociatedObject);
if (this.Peer == null)
this.Peer = FrameworkElementAutomationPeer.CreatePeerForElement(this.AssociatedObject);
this.AssociatedObject.MouseWheel += new MouseWheelEventHandler(AssociatedObject_MouseWheel);
base.OnAttached();
}
/// <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()
{
this.AssociatedObject.MouseWheel -= new MouseWheelEventHandler(AssociatedObject_MouseWheel);
base.OnDetaching();
}
/// <summary>
/// Handles the MouseWheel event of the AssociatedObject control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseWheelEventArgs"/> instance containing the event data.</param>
void AssociatedObject_MouseWheel(object sender, MouseWheelEventArgs e)
{
//Do not handle already handled events
if (e.Handled)
return;
this.AssociatedObject.Focus();
int direction = Math.Sign(e.Delta);
ScrollAmount scrollAmount =
(direction < 0) ? ScrollAmount.SmallIncrement : ScrollAmount.SmallDecrement;
if (this.Peer != null)
{
IScrollProvider scrollProvider =
this.Peer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
bool shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
if (scrollProvider != null && scrollProvider.VerticallyScrollable && !shiftKey)
{
scrollProvider.Scroll(ScrollAmount.NoAmount, scrollAmount);
e.Handled = true;
}
else if (scrollProvider != null && scrollProvider.VerticallyScrollable && shiftKey)
{
scrollProvider.Scroll(scrollAmount, ScrollAmount.NoAmount);
e.Handled = true;
}
}
}
}
}
How to attach the behavior:
<data:DataGrid>
<i:Interaction.Behaviors>
<b:MouseWheelScrollBehavior />
</i:Interaction.Behaviors>
</data:DataGrid>
An other way to do this (very basic example):
xaml:
<Grid x:Name="LayoutRoot">
<data:DataGrid x:Name="dg" Height="100">
<ScrollViewer.VerticalScrollBarVisibility>true</ScrollViewer.VerticalScrollBarVisibility>
</data:DataGrid>
CS:
public partial class MainPage : UserControl
{
IList<Person> list = new List<Person>();
public MainPage()
{
InitializeComponent();
list.Add(new Person("Pieter1","Nijs"));
list.Add(new Person("Pieter2", "Nijs"));
list.Add(new Person("Pieter3", "Nijs"));
list.Add(new Person("Pieter4", "Nijs"));
list.Add(new Person("Pieter5", "Nijs"));
list.Add(new Person("Pieter6", "Nijs"));
list.Add(new Person("Pieter7", "Nijs"));
list.Add(new Person("Pieter8", "Nijs"));
dg.ItemsSource = list;
dg.MouseWheel += new MouseWheelEventHandler(dg_MouseWheel);
}
void dg_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta < 0)
{
dg.ScrollIntoView(list[dg.SelectedIndex + 2], null);
}
else
{
dg.ScrollIntoView(list[dg.SelectedIndex - 2], null);
}
}
}
So, what I do here is quite simple!
I add an EventHandler to the DataGrid MouseWheel - event. In that handler I retreive the e.Delta (this is the amount the wheel has changed since the last time) so I know if the user scrolled up (positive Delta) or down (negative Delta). And then I call the ScrollIntoView-method of the DataGrid, where I can specify to what row the Grid should scroll to.
As mentioned, this is a very basic example! This is just to show you how it could work! You should add extra logic to make sure you don't go out of bounce!