WPF behaviors and events - attaching to proper events - wpf

I have here a behavior handling drag & drop, and I attach the mouse events through the view. The item's viewmodel inherit from IDraggable. I use Caliburn.Micro as MVVM framework.
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<!-- item contents -->
<i:Interaction.Behaviors>
<behaviors:DragOnCanvasBehavior DraggableItem="{Binding}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction CommandName="StartDrag" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseLeftButtonUp">
<i:InvokeCommandAction CommandName="StopDrag" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseMove">
<i:InvokeCommandAction CommandName="Dragging" />
</i:EventTrigger>
</i:Interaction.Triggers>
</behaviors:DragOnCanvasBehavior>
</i:Interaction.Behaviors>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then the behavior attaches the mouse events to the element's mouse handlers :
public class DragOnCanvasBehavior : Behavior<DependencyObject>
{
public static readonly DependencyProperty DraggableItemProperty =
DependencyProperty.RegisterAttached(
"DraggableItem",
typeof(IDraggable),
typeof(DragOnCanvasBehavior),
new PropertyMetadata(new PropertyChangedCallback((d, e) =>
{
((DragOnCanvasBehavior)d).draggable = (IDraggable)e.NewValue;
})));
private IDraggable draggable;
public DragOnCanvasBehavior()
{
this.StartDrag = new RelayCommand((o) =>
{
((UIElement)this.AssociatedObject).MouseLeftButtonDown += this.ElementOnMouseLeftButtonDown;
});
this.StopDrag = new RelayCommand((o) =>
{
((UIElement)this.AssociatedObject).MouseLeftButtonUp += this.ElementOnMouseLeftButtonUp;
});
this.Dragging = new RelayCommand((o) =>
{
((UIElement)this.AssociatedObject).MouseMove += this.ElementOnMouseMove;
});
}
public IDraggable DraggableItem
{
get { return (IDraggable)this.GetValue(DraggableItemProperty); }
set { this.SetValue(DraggableItemProperty, value); }
}
public ICommand Dragging { get; private set; }
public ICommand StartDrag { get; private set; }
public ICommand StopDrag { get; private set; }
// these handle the drag through the IDraggable properties
// and the mouse event args
private void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {}
private void ElementOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {}
private void ElementOnMouseMove(object sender, MouseEventArgs e) {}
}
It works, but I'm pretty sure I'm doing something wrong here. The mouse events are attached in the behavior constructor, but they are "hard-linked" to the events (meaning I can't change the triggers to something else than mousedown/up/move).
I must, however, have access to the mouse position in the ElementOnMousexxx methods, so I'm not sure how to do it properly.

OK so it turns out that you don't need the MouseEventArgs to get the mouse cursor position, you can just use System.Windows.Input.Mouse. You also don't need the "sender" object, when using Behavior<T> you can just use this.AssociatedObject
public DragOnCanvasBehavior()
{
this.StartDrag = new RelayCommand((o) =>
{
this.OnStartDrag();
});
}
private void OnStartDrag()
{
// get mouse position
this.mouseStartPosition = Mouse.GetPosition(Application.Current.MainWindow);
// access control properties
((UIElement)this.AssociatedObject).CaptureMouse();
}

Related

WPF Drawing a list of rectangles in a collection

My WPF app has a ViewModel that has an ObservableCollection that holds objects of type Item. Each Item has a color and a Rect that is drawn on the canvas:
Item Class:
public class Item
{
public Color ItemColor {get; set;}
public Rect ScaledRectangle {get; set;}
}
XAML:
<Grid>
<ItemsControl Name="Items" ItemsSource="{Binding Items, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ItemView Visibility="Visible">
<local:ItemView.Background>
<SolidColorBrush Color="{Binding ItemColor}"/>
</local:ItemView.Background>
</local:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding ScaledRectangle.Left}"/>
<Setter Property="Canvas.Top" Value="{Binding ScaledRectangle.Top}"/>
<Setter Property="FrameworkElement.Width" Value="{Binding ScaledRectangle.Width}"/>
<Setter Property="FrameworkElement.Height" Value="{Binding ScaledRectangle.Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
In my ViewModel, all I have to do is add a new Item to the ObservableCollection to draw it on the screen.
This works really well but now I find I need to change the ScaledRectangle property to some kind of collection. I want to modify this XAML to draw each rectangle in the ScaledRectangles collection. Can I modify this XAML so I can keep the ViewModel functionality to something like viewModel.AddNewItem(newItem)?
You must modify your ItemsView to support handling of a collection of Rect instead of a single Rect:
ItemsView.cs
public class ItemsView : Control
{
public Item DataSource
{
get => (Item)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register(
"DataSource",
typeof(Item),
typeof(ItemsView),
new PropertyMetadata(default(Item), OnDataSourceChanged));
private Panel ItemsHost { get; set; }
private Dictionary<Rect, int> ContainerIndexTable { get; }
static ItemsView()
=> DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemsView), new FrameworkPropertyMetadata(typeof(ItemsView)));
public ItemsView()
=> this.ContainerIndexTable = new Dictionary<Rect, int>();
private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ItemsView;
this_.UnloadRectangles(e.OldValue as Item);
this_.LoadRectangles(e.NewValue as Item);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ItemsHost = GetTemplateChild("PART_ItemsHost") as Panel;
LoadRectangles(this.DataSource);
}
private void UnloadRectangles(Item item)
{
if (item is null
|| this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in item.ScaledRectangles)
{
if (this.ContainerIndexTable.TryGetValue(rectangleDefinition, out int containerIndex))
{
this.ItemsHost.Children.RemoveAt(containerIndex);
}
}
}
private void LoadRectangles(Item item)
{
if (item is null
|| this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in item.ScaledRectangles)
{
var container = new Rectangle()
{
Height = rectangleDefinition.Height,
Width = rectangleDefinition.Width,
Fill = new SolidColorBrush(item.ItemColor)
};
Canvas.SetLeft(container, rectangleDefinition.Left);
Canvas.SetTop(container, rectangleDefinition.Top);
int containerIndex = this.ItemsHost.Children.Add(container);
_ = this.ContainerIndexTable.TryAdd(rectangleDefinition, containerIndex);
}
}
}
Gernic.xaml
<Style TargetType="local:ItemsView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ItemsView">
<Canvas x:Name="PART_ItemsHost" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<local:ItemsView DataSource="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Wpf, How to drag object inside draggable element?

I am developing WPF application. I should implement designer page with draggable objects inside other draggable objects. Look at the picture:
Now my Drag and Drop 1 and 2 working, but when I am trying to drag Inner object 1 and execute Drag and Drop 3 it is not working properly. Instead of drag Inner object 1, I am dragging Master object 1.
My xaml code:
<ItemsControl ItemsSource="{Binding MasterObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid behaviors:DragBehavior.Drag="True">
<Rectangle Fill="LightGray" Stroke="SlateGray" StrokeThickness="1" />
<TextBlock Text="{Binding Name}" />
<ItemsControl ItemsSource="{Binding InnerObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid behaviors:DragBehavior.Drag="True">
<Rectangle Fill="LightGray" Stroke="SlateGray" StrokeThickness="1" />
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
And my DragBehavior class:
public class DragBehavior
{
public readonly TranslateTransform Transform = new TranslateTransform();
private Point _elementStartPosition2;
private Point _mouseStartPosition2;
private static DragBehavior _instance = new DragBehavior();
public static DragBehavior Instance
{
get { return _instance; }
set { _instance = value; }
}
public static bool GetDrag(DependencyObject obj)
{
return (bool)obj.GetValue(IsDragProperty);
}
public static void SetDrag(DependencyObject obj, bool value)
{
obj.SetValue(IsDragProperty, value);
}
public static readonly DependencyProperty IsDragProperty =
DependencyProperty.RegisterAttached(
"Drag",
typeof(bool),
typeof(DragBehavior),
new PropertyMetadata(false, OnDragChanged));
private static void OnDragChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// ignoring error checking
var element = (UIElement)sender;
var isDrag = (bool)(e.NewValue);
Instance = new DragBehavior();
((UIElement)sender).RenderTransform = Instance.Transform;
if (isDrag)
{
element.MouseLeftButtonDown += Instance.ElementOnMouseLeftButtonDown;
element.MouseLeftButtonUp += Instance.ElementOnMouseLeftButtonUp;
element.MouseMove += Instance.ElementOnMouseMove;
}
else
{
element.MouseLeftButtonDown -= Instance.ElementOnMouseLeftButtonDown;
element.MouseLeftButtonUp -= Instance.ElementOnMouseLeftButtonUp;
element.MouseMove -= Instance.ElementOnMouseMove;
}
}
private void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
var parent = Application.Current.MainWindow;
_mouseStartPosition2 = mouseButtonEventArgs.GetPosition(parent);
((UIElement)sender).CaptureMouse();
}
private void ElementOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
((UIElement)sender).ReleaseMouseCapture();
_elementStartPosition2.X = Transform.X;
_elementStartPosition2.Y = Transform.Y;
}
private void ElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var parent = Application.Current.MainWindow;
var mousePos = mouseEventArgs.GetPosition(parent);
var diff = (mousePos - _mouseStartPosition2);
if (!((UIElement)sender).IsMouseCaptured) return;
Transform.X = _elementStartPosition2.X + diff.X;
Transform.Y = _elementStartPosition2.Y + diff.Y;
}
}
I guess I should to fix DragBehaviour class, but I don't have any idea how. Now I'm really confused.
The MouseLeftButtonDown event is bubbling from the inner container to the parent one. Try setting the e.Handled property to true in your event handler. In this case, only the handler for the first clicked element will be raised and your code should work properly.
I also suggest that you use an attached behavior instead of a class with several attached properties. You can find it in a free MVVM Framework provided by DevExpress.
As a rule behaviors give you much more freedom in such scenarios.

WPF create a ListView with ContextMenu inside a WebBrowser using ICommand

I am trying to create a ListView with ContextMenu inside of a WebBrowser and then use ICommand to Bind. However, the example listed below when this is ran I am getting the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ListView', AncestorLevel='1''. BindingExpression:Path=DataContext.MyCode; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
Here is my code: XAML
<!--WebBrowser to Display Messages-->
<WebBrowser Name="webBrowser"
Source="http://www.whatever.com"
<!--OnContextMenuOpening event in LoadCompleted-->
LoadCompleted="webBrowser_LoadCompleted">
<WebBrowser.Resources>
<!--Show items on webBrowser on mouserightclick-->
<ListView x:Key="wbListView">
<ListView.Resources>
<ContextMenu x:Key="wbContextMenu" >
<MenuItem Header="Test" CommandParameter="{Binding}" Command="{Binding Path=DataContext.myCode, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}}"/>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource wbContextMenu}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</WebBrowser.Resources>
</WebBrowser>
Here is my code:C#
public myCodeCommand myCode
{
get { return new myCodeCommand(this); }
}
public class myCodeCommand : ICommand
{
public bool CanExecute(object parameter)
{
Console.WriteLine("WEBBROWSER SEND MESSAGE CAN EXECUTE?");
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
Console.WriteLine("WEBBROWSER SEND MESSAGE EXECUTE SOMETHING");
}
}
I was able to create the ContextMenu inside Xaml as follow:
<WebBrowser.ContextMenu>
<ContextMenu x:Name="wbContextMenu" >
<MenuItem x:Name="menuItemOne" Header="Item 1" Click="menuItemOne_Click" />
<MenuItem x:Name="menuItemTwo" Header="Item 2" Click="menuItemTwo_Click" />
</ContextMenu>
</WebBrowser.ContextMenu>
and on the wbContextMenu event handler as follows:
fyi each menu item has their own clickevent.
DocumentEvents.oncontextmenu += webBrowserChat_ContextMenuOpening;
private bool webBrowserChat_ContextMenuOpening(IHTMLEventObj pEvtObj)
{
wbContextMenu.PlacementTarget = pEvtObj as ContextMenu;
wbContextMenu.IsOpen = true;
}
private void menuItemOne_Click(object sender, RoutedEventArgs e)
{
//Put whatever you want handled here for menuitem
}
private void menuItemTwo_Click(object sender, RoutedEventArgs e)
{
//Put whatever you want handled here for menuitem
}

Databinding to Command in Silverlight Templated Button control

I am trying to create a templated button control with databinding for the Visibility, tooltip, and Command. The Visibility binding works, as does the tooltip, but the Command does not. Another process is responsible for injecting the viewmodel and associating it with the View, and the other data bindings are working so I am pretty confident that is working properly.
In the resource dictionary:
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
<Style TargetType="local:ImageButton">
<Setter Property="Visibility" Value="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
<Setter Property="Command" Value="{Binding ButtonCommand}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}"
ToolTipService.ToolTip="{Binding ToolName}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
the templated control
public class MyButton: ImageButton
{
public MyButton(MyCommandViewModel viewmodel)
{
this.DefaultStyleKey = typeof(ImageButton);
this.Image = new BitmapImage(new Uri("/MyProject;component/Themes/myimage.png", UriKind.Relative));
this.DataContext = viewmodel;
}
}
and in the view model
public MyCommandViewModel()
: base("My Tool", true)
{
}
public class CommandViewModel
{
public CommandViewModel(string toolName, bool isAvailable)
{
ToolIsAvailable = isAvailable;
ToolName = toolName;
_buttoncommand = new DelegateCommand(() =>
{
ExecuteCommand();
},
() => { return CanExecute; });
}
private bool _canExecute = true;
public bool CanExecute
{
get { return _canExecute; }
set
{
_canExecute = value;
OnPropertyChanged("CanExecute");
if (_command != null) _command.RaiseCanExecuteChanged();
}
}
private DelegateCommand _buttoncommand;
public ICommand ButtonCommand
{
get { return _buttoncommand; }
}
protected virtual void ExecuteCommand()
{
}
public bool ToolIsAvailable
{
get { return _toolIsReady; }
set { _toolIsReady = value; OnPropertyChanged("ToolIsAvailable"); }
}
public string ToolName
{
get { return _toolName; }
set { _toolName = value; OnPropertyChanged("ToolName"); }
}
}
Why are the other databindings functioning properly but not the Command data binding. I found this similar post
Overriding a templated Button's Command in WPF
Do I need to template a grid control instead and use RoutedCommands? I am not sure I understand why Silverlight treats the Command binding different than the others so I suspect I just have a bug in the code.
Does specifically looking for the datacontext work?
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ButtonCommand}"
This was my solution. Using the same commandviewmodel as above and same MyCommandViewModel
<Style TargetType="local:ImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The databinding is now done in a user control
<UserControl x:Class="SilverlightApplication11.Test"
...
>
<UserControl.Resources>
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
</UserControl.Resources>
<Grid>
<local:ImageButton Image="/SilverlightApplication11;component/Themes/hand.png" Command="{Binding ButtonCommand}" Visibility="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
</Grid>
</UserControl>
and the code behind
public Test(TestCommandViewModel vm)
{
InitializeComponent();
this.Loaded += (o, e) => this.DataContext = vm;
}

Wpf disable repeatbuttons when scrolled to top/bottom

I'm making a touchscreen interface that uses a listbox.
I have a button above and below the listbox for page up/down.
I'm trying to get it to where when scrolled all the way up the pageup button gets disabled.
and when scrolled all the way down the pagedown button gets disabled too.
Here's the code in my Styles.xaml for the Listbox
<Style x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<DockPanel>
<RepeatButton x:Name="LineUpButton" DockPanel.Dock="Top"
HorizontalAlignment="Stretch"
Height="50"
Content="/\"
Command="{x:Static ScrollBar.PageUpCommand}"
CommandTarget="{Binding ElementName=scrollviewer}" />
<RepeatButton x:Name="LineDownButton" DockPanel.Dock="Bottom"
HorizontalAlignment="Stretch"
Height="50"
Content="\/"
Command="{x:Static ScrollBar.PageDownCommand}"
CommandTarget="{Binding ElementName=scrollviewer}" />
<Border BorderThickness="1" BorderBrush="Gray" Background="White">
<ScrollViewer x:Name="scrollviewer">
<ItemsPresenter/>
</ScrollViewer>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
</Style>
And here's where I instantiate the listbox
<ListBox SelectedItem="{Binding SelectedCan}" ItemsSource="{Binding Path=SelectedKioskCashCans}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding image}" MaxWidth="75" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I searched all around yesterday with no luck.
I'm hoping to be able to do it all in xaml.
I'm using images for the buttons but took them out for readability above,
they really look like...
<RepeatButton x:Name="LineUpButton" DockPanel.Dock="Top" HorizontalAlignment="Stretch"
Height="50"
Command="{x:Static ScrollBar.PageUpCommand}"
CommandTarget="{Binding ElementName=scrollviewer}">
<RepeatButton.Template>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Grid>
<Image Name="Normal" Source="/Images/up.png"/>
<Image Name="Pressed" Source="/Images/up.png" Visibility="Hidden"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Normal" Property="Visibility" Value="Hidden"/>
<Setter TargetName="Pressed" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
Just use CanExecute method of the PageUpCommand for that. Return false if where are no pages left and the button will become disabled automatically.
EDIT:
I have created a simple attached behavior that can be used to fix this problem. Just set the following attached property on the ScrollViewer:
<ScrollViewer x:Name="scrollviewer"
z:ScrollBarCommandsCanExecuteFixBehavior.IsEnabled="True">
<ItemsPresenter/>
</ScrollViewer>
And here is the source code of the behavior:
public static class ScrollBarCommandsCanExecuteFixBehavior
{
#region Nested Types
public class CommandCanExecuteMonitor<T> where T : UIElement
{
protected T Target { get; private set; }
protected CommandCanExecuteMonitor(T target, RoutedCommand command)
{
Target = target;
var binding = new CommandBinding(command);
binding.CanExecute += OnCanExecute;
target.CommandBindings.Add(binding);
}
protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
}
}
public class PageUpCanExecuteMonitor : CommandCanExecuteMonitor<ScrollViewer>
{
public PageUpCanExecuteMonitor(ScrollViewer scrollViewer)
: base(scrollViewer, ScrollBar.PageUpCommand)
{
}
protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Handled)
{
return;
}
if (Equals(Target.VerticalOffset, 0.0))
{
e.CanExecute = false;
e.Handled = true;
}
}
}
public class PageDownCanExecuteMonitor : CommandCanExecuteMonitor<ScrollViewer>
{
public PageDownCanExecuteMonitor(ScrollViewer scrollViewer)
: base(scrollViewer, ScrollBar.PageDownCommand)
{
}
protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Handled)
{
return;
}
if (Equals(Target.VerticalOffset, Target.ScrollableHeight))
{
e.CanExecute = false;
e.Handled = true;
}
}
}
#endregion
#region IsEnabled Attached Property
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool) obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof (bool), typeof (ScrollBarCommandsCanExecuteFixBehavior), new PropertyMetadata(false, OnIsEnabledChanged));
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool) e.NewValue)
{
var scrollViewer = d as ScrollViewer;
if (scrollViewer != null)
{
OnAttached(scrollViewer);
}
else
{
throw new NotSupportedException("This behavior only supports ScrollViewer instances.");
}
}
}
private static void OnAttached(ScrollViewer target)
{
SetPageUpCanExecuteMonitor(target, new PageUpCanExecuteMonitor(target));
SetPageDownCanExecuteMonitor(target, new PageDownCanExecuteMonitor(target));
}
#endregion
#region PageUpCanExecuteMonitor Attached Property
private static void SetPageUpCanExecuteMonitor(DependencyObject obj, PageUpCanExecuteMonitor value)
{
obj.SetValue(PageUpCanExecuteMonitorProperty, value);
}
private static readonly DependencyProperty PageUpCanExecuteMonitorProperty =
DependencyProperty.RegisterAttached("PageUpCanExecuteMonitor", typeof (PageUpCanExecuteMonitor), typeof (ScrollBarCommandsCanExecuteFixBehavior), new PropertyMetadata(null));
#endregion
#region PageDownCanExecuteMonitor Attached Property
private static void SetPageDownCanExecuteMonitor(DependencyObject obj, PageDownCanExecuteMonitor value)
{
obj.SetValue(PageDownCanExecuteMonitorProperty, value);
}
private static readonly DependencyProperty PageDownCanExecuteMonitorProperty =
DependencyProperty.RegisterAttached("PageDownCanExecuteMonitor", typeof (PageDownCanExecuteMonitor), typeof (ScrollBarCommandsCanExecuteFixBehavior), new PropertyMetadata(null));
#endregion
}
The basic idea is that we add a CommandBinding to the ScrollViewer for each of the commands and subscribe to the CanExecute event on those bindings. In the event handler we check the current position of the scroll and set the e.CanExecute property accordingly.

Resources