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
{
}
Related
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'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>
I can't seem to get this working. It is easy with sliders and any controls which pass simple types, but I cannot seem to figure out how to bubble up a ComboBox SelectionChanged with its ComboBoxItem.
This always fails with:
InnerException: System.TypeInitializationException
Message=The type initializer for 'Module.Dashboard.KpiComboBox' threw an exception.
TypeName=Module.Dashboard.KpiComboBox
InnerException: System.ArgumentException
Message=Default value for the 'Value' property cannot be bound to a specific thread.
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;
using PA.DPW.PACSES.CAL.Infrastructure;
namespace Module.Dashboard
{
/// <summary>
/// Interaction logic for KpiComboBox.xaml
/// </summary>
public partial class KpiComboBox : UserControl
{
public KpiComboBox()
{
InitializeComponent();
}
/// <summary>
/// Called to bind the proper KPIs to the cboKpi, according to which View name you pass
/// </summary>
/// <param name="viewName">Use Constants viewnames</param>
/// <example>
/// kpiComboBox.BindComboBox(Constants.CountyMapViewName);
/// </example>
public void BindComboBox(string viewName)
{
List<KPICodeDescription> lstKpi = null;
switch (viewName)
{
case Constants.CountyMapViewName:
lstKpi = UtilityHelper.GetKpiList(Constants.CountyMapViewName);
break;
case Constants.CountyRankingsViewName:
lstKpi = UtilityHelper.GetKpiList(Constants.CountyRankingsViewName);
break;
case Constants.StateMapViewName:
lstKpi = UtilityHelper.GetKpiList(Constants.StateMapViewName);
break;
case Constants.StateRankingsViewName:
lstKpi = UtilityHelper.GetKpiList(Constants.StateRankingsViewName);
break;
default:
break;
}
if (lstKpi != null)
{
cboKpi2.ItemsSource = lstKpi;
cboKpi2.DisplayMemberPath = "KPIDescription";
cboKpi2.SelectedValuePath = "KPICode";
}
}
// Dependency Object for Bubbling
private ComboBoxItem value;
public ComboBoxItem Value
{
get { return (ComboBoxItem)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnSelectionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
KpiComboBox cboControl = (KpiComboBox)sender;
cboControl.value = (ComboBoxItem)args.NewValue;
cboControl.OnSelectionChanged((ComboBoxItem)args.OldValue, (ComboBoxItem)args.NewValue);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(KpiComboBox), new PropertyMetadata(new Object(), OnSelectionChanged));
//DependencyProperty.Register("Value", typeof(ComboBoxItem), typeof(KpiComboBox));
// Event Bubbling
public static readonly RoutedEvent SelectionChangedEvent =
EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(KpiComboBox));
// Provide CLR accessors for the event
public event RoutedPropertyChangedEventHandler<ComboBoxItem> SelectionChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove { RemoveHandler(SelectionChangedEvent, value); }
}
private void OnSelectionChanged(ComboBoxItem oldValue, ComboBoxItem newValue)
{
RoutedPropertyChangedEventArgs<ComboBoxItem> args = new RoutedPropertyChangedEventArgs<ComboBoxItem>(oldValue, newValue);
args.RoutedEvent = KpiComboBox.SelectionChangedEvent;
RaiseEvent(args);
}
// This method raises the SelectionChanged event
void RaiseSelectionChangedEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(KpiComboBox.SelectionChangedEvent);
RaiseEvent(newEventArgs);
}
private void cboKpi2_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Hello from UC");
RaiseSelectionChangedEvent();
//cboKpi2.SelectedItem =;
}
}
}
Well, the short answer would be did you try replacing
DependencyProperty.Register("Value", typeof(object), typeof(KpiComboBox), new PropertyMetadata(new Object(), OnSelectionChanged));
with
DependencyProperty.Register("Value", typeof(ComboBoxItem), typeof(KpiComboBox), new PropertyMetadata(null, OnSelectionChanged));
?
However, I fail to see why that's necessary - isn't ComboBox.SelectionChanged already a routed event? Meaning you can listen for it at any level, regardless of the type of actual control, if somewhere down the hierarchy you have ComboBox? Also, there are even easier ways to do it, if you're using MVVM. Go on, ask me more, I could go on for hours :)
I know I'm probably missing something simple and obvious, but at the moment it eludes me.
I'm attempting to use the MVVM pattern.
How do you update a reference to a command in a viewmodel that is linked to a child viewmodel?
I've got a view (MainView) bound to a viewmodel (MainViewModel).
On MainView, I've got an instance of another view (SummaryView) bound to a viewmodel (SummaryViewModel). SummaryViewModel contains a collection of a third viewmodel (SummaryFilterViewModel).
On SummaryView, there is a TabControl and each tab on it is bound to one of the SummaryFilterViewModel instances in the SummaryViewodel collection.
On MainView there is a button that is bound to a command in MainViewModel.
What I want to happen is for the command logic to live within the SummaryFilterViewModel class. So, whichever tab is currently displayed needs to be wired up to the command that the button on MainView fires.
What I tried to do was this:
The individual SummaryFilterViewModel objects stored in the collection in SummaryViewModel hold the actual implementations of the ShoutCommand.
A CommandReference object in the XAML of MainView binds to a ShoutCommand property of the MainViewModel
The ShoutCommand property of the MainViewModel returns a reference to the ShoutCommand property of the SummaryViewModel object stored in MainViewModel.
The ShoutCommand property of the SummaryViewModel returns a reference to the ShoutCommand property of whichever is the currently selected SummaryFilterViewModel.
What happens, is that the command does not get updated when the user changes tabs.
Am I way off base in how to implement this?
Do I need to move the implementation of the command into the SummaryViewModel class?
Thanks in advance for any help!
The source to my solution is listed below:
ViewModels
SummaryView.xaml
<UserControl x:Class="NestedCommands.Views.SummaryView"
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"
d:DesignHeight="309" d:DesignWidth="476">
<Grid>
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<TabItem DataContext="{Binding Filters[1]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<TabItem DataContext="{Binding Filters[2]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
</TabControl>
</Grid>
MainView.xaml
<Window x:Class="NestedCommands.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:NestedCommands.Views"
xmlns:c="clr-namespace:NestedCommands.Commands"
Title="MainView" Height="336" Width="420">
<Window.Resources>
<c:CommandReference x:Key="ShoutCommandReference" Command="{Binding ShoutCommand}" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<v:SummaryView Grid.Row="0"
DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
Grid.Row="1"
Command="{StaticResource ShoutCommandReference}" />
</Grid>
Command Classes
CommandReference.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace NestedCommands.Commands
{
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.
/// </summary>
public class CommandReference : Freezable, ICommand
{
public CommandReference()
{
// Blank
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (Command != null)
return Command.CanExecute(parameter);
return false;
}
public void Execute(object parameter)
{
Command.Execute(parameter);
}
public event EventHandler CanExecuteChanged;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandReference = d as CommandReference;
ICommand oldCommand = e.OldValue as ICommand;
ICommand newCommand = e.NewValue as ICommand;
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
}
if (newCommand != null)
{
newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
#endregion
}
}
DelegateCommand.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace NestedCommands.Commands
{
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
public class DelegateCommand : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute()
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod();
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute()
{
if (_executeMethod != null)
{
_executeMethod();
}
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
Execute();
}
#endregion
#region Data
private readonly Action _executeMethod = null;
private readonly Func<bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
/// <typeparam name="T">Type of the parameter passed to the delegates</typeparam>
public class DelegateCommand<T> : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute(T parameter)
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod(parameter);
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute(T parameter)
{
if (_executeMethod != null)
{
_executeMethod(parameter);
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
// if T is of value type and the parameter is not
// set yet, then return false if CanExecute delegate
// exists, else return true
if (parameter == null &&
typeof(T).IsValueType)
{
return (_canExecuteMethod == null);
}
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
#endregion
#region Data
private readonly Action<T> _executeMethod = null;
private readonly Func<T, bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class contains methods for the CommandManager that help avoid memory leaks by
/// using weak references.
/// </summary>
internal class CommandManagerHelper
{
internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
{
if (handlers != null)
{
// Take a snapshot of the handlers before we call out to them since the handlers
// could cause the array to me modified while we are reading it.
EventHandler[] callees = new EventHandler[handlers.Count];
int count = 0;
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler handler = reference.Target as EventHandler;
if (handler == null)
{
// Clean up old handlers that have been collected
handlers.RemoveAt(i);
}
else
{
callees[count] = handler;
count++;
}
}
// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
EventHandler handler = callees[i];
handler(null, EventArgs.Empty);
}
}
}
internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested += handler;
}
}
}
}
internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested -= handler;
}
}
}
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
{
AddWeakReferenceHandler(ref handlers, handler, -1);
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
{
if (handlers == null)
{
handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
}
handlers.Add(new WeakReference(handler));
}
internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
{
if (handlers != null)
{
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler existingHandler = reference.Target as EventHandler;
if ((existingHandler == null) || (existingHandler == handler))
{
// Clean up old handlers that have been collected
// in addition to the handler that is to be removed.
handlers.RemoveAt(i);
}
}
}
}
}
}
View Models
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace NestedCommands.ViewModels
{
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
}
}
MainViewModel.cs
using System;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_SummaryViewModel = new SummaryViewModel();
}
private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel
{
get { return _SummaryViewModel; }
set
{
_SummaryViewModel = value;
OnPropertyChanged(this, "SummaryViewModel");
}
}
public ICommand ShoutCommand
{
get { return _SummaryViewModel.ShoutCommand; }
}
}
}
SummaryViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NestedCommands.Commands;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class SummaryViewModel : ViewModelBase
{
#region Constructor
public SummaryViewModel()
{
List<SummaryFilterViewModel> filters = new List<SummaryFilterViewModel>();
filters.Add(new SummaryFilterViewModel("Filter 1"));
filters.Add(new SummaryFilterViewModel("Filter 2"));
filters.Add(new SummaryFilterViewModel("Filter 3"));
Filters = filters;
}
#endregion
#region Properties
private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters
{
get { return _Filters; }
set
{
_Filters = value;
OnPropertyChanged(this, "Filters");
}
}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get { return _SelectedTabIndex; }
set
{
_SelectedTabIndex = value;
OnPropertyChanged(this, "SelectedTabIndex");
}
}
#endregion
#region Command References
public ICommand ShoutCommand
{
get { return Filters[SelectedTabIndex].ShoutCommand; }
}
#endregion
}
}
SummaryFilterViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NestedCommands.Commands;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class SummaryFilterViewModel : ViewModelBase
{
#region Constructor
public SummaryFilterViewModel(string FilterName)
{
this.FilterName = FilterName;
List<string> listData = new List<string>();
for (int i = 1; i < 10; i++)
{
listData.Add(string.Format("{0}: {1}", FilterName, i));
}
ListData = listData;
}
#endregion
#region Properties
private string _FilterName;
public string FilterName
{
get { return _FilterName; }
set
{
_FilterName = value;
OnPropertyChanged(this, "FilterName");
}
}
private List<string> _ListData;
public List<string> ListData
{
get { return _ListData; }
set
{
_ListData = value;
OnPropertyChanged(this, "ListData");
}
}
#endregion
#region Shout Command
private DelegateCommand _ShoutCommand;
public ICommand ShoutCommand
{
get { return _ShoutCommand ?? (_ShoutCommand = new DelegateCommand(Shout, CanShout)); }
}
private void Shout()
{
System.Windows.MessageBox.Show(string.Format("Called from SummaryFilterViewModel: {0}", FilterName));
}
private bool CanShout()
{
return true;
}
#endregion
}
}
I think the path you are going down is quickly going to end up complex and tightly coupled. You should probably take a look at using the Mediator Pattern to facilitate communication of changes in your SummaryFilterViewModel to your MainViewModel.
Using the mediator pattern, you can implement a means of subscribing to and publishing messages that allows one view model to communicate with another view model without ending up with tightly coupled view models.
Basically, when your tab selection changes, the summary view model would publish the change with the message payload containing the reference object or other data. The main view model would be subscribed to publication of this message and modify its state accordingly.
Some resources on the Mediator Pattern you can take a look at:
http://www.eggheadcafe.com/tutorials/aspnet/ec832ac7-6e4c-4ea8-81ab-7374d3da3425/wpf-and-the-model-view-vi.aspx
http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/
http://marlongrech.wordpress.com/2009/04/16/mediator-v2-for-mvvm-wpf-and-silverlight-applications/
I've made some changes to my sample solution in response to some suggestions that were made by Kent Boogaart. Kent, thank you again for your reply it gave me a new direction to move.
I'll try to keep this as short as possible.
The MainView is basically a frameset that houses the application's main command interface. In the sample the SummaryView is embedded directly in MainView's XAML. In the real solution it's a content control that may contain different types of child views. Each type of child view may or may not implement the command.
I was able to wire the SelectedIndex to a property so that I wouldn't need a dependency on the System.Windows.Control library. When that property changes, I also call OnPropertyChanged for the ShoutCommand property.
This, however, did not relay that change to the MainView object. So, in MainViewModel, I listen for the _SummaryViewModel.PropertyChanged event.
When MainView hears that the _SummaryViewModel.PropertyChanged event fired, I call OnPropertyChanged(this, "ShoutCommand") which propagates the change to the MainView.
So, I guess I want to know if it's necessary for the MainViewModel to listen to the _SummaryViewModel's PropertyChanged event like I'm doing, or if there is a cleaner way to do it.
My code is listed below: (I tried to take out as much as I could)
Thanks!
MainView
<v:SummaryView Grid.Row="0"
DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
Grid.Row="1"
Command="{Binding ShoutCommand}" />
MainViewModel
public MainViewModel()
{
_SummaryViewModel = new SummaryViewModel();
_SummaryViewModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_SummaryViewModel_PropertyChanged);
}
void _SummaryViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "ShoutCommand":
OnPropertyChanged(this, "ShoutCommand");
break;
}
}
private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel {...}
public ICommand ShoutCommand
{
get { return _SummaryViewModel.ShoutCommand; }
}
SummaryView
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<!-- TabItem repeated two more times -->
</TabControl>
SummaryViewModel
private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters {...}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get { return _SelectedTabIndex; }
set
{
_SelectedTabIndex = value;
OnPropertyChanged(this, "SelectedTabIndex");
OnPropertyChanged(this, "ShoutCommand");
}
}
public ICommand ShoutCommand
{
get {
int selectedTabIndex = SelectedTabIndex;
return (selectedTabIndex == -1) ? null : Filters[SelectedTabIndex].ShoutCommand;
}
}
Your post was long and I confess I didn't fully read it. However, I don't understand the purpose of CommandReference. Why not just bind directly to MainViewModel.ShoutCommand? Consider:
Bind the ItemsSource of the TabControl to the collection of child view models
Bind the SelectedItem of the TabControl to another property that tracks the selected child view model
When the aforementioned property changes, raise the PropertyChanged event for the ShoutCommand property, too
In the getter for ShoutCommand property, simply return the ShoutCommand of the selected child view model
I have been trying to get Mike Eshva's code from his answer about Animated Gifs to work, from here:
How do I get an animated gif to work in WPF?
Restated here (including translated comments):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Diagnostics;
namespace WpfBrowserApplication1 {
/// <summary>
/// Control of the "Images", which supports animated GIF.
/// </summary>
public class AnimatedImage : Image {
public AnimatedImage() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(AnimatedImage), new FrameworkPropertyMetadata(typeof(AnimatedImage)));
}
#region Public properties
/// <summary>
/// Gets/sets the number of current frame.
/// </summary>
public int FrameIndex {
get { return (int)GetValue(FrameIndexProperty); }
set { SetValue(FrameIndexProperty, value); }
}
/// <summary>
/// Get the BitmapFrame List.
/// </summary>
public List<BitmapFrame> Frames { get; private set; }
/// <summary>
/// Get or set the repeatBehavior of the animation when source is gif formart.This is a dependency object.
/// </summary>
public RepeatBehavior AnimationRepeatBehavior {
get { return (RepeatBehavior)GetValue(AnimationRepeatBehaviorProperty); }
set { SetValue(AnimationRepeatBehaviorProperty, value); }
}
public new BitmapImage Source {
get { return (BitmapImage)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public Uri UriSource {
get { return (Uri)GetValue(UriSourceProperty); }
set { SetValue(UriSourceProperty, value); }
}
#endregion
#region Protected interface
/// <summary>
/// Provides derived classes an opportunity to handle changes to the Source property.
/// </summary>
protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e) {
ClearAnimation();
BitmapImage source;
if (e.NewValue is Uri) {
source = new BitmapImage();
source.BeginInit();
source.UriSource = e.NewValue as Uri;
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
} else if (e.NewValue is BitmapImage) {
source = e.NewValue as BitmapImage;
} else {
return;
}
BitmapDecoder decoder;
if (source.StreamSource != null) {
decoder = BitmapDecoder.Create(source.StreamSource, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnLoad);
} else if (source.UriSource != null) {
decoder = BitmapDecoder.Create(source.UriSource, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnLoad);
} else {
return;
}
if (decoder.Frames.Count == 1) {
base.Source = decoder.Frames[0];
return;
}
this.Frames = decoder.Frames.ToList();
PrepareAnimation();
}
#endregion
#region Private properties
private Int32Animation Animation { get; set; }
private bool IsAnimationWorking { get; set; }
#endregion
#region Private methods
private void ClearAnimation() {
if (Animation != null) {
BeginAnimation(FrameIndexProperty, null);
}
IsAnimationWorking = false;
Animation = null;
this.Frames = null;
}
private void PrepareAnimation() {
Animation =
new Int32Animation(
0,
this.Frames.Count - 1,
new Duration(
new TimeSpan(
0,
0,
0,
this.Frames.Count / 10,
(int)((this.Frames.Count / 10.0 - this.Frames.Count / 10) * 1000)))) {
RepeatBehavior = RepeatBehavior.Forever
};
base.Source = this.Frames[0];
BeginAnimation(FrameIndexProperty, Animation);
IsAnimationWorking = true;
}
private static void ChangingFrameIndex
(DependencyObject dp, DependencyPropertyChangedEventArgs e) {
AnimatedImage animatedImage = dp as AnimatedImage;
if (animatedImage == null || !animatedImage.IsAnimationWorking) {
return;
}
int frameIndex = (int)e.NewValue;
((Image)animatedImage).Source = animatedImage.Frames[frameIndex];
animatedImage.InvalidateVisual();
}
/// <summary>
/// Handles changes to the Source property.
/// </summary>
private static void OnSourceChanged
(DependencyObject dp, DependencyPropertyChangedEventArgs e) {
((AnimatedImage)dp).OnSourceChanged(e);
}
#endregion
#region Dependency Properties
/// <summary>
/// FrameIndex Dependency Property
/// </summary>
public static readonly DependencyProperty FrameIndexProperty =
DependencyProperty.Register(
"FrameIndex",
typeof(int),
typeof(AnimatedImage),
new UIPropertyMetadata(0, ChangingFrameIndex));
/// <summary>
/// Source Dependency Property
/// </summary>
public new static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(
"Source",
typeof(BitmapImage),
typeof(AnimatedImage),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure,
OnSourceChanged));
/// <summary>
/// AnimationRepeatBehavior Dependency Property
/// </summary>
public static readonly DependencyProperty AnimationRepeatBehaviorProperty =
DependencyProperty.Register(
"AnimationRepeatBehavior",
typeof(RepeatBehavior),
typeof(AnimatedImage),
new PropertyMetadata(null));
public static readonly DependencyProperty UriSourceProperty =
DependencyProperty.Register(
"UriSource",
typeof(Uri),
typeof(AnimatedImage),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure,
OnSourceChanged));
#endregion
}
}
Here's the XAML for my page just trying to show one animated GIF:
<Page x:Class="WpfBrowserApplication1.Page1"
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"
xmlns:my="clr-namespace:WpfBrowserApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Grid>
<my:AnimatedImage x:Name="Wait" Source="arrows.gif" Width="16" Height="16" />
</Grid>
</Page>
In the design view of Page1, I see the animated gif happily doing what it's supposed to do, no build errors, no design errors, it just works the way it's supposed to. As soon as I start the page to make sure it works in the browser, I get the following error pointed at the line: <my:AnimatedImage x:Name="Wait" Source="arrows.gif" Width="16" Height="16" />
'Set property 'WpfBrowserApplication1.AnimatedImage.Source' threw an exception.' Line number '11' and line position '21'.
I've been trying to get this to work for awhile. What can I do to get the image to animate when the project runs?
EDIT:
You can download the whole project from my blog (Click on Steve)
This one works fine:
XAML:
<controls:GifImage x:Name="gifImage" Stretch="None" GifSource="/SomeImage.gif" AutoStart="True" />
Note:
If AutoStart is set to false, you need to call either Show() or StartAnimation() manually from your code.
C#:
public class GifImage : Image
{
#region Memmbers
private GifBitmapDecoder _gifDecoder;
private Int32Animation _animation;
private bool _isInitialized;
#endregion Memmbers
#region Properties
private int FrameIndex
{
get { return (int)GetValue(FrameIndexProperty); }
set { SetValue(FrameIndexProperty, value); }
}
private static readonly DependencyProperty FrameIndexProperty =
DependencyProperty.Register("FrameIndex", typeof(int), typeof(GifImage), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(ChangingFrameIndex)));
private static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
{
GifImage image = obj as GifImage;
image.Source = image._gifDecoder.Frames[(int)ev.NewValue];
}
/// <summary>
/// Defines whether the animation starts on it's own
/// </summary>
public bool AutoStart
{
get { return (bool)GetValue(AutoStartProperty); }
set { SetValue(AutoStartProperty, value); }
}
public static readonly DependencyProperty AutoStartProperty =
DependencyProperty.Register("AutoStart", typeof(bool), typeof(GifImage), new UIPropertyMetadata(false, AutoStartPropertyChanged));
private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
(sender as GifImage).StartAnimation();
}
public string GifSource
{
get { return (string)GetValue(GifSourceProperty); }
set { SetValue(GifSourceProperty, value); }
}
public static readonly DependencyProperty GifSourceProperty =
DependencyProperty.Register("GifSource", typeof(string), typeof(GifImage), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged));
private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// CARLO 20100622: Reinitialize animation everytime image is changed
(sender as GifImage).Initialize();
}
#endregion Properties
#region Private Instance Methods
private void Initialize()
{
_gifDecoder = new GifBitmapDecoder(new Uri("pack://application:,,," + this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
_animation = new Int32Animation(0, _gifDecoder.Frames.Count - 1, new Duration(new TimeSpan(0, 0, 0, _gifDecoder.Frames.Count / 10, (int)((_gifDecoder.Frames.Count / 10.0 - _gifDecoder.Frames.Count / 10) * 1000))));
_animation.RepeatBehavior = RepeatBehavior.Forever;
this.Source = _gifDecoder.Frames[0];
_isInitialized = true;
}
#endregion Private Instance Methods
#region Public Instance Methods
/// <summary>
/// Shows and starts the gif animation
/// </summary>
public void Show()
{
this.Visibility = Visibility.Visible;
this.StartAnimation();
}
/// <summary>
/// Hides and stops the gif animation
/// </summary>
public void Hide()
{
this.Visibility = Visibility.Collapsed;
this.StopAnimation();
}
/// <summary>
/// Starts the animation
/// </summary>
public void StartAnimation()
{
if (!_isInitialized)
this.Initialize();
BeginAnimation(FrameIndexProperty, _animation);
}
/// <summary>
/// Stops the animation
/// </summary>
public void StopAnimation()
{
BeginAnimation(FrameIndexProperty, null);
}
#endregion Public Instance Methods
}