I'm trying to make a composite behavior which is composed of arbitrary simple behaviors. I found behaviors very flexible way of making custom controls.
Currently I have 5 behaviors implemented for slider. but they can conflict with each other.
These behaviors are designed for one control. I could design each of them to work independently without conflicting with each other (its worth mentioning that I did this and it worked successfully. but I removed all of it because it was just ugly.)
There are a lot of share points, I don't want to rewrite same code for every behavior.
So I'm trying to make a composite behavior for one control. this behavior has some attached properties which is shared for all of its containing behaviors. therefor these behaviors don't conflict with each other. And also a lot of code redundancy is gone. now containing behaviors becomes a lot simpler.
Here is the XAML sample for you to better get the idea.
<i:Interaction.Behaviors>
<b:SliderCompositeBehavior SourceValue="{Binding SharedValue}">
<sb:FreeSlideBehavior/>
<sb:LockOnDragBehavior/>
<sb:CancellableDragBehavior/>
<sb:KeepRatioBehavior/>
<sb:DragCompletedCommandBehavior Command="{Binding SeekTo}"/>
</b:SliderCompositeBehavior>
</i:Interaction.Behaviors>
Also all of these behaviors are designed to work stand alone. i.e putting it like this works just fine.
<i:Interaction.Behaviors>
<sb:FreeSlideBehavior/>
</i:Interaction.Behaviors>
Here is CompositeBehavior<T> : Behavior<T> :
[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
where T : DependencyObject
{
public static readonly DependencyProperty BehaviorCollectionProperty =
DependencyProperty.Register(
$"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
typeof(ObservableCollection<Behavior<T>>),
typeof(CompositeBehavior<T>),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.NotDataBindable));
public ObservableCollection<Behavior<T>> BehaviorCollection
{
get
{
var collection = GetValue(BehaviorCollectionProperty) as ObservableCollection<Behavior<T>>;
if (collection == null)
{
collection = new ObservableCollection<Behavior<T>>();
collection.CollectionChanged += OnCollectionChanged;
SetValue(BehaviorCollectionProperty, collection);
}
return collection;
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// some code to throw exception when same behavior is set more than once.
}
protected override void OnAttached()
{
foreach (var behavior in BehaviorCollection)
{
behavior.Attach(AssociatedObject);
}
}
protected override void OnDetaching()
{
foreach (var behavior in BehaviorCollection)
{
behavior.Detach();
}
}
}
Here is the SliderCompositeBehavior : CompositeBehavior<Slider> (only one dependency is shown for sake of simplicity)
public sealed class SliderCompositeBehavior : CompositeBehavior<Slider>
{
private Slider Host => AssociatedObject;
public static readonly DependencyProperty SourceValueProperty =
DependencyProperty.Register(
nameof(SourceValue),
typeof(double),
typeof(SliderCompositeBehavior),
new FrameworkPropertyMetadata(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSourceValueChanged));
// does the binding
public double SourceValue
{
get { return (double)Host.GetValue(SourceValueProperty); }
set { Host.SetValue(SourceValueProperty, value); }
}
// attached property for containing behaviors.
public static void SetSourceValue(Slider host, double value)
{
host.SetValue(SourceValueProperty, value);
}
public static double GetSourceValue(Slider host)
{
return (double)host.GetValue(SourceValueProperty);
}
private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var soruce = (SliderCompositeBehavior)dpo;
soruce.Host.Value = (double)args.NewValue;
}
}
Now there are two problems I can see.
Dependency property definitions inside containing behaviors do not function at all.
Overrinding metadata of dependency property does not work for containing properties.
Inside DragCompletedCommandBehavior : Behavior<Slider> I have
public sealed class DragCompletedCommandBehavior : Behavior<Slider>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
nameof(Command),
typeof(ICommand),
typeof(DragCompletedCommandBehavior));
}
I get this error on output. (this does not throw exception. it was hidden somewhere in output display after program started.)
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=SeekTo; DataItem=null; target element is 'DragCompletedCommandBehavior' (HashCode=52056421); target property is 'Command' (type 'ICommand')
In another behavior I have this.
public sealed class LockOnDragBehavior : Behavior<Slider>
{
static LockOnDragBehavior()
{
SliderCompositeBehavior.SourceValueProperty.OverrideMetadata(
typeof(LockOnDragBehavior),
new FrameworkPropertyMetadata(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSourceValueChanged));
}
private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
// do something
}
}
But OnSourceValueChanged never fires. the main OnSourceValueChanged inside SliderCompositeBehavior still fires though. but new meta data is just doing nothing.
How can I fix these problems? I don't understand why Dependency properties inside containing behaviors does not work. can some one please explain why? Thank you so much.
I found it out. after reading this post I understood that elements (in my case nested behaviors) were not part of the visual or logical tree. So data context was not accessible. and therefor binding did not work.
But instead of using ProxyBinding which was used here I came up with better solution.
The special collection BehaviorCollection does some magic when attaching behaviors. but I was using ObservableCollection therfor behaviors did not attached correctly.
Unfortunately the constructor of BehaviorCollection is internal. But who cares when you have power of reflection? ;)
Using BehaviorCollection instead essentially fixed the binding problem.
how ever overriding metadata problem is still not fixed. but I guess I will try other approaches (like using another dependency property) rather than overriding metadata of dependency property.
Here is the correction to CompositeBehavior<T> class.
[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
where T : DependencyObject
{
#region Behavior Collection
public static readonly DependencyProperty BehaviorCollectionProperty =
DependencyProperty.Register(
$"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
typeof(BehaviorCollection),
typeof(CompositeBehavior<T>),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.NotDataBindable));
public BehaviorCollection BehaviorCollection
{
get
{
var collection = GetValue(BehaviorCollectionProperty) as BehaviorCollection;
if (collection == null)
{
var constructor = typeof(BehaviorCollection)
.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, Type.EmptyTypes, null);
collection = (BehaviorCollection) constructor.Invoke(null);
collection.Changed += OnCollectionChanged;
SetValue(BehaviorCollectionProperty, collection);
}
return collection;
}
}
private void OnCollectionChanged(object sender, EventArgs eventArgs)
{
var hashset = new HashSet<Type>();
foreach (var behavior in BehaviorCollection)
{
if (behavior is Behavior<T> == false)
{
throw new InvalidOperationException($"{behavior.GetType()} does not inherit from {typeof(Behavior<T>)}.");
}
if (hashset.Add(behavior.GetType()) == false)
{
throw new InvalidOperationException($"{behavior.GetType()} is set more than once.");
}
}
}
#endregion
protected sealed override void OnAttached()
{
OnSelfAttached();
foreach (var behavior in BehaviorCollection)
{
behavior.Attach(AssociatedObject);
}
}
protected sealed override void OnDetaching()
{
OnSelfDetaching();
foreach (var behavior in BehaviorCollection)
{
behavior.Detach();
}
}
protected virtual void OnSelfAttached()
{
}
protected virtual void OnSelfDetaching()
{
}
}
Related
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 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 :)
Simple question. Having:
<ScrollBar ... />
How can I detect when Maximum is changed? E.g. for Value there is an event.
Typically there would be a binding of some kind. I was thinking maybe it is possible to get this binding, create dependency property and bind to it instead, then I can register a callback when this new dependency property is changed... but that sounds complicated nor I am sure it is acceptable solution to all cases (e.g. what if another binding is set, how can I detect this kind of change). Polling?
You can create a custom class such as:
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
// do stuff
base.OnMaximumChanged(oldMaximum, newMaximum);
}
}
Or
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == System.Windows.Controls.Primitives.ScrollBar.MaximumProperty)
{
// do stuff
}
base.OnPropertyChanged(e);
}
}
It is important to understand what any property can be a source for multiple bindings. We can create a new target (new dependency property) which is then perfectly able to report about any change done to a property:
Create a new dependency property with callback.
Bind it to any other property to monitor for changes.
public partial class MainWindow : Window
{
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(MainWindow), new PropertyMetadata(0, (d, e) =>
{
// value has changed
}));
public MainWindow()
{
InitializeComponent();
var scrollBar = ... // instance of scrollbar
BindingOperations.SetBinding(this, MaximumProperty,
new Binding(nameof(RangeBase.Maximum)) { Source = scrollBar });
}
}
Is there any way to listen to changes of a DependencyProperty? I want to be notified and perform some actions when the value changes but I cannot use binding. It is a DependencyProperty of another class.
This method is definitely missing here:
DependencyPropertyDescriptor
.FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
.AddValueChanged(radioButton, (s,e) => { /* ... */ });
Caution: Because DependencyPropertyDescriptor has a static list of all handlers in application every object referenced in those handlers will leak if the handler is not eventually removed. (It does not work like common events on instance objects.)
Always remove a handler again using descriptor.RemoveValueChanged(...).
If it's a DependencyProperty of a separate class, the easiest way is to bind a value to it, and listen to changes on that value.
If the DP is one you're implementing in your own class, then you can register a PropertyChangedCallback when you create the DependencyProperty. You can use this to listen to changes of the property.
If you're working with a subclass, you can use OverrideMetadata to add your own PropertyChangedCallback to the DP that will get called instead of any original one.
I wrote this utility class:
It gives DependencyPropertyChangedEventArgs with old & new value.
The source is stored in a weak reference in the binding.
Not sure if exposing Binding & BindingExpression is a good idea.
No leaks.
using System;
using System.Collections.Concurrent;
using System.Windows;
using System.Windows.Data;
public sealed class DependencyPropertyListener : DependencyObject, IDisposable
{
private static readonly ConcurrentDictionary<DependencyProperty, PropertyPath> Cache = new ConcurrentDictionary<DependencyProperty, PropertyPath>();
private static readonly DependencyProperty ProxyProperty = DependencyProperty.Register(
"Proxy",
typeof(object),
typeof(DependencyPropertyListener),
new PropertyMetadata(null, OnSourceChanged));
private readonly Action<DependencyPropertyChangedEventArgs> onChanged;
private bool disposed;
public DependencyPropertyListener(
DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
: this(source, Cache.GetOrAdd(property, x => new PropertyPath(x)), onChanged)
{
}
public DependencyPropertyListener(
DependencyObject source,
PropertyPath property,
Action<DependencyPropertyChangedEventArgs> onChanged)
{
this.Binding = new Binding
{
Source = source,
Path = property,
Mode = BindingMode.OneWay,
};
this.BindingExpression = (BindingExpression)BindingOperations.SetBinding(this, ProxyProperty, this.Binding);
this.onChanged = onChanged;
}
public event EventHandler<DependencyPropertyChangedEventArgs> Changed;
public BindingExpression BindingExpression { get; }
public Binding Binding { get; }
public DependencyObject Source => (DependencyObject)this.Binding.Source;
public void Dispose()
{
if (this.disposed)
{
return;
}
this.disposed = true;
BindingOperations.ClearBinding(this, ProxyProperty);
}
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listener = (DependencyPropertyListener)d;
if (listener.disposed)
{
return;
}
listener.onChanged?.Invoke(e);
listener.OnChanged(e);
}
private void OnChanged(DependencyPropertyChangedEventArgs e)
{
this.Changed?.Invoke(this, e);
}
}
using System;
using System.Windows;
public static class Observe
{
public static IDisposable PropertyChanged(
this DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
{
return new DependencyPropertyListener(source, property, onChanged);
}
}
You could inherit the Control you're trying to listen, and then have direct access to:
protected void OnPropertyChanged(string name)
No risk of memory leak.
Don't be afraid of standard OO techniques.
There are multiple ways to achieve this. Here is a way to convert a dependent property to an observable, such that it can be subscribed to using System.Reactive:
public static class DependencyObjectExtensions
{
public static IObservable<EventArgs> Observe<T>(this T component, DependencyProperty dependencyProperty)
where T:DependencyObject
{
return Observable.Create<EventArgs>(observer =>
{
EventHandler update = (sender, args) => observer.OnNext(args);
var property = DependencyPropertyDescriptor.FromProperty(dependencyProperty, typeof(T));
property.AddValueChanged(component, update);
return Disposable.Create(() => property.RemoveValueChanged(component, update));
});
}
}
Usage
Remember to dispose the subscriptions to prevent memory leaks:
public partial sealed class MyControl : UserControl, IDisposable
{
public MyControl()
{
InitializeComponent();
// this is the interesting part
var subscription = this.Observe(MyProperty)
.Subscribe(args => { /* ... */}));
// the rest of the class is infrastructure for proper disposing
Subscriptions.Add(subscription);
Dispatcher.ShutdownStarted += DispatcherOnShutdownStarted;
}
private IList<IDisposable> Subscriptions { get; } = new List<IDisposable>();
private void DispatcherOnShutdownStarted(object sender, EventArgs eventArgs)
{
Dispose();
}
Dispose(){
Dispose(true);
}
~MyClass(){
Dispose(false);
}
bool _isDisposed;
void Dispose(bool isDisposing)
{
if(_disposed) return;
foreach(var subscription in Subscriptions)
{
subscription?.Dispose();
}
_isDisposed = true;
if(isDisposing) GC.SupressFinalize(this);
}
}
If that is the case, One hack. You could introduce a Static class with a DependencyProperty. You source class also binds to that dp and your destination class also binds to the DP.
I am using the Model-View-ViewModel architecture in a WPF application I am building, and I would like a specific ViewModel to actually be reactive to the size of the view (not a normal use-case of the MVVM approach, I know).
Essentially, I have a ScrollViewer object and I want the viewmodel to observe the width and height of the scrollviewer and then be able to do things accordingly depending on what that width and height are.
I'd like to do something like this:
<ScrollViewer ViewportWidth="{Binding Path=MyViewportWidth, Mode=OneWayToSource}" ViewportHeight="{Binding Path=MyViewportHeight, Mode=OneWayToSource}" />
But of course this is impossible to do because "ViewportWidth" and "ViewportHeight" cannot be "bound to" (a.k.a. act as binding targets) because they are read-only dependency properties (even though I am not writing to them at all in this binding since it is OneWayToSource).
Anyone know of a good method to be able to do something like this?
You could try running something OnLoaded or OnResizeChanged that updates the viewmodel
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
ViewModel vm = sv.DataContext as ViewModel;
vm.ScrollViewerHeight = sv.ViewportHeight;
vm.ScrollViewerWidth = sv.ViewportWidth;
}
Ok, this is a really old question, but I thought I'd share for posterity, since I've solved this one myself. The best solution I've found is to create a user control that derives from the ScrollView class and implements the properties you want - which are of course linked to the non-bindable properties of the base class.
You can use the OnPropertyChanged function to monitor those properties and keep the values in sync.
Here's the full code-behind of my custom usercontrol called DynamicScrollViewer. Notice that I have four bindable dependency properties called DynamicHorizontalOffset, DynamicVerticalOffset, DynamicViewportWidth, and DynamicViewportHeight.
The two offset properties allow both read and write control of the offset, while the viewport properties are essentially read-only.
I had to use this class when creating a complex animation editor control in which various components (labels at the left, nodes in the middle, timeline at top) needed to scroll synchronously, but only in limited aspects, and were all bound to common external scrollbars. Think of locking a section of rows in spreadsheet, and you get the idea.
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class DynamicScrollViewer : ScrollViewer
{
public DynamicScrollViewer()
{
InitializeComponent();
}
public double DynamicHorizontalOffset
{
get { return (double)GetValue(DynamicHorizontalOffsetProperty); }
set { SetValue(DynamicHorizontalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicHorizontalOffsetProperty =
DependencyProperty.Register("DynamicHorizontalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicVerticalOffset
{
get { return (double)GetValue(DynamicVerticalOffsetProperty); }
set { SetValue(DynamicVerticalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicVerticalOffsetProperty =
DependencyProperty.Register("DynamicVerticalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportWidth
{
get { return (double)GetValue(DynamicViewportWidthProperty); }
set { SetValue(DynamicViewportWidthProperty, value); }
}
public static readonly DependencyProperty DynamicViewportWidthProperty =
DependencyProperty.Register("DynamicViewportWidth", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportHeight
{
get { return (double)GetValue(DynamicViewportHeightProperty); }
set { SetValue(DynamicViewportHeightProperty, value); }
}
public static readonly DependencyProperty DynamicViewportHeightProperty =
DependencyProperty.Register("DynamicViewportHeight", typeof(double), typeof(DynamicScrollViewer));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DynamicVerticalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetVerticalOffset(DynamicVerticalOffset);
}
else if (e.Property == DynamicHorizontalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetHorizontalOffset(DynamicHorizontalOffset);
}
else if (e.Property == HorizontalOffsetProperty)
{
DynamicHorizontalOffset = (double)e.NewValue;
}
else if (e.Property == VerticalOffsetProperty)
{
DynamicVerticalOffset = (double)e.NewValue;
}
else if (e.Property == ViewportWidthProperty)
{
DynamicViewportWidth = (double)e.NewValue;
}
else if (e.Property == ViewportHeightProperty)
{
DynamicViewportHeight = (double)e.NewValue;
}
}
}
}