I have created the following custom Dependency Property in the code behind.
This Dependency property of type infragistics XamDataGrid and so the owner.
I'm trying to get a reference of the grid through this property.
The following code compiles with no errors or warnings. However, the Dependency Property does not show in the XAML intelliSense.
I have tried typing the full name as well. It is not recognizing this DP.
I have cleaned the project and Rebuilt it.
I have even closed Visual Studio and reopened it.
using System.Windows;
using Infragistics.Windows.DataPresenter;
namespace Demo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty DPRProperty =
DependencyProperty.Register(
"DPR",
typeof(XamDataGrid),
typeof(MainWindow),
new FrameworkPropertyMetadata(null));
public XamDataGrid DPR
{
get { return (XamDataGrid)GetValue(DPRProperty); }
set { SetValue(DPRProperty, value); }
}
}
}
The problem is that you expect the Dependency Property to show up in your base classes XAML "code"
however, you did not created a DP for Window but for MainWindow
If you would use a <MainWindow /> tag, it would show up
if you could explain what you try to archive, one may could help you further
edit
Think i understood now what your goal is
To get a reference to any object, you do not need a DP but rather have to order everything correctly
things required:
The Window (obviously)
A DataContext
Some Code-Behind
An Attached-Property
The Attached Property is looking pretty much like this
public class Initialized
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(Initialized),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(Initialized),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var type = target.GetType();
var ev = type.GetEvent("Initialized");
var method = typeof(Initialized).GetMethod("OnInitialized");
if ((e.NewValue != null) && (e.OldValue == null))
{
ev.AddEventHandler(target, Delegate.CreateDelegate(ev.EventHandlerType, method));
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
ev.RemoveEventHandler(target, Delegate.CreateDelegate(ev.EventHandlerType, method));
}
}
public static void OnInitialized(object sender, EventArgs e)
{
var control = sender as FrameworkElement;
var command = (ICommand)control.GetValue(CommandProperty);
var commandParameter = control.GetValue(CommandParameterProperty);
command.Execute(commandParameter);
}
}
In your DataContext (lets name it MainWindowDataContext), create a new ICommand property (Lets name it CmdInitialized) that puts the parameter passed to the command into your instance variable
Then, in your XAML code you just use the AttachedProperty like usual
<!-- `ev:` is the XAML namespace the Initialized class is located in -->
ev:Initialized.Command="{Binding CmdInitialized}"
ev:Initialized.CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
the important part however is: the DataContext needs to be already attached in the window BEFORE the compontents are initialized
this means that you have to edit your window code to something like this:
public MainWindow()
{
this.DataContext = new MainWindowDataContext();
InitializeComponent();
}
Afterwards, you should be fine
if you need solutions for the ICommand, search for RelayCommand on google :)
Related
This question already has an answer here:
Notify binding for static properties in static classes
(1 answer)
Closed 7 months ago.
I've been working on a unique kind of project for a while now and recently I've written a custom "binding system"(for external code) which works great, however today I needed to get some MVVM style bindings to work(for internal UI). After an entire day of googling and trying different things, I still don't have an answer or working code. I'm almost at a point where I'll just add "normal" bindings to my existing binding system and call it a day.
anyway... my question...
I'm trying to make a one-way binding from a ViewModel class to a UI element. There is are some "rules" I have to conform to though (like all (public) properties MUST be in a static class). At design-time everything works and VS can resolve the bindings (if the datacontext is set in xaml, NOT in cs). The bindings even update once to their default value at startup, but NOT when the property source changed.
TLDR;
read the bold text :)
Code:
[Public static class]
here the property is set by external code at runtime
public static class StaticClass
{
public static string ExampleProperty
{
get
{
return ViewModel.Instance.ExampleProperty;
}
set
{
if (ViewModel.Instance.ExampleProperty != value) ViewModel.Instance.ExampleProperty = value;
}
}
}
[ViewModel]
a singleton class that holds the non-static backing field for the static properties in the class above and implements INotifyPropertyChanged
internal class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static ViewModel _instance = null;
internal static ViewModel Instance
{
get
{
if (_instance is null) _instance = new();
return _instance;
}
}
private static string _exampleProperty { get; set; } = "Pls work";
public string ExampleProperty
{
get
{
return _exampleProperty;
}
set
{
_exampleProperty = value;
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName is not null) PropertyChanged?.Invoke(null, new(propertyName));
}
}
[Xaml example binding]
<Button Content="{Binding ExampleProperty, UpdateSourceTrigger=PropertyChanged}" Click="Button_Click"/>
[MainWindow.cs]
obviously a test project so this just changes the ExampleProperty to a random number on the Button.Click event
public partial class MainWindow : Window
{
Random random = new();
public MainWindow()
{
InitializeComponent();
this.DataContext = ViewModel.Instance;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
StaticClass.ExampleProperty = random.Next(0, 69).ToString();
}
}
so what am I doing wrong? Any help is greatly appreciated.
The expression
DataContext = new ViewModel();
assigns a different instance of the ViewModel class to the DataContext than the one returned from ViewModel.Instance, so that
StaticClass.ExampleProperty = random.Next(0, 69).ToString();
does not affect the DataContext. You should have assigned
DataContext = ViewModel.Instance;
Your ViewModel class does however not implement the singleton pattern correctly - it would have to avoid the creation of more than one instance, e.g. by declaring a private constructor.
The view does not need to use the singleton aspect of the view model at all. In order to access the current view model instance, cast the DataContext:
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
ViewModel vm = (ViewModel)btn.DataContext;
vm.ExampleProperty = random.Next(0, 69).ToString();
}
Thanks to a comment on the question:
Also be aware that it is possible to bind directly to static properties of a static class, even with change notification, thus eliminating the need for a singleton. See e.g. here: stackoverflow.com/a/41823852/1136211
(and answer) I've had success with both a static and a non-static binding (FINALLY...).
For binding UI to a static class
The static class:
public static class StaticClass
{
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
#region Properties
public static string _exampleProperty = "Default value";
public static string ExampleProperty
{
get
{
return _exampleProperty;
}
set
{
if (_exampleProperty != value)
{
_exampleProperty = value;
OnStaticPropertyChanged();
}
}
}
#endregion
private static void OnStaticPropertyChanged([CallerMemberName]string propertyName = null)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
}
How to bind to UI:
<TextBlock Text="{Binding Path=(local:StaticClass.ExampleProperty)}"/>
How to set the property:
StaticClass.ExampleProperty = "New value that automatically updates the UI :)";
For binding UI to a non-static class
Use the code from the other answer.
I am creating a behavior for Keypress as I have a specific requirement for triggering a command when a key is pressed in an application. I am not using the WPF KeyBinding as I have several user controls with all having their respective ViewModels. I wrote the below behavior which works fine :-
public class WindowKeyPressBehavior : Behavior<Control>
{
#region Public Properties.
public KeyCommandCollection KeyCommandCollection
{
get => (KeyCommandCollection)GetValue(KeyCommandCollectionProperty);
set => SetValue(KeyCommandCollectionProperty, value);
}
public static readonly DependencyProperty KeyCommandCollectionProperty =
DependencyProperty.Register("KeyCommandCollection",
typeof(KeyCommandCollection),
typeof(WindowKeyPressBehavior),
new PropertyMetadata(null));
#endregion
#region Constructors.
public WindowKeyPressBehavior()
{
KeyCommandCollection = new KeyCommandCollection();
}
#endregion
#region Protected Method Declarations.
protected override void OnAttached()
{
base.OnAttached();
WeakEventManager<Control, RoutedEventArgs>.RemoveHandler(AssociatedObject, nameof(AssociatedObject.Loaded), AssociatedObject_Loaded);
WeakEventManager<Control, RoutedEventArgs>.AddHandler(AssociatedObject, nameof(AssociatedObject.Loaded), AssociatedObject_Loaded);
WeakEventManager<Control, RoutedEventArgs>.RemoveHandler(AssociatedObject, nameof(AssociatedObject.Unloaded), AssociatedObject_Unloaded);
WeakEventManager<Control, RoutedEventArgs>.AddHandler(AssociatedObject, nameof(AssociatedObject.Unloaded), AssociatedObject_Unloaded);
}
#endregion
#region Private Method Declarations.
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
WeakEventManager<Window, KeyEventArgs>.RemoveHandler(Application.Current.MainWindow, nameof(Application.Current.MainWindow.PreviewKeyDown), MainWindow_PreviewKeyDown);
WeakEventManager<Window, KeyEventArgs>.AddHandler(Application.Current.MainWindow, nameof(Application.Current.MainWindow.PreviewKeyDown), MainWindow_PreviewKeyDown);
LoadDataContexts();
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
UnloadDataContexts();
WeakEventManager<Window, KeyEventArgs>.RemoveHandler(Application.Current.MainWindow, nameof(Application.Current.MainWindow.PreviewKeyDown), MainWindow_PreviewKeyDown);
}
private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (KeyCommandCollection == null || !KeyCommandCollection.Any(x => x.Key == e.Key))
{
return;
}
try
{
IEnumerable<KeyElement> keyCommands = KeyCommandCollection.Where(x => x.Key == e.Key);
foreach (KeyElement keyCommand in keyCommands)
{
// Run it in an asynchronous way as CanExecute/Execute can be time consuming in certain cases
// preventing the next command to run.
Application.Current?.Dispatcher?.Invoke(() =>
{
KeyElement command = keyCommand;
// If parameterized command is available execute it.
if (command.ParameterizedCommand?.CanExecute(command.CommandParameter) == true)
{
command.ParameterizedCommand.Execute(command.CommandParameter);
}
// If non-parameterized command is available, execute it.
else if (command.Command?.CanExecute() == true)
{
command.Command.Execute();
}
});
}
}
catch (System.Exception)
{
//TODO : Log Any Errors.
}
}
private void LoadDataContexts()
{
foreach (KeyElement keyElement in KeyCommandCollection.Where(x => x?.DataContext == null))
{
keyElement.DataContext = AssociatedObject?.DataContext;
}
}
private void UnloadDataContexts()
{
foreach (KeyElement keyElement in KeyCommandCollection.Where(x => x?.DataContext != null))
{
keyElement.DataContext = null;
}
}
#endregion
}
The KeyElement class inherits from FrameworkElement as below
public class KeyElement : FrameworkElement
{
public Key Key
{
get => (Key)GetValue(KeyProperty);
set => SetValue(KeyProperty, value);
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(
"Key",
typeof(Key),
typeof(KeyElement),
new PropertyMetadata(System.Windows.Input.Key.None));
public DelegateCommand Command
{
get => (DelegateCommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(DelegateCommand),
typeof(KeyElement),
new PropertyMetadata(null));
public DelegateCommand<object> ParameterizedCommand
{
get => (DelegateCommand<object>)GetValue(ParameterizedCommandProperty);
set => SetValue(ParameterizedCommandProperty, value);
}
public static readonly DependencyProperty ParameterizedCommandProperty =
DependencyProperty.Register(
"ParameterizedCommand",
typeof(DelegateCommand<object>),
typeof(KeyElement),
new PropertyMetadata(null));
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(KeyElement),
new PropertyMetadata(null));
public override string ToString()
{
return $"Key : {Key}";
}
}
/// <summary>
/// Collection of Key-Commands.
/// </summary>
public class KeyCommandCollection : ObservableCollection<KeyElement>
{
}
My XAML file is as below :-
<UserControl
x:Class="Test.Views.LanguageSelectionPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:behaviors="clr-namespace:Test.Behaviors;assembly=Test.UI.Core"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DesignHeight="1024"
d:DesignWidth="1280">
<i:Interaction.Behaviors>
<behaviors:WindowKeyPressBehavior>
<behaviors:WindowKeyPressBehavior.KeyCommandCollection>
<behaviors:KeyElement
Key="{Binding KioskWorkflow.PrmConfiguration.UpKey}"
ParameterizedCommand="{Binding SwitchCommand}"
CommandParameter="{Binding KioskWorkflow.PrmConfiguration.UpKey}" />
<behaviors:KeyElement
Key="{Binding KioskWorkflow.PrmConfiguration.DownKey}"
ParameterizedCommand="{Binding SwitchCommand}"
CommandParameter="{Binding KioskWorkflow.PrmConfiguration.DownKey}" />
<behaviors:KeyElement
Key="{Binding KioskWorkflow.PrmConfiguration.EnterKey}"
Command="{Binding SubmitCommand}" />
</behaviors:WindowKeyPressBehavior.KeyCommandCollection>
</behaviors:WindowKeyPressBehavior>
</i:Interaction.Behaviors>
<Grid></Grid>
</UserControl>
The Problem I have
I am able to run the application properly and everything seems to be working. The only reason I am unhappy is that I don't want the KeyElement to be a FrameworkElement. Instead I want it to be a plain DependencyObject. Problem I am facing with this being a Dependency Object is that I don't get the DataContext property because of which I am not able to set the Binding properly in Code.
Also I don't want to call the LoadDataContexts method in the behavior.
I tried using the BindingProxy approach but I don't like that approach as well as it needs me to declare a static resource in every page.
I have tried making the class KeyElement inherit from Freezable but that is not helping me either, as Binding is not happening, and even though it is freezable, it is not inheriting the DataContext from the parent i.e. the Behavior/UserControl. Tried several options by using RelativeSource but the DataContext is always null.
Also I get the below error in Output if I replace the FrameworkElement with Freezable:-
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element
I had a look at the KeyBinding class and it does not inherit from FrameworkElement still does not throw any error in output window.
Any way of achieving the behavior without using a FrameworkElement ?
Any suggestions on improving the above code is also welcome.
I am trying to redirect the Window Closed event to my ViewModel, but lack the proper hands on experience with AttachedProperties.
The class that holds the AttachedProperty
public class WindowClosedBehavior
{
public static readonly DependencyProperty ClosedProperty = DependencyProperty.RegisterAttached(
"Closed",
typeof (ICommand),
typeof (WindowClosedBehavior),
new UIPropertyMetadata(ClosedChanged));
private static void ClosedChanged(
DependencyObject target,
DependencyPropertyChangedEventArgs e)
{
var window = target as Window;
if (window != null)
{
// ??
}
}
public static void SetClosed(Window target, ICommand value)
{
target.SetValue(ClosedProperty, value);
}
}
How can I implement the behavior so that it will close the window and trigger the RelayCommand?
The (stripped) ViewModel :
public RelayCommand WindowClosedCommand { get; private set; }
public MainCommandsViewModel()
{
WindowClosedCommand = new RelayCommand(WindowClosedCommandOnExecuted, WindowClosedCommandOnCanExecute);
}
MainWindow.xaml
<Window x:Class="TvShowManager.UserInterface.Views.MainWindow"
<!-- left out irrelevant parts -->
xmlns:closeBehaviors="clr-namespace:TvShowManager.UserInterface.CloseBehaviors"
closeBehaviors:WindowClosedBehavior.Closed="{Binding WindowCloseCommand}" >
I simply bind a RelayCommand (WindowCloseCommand) to the attached property.
I tried debugging through this to get better understanding and hopefully figure out how to proceed, but no breakpoints are being hit in the class that holds my attached property. If anybody can explain why my code in WindowClosedBehavior never gets executed I would also greatly appreciate the advice there.
I hope it's clear what I am trying to achieve and that somebody can help me out.
Many thanks
Within the ClosedChanged callback, just store the command and register an event handler to the window's Closed event to invoke the command:
private static ICommand _command;
private static void ClosedChanged(
DependencyObject target,
DependencyPropertyChangedEventArgs e)
{
var window = target as Window;
if (window != null)
{
_command = e.NewValue as ICommand;
window.Closed += (sender, args) =>
{
if (_command != null)
_command.Execute(null);
}
}
}
In addition, you might want to un-register all previously existing event handlers on the window's Closed event, but that is only necessary if you plan to change the WindowClosedBenahior during runtime.
Is it possible to expose a public event from my ViewModel is such a way as to allow it to be bound to a custom DependencyProperty in my View?
My application is written in C# using the .NET 4.5 framework. It has a MVVM architecture with no code-behind in the view and custom DependencyProperty classes to bind WPF-specific behvaiours of the View to properties exposed by the ViewModel.
There is a set of properties that I would like the ViewModel to be able to expose that represent events to which the View needs to respond. For example, when a top level ViewModel object is about to be Disposed I would like the WPF View implementation to respond by closing the corresponding Window. This could occur when a configuration process has displayed a Dialog Window, the user has enetered and confirmed the information and the ViewModel has passed it to the Model and is no longer required.
I am aware that there are many questions that are specific to solving the 'show dialog from ViewModel' question; this is not one of them and I have a solution to that one.
I've read through the MSDN documentation for DependencyProperties and can't find anything specific to binding to event properties.
What I would like to achieve is something similar to the code below. This code builds, but results in a typical System.Windows.Data Error: 40 : BindingExpression path error: 'RequestCloseEvent' property not found error when the MainWindow is shown.
I am aware that there are many questions that go along the lines of 'please help me debug my System.Windows.Data Error: 40 issue'; this is (probably) not one of these either. (But I'd be happy if that's all it really is.)
Source for the custom DependencyProperty in WindowBindableProperties.cs:
using System;
using System.Threading;
using System.Windows;
namespace WpfEventBinding
{
public static class WindowBindableProperties
{
#region ViewModelTerminatingEventProperty
/// <summary>
/// Register the ViewModelTerminatingEvent custom DependencyProperty.
/// </summary>
private static DependencyProperty _viewModelTerminatingEventProperty =
DependencyProperty.RegisterAttached
(
"ViewModelTerminatingEvent",
typeof(ViewModelTerminatingEventHandler),
typeof(WindowBindableProperties),
new PropertyMetadata(null, ViewModelTerminatingEventPropertyChanged)
);
/// <summary>
/// Identifies the ViewModelTerminatingEvent dependency property.
/// </summary>
public static DependencyProperty ViewModelTerminatingEventProperty
{ get { return _viewModelTerminatingEventProperty; } }
/// <summary>
/// Gets the attached ViewModelTerminatingEvent dependecy property.
/// </summary>
/// <param name="dependencyObject">The window attached to the WindowViewModel.</param>
/// <returns>The ViewModelTerminatingEventHandler bound to this property</returns>
public static ViewModelTerminatingEventHandler GetViewModelTerminatingEvent
(DependencyObject dependencyObject)
{
return (dependencyObject.GetValue(ViewModelTerminatingEventProperty)
as ViewModelTerminatingEventHandler);
}
/// <summary>
/// Sets the ViewModelTerminatingEvent dependency property.
/// </summary>
public static void SetViewModelTerminatingEvent(
DependencyObject dependencyObject,
ViewModelTerminatingEventHandler value)
{
dependencyObject.SetValue(ViewModelTerminatingEventProperty, value);
}
/// <summary>
/// Gets the ViewModelTerminatingEvent dependency property.
/// </summary>
private static void ViewModelTerminatingEventPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Window instance = d as Window;
if (null != instance)
{
if (null != e.OldValue)
{
throw new System.InvalidOperationException(
"ViewModelTerminatingEvent dependency property cannot be changed.");
}
if (null != e.NewValue)
{
// Attach the Window.Close() method to the ViewModel's event
var newEvent = (e.NewValue as ViewModelTerminatingEventHandler);
newEvent += new ViewModelTerminatingEventHandler(() => instance.Close());
}
}
}
#endregion
}
}
Source for MainWindow.xaml:
(This example contains code-behind to simplify the Stop Button implementation.)
<Window x:Class="WpfEventBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WpfEventBinding"
v:WindowBindableProperties.ViewModelTerminatingEvent="{Binding Path=RequestCloseEvent}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="{Binding Path=CloseCommandName}" Click="StopButton_Click" ></Button>
</Grid>
</Window>
Source for MainWindow.xaml.cs (code behind):
using System.Windows;
namespace WpfEventBinding
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
MainWindowViewModel vm = (DataContext as MainWindowViewModel);
if (null != vm)
{
vm.Stop();
}
}
}
}
Source for the MainWindowViewModel.cs:
using System;
using System.ComponentModel;
namespace WpfEventBinding
{
public delegate void ViewModelTerminatingEventHandler();
class MainWindowViewModel
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Raised by the ViewModel to indicate to the view that it is no longer required.
// Causes System.Windows.Data Error: 40 : BindingExpression path error. Is it
// Possible to bind to an 'event' property?
public event ViewModelTerminatingEventHandler RequestCloseEvent;
// This has to have the public 'get' to allow binding. Is there some way to
// do the same thing for the 'event'?
public String CloseCommandName { get; private set; }
public MainWindowViewModel()
{
CloseCommandName = "Close";
}
internal void Stop()
{
ViewModelTerminatingEventHandler RaiseRequestCloseEvent =
RequestCloseEvent;
if (null != RaiseRequestCloseEvent)
{
RaiseRequestCloseEvent();
}
}
internal void Start()
{
OnPropertyChanged("CloseCommandName");
OnPropertyChanged("ViewModelTerminatingEvent");
}
private void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler RaisePropertyChangedEvent = PropertyChanged;
if (RaisePropertyChangedEvent != null)
{
var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
RaisePropertyChangedEvent(this, propertyChangedEventArgs);
}
}
}
}
Source for App.xaml:
<Application x:Class="WpfEventBinding.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Application.Resources>
<!-- Nothing to see here. Move along... -->
</Application.Resources>
</Application>
Source for App.xaml.cs
using System.Windows;
namespace WpfEventBinding
{
public partial class App : Application
{
public App()
{
Startup += new StartupEventHandler(App_Startup);
}
void App_Startup(object sender, StartupEventArgs e)
{
MainWindowViewModel vm = new MainWindowViewModel();
MainWindow window = new MainWindow();
// Make sure this is set before attempting binding!
window.DataContext = vm;
vm.Start();
window.Show();
}
}
}
It appears that the public event ViewModelTerminatingEventHandler RequestCloseEvent; syntax is not sufficient to allo the data binding to occur. A similar problem is see if the public String CloseCommandName { get; private set; } is declared as public String CloseCommandName; without the { get; private set; }. However, there is no { get; private set; } for events, which use the {add{} remove{}} syntax (and that does not solve the problem either).
Is what I'm attempting possible and if so, what have I missed?
View closing means window closing event. So you basically want react on events in the view. I read recently this arcticle, there was a very good image
and also mentioned EventBehavior existence.
Your best bet, if you don't want any code behind, is to use behaviors. Behavior is a simple attached property, which can perform actions, to example rising application-wide commands, which ViewModel can then catch without MVVM issues.
Here is an example of behavior:
public static class FreezeBehavior
{
public static bool GetIsFrozen(DependencyObject obj)
{
return (bool)obj.GetValue(IsFrozenProperty);
}
public static void SetIsFrozen(DependencyObject obj, bool value)
{
obj.SetValue(IsFrozenProperty, value);
}
public static readonly DependencyProperty IsFrozenProperty =
DependencyProperty.RegisterAttached("IsFrozen", typeof(bool), typeof(FreezeBehavior), new PropertyMetadata(OnIsFrozenChanged));
private static void OnIsFrozenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var freezable = d as Freezable;
if (freezable != null && freezable.CanFreeze)
freezable.Freeze();
}
}
}
it's used like this
<DropShadowEffect ShadowDepth="2" local:FreezeBehavior.IsFrozen="True"/>
It can be attached to any freezable to freeze it. In your case you want to subscribe to event and invoke command or set property, or whatever to inform ViewModel.
What you are asking for is kinda weird, but I'm not going to get into a big long discussion about that....
You don't bind to events - you expose them and the view can add handlers for the events.
Of course this means you will have to put some code behind into the view - but this is fine provided it is UI related. To complete the decoupling your view should only handle the viewmodel as an interface, this means you can easily swap out viewmodels at a later stage.
(Note that I've avoided talking about event triggers).
I am using the free version of the DevExpress Silverlight Menu (AgMenu 8.4). Sadly the MenuItems of this menu have no "Command" and "CommandParameter" properties.
I decided to inherit from the MenuItem class and implement two DependencyProperties, "Command" and "CommandProperty".
The code for this looks like this:
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
private Object _CommandParameter = null;
public Object CommandParameter
{
get { return _CommandParameter; }
set { _CommandParameter = value; } //This one is triggered. This is ok.
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged));
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//CommandParameter Object is arriving here. That is ok.
}
private ICommand _Command = null;
public ICommand Command
{
get { return _Command; }
set
{
//HERE is the problem.
//This one is NOT triggered. I dont' know why....?
_Command = value;
}
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//ICommand Object is arriving here. That is also ok.
//I don't understand, why the ICommand Object is not arriving in the set value prop
}
}
Now I am using this two DPs in my XAML. This looks like this for one MenuItem:
<cc:MenuItem x:Name ="_mnuItemLogout"
DataContext ="{Binding Source={StaticResource ViewModel}}"
Header ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutText, Mode=OneWay}"
IsEnabled ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutEnabled, Mode=OneWay}"
Command ="{Binding Source={StaticResource ViewModel}, Path=Command_FormOpen}"
CommandParameter ="{gui:FormOpen e=Login}"
IsCheckable ="False"
>
</cc:MenuItem>
When I am testing my silverlight application, I assume that both, the "Command" and "CommandParameter" set value properties are called, and the values are set to _Command and _CommandParameter, but only the CommandParameter set value is called.
Strangely, both static procedures "OnCommandChanged" and "OnCommandParameterChanged" are called. While debugging, I can see, both expected objects (ICommand and CommandParameter) are arriving in this two procedures.
So my question is:
What am I doing wrong, that the ICommand Object is not set in the "Set ICommand" property?
Thank you.
This case is solved. What I needed to do, is not using DependencyProperties, but attached DependencyProperties.
The MenuItem of the DevExpress Silverlight menu now accepts Command and CommandParameter objects. The Command is triggered, when the LeftMouseButtonUp event is fired. The following is the working code. The XAML stays the same (see above). You just need to make a silverlight user control and inherit from DevExpress.AgMenu.AgMenuItem. You then use this as MenuItem, instead of the original.
using System;
using System.Windows;
using System.Windows.Input;
namespace Gui.CustomControls
{
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
#region CommandParameter DependencyProperty
public static Object GetCommandParameter(DependencyObject obj)
{
return (Object)obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, Object value)
{
obj.SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged) );
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
if ((args.NewValue != null) && (_DependencyObject != null))
{
MenuItem.SetCommandParameter(_DependencyObject, args.NewValue);
}
}
#endregion
#region Command
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
ICommand _ICommand = (ICommand)args.NewValue;
if ((_ICommand != null) && (_DependencyObject != null))
{
SetCommand(_DependencyObject, _ICommand);
}
}
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
#endregion
#region LeftMouseButtonUp (Command Trigger)
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
ICommand _ICommand = MenuItem.GetCommand(this);
Object _CommandParameter = MenuItem.GetCommandParameter(this);
if (_ICommand != null)
{
_ICommand.Execute(_CommandParameter);
}
}
#endregion
}
}