Create Dependency Properties for setting Custom EventHandlers in XAML - wpf

i want to do add custom event handlers to default framework elements using DependencyProperties.
Something like the following:
<Border custom:MyProps.HandleMyEvent="someHandler">...</Border>
Here is the code behind for the control that contains the Border element:
public class MyPage : Page{
public void someHandler(object sender, EventArgs e){
//do something
}
}
Here is rough sample of how i imagine the class that defines the property:
public class MyProps{
public event EventHandler MyInternalHandler;
public static readonly DependencyProperty HandleMyEventProperty = ...
public void SetHandleMyEvent(object sender, EventHandler e){
MyInternalHandler += e;
}
}
The problem is that I don't know/didn't find any hints how to combine DependencyProperties with events/delegates and EventHandlers.
Do you have a clue?

I'm going to assume this has nothing to do with WPF, this is a silverlight question.
First of all you can't simply add an Event to an existing control. After all you are adding attached Properties whereas events are handled differently, they're not properties.
You need to create a new type which has this event then create an attached property of this type.
Here is a basic type that simply has an event:-
public class MyEventer
{
public event EventHandler MyEvent;
// What would call this??
protected void OnMyEvent(EventArgs e)
{
if (MyEvent != null)
MyEvent(this, e);
}
}
Now we create an attached property which has MyEventer as its property, I prefer to place these in a separate static class.
public static class MyProps
{
public static MyEventer GetEventer(DependencyObject obj)
{
return (MyEventer)obj.GetValue(EventerProperty );
}
public static void SetEventer(DependencyObject obj, MyEventer value)
{
obj.SetValue(EventerProperty , value);
}
public static readonly DependencyProperty EventerProperty =
DepencencyProperty.RegisterAttached("Eventer", typeof(MyEventer), typeof(MyProps), null)
}
}
Now you attach this to a control like this:-
<Border ...>
<custom:MyProps.Eventer>
<custom:MyEventer MyEvent="someHandler" />
</custom:MyProps.Eventer>
</Border>
If you compile the project before writing this xaml you'll note that Visual Studio will offer you the option for it to create the event handler in the code behind for you.
Of course this still leaves a significant question: How did you intend to cause the event to fire?

Related

Mixing events from CustomControls, in an adorner, with a Viewmodel

I've been going back and forth over the pros and cons of the two following approaches to Events from custom controls. My debate basically revolves around how much "logic" should be placed within a custom (not user) control and to best get events into a viewmodel.
The "control", DataGridAnnotationControl, resides within an adorner to my data grid. The goal here is to respond to the user selecting an item from a combobox displayed within the custom control.
The first example, Example #1, uses a pretty standard custom event in the DataGridAnnotationControl
which is then mapped by way of the adorner to the target AppointmentEditor (viewmodel). My biggest complaint with this is the obvious dependency to the (AppointmentEditor) from the adorner to achieve proper event routing.
♦ Example #1:
♦ CustomControl DataGridAnnotationControl
public override void OnApplyTemplate()
{
......
_cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
}
private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaiseSelectionChanged();
}
public event Action SelectionChanged;
public void RaiseSelectionChanged()
{
SelectionChanged?.Invoke();
}
♦ Adorner DataGridAnnotationAdorner
public DataGridAnnotationAdorner(DataGrid adornedDataGrid)
: base(adornedDataGrid)
{
......
Control = new DataGridAnnotationControl();
this.SelectionChanged += ((AppointmentEditor)adornedDataGrid.DataContext).SelectionChanged; <--This requires a reference to Patient_Registration.Editors. THIS IS FORCING
A DEPENDENCY ON THE PATIENT_REGISTRATION PROJECT.
}
public event Action SelectionChanged
{
add { Control.SelectionChanged += value; }
remove { Control.SelectionChanged -= value; }
}
♦ AppointmentEditor
public void SelectionChanged()
{
throw new NotImplementedException();
}
Example #2 This example uses pretty standard event routing up to the mainwindow from which an event aggregator is being used to hit the AppointmentEditor as a subscriber to the event. My biggest complaint here is all the additional code needed (over Example #1). In addition, it seems like a complicating factor to climb the visual tree just to jump into the one viewmodel designed to support this customcontrol.
Example #2:
♦ CustomControl DataGridAnnotationControl
public override void OnApplyTemplate()
{
.....
_cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
}
private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaisePatientNameSelectionChangedEvent();
}
public static readonly RoutedEvent PatientNameSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"PatientNameSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridAnnotationControl));
// Provide CLR accessors for the event
public event RoutedEventHandler PatientNameSelectionChanged
{
add { AddHandler(PatientNameSelectionChangedEvent, value); }
remove { RemoveHandler(PatientNameSelectionChangedEvent, value); }
}
protected virtual void RaisePatientNameSelectionChangedEvent()
{
RoutedEventArgs args = new RoutedEventArgs(DataGridAnnotationControl.PatientNameSelectionChangedEvent);
RaiseEvent(args);
}
♦ public partial class MainWindow : Window
{
public MainWindow(IMainWindowViewModel mainWindowViewModel, EventAggregator eventAggregator)
{
InitializeComponent();
EventAggregator = eventAggregator;
DataContext = mainWindowViewModel;
....
AddHandler(DataGridAnnotationControl.PatientNameSelectionChangedEvent, new RoutedEventHandler(PatientNameSelectionChangedHandler));
}
private void PatientNameSelectionChangedHandler(object sender, RoutedEventArgs e)
{
EventAggregator.PublishEvent( new PatientNameSelected() );
}
}
♦ public class AppointmentEditor : INotifyPropertyChanged, ISubscriber<PatientNameSelected>
public void OnEventHandlerAsync(PatientNameSelected e)
{
throw new NotImplementedException();
}
Is there a preferred way of doing this?
TIA
Ideally, your custom control should have no knowledge of your view-models.
Using MVVM, you would bind an event in your custom control to a command in your view-model.
I author and maintain tons of custom controls that are used by a lot of other teams. I always expose an associated ICommand with any event to make it easy for MVVM users to use my controls in the easiest way possible.

Generic Behavior

I would like create generic behavior. My problem is generic declaration in XAML.
public class GenericBehavior<T> : Behavior<DataGrid>
where T : class
{
}
I can’t use x:TypeArguments because I don’t have loose XAML file.
In WPF and when targeting .NET Framework 4, you can use XAML 2009
features together with x:TypeArguments but only for loose XAML (XAML
that is not markup-compiled). Markup-compiled XAML for WPF and the
BAML form of XAML do not currently support the XAML 2009 keywords and
features
I found some workaround with MarkupExtension but with Behaviors not work.
In my current solution I attach behavior in code.
Any idea?
you can create the generic Behavior inside your view model then inject it to your control using attached properties.
public class ViewModel
{
public Behavior MyBehavior
{
get
{
return new GenericBehavior<SomeType>();
}
}
}
public class AttachedBehaviors
{
public static Behavior GetBehavior(DependencyObject sender) => (Behavior)sender.GetValue(BehaviorProperty);
public static void SetBehavior(DependencyObject sender, Behavior value) => sender.SetValue(BehaviorProperty, value);
public static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached("Behavior", typeof(Behavior), typeof(AttachedBehaviors),
new PropertyMetadata(null, new PropertyChangedCallback(BehaviorChanged)));
private static void BehaviorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement elem)
{
if (e.NewValue is Behavior behavior)
{
BehaviorCollection Behaviors = Interaction.GetBehaviors(elem);
Behaviors.Add(behavior);
}
}
}
}
public class GenericBehavior<T> : Behavior<DataGrid> where T : class
{
public T TestValue { get; set; }
protected override void OnAttached()
{
}
}
now you can use it like this
<DataGrid local:AttachedBehaviors.Behavior="{Binding MyBehavior}" >
</DataGrid>
PS: you just need to install Microsoft.Xaml.Behaviors.Wpf NuGet package.

How to pass initialization parameters to UserControl in WPF (using MVVM)

I have a UserControl called ActionsTreeView I built using MVVM practices where I have an IPluginsProvider interface that populates the data in my UserControl. I want to be able to provide an object implementating this IContentProvider interface as a parameter to initialize my UserControl's ViewModel.
Here is my approach so far, which isn't working. I am wondering if I'm going down the right path? I declare a DependencyProperty in my user control which is visible to my mainWindow where I want to instantiate this UserControl. This code just attempts to pass the PluginsProvider object to my UserControl which needs it to build its ViewModel.
My PluginProvider DependencyProperty setter in my UserControl never gets hit because my My PropertyChanged handler is always null in MainWindow.xaml.cs I think I have the code right, but not sure I'm going down the right road and what I'm missing to make this connection?
ActionsTreeView.xaml.cs
public partial class ActionsTreeView: UserControl
{
public static readonly DependencyProperty PluginProviderProperty = DependencyProperty.Register("PluginProvider", typeof(Models.IPluginsProvider), typeof(ActionsTreeView), new FrameworkPropertyMetadata(null, OnPluginProviderChanged));
private ViewModels.ActionsTreeViewModel vm;
public ActionsTreeView()
{
//Wire-up our ViewModel with the data provider and bind it to DataContext for our user control
//This is a Mock-up until I figure out a way to get the real provider here
Models.IPluginProvider pluginSource = new Models.MockPluginProvider();
vm = new ViewModels.ActionsTreeViewModel(pluginSource );
this.DataContext = vm;
InitializeComponent();
}
private static void OnPluginProviderChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ActionsTreeView)source).PluginProvider = (Models.IPluginsProvider)e.NewValue;
}
public Models.IPluginsProvider PluginProvider
{
get
{
return (Models.IPluginsProvider)GetValue(PluginProviderProperty);
}
set
{
SetValue(PluginProviderProperty, value);
vm.SetPluginSource(PluginProvider);
}
}...
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
}
private Models.IPluginsProvider _actionProvider;
public Models.IPluginsProvider ActionProvider
{
get { return _actionProvider; }
set
{
_actionProvider = value;
OnPropertyChanged("ActionProvider");
}
}
protected void OnPropertyChanged(string property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) //HANDLER IS ALWAYS NULL
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
Using my UserControl in MainWindow.xaml
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider}" />
</Grid>
I don't think you can pass a parameter in the ctor in xaml.
If you create control in code behind you can pass the parameter in the ctor(Param param)
Not sure if this fits in the MVVM model but I use it a lot in regular code behind
Use a frame in the XAML for a place to put the UserControl
Seems like you are missing the binding source
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" />
</Grid>
since your property ActionProvider is declared in MainWindow so during binding you are required to refer the same source unless you've set it as data context of the window
alternative to above you can also do the below if there is no other data context used in the MainWindow then you can use the original binding you have PluginProvider="{Binding ActionProvider}"
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
DataContext = this;
}
I've set the DataContext to this which will effectively resolve the value of ActionProvider in binding from the instance this
Extra
you may also choose to remove INotifyPropertyChanged from MainWindow as it is already DependencyObject and capable of property notification and declare a DependencyProperty for ActionProvider
eg
public Models.IPluginsProvider ActionProvider
{
get { return (Models.IPluginsProvider)GetValue(ActionProviderProperty); }
set { SetValue(ActionProviderProperty, value); }
}
// Using a DependencyProperty as the backing store for ActionProvider. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActionProviderProperty =
DependencyProperty.Register("ActionProvider", typeof(Models.IPluginsProvider), typeof(MainWindow), new PropertyMetadata(null));
so you don't need to worry for the notification change manually, you might be required to use this if the above solution does not work for you otherwise it is good to have.

Silverlight EventHandler DependencyProperty binding

I have a UserControl that I am trying to hook up event handlers from a parent control to. I want to do as little in code as I can and am running into some problems. Here is my setup:
in NewUserControl code behind I have:
public RoutedEventHandler PrintClickHandler { get; set; }
public DependencyProperty PrintClickHandlerProperty =
DependencyProperty.Register("PrintClickHandler", typeof(RoutedEventHandler),
typeof(NewUserControl), new PropertyMetadata(null));
In MyParentControl I have:
public RoutedEventHandler PrintClickHandler
{
get { return btnPrintCall_Click; }
}
private void btnPrintCall_Click(object sender, RoutedEventArgs e)
{
// some code
}
And finally in the MyParentControls xaml I have the binding for NewUserControl.PrintClickHandler:
<NewUserControl PrintClickHandler="{Binding Path=PrintClickHandler,
RelativeSource={RelativeSource AncestorType=local:MyParentControl, AncestorLevel=1}}" />
Now in the debugger I can implement all the getters and setters the classic way to hit breakpoints when they are called. I see the getter for MyParentControl.PrintClickHandler in the being hit, but the setter for NewUserControl.PrintClickHandler is never hit. I also have no errors or warnings related to this binding in the output.
I've never tried doing events like this, but it does look like your dependency property may by setup incorrectly, try:
public RoutedEventHandler PrintClickHandler
{
get { return (RoutedEventHandler)GetValue(PrintClickHandlerProperty); }
set { SetValue(PrintClickHandlerProperty, value); }
}
public static DependencyProperty PrintClickHandlerProperty = DependencyProperty.Register(
"PrintClickHandler",
typeof(RoutedEventHandler),
typeof(NewUserControl),
new PropertyMetadata(null));
This question is erroneous. As I am dealing with an event, using a dependency property is inappropriate. I can simple add a public Event property in my code behind which can be set in the xaml to an appropriate event handler method.

How to bind to a custom property in a Silverlight Custom control

I've created a custom control with, amongst others, the following:
public partial class MyButton : UserControl
{
public bool Enabled
{
get { return (bool)GetValue(EnabledProperty); }
set {
SetValue(EnabledProperty, value);
SomeOtherStuff();
}
}
}
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.Register("Enabled", typeof(bool), typeof(MyButton), new PropertyMetadata(true));
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool) obj.GetValue(EnabledProperty);
}
}
In my XAML, I (try to) use binding to set the Enabled property:
<MyButton x:Name="myButtom1" Enabled="{Binding CanEnableButton}"/>
I know the bind between my control and the underlying data model is valid and working as I can bind 'IsEnabled' (a native property of the underlying UserControl) and it works as expected. However, my Enabled property is never set via the above binding. I've put breakpoints on my property set/get and they never get hit at all.
I can only imaging I've missed something relating to binding in my custom control. Can anyone see what?
I've tried implementing INotifyPropertyChanged on my control (and calling the PropertyChanged event from my Enabled setter) ... but that didn't fix it.
[ BTW: In case you are wondering "Why?": I can't intercept changes to the IsEnabled state of the base control, so I decided to implement and use my own version of a Enable/disable property (which I called Enabled) - one where I could plug my own code into the property setter ]
First of all drop the SetEnabled and GetEnabled pair, these only make sense for an attached property which is not what you are doing.
Now your main problem is that you are under the false assumption that the get/set members of your propery get called during binding, they don't.
What you need is to pass a call back method in the property meta data, it's here that you intercept changes and take other actions like so:-
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.Register(
"IsEnabled",
typeof(bool),
typeof(MyButton),
new PropertyMetadata(true, OnIsEnabledPropertyChanged));
private static void OnIsEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyButton source = d as MyButton;
source.SomeOtherStuff();
}
private void SomeOtherStuff()
{
// Your other stuff here
}
With this in place regardless of how the propery is changed the SomeOtherStuff procedure will execute.
I'd suggest using the IsEnabledChanged event which is part of every Control/UserControl.
That would allow you to hook up to the event and do whatever actions you want to take.
public MainPage()
{
InitializeComponent();
this.IsEnabledChanged += new DependencyPropertyChangedEventHandler(MainPage_IsEnabledChanged);
}
void MainPage_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// Do SomeStuff
}

Resources