【问题标题】:How to enable/disable an attached behavior using a boolean property in the ViewModel如何使用 ViewModel 中的布尔属性启用/禁用附加行为
【发布时间】:2016-10-03 15:41:12
【问题描述】:

标题说明了我正在尝试做的事情。这是我所拥有的:

我正在使用this CodeProject 贡献将 RubberBand 行为附加到 ListBox,以便我可以使用鼠标进行拖动选择。我能够对其进行修改,以便在 ListBox 的实例化过程中禁用它,以便在我需要 ListBox 非交互式且仅显示项目时禁用它。

ListBox 嵌入在 UserControl 中,并包含一个显示元素的画布,在我的程序的一个部分中,我需要 UserControl 作为这些元素的非交互式表示,而在另一部分中,我需要它是交互式的。但是现在,我需要能够在这两种状态之间切换,不幸的是,这不适用于我有 ATM 的实现,我不明白为什么。

我已将附加属性“IsActive”绑定到我的 UserControl-ViewModel 的属性“IsEditable”中,该属性是我在修改后的 RubberBand 版本(参见下面的代码)中添加的,但由于某种原因,方法“IsActiveProperty_Changed”没有当 'IsEditable' 改变时执行。

这是我正在使用的行为并绑定到“IsEditable”:

<i:Interaction.Behaviors>
    <behavior:RubberBandBehavior IsActive="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable}"/>
</i:Interaction.Behaviors>

这个我也试过了,还是不行:

<behavior:RubberBandBehavior IsActive="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable, UpdateSourceTrigger=PropertyChanged}"/>

要禁用 ListBox 的命中检测,我还绑定到“IsEditable”,确实可以正常工作:

        <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>

因此,我怀疑这与我对 RubberBandBehavior 的实施/修改有关,因为我仍然没有实施附加属性的经验。我希望有人能发现我的错误。

修改后的 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;
    }
}

更新:

这里是 ViewModel 的 IsEditable 属性的代码。请注意,我使用的是 MvvmLight 中的 RaisePropertyChanged 方法:

private bool isEditable;
public bool IsEditable
{
    get { return isEditable; }
    set {
        if(value != isEditable)
        {
            isEditable = value;
            RaisePropertyChanged("IsEditable");
        }
    }
}

【问题讨论】:

    标签: c# wpf attached-properties attachedbehaviors rubber-band


    【解决方案1】:

    您的问题是 IsActive 不是 AttachedProperty,只是常规的 DependencyProperty。

    移除 DP。这段代码应该被删除:

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

    然后添加 IsActive 作为附加属性:

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

    您还必须更改设置/获取 IsActive 的代码:

    rubberBandBehavior.IsActive = newIsActiveValue;
    

    变成

    rubberBandBehavior.SetValue(RubberBandBehavior.IsActiveProperty, newIsActiveValue);
    

    if (IsActive == true)
    

    变成

    if (this.GetValue(IsActiveProperty).Equals(true))
    

    不过,我应该提一下,没有必要执行 SetValue 行,因为它已经设置为 newIsActiveValue... 应该不会造成任何伤害,但它也不会真正做任何事情。也没有必要检查旧值和新值是否不同,如果它们没有不同,则不会调用 IsActiveProperty_Changed。

    编辑:

    这是完整的 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 正在使用 ContainerFromItem,当您的项目相同(例如,具有相同文本的字符串列表)时,这将不起作用。我已修改代码以使用 ContainerFromIndex。外部 foreach 已更改为 for 循环。

    在 RubberBandAdorner.cs 中,在 adornedElement_PreviewMouseMove 方法中将部分代码更新为:

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

    【讨论】:

    • 嗨。首先:感谢您的回答。不幸的是,它仍然无法正常工作。在按照您的建议进行更改后,禁用继续按照我的问题中所述进行,但是当IsEditable 更改并且RubberBand 未禁用/启用时,方法IsActiveProperty_Changed 仍然不会触发。有什么想法还有什么问题吗?
    • 为了完整起见,我用属性 IsEditable 的定义更新了我的问题,并确保它不是罪魁祸首。
    • 我用 RubberBandBehavior.cs 的代码更新了我的答案。它确实对我有用,如果没有看到所有代码,我不能说为什么它对你不起作用。
    • 最初,我为 ListBox 的项目使用了一个包含 10 个字符串的列表,这些字符串都是字符串“Item”。不过,这不适用于橡皮筋。一旦我将项目更改为“项目 1”、“项目 2”等......然后它就起作用了。 RubberBandAdorner 的 adornedElement_PreviewMouseMove 对 _selector.ItemContainerGenerator.ContainerFromItem(obj) 的调用返回了错误的项目,因为它只会选择第一个项目或先前选择的项目(如果单击字符串 3,则橡皮筋将突出显示它)。我已经修复了该代码,并将再次更新我的答案。
    猜你喜欢
    • 1970-01-01
    • 2012-03-01
    • 2014-10-30
    • 1970-01-01
    • 2022-12-22
    • 2020-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多