I'm currently using the following code to be notified when a DependencyProperty's Value has changed:
DependencyPropertyDescriptor propDescriptor = DependencyPropertyDescriptor.FromProperty(property, control.GetType());
propDescriptor.AddValueChanged(control, controlChangedHandler);
This works great and is quite simple, but what I really need now is to be notified when a DependencyProperty's Value is about to change. I thought there would be a DependencyPropertyDescriptor.AddValueChanging() method, but it doesn't seem to exist. Any ideas how I can create this functionality?
I need to be able to cancel the change, fire off some asynchronous backend logic, and only have the control's property really change if the backend logic succeeds.
I solved the problem at hand by implementing wrapping my IODevice in an INotifyPropertyChanged implementation and binding it to the DependencyProperty.
The magic is in the fact that IODeviceWrapper.Value's setter doesn't actually set the value, but rather does the IO. It turns out that when the setter is called by the DependencyProperty it's bound to, the change hasn't yet been committed to the DependencyProperty's value. Hence, IODeviceWrapper.Value's setter gets called in by the DependencyProperty's sudo-, non-existent ValueChanging event.
At this time, if the DependencyProperty reads from the Value's getter it will get the old value until the IO is complete. When the IO is complete IODeviceWrapper.Value's PropertyChanged event gets fired, and the DependencyProperty then reads the new value.
My flawed design is now working flawlessly. Here's the code in case anyone else is interested. Ignore the naysayers.
public class IODeviceWrapper : INotifyPropertyChanged
{
public IODeviceWrapper(IODevice ioDevice)
{
_ioDevice = ioDevice;
_ioDevice.ValueChanged += ValueChanged;
}
private IODevice _ioDevice;
public event PropertyChangedEventHandler PropertyChanged;
private void ValueChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
public int Value
{
get { return _ioDevice.Value; }
set
{
//Do ansynchronous IO
Task task = new Task(() => _ioDevice.DoIO(value));
task.Start();
}
}
}
Related
The following is a stripped back version of a problem I'm encountering. It's a fairly common issue, but I'm struggling to find the solution.
I've an instantiated class which I've bound to an item on my main window. This class contains a DispatcherTimer which is used to update a value. In the example given it's incrementing this value by 1 every second.
I'd expect the bound item on my form to reflect this change by updating its value accordingly, however it never updates.
From reading other responses to similar questions on StackOverflow I've a feeling this is due to the nature of the main UI thread running separately to the thread which is causing the increment.
I'm banging my head against a wall though trying to get this binding to update with each call of my DispatcherTimer.
The following is the form element I'm wanting to update every second:
<TextBox Text="{Binding val}" Width="100"/>
Next, this is the instantiation of the class containing the timer and my applications configuration:
BasicTimer basictimer;
public MainWindow()
{
InitializeComponent();
basictimer = new BasicTimer();
DataContext = basictimer;
}
Lastly, here's the class I've created. When created it configures a timer which it uses to update a value every second. Each time this value is updated I'd expect the main UI to be notified of the change and update accordingly. However, this message doesn't seem to be getting through.
class BasicTimer: INotifyPropertyChanged
{
DispatcherTimer _timer;
uint _val = 10;
public uint val
{
get
{
return _val;
}
set
{
if(_val!=value)
{
_val = value;
OnPropertyChanged("Value");
}
}
}
public BasicTimer()
{
_timer = new DispatcherTimer();
_timer.Tick += new EventHandler(TimerTick);
_timer.Interval = new TimeSpan(0, 0, 1);
_timer.Start();
}
private void TimerTick(object sender, EventArgs e)
{
val++;
Console.WriteLine(val);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
I think I've managed to avoid the usual pitfalls of forgetting INotifyPropertChanged, and other bound values from other models are working just fine. It's just this property which is being updated via a thread that I'm having trouble with. I've also tried creating a similar timer using a simple Timer but I'm having the same problem.
Any thoughts would be very much appreciated, thanks!
I believe your problem is in the call to OnPropertyChanged:
uint _val = 10;
public uint val
{
get
{
return _val;
}
set
{
if(_val!=value)
{
_val = value;
OnPropertyChanged("Value");
}
}
}
That should be
OnPropertyChanged("val");
The string in the call to OnPropertyChanged has to match the name of the property.
EDIT
The reason you want the name passed to OnPropertyChanged to always match the name of the property is because the data binding subscribes to your object's PropertyChanged event and is watching for the value in that string in the parameter passed to its event handler. If the name passed doesn't match the name it is looking for, it ignores the notification. It only updates the value of the control bound to that property when the names match.
As Aron mentioned in the comments, you can use the CallerMemberAttribute in your OnPropertyChanged method to ensure the property name is always passed to the method properly. According to the answer to this StackOverflow question, your method would look like this:
protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
You would then call it with no parameter from the property's setter and the name will always be correct.
As the answer to the linked question says, this code compiles into IL code that is identical to what is produced if you hard code the string in your call, so this trick will always work and will be just as fast.
This is such a basic question, but I don't think I've done this before despite having bound so many properties. I originally was planning to bind a class called TimeScale to various objects.
In class A we have a dependency property that I want to call change notification on. However, change notification is not done manually through this class.
public TimeScale AxisTimeScale
{
get { return (TimeScale)GetValue(AxisTimeScaleProperty); }
set { SetValue(AxisTimeScaleProperty, value); }
}
public static readonly DependencyProperty AxisTimeScaleProperty =
DependencyProperty.Register("AxisTimeScale",
typeof(TimeScale), typeof(SignalPanel),
new FrameworkPropertyMetadata(new TimeScale()));
this binds to source class B
private class B
{
private TimeScale _GraphTimeScale;
public TimeScale GraphTimeScale
{
get { return _GraphTimeScale; }
set
{
if (value != _GraphTimeScale)
{
_GraphTimeScale = value;
OnPropertyChanged("GraphTimeScale");
}
}
}
}
Looking at it again I guess all I really want is to call propertychanged on a dependency property, but since I didn't implement Inotifypropertychanged, I am wondering how i do that.
I think DependencyObject already implements Inotifypropertychanged, so I have access to this:
OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, old value, new value));
However, inserting the same object into both the old value and new value slots results in the PropertyChanged event not firing (I assume the implementation checks whether the two values are the same before firing the event). I want to avoid creating a new object if possible. I guess one option is to override OnPropertyChanged. Nope that also requires me to have a dependency propertychanged event args.
Update
OnPropertyChanged("TimeScale");
to
OnPropertyChanged("GraphTimeScale");
Or,
you can wrap the TimeScale class with an ObservableObject so that you can subscribe to object change events and raise them from there.
More info: http://msdn.microsoft.com/en-us/library/ff653818.aspx
Subscribe to the PropertyChanged notification of NumberOfUnits, and then raise OnPropertyChanged("GraphTimeScale") in the property changed event handler.
Would be interested if there is a better way though.
I have a class that implements Inotifypropertychanged. I also have this property that I commented out the notification on.
private Color _CoolColor =Colors.Purple;
public Color CoolColor
{
get
{
return _CoolColor;
}
set
{
if (value != _CoolColor)
{
_CoolColor = (Color)value;
//OnPropertyChanged("CoolColor");
}
}
}
a binding in my xaml attaches to this property:
BusTextColor="{Binding Path=CoolColor}"
/// <summary>
/// Color used for the text containing the hex value of the bus
/// </summary>
public Color BusTextColor
{
get
{
return (Color)GetValue(BusTextColorProperty);
}
set
{
SetValue(BusTextColorProperty, value);
}
}
public static readonly DependencyProperty BusTextColorProperty =
DependencyProperty.Register("BusTextColor",
typeof(Color), typeof(SignalGraph),
new FrameworkPropertyMetadata(new Color(), new PropertyChangedCallback(CreateBrushesAndReDraw)));
I only bound this cause I wanted to make sure that I wasn't crazy, but I must be crazy because my BusTextColor is updating when CoolColor changes. Someone please make it stop working.
I was only doing this because another dependency property I have is not binding to my viewmodel properly. I know there is probably some obvious reason for this, but I'm definitely missing it.
edit:
that article was interesting. but in my case I have the Inotifypropertychanged interface implemented, I just don't raise the event OnPropertyChanged. I realized I should have posted that as well.
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
Someone please make it stop working.
Just set the BindingMode to OneTime
Since no one had an obvious answer, I knew I had done something wrong elsewhere and what I was posting should work.
Eventually realized it was a bug I had before. Somewhere I had changed the value of the target manually and broke the binding. Need to start checking for code interference with the binding in these situations rather than assuming I am using incorrect syntax for the binding in xaml
I have a simple usercontrol (WinForms) with some public properties. When I use this control, I want to databind to those properties with the DataSourceUpdateMode set to OnPropertyChanged. The datasource is a class which implements INotifyPropertyChanged.
I'm aware of the need to create bindings against the properties and I'm doing that.
I assumed that my usercontrol would have to implement an interface, or the properties would need to be decorated with some attribute, or something along those lines.But my research has come up blank.
How should this be accomplished? At the moment I'm doing it by calling OnValidating() in my usercontrol whenever a property changes, but that doesn't seem right.
I can get validation to happen if I set the CausesValidation to true on the usercontrol, but that's not very useful to me. I need to validate each child property as it changes.
Note this is a WinForms situation.
EDIT: Evidently I have no talent for explanation so hopefully this will clarify what I'm doing. This is an abbreviated example:
// I have a user control
public class MyControl : UserControl
{
// I'm binding to this property
public string ControlProperty { get; set; }
public void DoSomething()
{
// when the property value changes, the change should immediately be applied
// to the bound datasource
ControlProperty = "new value";
// This is how I make it work, but it seems wrong
OnValidating();
}
}
// the class being bound to the usercontrol
public class MyDataSource : INotifyPropertyChanged
{
private string sourceProperty;
public string SourceProperty
{
get { return sourceProperty; }
set
{
if (value != sourceProperty)
{
sourceProperty = value;
NotifyPropertyChanged("SourceProperty");
}
}
}
// boilerplate stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public class MyForm : Form
{
private MyControl myControl;
public MyForm()
{
// create the datasource
var dataSource = new MyDataSource() { SourceProperty = "test" };
// bind a property of the datasource to a property of the usercontrol
myControl.DataBindings.Add("ControlProperty", dataSource, "SourceProperty",
false, DataSourceUpdateMode.OnPropertyChanged); // note the update mode
}
}
(I have tried this using a BindingSource, but the result was the same.)
Now what I want to happen is that when the value of MyControl.ControlProperty changes, the change is immediately propagated to the datasource (the MyDataSource instance). To achieve this I call OnValidating() in the usercontrol after changing the property. If I don't do that, I have to wait until validation gets triggered by a focus change, which is the equivalent of the "OnValidation" update mode, rather than the desired "OnPropertyUpdate" validation mode. I just don't feel like calling OnValidating() after altering a property value is the right thing to do, even if it (kind of) works.
Am I right in assuming the calling OnValidating() is not the right way to do this? If so, how do I notify the datasource of the ControlProperty change?
I think I've got this figured out. I didn't understand how change notifications were sent from control to bound datasource.
Yes, calling OnValidating() is the wrong way.
From what I've pieced together, there are two ways a control can notify the datasource that a property has changed.
One way is for the control to implement INotifyPropertyChanged. I had never done this from the control side before, and I thought only the datasource side of the binding had to implement it.
When I implemented INotifyPropertyChanged on my user control, and raised the PropertyChanged event at the appropriate time, it worked.
The second way is for the control to raise a specific change event for each property. The event must follow the naming convention: <propertyname>Changed
e.g. for my example it would be
public event EventHandler ControlPropertyChanged
If my property was called Foo, it would be FooChanged.
I failed to notice the relavent part of the MSDN documentation, where it says:
For change notification to occur in a
binding between a bound client and a
data source, your bound type should
either:
Implement the INotifyPropertyChanged
interface (preferred).
Provide a change event for each
property of the bound type.
This second way is how all existing WinForms controls work, so this is how I'm doing it now. I use INotifyPropertyChanged on my datasource, but I raise the Changed events on my control. This seems to be the conventional way.
Implementing the INotifyPropertyChanged interface is very simple. Here is a sample that shows an object with a single public field...
public class Demo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
private string _demoField;
public string DemoField
{
get {return demoField; }
set
{
if (value != demoField)
{
demoField = value;
NotifyPropertyChanged("DemoField");
}
}
}
}
Then you would create a Binding instance to bind a control property to a property (DemoField) on your source instance (instance of Demo).
I feel like I'm missing something here, but I have this datagrid which when the datasource changes, automatically redraws it self with no logical reason for it doing so.
I have the datagrid bound to a DataView property which implements INotifyPropertyChanged and I want to do some other stuff when that event is fired before calling Refresh().
So here is the datasource.
public class MainScreenDataView : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
DataView _dataview;
public DataView GetDataView
{
get { return _dataview; }
set
{
_dataview = value;
OnPropertyChanged("GetDataView");
}
}
public MainScreenDataView()
{
}
}
And the binding (I call this in the constructor of the window)
public void MakeData()
{
MiddleMan midman = MiddleMan.Instance;
midman.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(midman_PropertyChanged); //unrelated event for error messages
midman.InstantiateAll();
Binding bind = new Binding();
bind.Source = midman.GetDict["contact"].GetDataView; //GetDict is a dictionary that holds instances of MainScreenDataView
bind.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
DG_Contacts.SetBinding(BetterDataGrid.ItemsSourceProperty, bind);
}
The class that updates the DataView with data from a database has access to that same instance of MainScreenDataView as the window. The instance is kept in a dictionary in a singleton.
Now I see no reason why the datagrid would refresh it self, I even tried removing the INotifyPropertyChanged stuff from MainScreenDataview and yet it keeps the same behavior.
Guess there's something I'm missing here. Default behavior somewhere that needs to be overridden or something?
You've got target and source swapped. Done it myself. The UpdateSourceTrigger.Explicit setting affects how the binding updates the source which is the MainScreenDataView.GetDataView property not the DataGrid.ItemSource. The DataGrid.ItemSource is the target.
Removing INotifyPropertyChanged from MainScreenDataView will have no effect on a singleton because the instance doesn't change, only the values inside the instance. In other words, GetDataView is a "set it and forget it" property.
As long as the binding is in effect, there is no way to prevent changes made to the collection from being propagated by the binding system unless you suppress DataView.CollectionChanged events from firing or block so that the binding subsystem simply doesn't run.
If you really want this you can disconnect the binding and set it again when you are ready or create an entirely new DataView and overwrite the binding when you are ready.