Commanding with Prism in Silverlight is usually done together with MVVM, and I assume most people have at least heard of MVVM. The most common use is the Click command on buttons, but we can also use commanding for other purposes, which I’m now going to demonstrate.
It’s not uncommon for business applications to have different kinds of users, certain users are admins, certain only have read-only rights, … The biggest problem is when all users are allowed to acces the same page, but cannot execute the same actions on that page. To solve this, we can use our own commanding to disable, hide, or make elements readonly on pages.
This implementation is based on Prism, and we just change it to our own needs. But let's begin. To keep it simple, I’ll first just create 2 enums which contains the actions and rights. You’ll probably take a different approach in a large application, but this is just for demonstrating purposes.
public enum UserRights
{
CanSeeName, CanSeeAddress, CanSeeEmail, CanSeeTelephone, CanSave, CanDelete
}
public enum ElementAction
{
Disable, Hide, ReadOnly
}Now, we create our base behavior that will allow us to use it in our XAML to attach to an element.
public class RightsBehaviorBase<T> where T : FrameworkElement
{
private UserRights _userRights;
private ElementAction _elementsAction;
private readonly WeakReference _targetObject;
public UserRights UserRights
{
get { return _userRights; }
set { _userRights = value; }
}
public ElementAction ElementsAction
{
get { return _elementsAction; }
set { _elementsAction = value; }
}
protected T TargetObject
{
get { return _targetObject.Target as T; }
}
public RightsBehaviorBase(T targetObject)
{
_targetObject = new WeakReference(targetObject);
}
protected virtual void ExecuteCommand()
{
// If the user has no right, apply the correct action
if (!RightsManager.HasUserRight(_userRights))
{
switch (_elementsAction)
{
//When action is disable, always make sure IsEnabled is false.
case ElementAction.Disable:
//Since the property IsEnabled can only be applied to members from the Control namespace, we have to check for this
if (TargetObject is Control)
{
var control = (Control)((FrameworkElement)TargetObject);
control.IsEnabled = false;
control.IsEnabledChanged += (sender, e) =>
{
if (((Control)sender).IsEnabled != false)
((Control)sender).IsEnabled = false;
};
}
else
{
throw new ArgumentException("Target object is not a member of the Control namespace.");
}
break;
//When action is Hide, never make the element visible.
case ElementAction.Hide:
TargetObject.Visibility = Visibility.Collapsed;
TargetObject.LayoutUpdated += (s, e) =>
{
if (TargetObject.Visibility == Visibility.Visible)
TargetObject.Visibility = Visibility.Collapsed;
};
break;
//When action is ReadOnly, make sure element is always ReadOnly. Because the property IsReadOnly can be
//applied to different elements (like TextBox or DataGrid), reflection is used to acces the property.
case ElementAction.ReadOnly:
var type = TargetObject.GetType();
var prop = type.GetProperty("IsReadOnly");
if (prop != null)
{
prop.SetValue(TargetObject, true, null);
TargetObject.LayoutUpdated += (s, e) =>
{
if (!(bool)prop.GetValue(TargetObject, null))
prop.SetValue(TargetObject, true, null);
};
}
else
throw new ArgumentException(string.Format("{0} has no property IsReadOnly.", TargetObject.GetType()));
break;
}
}
}
}This class contains the method that will actually execute the logic to decide which action should be taken on the element the behavior is set. Note that it reacts to changes made by the developer so he can be sure the element will always be in the state it should be. This is convenient in the way that the developer doesn’t have to care about it at all while creating an application. He can change the property in the UI but it will never change. So if it’s set to disabled, it will always be like that.
The next step is creating the behavior, that will trigger the ExecuteCommand on an event triggered by the element in the UI. This behavior is needed, because Silverlight 3 does not has commanding support.
Here we want that the right we set is applied when the FrameworkElement is loaded, but remember, you can hook it up to any event here, if you need it.
public class RightsBehavior : RightsBehaviorBase<FrameworkElement>
{
public RightsBehavior(FrameworkElement objectToSet)
: base(objectToSet)
{
objectToSet.Loaded += (sender, e) => ExecuteCommand();
}
}The third and last class we need to add is a static class that holds all the Dependency properties that will allow us to use the behavior in our XAML code.
public class RightsCommand
{
private static readonly DependencyProperty RightsBehaviorProperty = DependencyProperty.RegisterAttached(
"UmRightsManagementBehavior",
typeof(RightsBehavior),
typeof(RightsCommand),
null);
public static readonly DependencyProperty UserRightsProperty = DependencyProperty.RegisterAttached(
"UserRight",
typeof(UserRights),
typeof(RightsCommand),
new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty ElementActionProperty = DependencyProperty.RegisterAttached(
"ElementAction",
typeof(ElementAction),
typeof(RightsCommand),
new PropertyMetadata(OnSetCommandParameterCallback));
public static void SetUserRight(FrameworkElement fwe, UserRights userRight)
{
fwe.SetValue(UserRightsProperty, userRight);
}
public static UserRights GetUserRight(FrameworkElement fwe)
{
return (UserRights)fwe.GetValue(UserRightsProperty);
}
public static void SetElementAction(FrameworkElement fwe, ElementAction parameter)
{
fwe.SetValue(ElementActionProperty, parameter);
}
public static ElementAction GetElementAction(FrameworkElement fwe)
{
return (ElementAction)fwe.GetValue(ElementActionProperty);
}
private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
FrameworkElement fwe = dependencyObject as FrameworkElement;
if (fwe == null) return;
var behavior = GetOrCreateBehavior(fwe);
behavior.UserRights = (UserRights)e.NewValue;
}
private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var fwe = dependencyObject as FrameworkElement;
if (fwe == null) return;
var behavior = GetOrCreateBehavior(fwe);
behavior.ElementsAction = (ElementAction)e.NewValue;
}
private static RightsBehavior GetOrCreateBehavior(FrameworkElement fwe)
{
RightsBehavior behavior = fwe.GetValue(RightsBehaviorProperty) as RightsBehavior;
if (behavior == null)
{
behavior = new RightsBehavior(fwe);
fwe.SetValue(RightsBehaviorProperty, behavior);
}
return behavior;
}
}Now you can reference the commanding classes and use it like this
<TextBox Commands:RightsCommand.UserRight="CanSeeName" Commands:RightsCommand.ElementAction="ReadOnly" />
When the user has the right ‘CanSeeName’, the TextBox will be editable, but when the user doesn’t, it will be ReadOnly.
This concludes this post. But as I said, this is just one small example of taking a different approach on commanding. There are much more things where it can be useful. I hope this post helps people understand it and makes the commanding more understandable so you can use it!
I’ve also uploaded the full
source code with a little example.