Lucy82
Lucy82

Reputation: 693

Usercontrol - notify property change from ViewModel

I'm trying to implement custom Border, which binds to bool property in ViewModel, and whenever this property changes I want to do some animation with Border.

ViewModel property has OnPropertyChanged interface, It looks like this:

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

This is how I bind custom Border in xaml:

<cust_border:Border_slide ShowSlide={Binding Enable_control}"/>

And this is my custom border control code:

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

So, question is: how do you properly update UserControl's dependency property from Viewmodel property change, in order to do something with UserControl next?

Upvotes: 1

Views: 795

Answers (2)

Lucy82
Lucy82

Reputation: 693

Solved. Clemens was partially right - nothing was wrong with my code, property did get notified with changes. My problems were elsewhere - with a variable that didn't get value before property changed, and default value of dependency property. I sorted out those things now, and It works as expected.

Here is my full code, might get useful to someone, at least I find It so:

It's a custom Border, which can be used for a sliding animation of left Margin. Meaning you can use this Border to slide some menues to View from left side to desired right location, just add controls inside It and you're ready to go. Code can be used for other controls too (Panel etc.), but I used Border since I can round corners or do other nice UI presentations with it easily.

Logic behind is that If you set MoveTo property, Border will slide to desired left Margin location. If you don't, then Border will slide to center of screen - meaning you can use this Border for sliding menues into Views that are centered or left aligned in yout MainWindow. That was actually whole point of creating It - because you cannot use dynamic values in animation To or From, since these are freezables. So I had to create this custom control, because I couldn't do an animation to screen center (width needs to be calculated first). Anyway, here It is:

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

So, as you see - Border moves depending on what you set as start Margin.Left (that is a must) and/or MoveTo property, with binding to some bool value from ViewModel. In my case, I slide this Border when I disable all my other controls in View - so that I can make blur effect on View too. If you will use It for different things, than be careful about default value of ShowSlide property (mine is true). And surely you can re-adjust control for different things too...

E.g. usage in XAML:

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

Upvotes: 1

jamesnet214
jamesnet214

Reputation: 1162

There was a time when I did something similar to yours.

In my case, I hope it will help since I solved it this way. But it can be different from what you think, so please let me know if there's anything I missed!

And I have uploaded my sample source code to GitHub.

Here sample sourcecode. https://github.com/ncoresoftsource/stackoverflowsample/tree/main/src/answers/dependency-border-animation

ViewModel

public class MainViewModel : ObservableObject
{
    private bool _enable_ctl;
    public bool Enable_control
    {
        get { return _enable_ctl; }
        set { _enable_ctl = value; OnPropertyChanged(); }
    }
}

MainWindow

I used Label instead of Border. Because Border cannot use ControlTemplate, it can be constrained to use and expand animation.

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

Upvotes: 2

Related Questions