Adding custom blend behaviour on ListboxItem - wpf

I am creating a custom blend behavior to act on a ListboxItem i.e.
public class DragtestBehaviour : Behavior<ListBoxItem>
{
public DragtestBehaviour()
{ // Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
}
}
What I am not able to figure out is how do I attach it to the listboxitem?
Alternatively, do I have to style the Listbox item and attach it to Border or any other element in style? If this is true then do I have to also derive my behavior class from Border (i.e. Framework element)?
DragtestBehaviour : Behavior<Frameworkelement>

I have basically done what eran otzap suggested in his comment, and what is described in this article by Mark Smith. This allows to still use Blend Behaviors with its benefits (like AssociatedObject), but be more flexible about attaching it. It's a fusion of normal attached behavior with blend behavior.
For completeness sake, here's the relevant code as well.
Attach the behavior in ItemContainerStyle:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="behaviors:DragDropBehavior.IsAttached" Value="True" />
</Style>
</ListBox.ItemContainerStyle>
Behavior
public class DragDropBehavior : Behavior<ListBoxItem>
{
// IsAttached
public static DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached",typeof(bool), typeof(DragDropBehavior),new FrameworkPropertyMetadata(false, OnIsAttachedChanged));
public static bool GetIsAttached(DependencyObject o){return (bool)o.GetValue(IsAttachedProperty);}
public static void SetIsAttached(DependencyObject o, bool value){o.SetValue(IsAttachedProperty, value);}
// is called the same as when attached in Interaction.Behaviors tag
protected override void OnAttached()
{
base.OnAttached();
}
// manual attachement to listbox item
private static void OnIsAttachedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var el = o as UIElement;
if (el != null)
{
var behColl = Interaction.GetBehaviors(el);
var existingBehavior = behColl.FirstOrDefault(b => b.GetType() == typeof(DragDropBehavior)) as DragDropBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behColl.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behColl.Add(new DragDropBehavior());
}
}
}
}

Related

WPF ToolBar - Detect when item is set into ToolBarOverflowPanel

I am aware of the IsOverflowOpen and HasOverflowItems properties but I am looking for a way to tell whether the item (button, radiobutton...) has moved into the ToolBarOverflowPanel so I can use a trigger to change its style.
I need this to be able to reproduce some UWP ToolBar styles (Windows 10 Mail, Word, Excel...). I have successfully reproduced most of the style and the only missing bit is to be able to changed the style of my item when it is in the overflow panel.
On the screenshot of what I am trying to reproduce, you can clearly see that the Special Ident and Line Spacing buttons have changed style based on whether they are displayed or overflowed.
You can't do it with xaml only. You have to either use the code behind or create some attached properties.
Here is the solution with the AttachedProperty:
First you need to create an helper class exposing 2 properties:
The IsInOverflowPanel read-only property that you will use to trigger the style change.
The TrackParentPanel property, which is the enable/disable mechanism.
Here is the implementation:
public static class ToolBarHelper
{
public static readonly DependencyPropertyKey IsInOverflowPanelKey =
DependencyProperty.RegisterAttachedReadOnly("IsInOverflowPanel", typeof(bool), typeof(ToolBarHelper), new PropertyMetadata(false));
public static readonly DependencyProperty IsInOverflowPanelProperty = IsInOverflowPanelKey.DependencyProperty;
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static bool GetIsInOverflowPanel(UIElement target)
{
return (bool)target.GetValue(IsInOverflowPanelProperty);
}
public static readonly DependencyProperty TrackParentPanelProperty =
DependencyProperty.RegisterAttached("TrackParentPanel", typeof(bool), typeof(ToolBarHelper),
new PropertyMetadata(false, OnTrackParentPanelPropertyChanged));
public static void SetTrackParentPanel(DependencyObject d, bool value)
{
d.SetValue(TrackParentPanelProperty, value);
}
public static bool GetTrackParentPanel(DependencyObject d)
{
return (bool)d.GetValue(TrackParentPanelProperty);
}
private static void OnTrackParentPanelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element != null)
{
bool newValue = (bool)e.NewValue;
if (newValue)
{
element.LayoutUpdated += (s, arg) => OnControlLayoutUpdated(element);
}
}
}
private static void OnControlLayoutUpdated(UIElement element)
{
var isInOverflow = TreeHelper.FindParent<ToolBarOverflowPanel>(element) != null;
element.SetValue(IsInOverflowPanelKey, isInOverflow);
}
}
public static class TreeHelper
{
public static T FindParent<T>(this DependencyObject obj) where T : DependencyObject
{
return obj.GetAncestors().OfType<T>().FirstOrDefault();
}
public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject element)
{
do
{
yield return element;
element = VisualTreeHelper.GetParent(element);
} while (element != null);
}
}
Then, for every items which need to change style do the following:
<Button x:Name="DeleteButton" Content="Delete" helpers:ToolBarHelper.TrackParentPanel="True">
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="helpers:ToolBarHelper.IsInOverflowPanel" Value="True">
<!-- The Overflow style setters -->
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Add trigger to every UserControl

My application has about 15 different UserControls that are uniformly loaded into a content-area at runtime.
My project is 100% MVVM-compliant, so I have the following XAML inserted into every UserControl's XAML:
<UserControl
...
xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
>
<intr:Interaction.Triggers>
<intr:EventTrigger EventName="Loaded">
<intr:InvokeCommandAction Command="{Binding ViewLoadedCommand}"/>
</intr:EventTrigger>
</intr:Interaction.Triggers>
<!-- Rest of UserControl content here -->
</UserControl>
Obviously this isn't ideal and is also a violation of DRY.
What is the best way to have this XAML applied to this set of UserControls? (but not every UserControl, so a simple <Style Type="UserControl" /> would be inappropriate).
I use behavior implemented as attached property. It has two major advantages over System.Windows.Interactivity:
it can be defined in style.
much less xaml code in the views
in your case, the view could look like:
<UserControl ...
my:AttachedCommands.LoadedCommand="{Binding ViewLoadedCommand}">
In my solution, I'm not using commands, but I call methods on viewmodel if the viewmodel implements IViewModelLifeCycle interface:
public interface IViewModelLifeCycle
{
void Activate(object extraData);
void Deactivate();
}
All my views uses this style:
<Style x:Key="ViewBaseStyle">
<Setter Property="my:ViewModelLifeCycleBehavior.ActivateOnLoad" Value="True" />
and the behavior:
public static class ViewModelLifeCycleBehavior
{
public static readonly DependencyProperty ActivateOnLoadProperty = DependencyProperty.RegisterAttached("ActivateOnLoad", typeof (bool), typeof (ViewModelLifeCycleBehavior),
new PropertyMetadata(ActivateOnLoadPropertyChanged));
public static void SetActivateOnLoad(FrameworkElement element, bool value)
{
element.SetValue(ActivateOnLoadProperty, value);
}
public static bool GetActivateOnLoad(FrameworkElement element)
{
return (bool)element.GetValue(ActivateOnLoadProperty);
}
private static void ActivateOnLoadPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (DesignerProperties.GetIsInDesignMode(obj)) return;
var element = (FrameworkElement)obj;
element.Loaded -= ElementLoaded;
element.Unloaded -= ElementUnloaded;
if ((bool) args.NewValue == true)
{
element.Loaded += ElementLoaded;
element.Unloaded += ElementUnloaded;
}
}
static void ElementLoaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement) sender;
var viewModel = (IViewModelLifeCycle) element.DataContext;
if (viewModel == null)
{
DependencyPropertyChangedEventHandler dataContextChanged = null;
dataContextChanged = (o, _e) =>
{
ElementLoaded(sender, e);
element.DataContextChanged -= dataContextChanged;
};
element.DataContextChanged += dataContextChanged;
}
else if (element.ActualHeight > 0 && element.ActualWidth > 0) //to avoid activating twice since loaded event is called twice on TabItems' subtrees
{
viewModel.Activate(null);
}
}
private static void ElementUnloaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
var viewModel = (IViewModelLifeCycle)element.DataContext;
viewModel.Deactivate();
}
}
TIP:
Create your custom Item Template in Visual Studio for View and ViewModel. its very easy and saves a lot of time. The item template can contain xaml code with the trigger/behaviour, pointing to your base style, your d:DataContext definition and your viewmodel class.
you could create a "base-load" Usercontrol with the Interaction.Triggers and just put a ContentPresenter in it where you bind the real content to.
<UserControl x:class="OneOfMyOtherControls">
<MyBaseUserControl>
<!-- your UserControl Content-->
</MyBaseUserControl>
</UserControl>

wpf ListView item expand at mouseover

I have a ListView with pretty long listelement at times. I would like to create an event, where if I drag the mouse over an element, the whole name appears in a tooltip-like small window with the whole text of the item. This way the user can read it even if it is too long for the ListView window width.
I am a bit stuck, because I find no MouseOver event for the ListView elements. I would probably have to go on with a custom Style for my ListView, but I don't have experience with Styles.
I would really appreciate a little help, to get me started!
Create an AttachedProperty for MouseMove and hook your list view with the property. The attached property can be used to any element in your application.
Attached Property
public class MouseBehaviour
{
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseMoveCommandChanged)));
private static void MouseMoveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseMove += new MouseEventHandler(element_MouseMove);
}
static void element_MouseMove(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseMoveCommand(element);
command.Execute(e);
}
public static void SetMouseMoveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseMoveCommandProperty, value);
}
public static ICommand GetMouseMoveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseMoveCommandProperty);
}
}
XAML
xmlns:mousebehav="clr-namespace:your namespace"
<ListView mousebehav:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
VM
private RelayCommand _MouseMoveCommand;
public RelayCommand MouseMoveCommand
{
get
{
if (_MouseMoveCommand== null) return _MouseMoveCommand= new RelayCommand(param => Execute_MouseMoveCommand((MouseEventArgs)param));
return _MouseMoveCommand;
}
set { _MouseMoveCommand= value; }
}
private void Execute_MouseMoveCommand(MouseEventArgs param)
{
//your logic to expand or ??
}
Thanks for the answer. After a few hours of experimenting, I managed to solve it quite compact from the xaml:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ToolTip" Value="{Binding Name}" />
</Style>
</ListView.ItemContainerStyle>

refactoring to an attached property

I am trying to change the code below from being an event setter to an attached property (just so I can clean up the code behind). I get an error in the setter saying the value cannot be null, but I don't see why yet.
Forgetting for a second whether this is a good idea or not, can someone help me get the attached property right?
Cheers,
Berryl
EventSetter (works but with code behind as shown)
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnPreviewMouseLeftButtonDown"/>
</Style>
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var cell = sender as DataGridCell;
cell.Activate();
}
Property setter (error)
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="begavior:DataGridCellProperties.SingleClickToEdit" Value="True"/>
</Style>
public class DataGridCellProperties
{
public static readonly DependencyProperty SingleClickToEditProperty =
DependencyProperty.RegisterAttached("SingleClickToEditProperty",
typeof(bool), typeof(DataGridCellProperties),
new PropertyMetadata(false, OnSingleClickToEditPropertyChanged));
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(DataGridCell))]
public static bool GetSingleClickToEdit(DataGridCell obj) { return (bool)obj.GetValue(SingleClickToEditProperty); }
public static void SetSingleClickToEdit(DataGridCell obj, bool value) { obj.SetValue(SingleClickToEditProperty, value); }
private static void OnSingleClickToEditPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (sender == null) return;
if ((bool)e.NewValue)
{
sender.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown_EditCell;
}
else
{
sender.PreviewMouseLeftButtonDown -= OnPreviewMouseLeftButtonDown_EditCell;
}
}
private static void OnPreviewMouseLeftButtonDown_EditCell(object sender, MouseButtonEventArgs e)
{
var cell = sender as DataGridCell;
cell.Activate();
}
}
"SingleClickToEditProperty" in your d-prop registration should be "SingleClickToEdit".

WP7 - drag/drop of objects created from a DataTemplate within Canvas

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);
}
}
}

Resources