I'd like to implement a set of similar attached behaviors for use in a WPF application.
Since their all share a chunk of boilerplate code, that I don't really want to repeat for every one, I'd like to create a base behavior inherit from it.
But since everything inside attached behaviors is static, I am at a loss of how to do it.
As an example, take this behavior, which executes a method on mousedown (the real behaviors would of course do something not easily done in an eventhandler):
public static class StupidBehavior
{
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for ChangeTooltip. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(StupidBehavior), new UIPropertyMetadata(false, IsEnabledChanged));
private static void IsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((UIElement)sender).MouseDown += { (o,e) => MyMethod(); };
}
private static void MyMethod()
{
MessageBox.Show("Boo");
}
}
Now, I'd like to create a new behavior that should have a different implementation of MyMethod, as well as a few additional properties controlling it. How should this be done?
You could create another attached property which contains the detailed implementation which is being called by the main behavior as a sub-class replacement. The object that property holds could be non-static and be used like a state-object.
(You could probably fit this into one property as well, where property == null means off)
You could use a static constructor to form a Dictionary<DependencyProperty,EventHandler> to map the specific DP to a specific handler and use a common DependencyPropertyChanged callback:
static StupidBehavior()
{
handlerDictionary[IsEnabledProperty] = (o,e) => MyMethod();
handlerDictionary[SomeOtherProperty] = (o,e) => SomeOtherMethod();
}
private static void CommonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var uie = sender as UIElement;
if (uie != null)
{
//removing before possibly adding makes sure the multicast delegate only has 1 instance of this delegate
sender.MouseDown -= handlerDictionary[args.Property];
if (args.NewValue != null)
{
sender.MouseDown += handlerDictionary[args.Property];
}
}
}
Or simply do a switch on args.Property. Or something in-between that involves a common method and branching based on the DependencyProperty.
And I'm not sure why your IsEnabled property deals with a value of type DependencyProperty rather than something that would make more semantic sense like bool.
Related
There are two Viewmodels, both of them had implemented the INotifyPropertyChanged interface (I have called the OnpropertyChanged("propertyname") in my actual code).
Public Class A{
public B BProperty
{
get
{
return _BProperty;
}
set
{
if (_BProperty != null)
_BProperty.PropertyChanged -= _BProperty_PropertyChanged;
_BProperty = value;
OnPropertyChanged("BProperty");
if (_BProperty != null)
_BProperty.PropertyChanged += _BProperty_PropertyChanged;
}
}
void _BProperty_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "status")
{
OnPropertyChanged("BProperty");
}
}
B _BProperty;
}
Public Class B
{
public int status{get;set;}
}
I also had a userControl:
<MyUserControl ...
... >
<Grid>
</Grid>
</MyUserControl>
And I had a dependencyProperty:
/// <summary>
/// File status
/// </summary>
public int Filestatus
{
get { return (int)GetValue(FilestatusProperty); }
set { SetValue(FilestatusProperty, value); }
}
public static readonly DependencyProperty FilestatusProperty =
DependencyProperty.Register(
"Filestatus",
typeof(int),
typeof(MyUserControl),
new PropertyMetadata(0, OnFilestatusPropertyChanged));
private static void OnFilestatusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl control = (MyUserControl)d;
if (e.NewValue != null)
{
}
}
edit:2015/09/21
Add the get/set methods:
public static readonly DependencyProperty FileStatusProperty = DependencyProperty.RegisterAttached(
"FileStatus", typeof(int), typeof(FileStatusIconControl), new PropertyMetadata(0, PropertyChangedCallback));
public static int GetFileStatus(DependencyObject source)
{
return (int)source.GetValue(FileStatusProperty);
}
public static void SetFileStatus(DependencyObject target, int value)
{
target.SetValue(FileStatusProperty, value);
}
private static void PropertyChangedCallback(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
FileStatusIconControl fsic = dependencyObject as FileStatusIconControl;
if(fsic != null)
wahahahahaha;
}
edit end
I used this userControl in my mainPage like this:
<mainPage ...
...>
<Grid>
<MyUserControl Filestatus={Binding Bproperty.status} />
<TextBox Text={Binding Bproperty.status} />
</Grid>
</mainPage>
The datacontext of mainPage is an instance of Class A.
My question is:
When status is changed, the text of textbox is changed, but the OnFilestatusPropertyChanged method only was called once when Usercontrol is loaded. Why?
Thanks.
I will start by saying that while I was looking into your question I ran into some problems with the code you have provided. I appreciate that somewhere you have some real code which has a problem and you cannot share this code with us so have tried to reproduce the problem in a smaller set of files.
However, if you do do this, please at least verify that the code you have provided runs and exhibits the problem. It is evident that you haven't tried to run your sample code (particularly the XAML) as there are problems with it:
attribute values should be surrounded in double-quotes,
the binding path Bproperty.status should be BProperty.status (first P capitalised).
All these things slow down someone trying to help you. Worse still, when I do find a problem with your code I can't be sure whether it's a genuine problem that your real code also has or whether it's something you introduced putting together your sample code. So all I can do is point out all the problems I find in the hope that one of them is the problem you have in your real code.
Firstly, your TextBox's Text property binding doesn't contain Mode=TwoWay. In WPF, this binding is TwoWay by default, but in Silverlight all bindings are OneWay by default. If you are familiar with WPF, this may be why you omitted Mode=TwoWay.
Secondly, I don't see why you have implemented class B as you have, apparently leaving it up to class A to fire property-changed events on its behalf. This approach doesn't work: when Silverlight updates the value in the status property of a B instance, it does so by calling the status setter of the B instance. Your B class has only an autogenerated property setter, which certainly doesn't fire the PropertyChanged event. Because this event wasn't fired, Silverlight doesn't know that is has some updates to do, and furthermore your A class isn't aware that it has changed either.
I would implement INotifyPropertyChanged in the usual way in class B, by calling OnPropertyChanged in the status setter. I would also remove the BProperty_PropertyChanged event handler in class A as I don't think it does you any favours.
Edit: Let us assume the classes actually do not share an interface! Major slip on my part...
For reasons unknown to me there are for example two WPF classes which both have the same method with the same signature:
UIElement
Animatable
So i have been wondering (for the heck of it) how i should construct a method (e.g. typechecking & exceptions) which ultimately calls the above mentioned method.
(I will be posting what i would be inclined to do but i am looking for what more experienced people would recommend.)
My approach:
public static void Animate(object target)
{
target.ThrowIfNull(); //Extension
if (!(target is Animatable || target is UIElement))
throw new ArgumentException("The target is not animatable");
//Construct animation
(target as dynamic).BeginAnimation(property, animation);
}
In the case that you have proposed, both classes share the same interface IAnimatable.
((IAnimatable)target).BeginAnimation(property, animation);
Should be sufficient
Here is the documentation
public static void Animate(this object target, DependencyProperty property, AnimationTimeline animation)
{
target.ThrowIfNull();
DoAnimate(target as dynamic);
}
private static void DoAnimate(object target, DependencyProperty property, AnimationTimeline animation)
{
throw new ArgumentException("The target is not animatable")
}
private static void DoAnimate(Animatable target, DependencyProperty property, AnimationTimeline animation)
{
target.BeginAnimation(property, animation);
}
private static void DoAnimate(UIElement target, DependencyProperty property, AnimationTimeline animation)
{
target.BeginAnimation(property, animation);
}
In my point of view it is cleaner.
UPDATED Example with AnimationContext
class AnimationContext
{
private readonly DependencyProperty property;
private readonly AnimationTimeline animation;
public AnimationContext(DependencyProperty property, AnimationTimeline animation)
{
this.property = property;
this.animation = animation;
}
public void Animate(UIElement target)
{
target.BeginAnimation(property, animation);
}
public void Animate(Animatable target)
{
target.BeginAnimation(property, animation);
}
public void Animate(object target)
{
throw new ArgumentException("The target is not animatable");
}
}
static class AnimateExtensions
{
public static void Animate(this object target, DependencyProperty property, AnimationTimeline animation)
{
target.ThrowIfNull();
new AnimationContext(property, animation).Animate(target as dynamic);
}
}
After your edit on the two classes not inheriting from the same interface, the answer would be to use one of the following:
Reflection
Dynamic
None of these would of course check that the method exists until runtime, but I think that is kind of implied in the question.
Given the following two classes A and B, which contain a method with the same signature, but do not implement the same interface:
class A
public void Handle(string s) {
Console.WriteLine("Hello from A: " + s);
}
}
and:
class B
public void Handle(string s) {
Console.WriteLine("Hello from B: " + s);
}
}
You can create a method that handles any object that has a method with that signature, like this:
static void HandleObject(dynamic anything) {
anything.Handle("It works!");
}
The HandleObject will basically take any object as input, and at runtime try to blindly call a method called Handle on it. If the object has no Handle method, the call will fail at runtime.
The compiler doesn't help (or stop) you with dynamics. Failures are postponed until runtime :)
I was working on creating a custom control with Command behavior and came across something odd. Some articles I found declared the CanExecuteChangedHandler EventHandler as static and others were non-static. Microsoft's SDK documentation shows static but when I declare it as static I get odd behavior when using multiple controls.
private static EventHandler canExecuteChangedHandler;
private void AddSecureCommand(ISecureCommand secureCommand)
{
canExecuteChangedHandler = new EventHandler(CanExecuteChanged);
securityTypeChangedHandler = new EventHandler(SecurityTypeChanged);
if (secureCommand != null)
{
secureCommand.CanExecuteChanged += canExecuteChangedHandler;
secureCommand.SecurityTypeChanged += securityTypeChangedHandler;
}
}
Does anyone know the proper way? Am I doing something wrong that is causing the static EventHandler not to work?
The stated reason for keeping a local copy of EventHandler is that the WPF commanding sub-system uses weak references internally and therefore we need to keep a reference to the specific delegate object that is added to the CanExecuteChanged event. If fact, anytime we are adding to any commanding sub-system event, we should also observe this practice, as you have for SecurityTypeChanged.
The short answer to your question is that canExecuteChangedHandler can be static, but you must be careful to only initialize it once. The reason it can be static is that all new EventHandler(CanExecuteChanged) will do the same thing if CanExecuteChanged is static. The reason to initialize it once is that different instances are different.
A private property that has the right read-only semantics is:
static EventHandler canExecuteChangedHandler
{
get
{
if (internalCanExecuteChangedHandler == null)
internalCanExecuteChangedHandler = new EventHandler(CanExecuteChanged);
return internalCanExecuteChangedHandler;
}
}
static EventHandler internalCanExecuteChangedHandler;
but this only works if CanExecuteChanged is static. If it is not, then remove the static qualifiers. In either case you have to be careful to actually use the property.
In this particular example, the second time that AddSecureCommand is called the first canExecuteChangedHandler is at risk of being garbage collected.
Finally, if this all sounds like black-magic, here is a code example to show what is happening.
public class Container
{
private WeakReference reference;
public object Object
{
get { return reference.IsAlive ? reference.Target : null; }
set { reference = new WeakReference(value); }
}
}
public class DelegateTest
{
private EventHandler eventHandler;
private Container container1;
private Container container2;
void MyEventHandler(object sender, EventArgs args)
{
}
public DelegateTest()
{
this.eventHandler = new EventHandler(MyEventHandler);
this.container1 = new Container { Object = this.eventHandler };
this.container2 = new Container { Object = new EventHandler(MyEventHandler) };
GC.Collect();
Console.WriteLine("container1: {0}", this.container1.Object == null);
Console.WriteLine("container2: {0}", this.container2.Object == null);
}
}
This produces this output:
container1: False
container2: True
which indicates that during the garbage collection that the second container had its EventHandler garbage-collected "out from underneath it". This is by design the way that weak references work and the explanation for you need to keep a reference to it yourself.
I'm using AvalonDock to layout my application.
I want to create a "View" MenuItem with a checkable MenuItem for each of my DockableContents that will show/hide each item.
I'm not finding an example of anyone doing this, and it appears to me the State property is readonly, making it not possible to create a 2-way binding to the MenuItem. It also looks like you have to call methods to change the State.
Anyone have a clever way to do this with bindings? Or is there a simple way to do it I'm missing.
One possible solution is to use an attached property. The attached property would call the necessary methods to change the state. You could then bind to that.
public static class ContentAttach
{
public static readonly DependencyProperty StateProperty = DependencyProperty.RegisterAttached(
"State", typeof(DockableContentState), typeof(ContentAttach), new PropertyMetadata(StateChanged));
public static void SetState(DockableContent element, DockableContentState value)
{
element.SetValue(StateProperty, value);
}
public static DockableContentState GetState(DockableContent element)
{
return (DockableContentState)element.GetValue(StateProperty);
}
private static void StateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (DockableContent)d;
var state = (DockableContentState)e.NewValue;
switch (state)
{
// Call methods in here to change State.
}
}
}
I'm trying to create a GUI (WPF) Library where each (custom) control basically wraps an internal (third party) control. Then, I'm manually exposing each property (not all of them, but almost). In XAML the resulting control is pretty straightforward:
<my:CustomButton Content="ClickMe" />
And the code behind is quite simple as well:
public class CustomButton : Control
{
private MyThirdPartyButton _button = null;
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public CustomButton()
{
_button = new MyThirdPartyButton();
this.AddVisualChild(_button);
}
protected override int VisualChildrenCount
{
get
{ return _button == null ? 0 : 1; }
}
protected override Visual GetVisualChild(int index)
{
if (_button == null)
{
throw new ArgumentOutOfRangeException();
}
return _button;
}
#region Property: Content
public Object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
"Content", typeof(Object),
typeof(CustomButton),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeContent))
);
private static void ChangeContent(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as CustomButton).UpdateContent(e.NewValue);
}
private void UpdateContent(Object sel)
{
_button.Content = sel;
}
#endregion
}
The problem comes after we expose MyThirdPartyButton as a property (in case we don't expose something, we would like to give the programmer the means to use it directly). By simply creating the property, like this:
public MyThirdPartyButton InternalControl
{
get { return _button; }
set
{
if (_button != value)
{
this.RemoveVisualChild(_button);
_button = value;
this.AddVisualChild(_button);
}
}
}
The resulting XAML would be this:
<my:CustomButton>
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
And what I'm looking for, is something like this:
<my:CustomButton>
<my:CustomButton.InternalControl Content="ClickMe" />
But (with the code I have) its impossible to add attributes to InternalControl...
Any ideas/suggestions?
Thanks a lot,
--
Robert
WPF's animation system has the ability to set sub-properties of objects, but the XAML parser does not.
Two workarounds:
In the InternalControl property setter, take the value passed in and iterate through its DependencyProperties copying them to your actual InternalControl.
Use a build event to programmatically create attached properties for all internal control properties.
I'll explain each of these in turn.
Setting properties using the property setter
This solution will not result in the simplified syntax you desire, but it is simple to implement and will probably solve the main problem with is, how to merge values set on your container control with values set on the internal control.
For this solution you continue to use the XAML you didn't like:
<my:CustomButton Something="Abc">
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
but you don't actually end up replacing your InternalControl.
To do this, your InternalControl's setter would be:
public InternalControl InternalControl
{
get { return _internalControl; }
set
{
var enumerator = value.GetLocalValueEnumerator();
while(enumerator.MoveNext())
{
var entry = enumerator.Current as LocalValueEntry;
_internalControl.SetValue(entry.Property, entry.Value);
}
}
}
You may need some additional logic to exclude DPs not publically visible or that are set by default. This can actually be handled easily by creating a dummy object in the static constructor and making a list of DPs that have local values by default.
Using a build event to create attached properties
This solution allows you to write very pretty XAML:
<my:CustomButton Something="Abc"
my:ThirdPartyButtonProperty.Content="ClickMe" />
The implementation is to automatically create the ThirdPartyButtonProperty class in a build event. The build event will use CodeDOM to construct attached properties for each property declared in ThirdPartyButton that isn't already mirrored in CustomButton. In each case, the PropertyChangedCallback for the attached property will copy the value into the corresponding property of InternalControl:
public class ThirdPartyButtonProperty
{
public static object GetContent(...
public static void SetContent(...
public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached("Content", typeof(object), typeof(ThirdPartyButtonProperty), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((CustomButton)obj).InternalControl.Content = (object)e.NewValue;
}
});
}
This part of the implementation is straightforward: The tricky part is creating the MSBuild task, referencing it from your .csproj, and sequencing it so that it runs after the precompile of my:CustomButton so it can see what additional properties it needs to add.