Usercontrol - 从 ViewModel 通知属性更改

Posted

技术标签:

【中文标题】Usercontrol - 从 ViewModel 通知属性更改【英文标题】:Usercontrol - notify property change from ViewModel 【发布时间】:2021-04-16 19:28:23 【问题描述】:

我正在尝试实现自定义边框,它绑定到 ViewModel 中的 bool 属性,并且每当这个属性发生变化时,我想用边框做一些动画。

ViewModel 属性有 OnPropertyChanged 接口,看起来像这样:

public bool Enable_control

   get  return _enable_ctl; 
   set  _enable_ctl = value; OnPropertyChanged(); 

private bool _enable_ctl;

这就是我在 xaml 中绑定自定义边框的方式:

<cust_border:Border_slide ShowSlide=Binding Enable_control"/>

这是我的自定义边框控制代码:

public class Border_slide : Border

    public Border_slide()
    
    
 
     public bool ShowSlide
     
        get  return (bool)GetValue(ShowSlideProperty); 
        set  SetValue(ShowSlideProperty, value);
     
    
      public static readonly DependencyProperty ShowSlideProperty =
      DependencyProperty.Register("ShowSlide", typeof(bool), typeof(Border_slide), new 
      FrameworkPropertyMetadata(true, new PropertyChangedCallback(ShowSlideChanged)));
           
      private static void ShowSlideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      
           //I would like to do animation of control when property of ViewModel changes - presumably here,
           //but PropertyChangedCallback gets triggered only once - not on every Enable_control change!
      

所以,问题是:如何从 Viewmodel 属性更改中正确更新 UserControl 的依赖属性,以便接下来对 UserControl 做一些事情?

【问题讨论】:

ShowSlideChanged 在 ShowSlide 更改其值时调用,即只要 Enable_control 更改其值。你到底在挣扎什么?请详细说明属性更改时控件应该发生的确切情况。 @Clemens,对我来说不是,我不知道为什么。如果我在 ShowSlideChanged 中设置断点,它只会在 Viewmodel 中的属性更改一次时停止。例如。我的启用,控件在启动时具有真值,当它得到假值时,我会收到通知。但是当 Enable_control 取回真正的值时,什么也没有发生。 @Clemens,我想做的只是一个边框的边距动画——从左到右,当属性改变时向后。我可以让它在一个方向上工作,但不能向后工作,因为 SlideChanged 不再更新。我的动画代码也在那里。 从您在此处显示的内容,我们无法判断。看起来一切正常。也许您正在其他地方设置 ShowSlide 属性,这会覆盖 Binding。只是猜测。 【参考方案1】:

我曾经做过类似你的事情。

就我而言,我希望它会有所帮助,因为我以这种方式解决了它。但它可能与您的想法不同,所以如果我错过了什么,请告诉我!

我已将示例源代码上传到 GitHub。

这里是示例源代码。 https://github.com/ncoresoftsource/***sample/tree/main/src/answers/dependency-border-animation

视图模型

public class MainViewModel : ObservableObject

    private bool _enable_ctl;
    public bool Enable_control
    
        get  return _enable_ctl; 
        set  _enable_ctl = value; OnPropertyChanged(); 
    

主窗口

我使用标签而不是边框​​。因为Border不能使用ControlTemplate,所以可以限制使用和展开动画。

<CheckBox Content="Switch" IsChecked="Binding Enable_control"/>
<Label Style="StaticResource SLIDER"/>

App.xaml

<Style TargetType="Label" x:Key="SLIDER">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderBrush" Value="#AAAAAA"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Width" Value="100"/>
    <Setter Property="Height" Value="100"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Label">
                <Border x:Name="border" Background="TemplateBinding Background"
                        BorderThickness="TemplateBinding BorderThickness"
                        BorderBrush="TemplateBinding BorderBrush"
                        Opacity="0">
                    <ContentPresenter/>
                </Border>
                <ControlTemplate.Triggers>
                    <DataTrigger Binding="Binding Enable_control" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation From="0" To="1" Duration="00:00:0.5" 
                                                     Storyboard.TargetName="border" 
                                                     Storyboard.TargetProperty="Opacity"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation From="1" To="0" Duration="00:00:0.5" 
                                                     Storyboard.TargetName="border" 
                                                     Storyboard.TargetProperty="Opacity"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

【讨论】:

这不是我的主要目标,但这是非常相似的问题,也是我在后面的代码中创建控制的原因。您的代码仅适用于 XAML,但是如果您想从 ViewModel 控制不透明度值呢?...简而言之,您不能,动画是可冻结的,这意味着您不能绑定到 From。为此,您需要创建一个具有自定义依赖属性的控件。看看我下面的回答,可能会派上用场。感谢您的回复:)【参考方案2】:

解决了。 Clemens 部分正确——我的代码没有任何问题,属性确实收到了更改通知。我的问题在其他地方 - 在属性更改之前没有获得值的变量,以及依赖属性的默认值。我现在整理了这些东西,它按预期工作。

这是我的完整代码,可能对某人有用,至少我是这样认为的:

自定义Border,可用于左侧Margin的滑动动画。这意味着您可以使用此边框将一些菜单从左侧滑动到所需的右侧位置,只需在其中添加控件即可开始使用。代码也可以用于其他控件(面板等),但我使用了边框,因为我可以轻松地进行圆角或其他漂亮的 UI 演示。

背后的逻辑是,如果你设置 MoveTo 属性,Border 将滑动到所需的左边距位置。如果你不这样做,那么边框将滑动到屏幕的中心——这意味着你可以使用这个边框将菜单滑动到在你的 MainWindow 中居中或左对齐的视图中。这实际上是创建 It 的重点——因为您不能在动画 ToFrom 中使用动态值,因为这些是可冻结的。所以我不得不创建这个自定义控件,因为我无法对屏幕中心进行动画处理(需要先计算宽度)。无论如何,这里是:

  public class Border_slide : Border
    
        public Border_slide()
        
            Loaded += Border_slide_Loaded;
        

        private void Border_slide_Loaded(object sender, System.Windows.RoutedEventArgs e)
        
            //Save starting Margin.Left
            StartX = Margin.Left;
        
                     
        ///<summary>Property for starting X location</summary>
        private double StartX
        
            get  return _startX; 
            set  _startX = value; 
        
        private double _startX;
    
        public static DependencyProperty MoveToProperty =
            DependencyProperty.Register("MoveTo", typeof(double), typeof(Border_slide), new PropertyMetadata(default(double)));
    
        ///<summary>Property thats sets to what Margin.Left we want move Border</summary>
        public double MoveTo
        
            get  return (double)GetValue(MoveToProperty); 
            set  SetValue(MoveToProperty, value); 
        
        
        public bool ShowSlide
        
            get  return (bool)GetValue(ShowSlideProperty); 
            set  SetValue(ShowSlideProperty, value); 
        

        public static readonly DependencyProperty ShowSlideProperty =
            DependencyProperty.Register("ShowSlide", typeof(bool), typeof(Border_slide), new FrameworkPropertyMetadata(true,     
      new PropertyChangedCallback(ShowSlideChanged)));
          
        private static void ShowSlideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        
            var brd = d as Border_slide;
      
            //Animate Margin, when Property changes

            if (((bool)e.NewValue) == true) //When property is true, Border is allready at desired location, so we move It to first x- location
            

                ThicknessAnimation back_to_start_X_location = new ThicknessAnimation
                
                    BeginTime = TimeSpan.FromSeconds(0),
                    Duration = TimeSpan.FromSeconds(1),
                    From = new Thickness(brd.Margin.Left, 0, 0, 0),
                    To = new Thickness(brd.StartX, 0, 0, 0)
                ;
                brd.BeginAnimation(Border.MarginProperty, back_to_start_X_location);

            
            else //If property is False we need to move Border to desired X- location
            
                             
                //If we don't set MoveTo property then move Border to center of screen
                if (brd.MoveTo == default(double))
                
                    var X_center = Application.Current.MainWindow.ActualWidth / 2 - brd.Width / 2;

                    ThicknessAnimation to_center_X_location = new ThicknessAnimation
                    
                        BeginTime = TimeSpan.FromSeconds(0),
                        Duration = TimeSpan.FromSeconds(1),
                        From = new Thickness(brd.ZacetniX, 0, 0, 0),
                        To = new Thickness(X_center, 0, 0, 0)
                    ;
                    brd.BeginAnimation(Border.MarginProperty, to_center_X_location);

                
                else //If MoveTo property is set then move Border to desired X-location
                
                    ThicknessAnimation to_desired_X_location = new ThicknessAnimation
                    
                        BeginTime = TimeSpan.FromSeconds(0),
                        Duration = TimeSpan.FromSeconds(1),
                        From = new Thickness(brd.StartX, 0, 0, 0),
                        To = new Thickness(brd.MoveTo, 0, 0, 0)
                    ;

                    brd.BeginAnimation(Border.MarginProperty, to_desired_X_location);
                
            
        
    

因此,如您所见 - 边框移动取决于您设置为 start Margin.Left(这是必须)和/或 MoveTo 属性,并绑定到来自 ViewModel 的一些 bool 值。就我而言,当我禁用 View 中的所有其他控件时,我会滑动此 Border - 这样我也可以在 View 上制作模糊效果。如果您将它用于不同的事情,请注意 ShowSlide 属性的默认值(我的是 true)。当然,您也可以针对不同的事物重新调整控制...

例如XAML 中的用法:

  <my_controls:Border_slide Margin="-500,0,0,0" MoveTo="5" ShowSlide="Binding Enable_control">

【讨论】:

谢谢!谢谢你。这对我也很有帮助。而且我有使用与您相同的方法制作 Youtube 视频的经验。 (youtube.com/watch?v=LHgUG9Rheuc&t=50s)

以上是关于Usercontrol - 从 ViewModel 通知属性更改的主要内容,如果未能解决你的问题,请参考以下文章

将 IsEnabled 绑定到父 ViewModel 而不是 UserControl ViewModel

如何使用ViewModel为UserControl设置DataBinding

DataContext 用于在 DataTemplate 中将 UserControl 与 ViewModel 绑定

如何将 WPF DataGrid DataColumns 可见性绑定到 UserControl 的 ViewModel 上的属性?

将 ViewModel 的属性绑定到 UserControl 的子控件和自身的 DependencyProperty

将参数传递给 UserControl viewmodel