This is what my control tree looks like:
<window>
<scrollviewer>
<expander>
<scrollviewer>
<grid>
</grid>
</scrollviewer>
</expander>
<expander>
<scrollviewer>
<grid>
</grid>
</scrollviewer>
</expander>
</scrollviewer>
</window>
Using the mouse wheel, the control automatically passes from parent to child scrollviewer, but when I scroll to the end of the child scrollviewer the control doesn't pass back to the parent scorllviewer. How do I achieve this?
The expander, grid and the scrollviewers are dynamically generated.
I get a similar trouble in my application. I correct it by a depency property that will catch and pass the event too his parent. This can be applied to any control that have a scroll in it. But for me, i didn't need to validate if it was at the end of the scroll to send to his parent. You will just have to add, in the OnValueChanged method, a validation for if the scroll is at the end or at the top to send to his parent.
using System.Windows.Controls;
public static class SendMouseWheelToParent
{
public static readonly DependencyProperty ScrollProperty
= DependencyProperty.RegisterAttached("IsSendingMouseWheelEventToParent",
typeof(bool),
typeof(SendMouseWheelToParent),
new FrameworkPropertyMetadata(OnValueChanged));
/// <summary>
/// Gets the IsSendingMouseWheelEventToParent for a given <see cref="TextBox"/>.
/// </summary>
/// <param name="control">
/// The <see cref="TextBox"/> whose IsSendingMouseWheelEventToParent is to be retrieved.
/// </param>
/// <returns>
/// The IsSendingMouseWheelEventToParent, or <see langword="null"/>
/// if no IsSendingMouseWheelEventToParent has been set.
/// </returns>
public static bool? GetIsSendingMouseWheelEventToParent(Control control)
{
if (control == null)
throw new ArgumentNullException("");
return control.GetValue(ScrollProperty) as bool?;
}
/// <summary>
/// Sets the IsSendingMouseWheelEventToParent for a given <see cref="TextBox"/>.
/// </summary>
/// <param name="control">
/// The <see cref="TextBox"/> whose IsSendingMouseWheelEventToParent is to be set.
/// </param>
/// <param name="IsSendingMouseWheelEventToParent">
/// The IsSendingMouseWheelEventToParent to set, or <see langword="null"/>
/// to remove any existing IsSendingMouseWheelEventToParent from <paramref name="control"/>.
/// </param>
public static void SetIsSendingMouseWheelEventToParent(Control control, bool? sendToParent)
{
if (control == null)
throw new ArgumentNullException("");
control.SetValue(ScrollProperty, sendToParent);
}
private static void OnValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var scrollViewer = dependencyObject as Control;
bool? IsSendingMouseWheelEventToParent = e.NewValue as bool?;
scrollViewer.PreviewMouseWheel -= scrollViewer_PreviewMouseWheel;
if (IsSendingMouseWheelEventToParent != null && IsSendingMouseWheelEventToParent != false)
{
scrollViewer.SetValue(ScrollProperty, IsSendingMouseWheelEventToParent);
scrollViewer.PreviewMouseWheel += scrollViewer_PreviewMouseWheel;
}
}
private static void scrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollview = sender as Control;
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
eventArg.RoutedEvent = UIElement.MouseWheelEvent;
eventArg.Source = sender;
var parent = scrollview.Parent as UIElement;
parent.RaiseEvent(eventArg);
}
}
Inspired by Janick's answer and some others, I have an implementation that scrolls ancestor ScrollViewers when inner ones (including from ListView, ListBox, DataGrid) scroll past their top/bottom. The code is in my answer here to a very similar question.
Related
Very high up the visual tree in WPF's Expander control is a border element (see screenshot). By default this has a CornerRadius of 3. Is it possible to modify this value?
I'll leave marking as answer for now but I managed to implement the solution as follows:
Using stylesnooper I obtained the style / control template used for the 'standard' Expander control.
Then after discovering it didn't quite behave as expected, figured out that the line <ToggleButton IsChecked="False" ... is wrong and should actually be <ToggleButton IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"...
Everything then worked as expected.
I made a behavior which modifies the first found border in the ControlTemplate. You can easily extend the behavior with new properties where u want to modify
/// <summary>
/// modifies the first found <see cref="Border"/> in the <see cref="ControlTemplate"/> of the attached <see cref="Control"/>
/// </summary>
public class ModifyBorderBehavior : Behavior<Control>
{
// ##############################################################################################################################
// Properties
// ##############################################################################################################################
#region Properties
/// <summary>
/// The new corner radius
/// </summary>
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
/// <summary>
/// The <see cref="CornerRadius"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(ModifyBorderBehavior));
#endregion
// ##############################################################################################################################
// Constructor
// ##############################################################################################################################
#region Constructor
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
//var children = VisualTree.GetVisualChildCollection<Border>(sender);
if (sender is Control control)
{
Border border = VisualTree.GetVisualChild<Border>(control);
if(ReadLocalValue(CornerRadiusProperty) != DependencyProperty.UnsetValue)
border.CornerRadius = CornerRadius;
}
}
#endregion
}
<Expander>
<i:Interaction.Behaviors>
<zls:ModifyBorderBehavior CornerRadius="0"/>
</i:Interaction.Behaviors>
</Expander>
I am writing a timeline-style control. I want to keep the label in the same place while the item is scrolled until it gets to the end of that item. I made up a picture to show exactly what I am trying to do.
Notice that as it is scrolled the "MDF" label stays put until it reaches the end of the control. I could spend the time to write my own, but I was wondering if there was a more straight forward way to do this using the framework itself.
Here is the solution I came up with. This works, but if anyone can provide a simpler way of doing this, let me know.
<UserControl x:Class="CatalogEditor.MapItem"
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"
mc:Ignorable="d"
x:Name="UserControl"
d:DesignWidth="100" d:DesignHeight="75">
<Border>
<Grid x:Name="InnerBox" HorizontalAlignment="Left">
<Label/>
</Grid>
</Border>
</UserControl>
Code behind:
/// <summary>
/// Interaction logic for MapItem.xaml
/// </summary>
public partial class MapItem : UserControl
{
private ScrollViewer _scrollViewer = null;
private double _maxMargin;
/// <summary>
/// Constructor.
/// </summary>
public MapItem()
{
this.InitializeComponent();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
SizeChanged += OnSizeChanged;
InnerBox.SizeChanged += OnSizeChanged;
}
/// <summary>
/// When the size changes for this or the inner box element.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
_maxMargin = Math.Max(ActualWidth - InnerBox.ActualWidth, 0) - 5;
}
/// <summary>
/// When this item is loaded into the visual tree.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnLoaded(object sender, RoutedEventArgs e)
{
var element = VisualTreeHelper.GetParent(this);
while (element != null && element is ScrollViewer == false)
element = VisualTreeHelper.GetParent(element);
var scrollViewer = element as ScrollViewer;
if (scrollViewer == null)
return;
_scrollViewer = scrollViewer;
_scrollViewer.ScrollChanged += OnScrollChanged;
}
/// <summary>
/// When the scroll viewer's scroll offset has changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
var rectangle = TransformToAncestor(_scrollViewer).TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
var margin = Math.Min(_maxMargin, Math.Max(0, -rectangle.X));
InnerBox.Margin = new Thickness(margin, 0, 0, 0);
}
/// <summary>
/// When this item is unloaded from the visual tree.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnUnloaded(object sender, RoutedEventArgs e)
{
_scrollViewer.ScrollChanged -= OnScrollChanged;
_scrollViewer = null;
}
}
I'm building an application where a ListBox is displaying the Description properties of its items. I want to implement the same kind of edit-in-place functionality that you'd find, say, when editing filenames in Windows Explorer, and I'm finding it to be a whole lot of work.
What I have so far is a ContextMenu that initiates the edit. It's bound to a command in the view model that sets an IsEditingDescription property. The item template is styled so that it displays the Description in a TextBlock when IsEditingDescription is false, and in a TextBox when IsEditingDescription is true. The setter on Description sets IsEditingDescription to false after setting the description.
This works remarkably well. But there are some things that it doesn't do that it should:
The user should be able to initiate the edit by pressing F2.
The user should be able to cancel the edit by pressing ESC.
The user should be able to confirm the edit by pressing ENTER.
When the user clicks a second time on the currently selected item, it should initiate the edit.
The text of the description should be selected when the text box appears
I think I can handle the first three items with commands and key bindings, though I don't really understand how to do key bindings yet. But I can't really figure out an MVVMish way to do the other two. Is there one?
this was something I was having to do over and over so I decided to extend the ListView control and added ItemKeyDown, ItemKeyUp and ItemDoubleClick events which are fired where those events occur when an item in the ListView is in focus. The ListView is derived a ListBox so you should be able to port it over pretty easily.
/Fx/ListView.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls;
namespace Fx
{
public static class Func
{
/// <summary>
/// Finds a specific type of parent up the visual tree.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dep"></param>
/// <returns></returns>
public static T FindParent<T>(this object obj)
{
DependencyObject dep = obj as DependencyObject;
while ((dep != null) && !(dep is T))
{
dep = VisualTreeHelper.GetParent(dep);
}
return (dep != null) ? (T)Convert.ChangeType(dep, typeof(T)) : default(T);
}
}
public class ListView:System.Windows.Controls.ListView
{
#region public event KeyboardEventHandler ItemKeyDown;
/// <summary>
/// Occurs when a key is pressed when a ListViewItem in this
/// ListView is in focus.
/// </summary>
public event KeyEventHandler ItemKeyDown;
/// <summary>
/// Raises the ItemKeyDown event for a ListViewitem in this ListView.
/// </summary>
/// <param name="item"></param>
/// <param name="e"></param>
public void OnItemKeyDown(ListViewItem item, KeyEventArgs e)
{
if (ItemKeyDown != null)
ItemKeyDown(item, e);
}
/// <summary>
/// Handle they KeyDown event on the ListView, find the related
/// ListViewItem and raise the ItemKeyDown event respectively.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_KeyDown(object sender, KeyEventArgs e)
{
ListViewItem item = Func.FindParent<ListViewItem>(e.OriginalSource);
if (item != null)
OnItemKeyDown(item, e);
}
#endregion
#region public event KeyboardEventHandler ItemKeyUp;
/// <summary>
/// Occurs when a key is released when a ListViewItem in this
/// ListView is in focus.
/// </summary>
public event KeyEventHandler ItemKeyUp;
/// <summary>
/// Raises the ItemKeyUp event for a ListViewitem in this ListView.
/// </summary>
/// <param name="item"></param>
/// <param name="e"></param>
public void OnItemKeyUp(ListViewItem item, KeyEventArgs e)
{
if (ItemKeyUp != null)
ItemKeyUp(item, e);
}
/// <summary>
/// Handle they KeyUp event on the ListView, find the related
/// ListViewItem and raise the ItemKeyUp event respectively.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_KeyUp(object sender, KeyEventArgs e)
{
ListViewItem item = Func.FindParent<ListViewItem>(e.OriginalSource);
if (item != null)
OnItemKeyUp(item, e);
}
#endregion
#region public event MouseButtonEventHandler ItemDoubleClick;
/// <summary>
/// Occurs when a ListViewItem in this Listview is double clicked.
/// </summary>
public event MouseButtonEventHandler ItemDoubleClick;
/// <summary>
/// Raise the ItemDoubleClick event for a ListViewItem.
/// </summary>
/// <param name="item"></param>
/// <param name="e"></param>
public void OnItemDoubleClick(ListViewItem item, MouseButtonEventArgs e)
{
if (ItemDoubleClick != null)
ItemDoubleClick(item, e);
}
/// <summary>
/// Handle the MouseDoubleClick event for the ListView, find the related
/// ListViewItem and raise the ItemDoubleClick event respectively.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ListViewItem item = Func.FindParent<ListViewItem>(e.OriginalSource);
if (item != null)
OnItemDoubleClick(item, e);
}
#endregion
public ListView()
{
MouseDoubleClick += new MouseButtonEventHandler(ListView_MouseDoubleClick);
KeyDown += new KeyEventHandler(ListView_KeyDown);
KeyUp += new KeyEventHandler(ListView_KeyUp);
}
}
}
Now to use it...
/Pages/EmployeesPage.xaml
<UserControl x:Class="TestApp.Pages.EmployeesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fx="clr-namespace:Fx"
Width="800" Height="450">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="List Items" FontWeight="Bold" FontSize="18" />
<!-- here is the main part -->
<fx:ListView x:Name="EmployeesList" ItemDoubleClick="EmployeesList_ItemDoubleClick" ItemKeyDown="EmployeesList_ItemKeyDown" />
<!-- main part ends here -->
</StackPanel>
</Grid>
</UserControl>
/Pages/EmployeesPage.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 TestApp.Pages
{
/// <summary>
/// Interaction logic for EmployeesPage.xaml
/// </summary>
public partial class EmployeesPage : UserControl
{
public EmployeesPage()
{
InitializeComponent();
// Fill the ListView with data...
// EmployeesList.ItemsSource = SomeObservableCollectionOrDataSource
}
private void EmployeesList_ItemDoubleClick(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("an item was double clicked");
ListViewItem item = sender as ListViewItem;
// entityType obj = item.DataContext as entityType
// you can begin editing here
}
private void EmployeesList_ItemKeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show(e.Key.ToString() + " key was pressed on an item");
ListViewItem item = sender as ListViewItem;
// entityType obj = item.DataContext as entityType
if (e.Key == Key.F2)
{
// begin editing here
}
else if (e.Key == Key.Enter)
{
// end editing here
}
else if (e.Key == Key.Escape)
{
// cancel editing here
}
}
}
}
Hope this is of at least some use to you.. Good luck.
As for ensuring the text is selected on entering edit mode, I previously used the following behaviour attached to textboxes in another project of mine. It is in VB, but should be pretty straight-forward.
Public Class SelectAllOnFocusTextboxBehavior
Inherits Behavior(Of TextBox)
Protected Overrides Sub OnAttached()
MyBase.OnAttached()
AddHandler AssociatedObject.GotKeyboardFocus, AddressOf AssociatedObjectGotKeyboardFocus
End Sub
Protected Overrides Sub OnDetaching()
MyBase.OnDetaching()
RemoveHandler AssociatedObject.GotKeyboardFocus, AddressOf AssociatedObjectGotKeyboardFocus
End Sub
Private Sub AssociatedObjectGotKeyboardFocus(ByVal sender As Object, ByVal e As KeyboardFocusChangedEventArgs)
AssociatedObject.SelectAll()
End Sub
End Class
I'm trying to convert an event to a command on a devexpress wpf grid context menu item which is derived from FrameworkContentElement instead of FrameworkElement. This causes a runtime error :
{"Cannot attach type \"EventToCommand\" to type \"BarButtonItem\". Instances of type \"EventToCommand\" can only be attached to objects of type \"FrameworkElement\"."}
Is there any workaround?
<dxg:TableView.RowCellMenuCustomizations>
<dxb:BarButtonItem Name="deleteRowItem" Content="Delete" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="ItemClick">
<cmd:EventToCommand Command="{Binding FooChangeCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</dxb:BarButtonItem>
<!--ItemClick="deleteRowItem_ItemClick"/>-->
</dxg:TableView.RowCellMenuCustomizations>
Unfortunately devexpress have run into problems changing the base class to FrameworkElement having intended to make that change...
The FrameworkConentElement is a class that is only available in WPF and not in Silverlight. As MVVM Light is intended to provide a common functionality for all WPF dialects (WPF 3.5, WPF 4, Silverlight 3, Silverlight 4, Sivlverlight 5, WP 7, WP 7.1) it cannot include an implementation that only works in one of the frameworks.
For a discussion about the differences between FrameworkElement and FrameworkContentElement see here.
However, you can just easily implement your own EventToCommand class supporting ContentElement (from which FrameworkContentElement inherits). The class was copied from BL0015 of the MVVM Light source code and modified:
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace GalaSoft.MvvmLight.Command
{
/// <summary>
/// This <see cref="System.Windows.Interactivity.TriggerAction" /> can be
/// used to bind any event on any FrameworkElement to an <see cref="ICommand" />.
/// Typically, this element is used in XAML to connect the attached element
/// to a command located in a ViewModel. This trigger can only be attached
/// to a FrameworkElement or a class deriving from FrameworkElement.
/// <para>To access the EventArgs of the fired event, use a RelayCommand<EventArgs>
/// and leave the CommandParameter and CommandParameterValue empty!</para>
/// </summary>
////[ClassInfo(typeof(EventToCommand),
//// VersionString = "3.0.0.0",
//// DateString = "201003041420",
//// Description = "A Trigger used to bind any event to an ICommand.",
//// UrlContacts = "http://stackoverflow.com/q/6955785/266919",
//// Email = "")]
public partial class EventToCommandWpf : TriggerAction<DependencyObject>
{
/// <summary>
/// Gets or sets a value indicating whether the EventArgs passed to the
/// event handler will be forwarded to the ICommand's Execute method
/// when the event is fired (if the bound ICommand accepts an argument
/// of type EventArgs).
/// <para>For example, use a RelayCommand<MouseEventArgs> to get
/// the arguments of a MouseMove event.</para>
/// </summary>
public bool PassEventArgsToCommand
{
get;
set;
}
/// <summary>
/// Provides a simple way to invoke this trigger programatically
/// without any EventArgs.
/// </summary>
public void Invoke()
{
Invoke(null);
}
/// <summary>
/// Executes the trigger.
/// <para>To access the EventArgs of the fired event, use a RelayCommand<EventArgs>
/// and leave the CommandParameter and CommandParameterValue empty!</para>
/// </summary>
/// <param name="parameter">The EventArgs of the fired event.</param>
protected override void Invoke(object parameter)
{
if (AssociatedElementIsDisabled())
{
return;
}
var command = GetCommand();
var commandParameter = CommandParameterValue;
if (commandParameter == null
&& PassEventArgsToCommand)
{
commandParameter = parameter;
}
if (command != null
&& command.CanExecute(commandParameter))
{
command.Execute(commandParameter);
}
}
private static void OnCommandChanged(
EventToCommandWpf element,
DependencyPropertyChangedEventArgs e)
{
if (element == null)
{
return;
}
if (e.OldValue != null)
{
((ICommand)e.OldValue).CanExecuteChanged -= element.OnCommandCanExecuteChanged;
}
var command = (ICommand)e.NewValue;
if (command != null)
{
command.CanExecuteChanged += element.OnCommandCanExecuteChanged;
}
element.EnableDisableElement();
}
private bool AssociatedElementIsDisabled()
{
var element = GetAssociatedObject();
return AssociatedObject == null
|| (element != null
&& !element.IsEnabled);
}
private void EnableDisableElement()
{
var element = GetAssociatedObject();
if (element == null)
{
return;
}
var command = this.GetCommand();
if (this.MustToggleIsEnabledValue
&& command != null)
{
SetIsEnabled(element, command.CanExecute(this.CommandParameterValue));
}
}
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
EnableDisableElement();
}
/// <summary>
/// Identifies the <see cref="CommandParameter" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(EventToCommandWpf),
new PropertyMetadata(
null,
(s, e) => {
var sender = s as EventToCommandWpf;
if (sender == null)
{
return;
}
if (sender.AssociatedObject == null)
{
return;
}
sender.EnableDisableElement();
}));
/// <summary>
/// Identifies the <see cref="Command" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(EventToCommandWpf),
new PropertyMetadata(
null,
(s, e) => OnCommandChanged(s as EventToCommandWpf, e)));
/// <summary>
/// Identifies the <see cref="MustToggleIsEnabled" /> dependency property
/// </summary>
public static readonly DependencyProperty MustToggleIsEnabledProperty = DependencyProperty.Register(
"MustToggleIsEnabled",
typeof(bool),
typeof(EventToCommandWpf),
new PropertyMetadata(
false,
(s, e) => {
var sender = s as EventToCommandWpf;
if (sender == null)
{
return;
}
if (sender.AssociatedObject == null)
{
return;
}
sender.EnableDisableElement();
}));
private object _commandParameterValue;
private bool? _mustToggleValue;
/// <summary>
/// Gets or sets the ICommand that this trigger is bound to. This
/// is a DependencyProperty.
/// </summary>
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" />
/// attached to this trigger. This is a DependencyProperty.
/// </summary>
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" />
/// attached to this trigger. This property is here for compatibility
/// with the Silverlight version. This is NOT a DependencyProperty.
/// For databinding, use the <see cref="CommandParameter" /> property.
/// </summary>
public object CommandParameterValue
{
get
{
return this._commandParameterValue ?? this.CommandParameter;
}
set
{
_commandParameterValue = value;
EnableDisableElement();
}
}
/// <summary>
/// Gets or sets a value indicating whether the attached element must be
/// disabled when the <see cref="Command" /> property's CanExecuteChanged
/// event fires. If this property is true, and the command's CanExecute
/// method returns false, the element will be disabled. If this property
/// is false, the element will not be disabled when the command's
/// CanExecute method changes. This is a DependencyProperty.
/// </summary>
public bool MustToggleIsEnabled
{
get
{
return (bool)this.GetValue(MustToggleIsEnabledProperty);
}
set
{
SetValue(MustToggleIsEnabledProperty, value);
}
}
/// <summary>
/// Gets or sets a value indicating whether the attached element must be
/// disabled when the <see cref="Command" /> property's CanExecuteChanged
/// event fires. If this property is true, and the command's CanExecute
/// method returns false, the element will be disabled. This property is here for
/// compatibility with the Silverlight version. This is NOT a DependencyProperty.
/// For databinding, use the <see cref="MustToggleIsEnabled" /> property.
/// </summary>
public bool MustToggleIsEnabledValue
{
get
{
return this._mustToggleValue == null
? this.MustToggleIsEnabled
: this._mustToggleValue.Value;
}
set
{
_mustToggleValue = value;
EnableDisableElement();
}
}
/// <summary>
/// Called when this trigger is attached to a DependencyObject.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
EnableDisableElement();
}
/// <summary>
/// This method is here for compatibility
/// with the Silverlight version.
/// </summary>
/// <returns>The object to which this trigger
/// is attached casted as a FrameworkElement.</returns>
private IInputElement GetAssociatedObject()
{
return AssociatedObject as IInputElement;
}
private void SetIsEnabled(IInputElement element, bool value)
{
if (element is UIElement)
{
((UIElement)element).IsEnabled = value;
}
else if (element is ContentElement)
{
((ContentElement)element).IsEnabled = value;
}
else
{
throw new InvalidOperationException("Cannot set IsEnabled. Element is neither ContentElemen, nor UIElement.");
}
}
/// <summary>
/// This method is here for compatibility
/// with the Silverlight version.
/// </summary>
/// <returns>The command that must be executed when
/// this trigger is invoked.</returns>
private ICommand GetCommand()
{
return Command;
}
}
}
To inlcude it into your code you have to define a xml namespace pointing to the correct dll and then use it just like the normal EventToCommand class.
NOTE: This class does not work in Silverlight!
For those trying to solve this specific issue using dev express, this will do the trick!
<dxg:TableView.RowCellMenuCustomizations>
<dxb:BarButtonItem Name="deleteRowItem" Content="Delete" Command="{Binding View.DataContext.DeleteSelectionCommand}" />
</dxg:TableView.RowCellMenuCustomizations>
I was following their example which had an event on the button, little realising there was also a command I could use. Then the challenge was working out the binding as the menu item is not on the main visual tree. However the above solves that.
I found this to work with DEV express
<dxb:BarButtonItem Content="123" Name="item1">
<dxmvvm:Interaction.Triggers>
<dxmvvm:EventToCommand EventName="ItemClick" Command="{Binding SomeCommand}" CommandParameter="{Binding ElementName=item1, Path=Content}"/>
</dxmvvm:Interaction.Triggers>
</dxb:BarButtonItem>
It seems like no matter what i do, i get AG_E_PARSER_PROPERTY_NOT_FOUND when trying to bind a property in DataGridTemplateColumn in silverlight. I've even tried tried the following
<data:DataGridTemplateColumn dataBehaviors:DataGridColumnBehaviors.BindableTextOverride="{Binding ElementName=LayoutRoot,
Path=DataContext.ColumnOneName}">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
But no luck... I know the DataGridTemplateColumn does not contain a DataContext, but i don't feel like this should be the cause of the problem when I'm giving it the element and path to bind to. Any ideas?
Turns out the only way to get this to work is to implement it like DataGridBoundColumn. The idea is to bind to the binding property. This property will internally set the binding to a private DependencyProperty. When that property changes, you can perform anything needed inside the DependencyProperty Change Callback.
Here is an example:
/// <summary>
/// Represents a System.Windows.Controls.DataGrid column that can bind to a property
/// in the grid's data source. This class provides bindable properties ending with the suffix Binding.
/// These properties will affect the properties with the same name without the suffix
/// </summary>
public class DataGridBindableTemplateColumn : DataGridBoundColumn
{
/// <summary>
/// Identifies the DataGridBindableTemplateColumn.HeaderValueProperty dependency property
/// </summary>
internal static readonly DependencyProperty HeaderValueProperty =
DependencyProperty.Register("HeaderValue", typeof(object), typeof(DataGridBindableTemplateColumn),
new PropertyMetadata(null, OnHeaderValuePropertyChanged));
/// <summary>
/// Identifies the DataGridBindableTemplateColumn.VisibilityValueProperty dependency property
/// </summary>
internal static readonly DependencyProperty VisibilityValueProperty =
DependencyProperty.Register("VisibilityValue", typeof(Visibility), typeof(DataGridBindableTemplateColumn),
new PropertyMetadata(Visibility.Visible, OnVisibilityPropertyPropertyChanged));
/// <summary>
/// The callback the fires when the VisibilityValueProperty value changes
/// </summary>
/// <param name="d">The DependencyObject from which the property changed</param>
/// <param name="e">The DependencyPropertyChangedEventArgs containing the old and new value for the depenendency property that changed.</param>
private static void OnVisibilityPropertyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridBindableTemplateColumn sender = d as DataGridBindableTemplateColumn;
if (sender != null)
{
sender.OnVisibilityPropertyChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
}
}
/// <summary>
/// The callback the fires when the HeaderValueProperty value changes
/// </summary>
/// <param name="d">The DependencyObject from which the property changed</param>
/// <param name="e">The DependencyPropertyChangedEventArgs containing the old and new value for the depenendency property that changed.</param>
private static void OnHeaderValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridBindableTemplateColumn sender = d as DataGridBindableTemplateColumn;
if (sender != null)
{
sender.OnHeaderValueChanged((object)e.OldValue, (object)e.NewValue);
}
}
private Binding _headerBinding;
private Binding _visibilityBinding;
private DataTemplate _cellEditingTemplate;
private DataTemplate _cellTemplate;
/// <summary>
/// Gets and sets the Binding object used to bind to the Header property
/// </summary>
public Binding HeaderBinding
{
get { return _headerBinding; }
set
{
if (_headerBinding != value)
{
_headerBinding = value;
if (_headerBinding != null)
{
_headerBinding.ValidatesOnExceptions = false;
_headerBinding.NotifyOnValidationError = false;
BindingOperations.SetBinding(this, HeaderValueProperty, _headerBinding);
}
}
}
}
/// <summary>
/// Gets and sets the Binding object used to bind to the Visibility property
/// </summary>
public Binding VisibilityBinding
{
get { return _visibilityBinding; }
set
{
if (_visibilityBinding != value)
{
_visibilityBinding = value;
if (_visibilityBinding != null)
{
_visibilityBinding.ValidatesOnExceptions = false;
_visibilityBinding.NotifyOnValidationError = false;
BindingOperations.SetBinding(this, VisibilityValueProperty, _visibilityBinding);
}
}
}
}
/// <summary>
/// Gets or sets the template that is used to display the contents of a cell
/// that is in editing mode.
/// </summary>
public DataTemplate CellEditingTemplate
{
get { return _cellEditingTemplate; }
set
{
if (_cellEditingTemplate != value)
{
_cellEditingTemplate = value;
}
}
}
/// <summary>
/// Gets or sets the template that is used to display the contents of a cell
/// that is not in editing mode.
/// </summary>
public DataTemplate CellTemplate
{
get { return _cellTemplate; }
set
{
if (_cellTemplate != value)
{
_cellTemplate = value;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="editingElement"></param>
/// <param name="uneditedValue"></param>
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
editingElement = GenerateEditingElement(null, null);
}
/// <summary>
///
/// </summary>
/// <param name="cell"></param>
/// <param name="dataItem"></param>
/// <returns></returns>
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
if (CellEditingTemplate != null)
{
return (CellEditingTemplate.LoadContent() as FrameworkElement);
}
if (CellTemplate != null)
{
return (CellTemplate.LoadContent() as FrameworkElement);
}
if (!DesignerProperties.IsInDesignTool)
{
throw new Exception(string.Format("Missing template for type '{0}'", typeof(DataGridBindableTemplateColumn)));
}
return null;
}
/// <summary>
///
/// </summary>
/// <param name="cell"></param>
/// <param name="dataItem"></param>
/// <returns></returns>
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
if (CellTemplate != null)
{
return (CellTemplate.LoadContent() as FrameworkElement);
}
if (CellEditingTemplate != null)
{
return (CellEditingTemplate.LoadContent() as FrameworkElement);
}
if (!DesignerProperties.IsInDesignTool)
{
throw new Exception(string.Format("Missing template for type '{0}'", typeof(DataGridBindableTemplateColumn)));
}
return null;
}
/// <summary>
///
/// </summary>
/// <param name="editingElement"></param>
/// <param name="editingEventArgs"></param>
/// <returns></returns>
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
return null;
}
/// <summary>
///
/// </summary>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected virtual void OnHeaderValueChanged(object oldValue, object newValue)
{
Header = newValue;
}
/// <summary>
/// I'm to lazy to write a comment
/// </summary>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
protected virtual void OnVisibilityPropertyChanged(Visibility oldValue, Visibility newValue)
{
Visibility = newValue;
}
}
XAML:
<data:DataGridBindableTemplateColumn HeaderBinding="{Binding HeaderOne, Source={StaticResource ViewModel}}"
VisibilityBinding="{Binding HeaderOneVisibility, Source={StaticResource ViewMode}}"
HeaderStyle="{StaticResource DataColumnStyle}"
MinWidth="58">
...
</data:DataGridBindableTemplateColumn>
Hope this helps anyone with the same issue... Enjoy!