I am trying to implement Attached Behaviors functionality in the MVVM pattern. I have a Calendar control and would like to handle the MouseDoubleClick event. I was doing that using System.Windows.Interactivity and Interaction.Triggers. However, I am also using BlackoutDates in the Calendar and double-clicking on a Blackout Date results in the last valid selected date being passed to the MouseDoubleClick method, not the date clicked on.
So I am now targeting the CalendarDayButton, which will get me the date clicked on, but CDB doesn't have Commands, so I need to use an Attached Behavior. But I'm still not understanding how to get the MouseDoubleClick handler info into the ViewModel. My current code:
View
<Calendar HorizontalAlignment="Left" VerticalAlignment="Top" Margin="20,48,0,0"
SelectedDate="{Binding ReportDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayDateStart="{Binding ReportDateStart, Mode=OneTime}"
DisplayDateEnd="{Binding ReportDateEnd, Mode=OneTime}"
local:AttachedProperties.RegisterBlackoutDates="{Binding NoProdDates, Mode=OneWay}">
<Calendar.CalendarDayButtonStyle>
<Style TargetType="CalendarDayButton">
<Setter Property="local:AttachedBehaviors.IsValidDateSelected"
Value="{Binding ValidDateSelected, Mode=TwoWay}"/>
</Style>
</Calendar.CalendarDayButtonStyle>
</Calendar>
ViewModel
...
private bool validDateSelected;
public bool ValidDateSelected
{
get { return validDateSelected; }
set
{
if (validDateSelected != value)
{
validDateSelected = value;
RaisePropertyChanged("ValidDateSelected");
}
}
}
...
Attached Behaviors class
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace MDOD
{
public class AttachedBehaviors : DependencyObject
{
public static readonly DependencyProperty IsValidDateSelectedProperty =
DependencyProperty.RegisterAttached("IsValidDateSelected", typeof(bool), typeof(AttachedBehaviors),
new UIPropertyMetadata(false, OnIsValidDateSelectedChanged));
public static bool GetIsValidDateSelected(DependencyObject obj)
{
return (bool)obj.GetValue(IsValidDateSelectedProperty);
}
public static void SetIsValidDateSelected(DependencyObject obj, bool value)
{
obj.SetValue(IsValidDateSelectedProperty, value);
}
private static void OnIsValidDateSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CalendarDayButton cdb = d as CalendarDayButton;
if (cdb != null)
{
if ((bool)e.NewValue)
{
cdb.MouseDoubleClick += cdb_MouseDoubleClick;
}
else
{
cdb.MouseDoubleClick -= cdb_MouseDoubleClick;
}
}
}
private static void cdb_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// How do I get this info to the ViewModel?
}
}
}
Related
I want to bind the DragCompleted event to one of my ViewModel's Command. I tried the following using Blend but it doesn't work:
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="41,147,0,0" VerticalAlignment="Top" Width="412">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Thumb.DragCompleted">
<i:InvokeCommandAction Command="{Binding DragCompletedCommand}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
But this doesn't work. When I use the normal binding of event to code behind, it works:
<Slider x:Name="slider" Thumb.DragCompleted="slider_DragCompleted" HorizontalAlignment="Left" Margin="41,147,0,0" VerticalAlignment="Top" Width="412"></Slider>
I tried searching but strangely couldn't find answer to this.
You can write an attached property for this which can look like:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace MyTestApplication
{
internal class SliderExtension
{
public static readonly DependencyProperty DragCompletedCommandProperty = DependencyProperty.RegisterAttached(
"DragCompletedCommand",
typeof(ICommand),
typeof(SliderExtension),
new PropertyMetadata(default(ICommand), OnDragCompletedCommandChanged));
private static void OnDragCompletedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Slider slider = d as Slider;
if (slider == null)
{
return;
}
if (e.NewValue is ICommand)
{
slider.Loaded += SliderOnLoaded;
}
}
private static void SliderOnLoaded(object sender, RoutedEventArgs e)
{
Slider slider = sender as Slider;
if (slider == null)
{
return;
}
slider.Loaded -= SliderOnLoaded;
Track track = slider.Template.FindName("PART_Track", slider) as Track;
if (track == null)
{
return;
}
track.Thumb.DragCompleted += (dragCompletedSender, dragCompletedArgs) =>
{
ICommand command = GetDragCompletedCommand(slider);
command.Execute(null);
};
}
public static void SetDragCompletedCommand(DependencyObject element, ICommand value)
{
element.SetValue(DragCompletedCommandProperty, value);
}
public static ICommand GetDragCompletedCommand(DependencyObject element)
{
return (ICommand)element.GetValue(DragCompletedCommandProperty);
}
}
}
And your Slider-Definition then looks like:
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="41,147,0,0" VerticalAlignment="Top" Width="412"
extensions:SliderExtension.DragCompletedCommand="{Binding SlideCompletedCommand}"/>
extensions is the namespace where your attached property is located.
And in your ViewModel you have an ICommand-Property called SlideCompletedCommand, which can look like:
private ICommand slideCompletedCommand;
public ICommand SlideCompletedCommand
{
get { return slideCompletedCommand ?? (slideCompletedCommand = new RelayCommand(p => SlideCompleted())); }
}
private void SlideCompleted()
{
// Your slide-completed-code here
}
I'm using MVVM and have the following problem. My TextBox.Text is bound with UpdateSourceTrigger=LostFocus (thats what the user want). I have a Button with a SaveCommand CommandBinding - this works. Now i have a KeyBinding with Strg+S wich also execute the SaveCommand. And here is the problem: when i m in the Textbox and press Strg+s, the changes are not in the viewmodel.
is there any way to get MVVM Commands with KeyBinding and TextBox UpdateSourceTrigger=LostFocus working together?
some code to check out the problem
<Window>
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand}"></KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding MyText1, UpdateSourceTrigger=LostFocus}" Width="100"></TextBox>
<Button Grid.Row="1" Content="_Save" Command="{Binding SaveCommand}" IsDefault="True"></Button>
</Grid>
</Window>
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
_data = new Viewmodel();
InitializeComponent();
this.DataContext = _data;
}
}
public class Viewmodel : INPCBase
{
private string _myText1;
private Lazy<DelegateCommand> _save;
public Viewmodel()
{
this._save = new Lazy<DelegateCommand>(()=> new DelegateCommand(this.SaveCommandExecute));
}
private void SaveCommandExecute()
{
MessageBox.Show(MyText1);
}
public string MyText1
{
get { return _myText1; }
set { _myText1 = value; this.NotifyPropertyChanged(()=>MyText1);}
}
public ICommand SaveCommand
{
get { return _save.Value; }
}
}
at the moment i came up with the following workaround. within the usercontrol/views where i define my KeyBindings, i also listen to the PreviewKeyDown event and set the focus to the next element when eg. Strg+S is pressed.
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
{
var fe = Keyboard.FocusedElement as UIElement;
if (fe != null)
{
fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
I have the same problem and end up with attached property for TextBox.
public static bool GetCommitOnSave(DependencyObject obj)
{
return (bool)obj.GetValue(CommitOnSaveProperty);
}
public static void SetCommitOnSave(DependencyObject obj, bool value)
{
obj.SetValue(CommitOnSaveProperty, value);
}
public static readonly DependencyProperty CommitOnSaveProperty =
DependencyProperty.RegisterAttached("CommitOnSave", typeof(bool), typeof(Helper), new PropertyMetadata(false, CommitOnSavePropertyChanged));
private static void CommitOnSavePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox)
{
if ((bool)e.NewValue)
{
if ((bool)e.NewValue)
{
textBox.KeyDown += TextBox_KeyDown;
}
else
{
textBox.KeyDown -= TextBox_KeyDown;
}
}
}
}
private static void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var textBox = (TextBox)sender;
if (e.Key == Key.S && Keyboard.Modifiers == ModifierKeys.Control)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
}
Using <TextBox Text="{Binding Name}" local:Helper.CommitOnSave="True" />
Of course you can set attached property in style for all TextBoxes in a form.
I think I find the best solution for me. I mix solution #blindmeis and my previous one with using attached property.
I create command which update binding source of actual keyboard focused element:
public class CommitValueCommand : ICommand
{
private static CommitValueCommand _instance;
public static CommitValueCommand Command => _instance ?? (_instance = new CommitValueCommand());
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (Keyboard.FocusedElement is TextBox textBox)
{
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
}
//for combobox etc.
else if (Keyboard.FocusedElement is Selector selector)
{
BindingOperations.GetBindingExpression(selector, Selector.SelectedValueProperty).UpdateSource();
}
}
}
In Execute method of command SaveCommand just at beginning invoke CommitValueCommand.Command.Execute().
I want to invoke a command when ENTER is pressed in a TextBox. Consider the following XAML:
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...>
...
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding MyCommand}"
CommandParameter="{Binding Text}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
and that MyCommand is as follows:
public ICommand MyCommand {
get { return new DelegateCommand<string>(MyCommandExecute); }
}
private void MyCommandExecute(string s) { ... }
With the above, my command is invoked for every key press. How can I restrict the command to only invoke when the ENTER key is pressed?
I understand that with Expression Blend I can use Conditions but those seem to be restricted to elements and can't consider event arguments.
I have also come across SLEX which offers its own InvokeCommandAction implementation that is built on top of the Systems.Windows.Interactivity implementation and can do what I need. Another consideration is to write my own trigger, but I'm hoping there's a way to do it without using external toolkits.
There is KeyTrigger in expression blend.
<UserControl
xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
xmlns:iex="clr-namespace:Microsoft.Expression.Interactivity.Input;
assembly=Microsoft.Expression.Interactions" ...>
<TextBox>
<i:Interaction.Triggers>
<iex:KeyTrigger Key="Enter">
<i:InvokeCommandAction Command="{Binding PasswordLoginCommand}" />
</iex:KeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
System.Windows.Interactivity and Microsoft.Expression.Interactions assemblies are available for WPF in the official Nuget package.
I like scottrudy's approach (to which I've given a +1) with the custom triggers approach as it stays true to my initial approach. I'm including a modified version of it below to use dependency properties instead of reflection info so that it's possible to bind directly to the ICommand. I'm also including an approach using attached properties to avoid using System.Windows.Interactivity if desired. The caveat to the latter approach is that you lose the feature of multiple invokations from an event, but you can apply it more generally.
Custom Triggers Approach
ExecuteCommandAction.cs
public class ExecuteCommandAction : TriggerAction<DependencyObject> {
#region Properties
public ICommand Command {
get { return (ICommand)base.GetValue(CommandProperty); }
set { base.SetValue(CommandProperty, value); }
}
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
// We use a DependencyProperty so we can bind commands directly rather
// than have to use reflection info to find them
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), null);
#endregion Properties
protected override void Invoke(object parameter) {
ICommand command = Command ?? GetCommand(AssociatedObject);
if (command != null && command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}
TextBoxEnterKeyTrigger.cs
public class TextBoxEnterKeyTrigger : TriggerBase<UIElement> {
protected override void OnAttached() {
base.OnAttached();
TextBox textBox = this.AssociatedObject as TextBox;
if (textBox != null) {
this.AssociatedObject.KeyUp += new System.Windows.Input.KeyEventHandler(AssociatedObject_KeyUp);
}
else {
throw new InvalidOperationException("This behavior only works with TextBoxes");
}
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObject_KeyUp);
}
private void AssociatedObject_KeyUp(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
TextBox textBox = AssociatedObject as TextBox;
//This checks for an mvvm style binding and updates the source before invoking the actions.
BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
expression.UpdateSource();
InvokeActions(textBox.Text);
}
}
}
MyUserControl.xaml
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox>
<i:Interaction.Triggers>
<b:TextBoxEnterKeyTrigger>
<b:ExecuteCommandAction Command="{Binding MyCommand}" />
</b:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
...
</UserControl>
Attached Properties Approach
EnterKeyDown.cs
public sealed class EnterKeyDown {
#region Properties
#region Command
public static ICommand GetCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandChanged));
#endregion Command
#region CommandArgument
public static object GetCommandArgument(DependencyObject obj) {
return (object)obj.GetValue(CommandArgumentProperty);
}
public static void SetCommandArgument(DependencyObject obj, object value) {
obj.SetValue(CommandArgumentProperty, value);
}
public static readonly DependencyProperty CommandArgumentProperty =
DependencyProperty.RegisterAttached("CommandArgument", typeof(object), typeof(EnterKeyDown),
new PropertyMetadata(null, OnCommandArgumentChanged));
#endregion CommandArgument
#region HasCommandArgument
private static bool GetHasCommandArgument(DependencyObject obj) {
return (bool)obj.GetValue(HasCommandArgumentProperty);
}
private static void SetHasCommandArgument(DependencyObject obj, bool value) {
obj.SetValue(HasCommandArgumentProperty, value);
}
private static readonly DependencyProperty HasCommandArgumentProperty =
DependencyProperty.RegisterAttached("HasCommandArgument", typeof(bool), typeof(EnterKeyDown),
new PropertyMetadata(false));
#endregion HasCommandArgument
#endregion Propreties
#region Event Handling
private static void OnCommandArgumentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
SetHasCommandArgument(o, true);
}
private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
FrameworkElement element = o as FrameworkElement;
if (element != null) {
if (e.NewValue == null) {
element.KeyDown -= new KeyEventHandler(FrameworkElement_KeyDown);
}
else if (e.OldValue == null) {
element.KeyDown += new KeyEventHandler(FrameworkElement_KeyDown);
}
}
}
private static void FrameworkElement_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
DependencyObject o = sender as DependencyObject;
ICommand command = GetCommand(sender as DependencyObject);
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null) {
// If the command argument has been explicitly set (even to NULL)
if (GetHasCommandArgument(o)) {
object commandArgument = GetCommandArgument(o);
// Execute the command
if (command.CanExecute(commandArgument)) {
command.Execute(commandArgument);
}
}
else if (command.CanExecute(element.DataContext)) {
command.Execute(element.DataContext);
}
}
}
}
#endregion
}
MyUserControl.xaml
<UserControl
...
xmlns:b="clr-namespace:MyNameSpace.Interactivity"
...
<TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}"
b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" />
...
</UserControl>
I ran into this same issue yesterday and solved it using custom triggers. It may seem a bit much at first, but I found this general pattern is usable for doing a lot of the things that I used to accomplish using event handlers directly in a view (like double click events). The first step is to create a trigger action that can accept a parameter since we will need it later.
public class ExecuteCommandAction : TriggerAction<FrameworkElement>
{
public string Command { get; set; }
protected override void Invoke(object o)
{
if (Command != null)
{
object ctx = AssociatedObject.DataContext;
if (ctx != null)
{
var cmd = ctx.GetType().GetProperty(Command)
.GetValue(ctx, null) as ICommand;
if (cmd != null && cmd.CanExecute(o))
{
cmd.Execute(o);
}
}
}
}
}
The next step is to create the trigger. You could do some interesting things with base classes to make it more generic for capturing different types of key presses, but we'll keep it simple.
public class TextBoxEnterKeyTrigger: TriggerBase<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyUp += AssociatedObject_KeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
}
void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox textBox = AssociatedObject as TextBox;
object o = textBox == null ? null : textBox.Text;
if (o != null)
{
InvokeActions(o);
}
}
}
}
Keep in mind that even though you may have a data binding in place to your TextBox value, the property changed event won't fire because your textbox hasn't lost focus. For this reason I am passing the value of the TextBox.Text property to the command. The last step is to use this feature in your XAML. You need to be sure to include the Interactivity namespace as well as the namespace that contains your code from above.
<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:common="clr-namespace:My.UI;assembly=My.UI">
<TextBox Text="{Binding Path=MyText, Mode=TwoWay}" IsEnabled="{Binding CanMyCommand}">
<i:Interaction.Triggers>
<common:TextBoxEnterKeyTrigger>
<common:ExecuteCommandAction Command=MyCommand" />
</common:TextBoxEnterKeyTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl>
I used scottrudy's code in my application however, my textbox text is bound to some property in viewmodel class and this property is not getting updated by the time command is invoked after pressiong ENTER key because my textbox hasn't lost focus yet. So, to resolved this, i added the following code snippets just above InvokeActions(o) in AssociatedObject_KeyUp method and updated text property is getting updated in viewmodel class.
BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
On top of my mind.. You can pass event args to command and than in ViewModel check if e.KeyPress = Keys.Enter.. this is not really code :) i dont have my VS on this computer.. this is rather an idea :)
At a glance:
My app displays an ItemsControl containing a Canvas as its ItemsPanel. The ItemsControl is bound to a collection of objects, each having Left/Top/Width/Height properties. A DataTemplate is used to generate rectangles that are rendered in the Canvas and positioned correctly (binding on the Left and Top properties).
How can I implement drag/drop to move these rectangles around the Canvas?
Background for my question:
My WP7 app displays a "CanvasItemsControl" defined as follows:
public class CanvasItemsControl : ItemsControl
{
public string XBindingPath { get; set; }
public string YBindingPath { get; set; }
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
if (XBindingPath != null && YBindingPath != null)
{
Binding xBinding = new Binding(XBindingPath);
Binding yBinding = new Binding(YBindingPath);
if (contentitem != null)
{
contentitem.SetBinding(Canvas.LeftProperty, xBinding);
contentitem.SetBinding(Canvas.TopProperty, yBinding);
}
}
base.PrepareContainerForItemOverride(element, item);
}
}
and used in XAML as follows:
<hh:CanvasItemsControl Grid.Row="1" x:Name="TheItemsControl"
Style="{StaticResource CanvasItemsControlStyle}"
ItemsSource="{Binding AllObjects}"
XBindingPath="Left" YBindingPath="Top" />
This is the style for the CanvasItemsControl:
<Style x:Key="CanvasItemsControlStyle" TargetType="local:CanvasItemsControl">
<Setter Property="ItemTemplate" Value="{StaticResource ObjectTemplate}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
And this is the DataTemplate I use to render my class:
<DataTemplate x:Key="ObjectTemplate" >
<Border Background="{Binding Brush}"
Width="{Binding Width}"
Height="{Binding Height}">
<TextBlock Text="{Binding Description}"/>
</Border>
</DataTemplate>
The source of the CanvasItemsControl is a collection of objects that have the properties Left, Top, Width, Height, Brush, etc.
My question
As you can see, the end result is, as you add items to the AllObjects collection, each object gets rendered and positioned correctly in the canvas. Now I need to drag/drop/move these objects around the canvas. What approach would you advise me to use to implement drag/drop? Can you please guide me through the process?
Thank you
Here's the solution to my question (at least the best one in my opinion):
1) Use of a regular Canvas as opposed of a custom control inherited from Canvas.
2) Use of a user control taking the data context (the instance of my business entity) via constructor
3) The binding between the Left/Top properties of my business class and the Canvas.Left/Top is declared at the UserControl level.
4) Use of a custom behavior inheriting from System.Windows.Interactivity.Behavior. This behavior is attached to the User Control.
I would like to acknowlege Calvin Schrotenboer and Joe Gershgorin for their immense help.
<!--____ The UserControl ____-->
<UserControl... Canvas.Left={Binding Left}" Canvas.Top={Binding Top}">
<Grid.... layout of the UserControl instead of using a DataTemplate/>
<i:Interaction.Behaviors>
<MyExample:MyMouseDragElementBehavior/>
</i:Interaction.Behaviors>
</UserControl>
The custom behavior:
public class MyMouseDragElementBehavior : Behavior<FrameworkElement>
{
public event MouseEventHandler DragBegun;
public event MouseEventHandler DragFinished;
public event MouseEventHandler Dragging;
private Point relativePosition;
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled", typeof(bool), typeof(MyMouseDragElementBehavior), new PropertyMetadata(true));
public bool IsEnabled
{
get
{
return (bool)GetValue(IsEnabledProperty);
}
set
{
SetValue(IsEnabledProperty, value);
}
}
protected override void OnAttached()
{
AssociatedObject.AddHandler(
UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), false);
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.RemoveHandler(
UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown));
base.OnDetaching();
}
private static int zIndex = 0;
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (!IsEnabled)
{
return;
}
zIndex++;
Canvas.SetZIndex(AssociatedObject, zIndex);
StartDrag(e.GetPosition(AssociatedObject));
if (DragBegun != null)
{
DragBegun(this, e);
}
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
AssociatedObject.ReleaseMouseCapture();
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
HandleDrag(e.GetPosition(AssociatedObject));
if (Dragging != null)
{
Dragging(this, e);
}
}
internal void HandleDrag(Point newPositionInElementCoordinates)
{
double x = newPositionInElementCoordinates.X - relativePosition.X;
double y = newPositionInElementCoordinates.Y - relativePosition.Y;
if (AssociatedObject != null)
{
var currentLeft = Canvas.GetLeft(AssociatedObject);
var currentTop = Canvas.GetTop(AssociatedObject);
Canvas.SetLeft(AssociatedObject, currentLeft + x);
Canvas.SetTop(AssociatedObject, currentTop + y);
}
}
internal void StartDrag(Point positionInElementCoordinates)
{
relativePosition = positionInElementCoordinates;
AssociatedObject.CaptureMouse();
AssociatedObject.MouseMove += OnMouseMove;
AssociatedObject.LostMouseCapture += OnLostMouseCapture;
AssociatedObject.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseLeftButtonUp), false);
}
internal void EndDrag()
{
AssociatedObject.MouseMove -= OnMouseMove;
AssociatedObject.LostMouseCapture -= OnLostMouseCapture;
AssociatedObject.RemoveHandler(
UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseLeftButtonUp));
}
private void OnLostMouseCapture(object sender, MouseEventArgs e)
{
EndDrag();
if (DragFinished != null)
{
DragFinished(this, e);
}
}
}
I want to create a Style for a WPF ListBox that includes a Button in the ControlTemplate that the user can click on and it clears the ListBox selection.
I dont want to use codebehind so that this Style can be applied to any ListBox.
I have tried using EventTriggers and Storyboards and it has proved problematic as it only works first time and stopping the Storyboard sets the previous selection back.
I know I could use a user control but I want to know if it is possible to achieve this using only a Style.
It is not possible to achieve this using XAML and only the classes provided by the .NET framework. However you can still produce a reusable solution by defining a new command (call it ClearSelectionCommand) and a new attached property (call it ClearSelectionOnCommand).
Then you can incorporate those elements into your style.
Example:
public class SelectorBehavior
{
public static RoutedCommand
ClearSelectionCommand =
new RoutedCommand(
"ClearSelectionCommand",
typeof(SelectorBehavior));
public static bool GetClearSelectionOnCommand(DependencyObject obj)
{
return (bool)obj.GetValue(ClearSelectionOnCommandProperty);
}
public static void SetClearSelectionOnCommand(
DependencyObject obj,
bool value)
{
obj.SetValue(ClearSelectionOnCommandProperty, value);
}
public static readonly DependencyProperty ClearSelectionOnCommandProperty =
DependencyProperty.RegisterAttached(
"ClearSelectionOnCommand",
typeof(bool),
typeof(SelectorBehavior),
new UIPropertyMetadata(false, OnClearSelectionOnCommandChanged));
public static void OnClearSelectionOnCommandChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null) return;
bool nv = (bool)e.NewValue, ov = (bool)e.OldValue;
if (nv == ov) return;
if (nv)
{
selector.CommandBindings.Add(
new CommandBinding(
ClearSelectionCommand,
ClearSelectionCommand_Executed,
ClearSelectionCommand_CanExecute));
}
else
{
var cmd = selector
.CommandBindings
.Cast<CommandBinding>()
.SingleOrDefault(x =>
x.Command == ClearSelectionCommand);
if (cmd != null)
selector.CommandBindings.Remove(cmd);
}
}
public static void ClearSelectionCommand_Executed(
object sender,
ExecutedRoutedEventArgs e)
{
Selector selector = (Selector)sender;
selector.SelectedIndex = -1;
}
public static void ClearSelectionCommand_CanExecute(
object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
Example usage - XAML:
<Window x:Class="ClearSelectionBehaviorLibrary.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClearSelectionBehaviorLibrary"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="Selector">
<Setter
Property="local:SelectorBehavior.ClearSelectionOnCommand"
Value="True"/>
</Style>
</Window.Resources>
<Grid>
<DockPanel>
<Button
DockPanel.Dock="Bottom"
Content="Clear"
Command="{x:Static local:SelectorBehavior.ClearSelectionCommand}"
CommandTarget="{Binding ElementName=TheListBox}"/>
<ListBox
Name="TheListBox"
ItemsSource="{Binding MyData}"
Style="{StaticResource MyStyle}"/>
</DockPanel>
</Grid>
</Window>
Example usage - Code Behind:
public partial class Window1 : Window
{
public List<string> MyData { get; set; }
public Window1()
{
MyData = new List<string>
{
"aa","bb","cc","dd","ee"
};
InitializeComponent();
DataContext = this;
}
}