Reputation: 1282
The title says what I am attempting to do. Here is what I have:
I am using this CodeProject contribution to attach a RubberBand behavior to a ListBox, so that I can drag-select using the mouse. I was able to modify it, so that I can disable it during instantiation of the ListBox for when I need the ListBox to be non-interactive and only show items.
The ListBox is embeded in a UserControl and contains a canvas that displays elements and in one section of my program I needed the UserControl to be a non-interactive representation of those elements, whereas in the other I needed it to be interactive. However now, I need to be able to toggle between these two states and unfortunately that does not work with the implementation I have ATM and I do not understand why.
I have bound the attached property 'IsActive', which I added in my modified RubberBand-version (see code below) to the property 'IsEditable' of my UserControl-ViewModel, but for some reason the method 'IsActiveProperty_Changed' does not execute, when 'IsEditable' changes.
This is I am using the behavior and binding to 'IsEditable':
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior IsActive="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable}"/>
</i:Interaction.Behaviors>
I have also tried this, which also does not work:
<behavior:RubberBandBehavior IsActive="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable, UpdateSourceTrigger=PropertyChanged}"/>
To disable the hit-detection of the ListBox, I am also binding to 'IsEditable', which does work fine:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable}" Value="False">
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Focusable" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
I therefore suspect, that it has to do with my implementation/modification of RubberBandBehavior, since I am still unexperienced to implementing Attached Properties. I hope somebody can spot my error.
Modified RubberBandBehavior.cs
public class RubberBandBehavior : Behavior<ListBox>
{
private RubberBandAdorner band;
private AdornerLayer adornerLayer;
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.Register("IsActive", typeof(bool), typeof(RubberBandBehavior),
new PropertyMetadata(IsActiveProperty_Changed));
private static void IsActiveProperty_Changed(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
RubberBandBehavior rubberBandBehavior = (RubberBandBehavior)sender;
if (args.Property.Name == "IsActive")
{
bool newIsActiveValue = (bool)args.NewValue;
bool oldIsActiveValue = (bool)args.OldValue;
if (newIsActiveValue != oldIsActiveValue)
{
rubberBandBehavior.IsActive = newIsActiveValue;
if (rubberBandBehavior.AssociatedObject != null)
{
if (newIsActiveValue == true)
{
rubberBandBehavior.AttachBehavior();
}
else
{
rubberBandBehavior.DetachBehavior();
}
}
}
}
}
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.Loaded += new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnAttached();
}
void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
if (IsActive == true)
{
AttachBehavior();
}
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnDetaching();
}
private void AttachBehavior()
{
band = new RubberBandAdorner(AssociatedObject);
adornerLayer = AdornerLayer.GetAdornerLayer(AssociatedObject);
adornerLayer.Add(band);
}
private void DetachBehavior()
{
adornerLayer.Remove(band);
}
}
RubberBandAdorner.cs:
public class RubberBandAdorner : Adorner
{
private Point startpoint;
private Point currentpoint;
private Brush brush;
private bool flag;
private ScrollViewer viewer;
private ScrollBar scrollbar;
public RubberBandAdorner(UIElement adornedElement)
:base(adornedElement)
{
IsHitTestVisible = false;
adornedElement.MouseMove += new MouseEventHandler(adornedElement_PreviewMouseMove);
adornedElement.MouseLeftButtonDown += new MouseButtonEventHandler(adornedElement_PreviewMouseLeftButtonDown);
adornedElement.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(adornedElement_PreviewMouseLeftButtonUp);
brush = new SolidColorBrush(SystemColors.HighlightColor);
brush.Opacity = 0.3;
}
void adornedElement_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
DisposeRubberBand();
}
void adornedElement_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ListBox _selector = AdornedElement as ListBox;
if (_selector.SelectedItems != null && (_selector.SelectionMode == SelectionMode.Extended || _selector.SelectionMode == SelectionMode.Multiple))
{
_selector.SelectedItems.Clear();
}
startpoint = Mouse.GetPosition(this.AdornedElement);
Mouse.Capture(_selector);
flag = true;
}
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
// Search immediate children first (breadth-first)
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
void adornedElement_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && flag)
{
currentpoint = Mouse.GetPosition(AdornedElement);
Selector _selector = AdornedElement as Selector;
if (viewer == null)
{
viewer = FindVisualChild<ScrollViewer>(_selector);
}
if (scrollbar == null)
{
scrollbar = FindVisualChild<ScrollBar>(viewer);
}
if (_selector.Items.Count > 0)
{
if (currentpoint.Y > ((FrameworkElement)AdornedElement).ActualHeight && viewer.VerticalOffset < _selector.ActualHeight && scrollbar.Visibility == System.Windows.Visibility.Visible)
{
startpoint.Y -= 50;
}
else if (currentpoint.Y < 0 && viewer.VerticalOffset > 0 && scrollbar.Visibility == System.Windows.Visibility.Visible)
{
startpoint.Y += 50;
}
}
InvalidateVisual();
foreach (var obj in _selector.Items)
{
ListBoxItem item = _selector.ItemContainerGenerator.ContainerFromItem(obj) as ListBoxItem;
if (item != null)
{
Point point = item.TransformToAncestor(AdornedElement).Transform(new Point(0, 0));
Rect bandrect = new Rect(startpoint, currentpoint);
Rect elementrect = new Rect(point.X, point.Y, item.ActualWidth, item.ActualHeight);
if (bandrect.IntersectsWith(elementrect))
{
item.IsSelected = true;
}
else
{
item.IsSelected = false;
}
}
}
}
}
protected override void OnRender(DrawingContext drawingContext)
{
Rect rect = new Rect(startpoint, currentpoint);
drawingContext.DrawGeometry(brush, new Pen(SystemColors.HighlightBrush, 1), new RectangleGeometry(rect));
base.OnRender(drawingContext);
}
private void DisposeRubberBand()
{
currentpoint = new Point(0, 0);
startpoint = new Point(0, 0);
AdornedElement.ReleaseMouseCapture();
InvalidateVisual();
flag = false;
}
}
Update:
Here is the code for the IsEditable
property of the ViewModel. Note that I am using the RaisePropertyChanged
method from MvvmLight:
private bool isEditable;
public bool IsEditable
{
get { return isEditable; }
set {
if(value != isEditable)
{
isEditable = value;
RaisePropertyChanged("IsEditable");
}
}
}
Upvotes: 3
Views: 1722
Reputation: 4322
Your problem is that IsActive is NOT an AttachedProperty, just a regular DependencyProperty.
Remove the DP. This code should be removed:
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.Register("IsActive", typeof(bool), typeof(RubberBandBehavior),
new PropertyMetadata(IsActiveProperty_Changed));
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
And then add IsActive as an attached property:
public static bool GetIsActive(DependencyObject obj)
{
return (bool)obj.GetValue(IsActiveProperty);
}
public static void SetIsActive(DependencyObject obj, bool value)
{
obj.SetValue(IsActiveProperty, value);
}
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(RubberBandBehavior), new PropertyMetadata(IsActiveProperty_Changed));
You will have to change the code that was setting/getting IsActive as well:
rubberBandBehavior.IsActive = newIsActiveValue;
becomes
rubberBandBehavior.SetValue(RubberBandBehavior.IsActiveProperty, newIsActiveValue);
And
if (IsActive == true)
becomes
if (this.GetValue(IsActiveProperty).Equals(true))
Although, I should mention that it is not necessary to execute the SetValue line since it would already be set to the newIsActiveValue... shouldn't hurt anything but it doesn't really do anything either. Nor is it necessary to check if the old and new values are different, if they weren't different then IsActiveProperty_Changed would not have been called.
Edit:
Here is the complete RubberBandBehavior.cs:
public class RubberBandBehavior : Behavior<ListBox>
{
private RubberBandAdorner band;
private AdornerLayer adornerLayer;
public static bool GetIsActive(DependencyObject obj)
{
return (bool)obj.GetValue(IsActiveProperty);
}
public static void SetIsActive(DependencyObject obj, bool value)
{
obj.SetValue(IsActiveProperty, value);
}
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(RubberBandBehavior), new PropertyMetadata(IsActiveProperty_Changed));
private static void IsActiveProperty_Changed(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
RubberBandBehavior rubberBandBehavior = (RubberBandBehavior)sender;
if (args.Property.Name == "IsActive")
{
bool newIsActiveValue = (bool)args.NewValue;
if (rubberBandBehavior.AssociatedObject != null)
{
if (newIsActiveValue == true)
{
rubberBandBehavior.AttachBehavior();
}
else
{
rubberBandBehavior.DetachBehavior();
}
}
}
}
protected override void OnAttached()
{
AssociatedObject.Loaded += new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnAttached();
}
void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
if (this.GetValue(IsActiveProperty).Equals(true))
{
AttachBehavior();
}
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnDetaching();
}
private void AttachBehavior()
{
band = new RubberBandAdorner(AssociatedObject);
adornerLayer = AdornerLayer.GetAdornerLayer(AssociatedObject);
adornerLayer.Add(band);
}
private void DetachBehavior()
{
adornerLayer.Remove(band);
}
}
RubberBandAdorner is using ContainerFromItem which will not work when your items are the same (a list of strings with the same text, for example). I have modified the code to use ContainerFromIndex. The outer foreach has been changed to a for loop.
In RubberBandAdorner.cs, in the adornedElement_PreviewMouseMove method update part of the code to this:
//foreach (var obj in _selector.Items)
//{
// ListBoxItem item = _selector.ItemContainerGenerator.ContainerFromItem(obj) as ListBoxItem;
for (int i=0; i<_selector.Items.Count; i++)
{
ListBoxItem item =_selector.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
if (item != null)
{
Point point = item.TransformToAncestor(AdornedElement).Transform(new Point(0, 0));
Rect bandrect = new Rect(startpoint, currentpoint);
Rect elementrect = new Rect(point.X, point.Y, item.ActualWidth, item.ActualHeight);
if (bandrect.IntersectsWith(elementrect))
{
item.IsSelected = true;
}
else
{
item.IsSelected = false;
}
}
}
Upvotes: 1