c# - 如何使用 ViewModel 中的 bool 属性启用/禁用附加行为

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

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

我正在使用 this将 RubberBand 行为附加到 ListBox 的 CodeProject 贡献,以便我可以使用鼠标进行拖动选择。我能够修改它,以便我可以在 ListBox 的实例化期间禁用它,因为当我需要 ListBox 是非交互式的并且只显示项目时。

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

我已将附加属性“IsActive”绑定(bind)到我的 UserControl-ViewModel 的属性“IsEditable”,但由于某些原因,方法“IsActiveProperty_Changed”没有在“IsEditable”更改时执行。

这是我正在使用行为并绑定(bind)到“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 的命中检测,我还绑定(bind)到“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");
        }
    }
}

最佳答案

您的问题是 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;
        }
    }
}

关于c# - 如何使用 ViewModel 中的 bool 属性启用/禁用附加行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37616640/

相关文章:

C#/WPF - 如何在 DatePicker 对象中仅启用一组特定的日期(而不是范围)

c# - 当引用不在 ItemsSource 中时 WPF 绑定(bind)到 ComboBox SelectedItem

c# - 用于 “SecureString”的TypeConverter不支持转换字符串

c# - 信箱不可用。服务器响应为 : 5. 7.1 无法中继错误

c# - 是否可以在 PC 的共享文件夹中使用 SQLite 数据库?

c# - 使用 Linq to Sqlite (linq2db) 创建表

c# - 在单独的线程中显示WPF ProgressBar窗口

c# - 将硬编码的 JSON 字符串存储到变量

WPF/附加属性 - 请解释为什么它有效

wpf - 使用元素语法设置 UserControl 上的附加属性值