Suppose we have a control with a dependency property:
public class MyControl : ContentControl
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
void DoSomething(object sender, RoutedEventArgs e)
{
//...
}
public MyControl()
{
this.Loaded += DoSomething;
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(string), typeof(MyControl),
new FrameworkPropertyMetadata(default(string),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//some actions
}
public string Value
{
get { return (string)GetValue(MyControl.ValueProperty); }
set { SetValue(MyControl.ValueProperty, value); }
}}
And somewhere in XAML we bind ValueProperty to some property Val of some ViewModel.
My question is, at the moment when Loaded event is fired, will the value of MyControl.Value be already set to the value of Val which it has or not? So, am I able to use Val in DoSomething method which is executed when MyControl is loaded or not?
Thank you in advance for any answers.
My question is, at the moment when Loaded event is fired, will the value of MyControl.Value be already set to the value of Val which it has or not?
You cannot rely on this. The Loaded event is not a 'data-binding-has-completed' event. Also, the value of a dependency property can change at any time and the place to handle any changes to your Value property is in the OnValueChanged callback. You could check whether the control has been loaded in the callback if you want to:
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl ctrl = d as MyControl;
if (ctrl.IsLoaded)
{
//...
}
}
The point is that you should perform any action that depends upon the value of your dependency property in the callback and not in the Loaded event handler.
public class MyControl : ContentControl
{
public MyControl()
{
this.Loaded += (s,e) => DoSomething();
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = (MyControl)d;
if ( c.IsLoaded )
c.DoSomething();
}
// this will be called only if the control is loaded
private void DoSomething()
{
var value = Value;
if ( value == null )
{
...
} else if ( value == string.Empty )
{
...
} else
{
}
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(string), typeof(MyControl),
new FrameworkPropertyMetadata(default(string),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnValueChanged));
public string Value
{
get { return (string)GetValue(MyControl.ValueProperty); }
set { SetValue(MyControl.ValueProperty, value); }
}
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
Related
I have a user control and within I defined the following property:
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string),
typeof(MyUserControlView), new PropertyMetadata(null, MyPropertyChangedCallback));
private static void MyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl) d;
control.MyProperty = (string)e.NewValue;
}
and the following command:
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
set { SetValue(MyCommandProperty, value); }
}
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(MyUserControlView),
new PropertyMetadata(null, MyCommandPropertyChangedCallback));
private static void MyCommandPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControlView)d;
control.MyCommand = (DelegateCommand)e.NewValue;
}
void ExecuteMyCommand() {...}
bool CanExecuteMyCommand(){
return !string.IsNullOrWhiteSpace(MyProperty);
}
I Initialize this command with
SetValue(MyCommandProperty, new DelegateCommand(ExecuteMyCommand, CanExecuteMyCommand));
The problem is that the CanExecuteMyCommand() method does not work, because it does not use the current value of MyProperty. Why is this and how can I use my command correctly in a user control?
You have to invoke CanExecuteChanged after changing MyProperty it will call CanExecute.
I need to raise an event when the value of the property is changed. In my case this is when webView.Source is changed. I can't make a derived class because the class is marked as sealed. Is there any way to raise an event ?
Thank you.
Raise event when property is changed
For this scenario, you could create a DependencyPropertyWatcher to detect DependencyProperty changed event. The follow is tool class that you could use directly.
public class DependencyPropertyWatcher<T> : DependencyObject, IDisposable
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(object),
typeof(DependencyPropertyWatcher<T>),
new PropertyMetadata(null, OnPropertyChanged));
public event DependencyPropertyChangedEventHandler PropertyChanged;
public DependencyPropertyWatcher(DependencyObject target, string propertyPath)
{
this.Target = target;
BindingOperations.SetBinding(
this,
ValueProperty,
new Binding() { Source = target, Path = new PropertyPath(propertyPath), Mode = BindingMode.OneWay });
}
public DependencyObject Target { get; private set; }
public T Value
{
get { return (T)this.GetValue(ValueProperty); }
}
public static void OnPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyPropertyWatcher<T> source = (DependencyPropertyWatcher<T>)sender;
if (source.PropertyChanged != null)
{
source.PropertyChanged(source.Target, args);
}
}
public void Dispose()
{
this.ClearValue(ValueProperty);
}
}
Usage
var watcher = new DependencyPropertyWatcher<string>(this.MyWebView, "Source");
watcher.PropertyChanged += Watcher_PropertyChanged;
private void Watcher_PropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
}
You can use a decorator to wrap the original class and raise an event for decorated properties.
Hello I have issue with binding to user control animation, after I bind data to user control(which is bool type) it sets correct values to user control data, but does not trigger animation, I tried to use PropertyChangedCallback but with no luck user control code below:
private static Switch_box AppWindow;
public Switch_box()
{
InitializeComponent();
AppWindow = this;
}
public static readonly DependencyProperty CheckboxStatusProperty = DependencyProperty.Register(nameof(CheckboxStatus), typeof(bool), typeof(Switch_box), new PropertyMetadata(false, new PropertyChangedCallback(OnCurrentReadingChanged)));//cant remove static otherwise throws error
public bool CheckboxStatus
{
get
{
return (bool)GetValue(CheckboxStatusProperty);
}
set
{
/* if (value == true)
{
((Storyboard)FindResource("OnChecking")).Begin(this);
}
else
{
((Storyboard)FindResource("OnUnchecking")).Begin(this);
}*/
SetValue(CheckboxStatusProperty, value);
}
}
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)//cant remove static due to PropertyChangedCallBack requires static otherwise it throws error
{
AppWindow.OnChecking((bool)d.GetValue(CheckboxStatusProperty));
}
private void OnChecking(bool Status)
{
switch (Status)
{
case true:
{
((Storyboard)FindResource("OnChecking")).Begin(this);
break;
}
case false:
{
((Storyboard)FindResource("OnUnchecking")).Begin(this);
break;
}
}
}
And my usercontrol bind line:
<local:Switch_box Tag="{Binding Index,IsAsync=True}" Checked="Switch_box_Checked" Unchecked="Switch_box_Unchecked" CheckboxStatus="{Binding IsEnabled,IsAsync=True}"/>
How to trigger animation after CheckboxStatus variable is changed?
EDIT 1: updated code.
There is a naming convention. _StatusBox should be named CheckboxStatusProperty, and it should be public:
public static readonly DependencyProperty CheckboxStatusProperty =
DependencyProperty.Register(
nameof(CheckboxStatus), typeof(bool), typeof(Switch_box),
new PropertyMetadata(false, OnCurrentReadingChanged));
You must not call anything else than GetValueand SetValue in the CLR wrapper of a dependency property. And you call the methods on the current instance, not on a static field:
public bool CheckboxStatus
{
get { return (bool)GetValue(CheckboxStatusProperty); }
set { SetValue(CheckboxStatusProperty , value); }
}
In the PropertyChangedCallback it is pointless to set the property another time. And again, you should operate on the current DependencyObject instance, i.e. d, not on a static field:
private static void OnCurrentReadingChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Switch_box)d).OnChecking((bool)e.NewValue);
}
private void OnChecking(bool status)
{
if (status)
{
((Storyboard)FindResource("OnChecking")).Begin(this);
}
else
{
((Storyboard)FindResource("OnUnchecking")).Begin(this);
}
}
MainWindow is using custom control:
<Window ... >
<wpfCustomControlLibrary1:CustomControl Parameter="{Binding ParameterValue}" />
</Window>
View-model of MainWindow contains ParameterValue for binding:
internal class MainViewModel
{
private string parameterValue;
public MainViewModel()
{
this.ParameterValue = "Test";
}
public string ParameterValue
{
get { return this.parameterValue; }
set
{
this.parameterValue = value;
$"Setter: {value != null}".TraceTime();
}
}
}
In custom control, I need to perform additional actions in Loaded event handler, and for this I need Parameter value:
public class CustomControl : Control
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
public CustomControl()
{
this.Loaded += this.OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
// HERE I need to use Parameter value
$"OnLoaded: {this.Parameter != null}".TraceTime();
}
public static readonly DependencyProperty ParameterProperty =
DependencyProperty.Register("Parameter", typeof(string), typeof(CustomControl), new PropertyMetadata(null, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
$"DP: {dependencyPropertyChangedEventArgs.NewValue != null}".TraceTime();
}
}
But at the moment when Loaded event triggers, Parameter value is still null. Here is trace output:
[14:24:13.961] Setter: True
[14:24:14.173] OnLoaded: False
[14:24:14.235] DP: True
You can see that despite actual ParameterValue has already been set, Parameter dependency property evaluate it with delay and after OnLoaded fires.
What custom control event can I use to be sure that all dependency properties evaluated?
I can use PropertyChangedCallback for initialization, but it will became more complicated if I would have several parameters.
By the way, when value assigning directly Parameter="Test", there is no delay, but I need binding.
public static void TraceTime(this object value)
{
System.Diagnostics.Trace.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] {value}");
}
I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}