I created an UserControl to use as a Data Navigator. I've defined two DependencyProperties in this control as follows (DependencyProperty implied):
public ICollection DataCollection
{
get { return GetValue(DataCollectionProperty) as ICollection; }
set { SetValue(DataCollectionProperty, value); }
}
public ICollectionView View
{
get { return (DataCollection == null ? null : CollectionViewSource.GetDefaultView(DataCollection)); }
}
Then, I've put four buttons to perform the basic navigation operations (first, prev, next, last). Each button gets the following style:
<Style x:Key="NavButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataCollection}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
All this trigger does is to check if the DataCollection DependencyProperty is null, assuming that the RelativeResource TemplatedParent is passed as the DataContext of each button, like this:
<Button (...) DataContext="{RelativeSource TemplatedParent}">
Then I created the following MarkupExtension to compare values and return true or false, based in the comparison operation and compared values:
[MarkupExtensionReturnType(typeof(bool))]
public class ComparisonBinding : BindingDecoratorBase
{
public ComparisonOperation Operation { get; set; }
public object Comparand { get; set; }
public override object ProvideValue(IServiceProvider provider)
{
base.ProvideValue(provider);
DependencyObject targetObject;
DependencyProperty targetProperty;
bool status = TryGetTargetItems(provider, out targetObject, out targetProperty);
if (status && Comparand != null)
{
if (Comparand is MarkupExtension)
Comparand = (Comparand as MarkupExtension).ProvideValue(provider);
return Compare(targetObject.GetValue(targetProperty), Comparand, Operation);
}
return false;
}
private static bool Compare(object source, object target, ComparisonOperation op)
}
Finally, I used this ME to test the "Enabling" conditions for each button. Here's the condition for the First button:
<Button (...) DataContext="{RelativeSource TemplatedParent}"
IsEnabled="{DynamicResource {mark:ComparisonBinding Path=View.CurrentPosition, RelativeSource={RelativeSource TemplatedParent}, Comparand={Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataCollection.Count}, Operation=EQ}}">
Unfortunately, this solution didn't work. I keep getting this design-time exception:
InvalidOperationException: Cannot get NodePath for ViewNode which is not a part of the view tree.
Does anyone have a better solution? Maybe I'm trying to kill a fly with a cannon here. :)
Thanks in advance. Eduardo Melo
In my opinion, the best way to implement enabled/disabled functionality for buttons is by using the ICommand interface and using the CanExecute method. For this purpose you could use a lightweight implementation like the DelegateCommand in Prism or RelayCommand in MVVMLight, and if you really want top efficiency don't register the commands with the CommandManager - instead trigger the CanExecuteChanged on specific conditions which should be available in code.
In practice this means your user control will contain (or itself be) some sort of micro-ViewModel (with the ICommand instances and the implementation for their Execute and CanExecute methods), which is the best way to go in WPF development anyway. In that case all you need in XAML is binding the Command property of the buttons to the appropriate ICommand. This will also cleanly expose the commands (which can be regarded as 'tasks' from a functional viewpoint) to any other callers, including unit tests if you're so inclined.
It's perfectly legal to have ICommands exposed by your control, for example, and have buttons inside your control template bound to those same commands (using RelativeSource Self); you could even bind their visibility to some other property (UseBuiltInButtons), and if you want to integrate your control later with some fancy interface, you can simply hide the buttons and link external ones to the same ICommands.
Let me know if that helps or is just confusing, and I'll try to shed more light on the matter! Of course, this is just an idea and there may be other equally good ones.