If I have XAML element that is binded to a data source property and the data source changes faster then the human eye can see I am assuming that the UI also gets re drawn faster then the human eye can see and wasting resources. Would it be a good idea for the property change to raise a flag instead of triggering re-draw of UI and then a timer to trigger the UI redraw if the property was changed? or am I missing how the UI gets re-drawn?
You Could use a delyed invocation of raising the property changed event, maybe like this...
public static class DispatcherExtensions
{
private static Dictionary<string, DispatcherTimer> timers =
new Dictionary<string, DispatcherTimer>();
private static readonly object syncRoot = new object();
public static void DelayInvoke(this Dispatcher dispatcher, string namedInvocation,
Action action, TimeSpan delay,
DispatcherPriority priority = DispatcherPriority.Normal)
{
lock (syncRoot)
{
RemoveTimer(namedInvocation);
var timer = new DispatcherTimer(delay, priority, (s, e) => action(), dispatcher);
timer.Start();
timers.Add(namedInvocation, timer);
}
}
public static void CancelNamedInvocation(this Dispatcher dispatcher, string namedInvocation)
{
lock (syncRoot)
{
RemoveTimer(namedInvocation);
}
}
private static void RemoveTimer(string namedInvocation)
{
if (!timers.ContainsKey(namedInvocation)) return;
timers[namedInvocation].Stop();
timers.Remove(namedInvocation);
}
}
Then
private object _property;
public object Property
{
get { return _property; }
set
{
if (_property != value)
{
_property = value;
Dispatcher.DelayInvoke("PropertyChanged_Property",(Action)(() =>
{
RaisePropertyChanged("Property");
}),TimeSpan.FromMilliseconds(500));
}
}
}
Not sure i like it though...
A typical pattern for this case, is implementing your property as
private object _property;
public object Property
{
get { return _property; }
set
{
if (_property != value)
{
_property = value;
RaisePropertyChanged("Property");
}
}
}
which will update your binding only if the value changed
In this case a timer triggering the UI update is the way to go. To keep the UI fluent take an timer interval of about 40ms.
public class ViewModel
{
private Timer updateTimer;
public ViewModel()
{
updateTimer = new Timer();
updateTimer.Interval = 40;
updateTimer.Elapsed +=new ElapsedEventHandler(updateTimer_Elapsed);
updateTimer.Start();
}
private object _property;
public object Property
{
get { return _property; }
set
{
if (_property != value)
{
_property = value;
}
}
}
void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
{
RaisePropertyChanged();
}
}
Calling RaisePropertyChanged() without an argument forces the UI to refresh all bindings. If you don't want this, you could use flags or a registry, to mark which property needs to be updated.
Related
Consider the following object, part of a WPF MVVM application:
public class MyObject : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private bool _isSelected;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
And its use in the following ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private List<MyObject> _myObjects;
public List<MyObject> MyObjects
{
get
{
return _myObjects;
}
set
{
_myObjects = value;
OnPropertyChanged("MyObjects");
}
}
public bool CanDoSomething
{
get
{
return MyObjects.Where(d => d.IsSelected).Count() > 0;
}
}
}
In this situation, I can track which of my objects have been selected, and selecting them will fire OnPropertyChanged and so can notify the parent view.
However, CanDoSomething will always be false because there's nowhere I can fire an OnPropertyChanged to create a notification. If I put it in MyObject, it doesn't know anything about the property and so does nothing. There's nowhere to put it in the ViewModel because there's nothing that reacts when an object in the list is selected.
I've tried substituting the List for an ObservableCollection and a custom "TrulyObservableCollection" (see Notify ObservableCollection when Item changes ) but neither work.
How can I get round this, without resorting to click events?
I feel like if I had a better idea of what your end goal was I might be able to recommend a better approach. There is some stuff going on that just feels a little out place. Like maybe 'CanDoSomething' should be part of a command object. And I am wondering if more than one MyObject be selected at a time? If not then I would approach this in an entirely differnt way.
So anyway, you want to update CanDoSomething any time the IsSelected property of one of the items in MyObjects changes. It sounds like you were using an ObservableCollection at one point and then abandoned it. That was a mistake. You need to update CanDoSomething any time one of two events occur; the first is when items are added to or removed from MyObjects and the second is when the IsSelected property of any of the objects in MyObjects changes. For the first event you need something that implements INotifyCollectionChanged, i.e. an ObservableCollection. You already have the second event covered because the objects implement INotifyPropertyChanged. So you just have to combine those two things.
In the following example I have taken your code and made some changes. To start with I changed MyObjects back to an ObservableCollection<MyObject>. It does not have a setter because I have found that there usually is not good reason to change an observable collection; just add and remove objects as necessary. Then in the viewmodel's constructor I am register for the CollectionChanged event of MyObjects. In that handler I am grabbing items that are added to the collection and hooking up their PropertyChanged event to the OnIsSelectedChanged event handler and I am unhooking the PropertyChanged event from OnIsSelectedChanged for any objects that were removed from the collection. Because items have been added or removed we have no idea what the state of IsSelected may be of the objects in MyObjects so this is a good opportunity to update CanDoSomething, and I do at the bottom of the event handler. Finally, the OnIsSelectedChanged is where the other half of the magic happens. Every object in MyObjects will have their PropertyChanged event hooked up to this event handler. Whenever the IsSelected property on any of these objects changes the event handler will update CanDoSomething.
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
public MyViewModel()
{
this._myObjects.CollectionChanged += (o, e) =>
{
if (e.NewItems != null)
{
foreach (var obj in e.NewItems.OfType<MyObject>())
{
obj.PropertyChanged += this.OnIsSelectedChanged;
}
}
if (e.OldItems != null)
{
foreach (var obj in e.OldItems.OfType<MyObject>())
{
obj.PropertyChanged -= this.OnIsSelectedChanged;
}
}
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
};
}
private readonly ObservableCollection<MyObject> _myObjects =
new ObservableCollection<MyObject>();
public ObservableCollection<MyObject> MyObjects
{
get
{
return _myObjects;
}
}
private void OnIsSelectedChanged(object o, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
}
private bool _canDoSomething;
public bool CanDoSomething
{
get { return this._canDoSomething; }
private set
{
if (_canDoSomething != value)
{
_canDoSomething = value;
OnPropertyChanged("CanDoSomething");
}
}
}
}
First create a class that defines this attached property:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
}
Then just bind this attached property to a delegate command in your view model: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}"
You can find more detail in this blog post: https://marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-clicked-item-to-it/
Let me know if it works
I'm doing some training project right now. It's supposed to convert numbers into different strings.
Heres the converted Control, and in the bottom way I use it in my Main Window.
So the first problem is that I want to create instance of converter based on value I pass to OutputFormatProperty so in this case I create converter that should be type OctalConverter but instead I get the default one, why is that?
Another thing is that I wan't to change InputValue in the converter by binding it to CurrentValue, which works with NotifyPropertyChanged, but it doesn't seem to work that way.
public partial class ConverterDisplay : UserControl {
private const int DEFAULT_INPUT_VALUE = 0;
private readonly ObservableCollection <DisplayField> _displayFields;
private AbstractNumberConverter _converter;
public static readonly DependencyProperty InputValueProperty = DependencyProperty.Register (
"InputValue",
typeof(int),
typeof(ConverterDisplay),
new PropertyMetadata (DEFAULT_INPUT_VALUE));
public static readonly DependencyProperty OutputFormatProperty = DependencyProperty.Register (
"OutputFormat",
typeof(NumberSystems),
typeof(ConverterDisplay),
new FrameworkPropertyMetadata (NumberSystems.Binary));
public int InputValue {
get {
return (int) GetValue (InputValueProperty);
}
set {
SetValue (InputValueProperty, value);
UpdateDisplay ();
}
}
public NumberSystems OutputFormat {
get {
return (NumberSystems) GetValue (OutputFormatProperty);
}
set {
SetValue (OutputFormatProperty, value);
}
}
public ObservableCollection <DisplayField> DisplayFields {
get { return _displayFields; }
}
public ConverterDisplay () {
_displayFields = new ObservableCollection<DisplayField> ();
InitializeComponent ();
CreateConverter ();
}
private void UpdateDisplay () {
var convertedNumberString = _converter.GetString (InputValue);
if (_displayFields.Count > convertedNumberString.Length)
ResetDisplayFields ();
while (_displayFields.Count < convertedNumberString.Length)
AddDisplayField ();
UpdateValues (convertedNumberString);
}
private void UpdateValues (string convertedString) {
if (_displayFields.Count == 0) return;
for (int i = 0; i < _displayFields.Count; i++) {
_displayFields [i].NumberValue = convertedString [i];
}
}
private void AddDisplayField () {
_displayFields.Insert (
0,
new DisplayField ((int)OutputFormat, _displayFields.Count));
}
private void ResetDisplayFields () {
_displayFields.Clear ();
}
private void CreateConverter () {
switch (OutputFormat) {
case NumberSystems.Binary:
_converter = new BinaryConverter ();
break;
case NumberSystems.Octal:
_converter = new OctalConverter ();
break;
case NumberSystems.Hexadecimal:
_converter = new HexadecimalConverter ();
break;
}
}
}
public enum NumberSystems {
Binary = 2,
Octal = 8,
Hexadecimal = 16
}
And then in the Main Window I'm trying to use that control
<converters:ConverterDisplay x:Name="octConverter"
InputValue="{Binding ElementName=Window,Path=CurrentValue}"
OutputFormat="Octal"/>
Just in case
public int CurrentValue {
get { return _currentValue; }
set {
if (value == _currentValue)
return;
ValidateNewValue (value);
OnPropertyChanged ();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
}
===========================
Edit #1
I don't really like that solution but I created public method in ConverterDisplay to create converter, it's being called after MainWindow is initialized so now the converters are correct.
Another thing is that how do i bind my UpdateDisplay method to InputValueProperty? I found through validation that it's getting correct value, but I can't see way how I can run that method without creating static stuff.
Concerning your second problem (binding the UpdateDisplay method to InputValueProperty: In general, it's not the best idea to call any method within a dependency property's setter, since this setter is never invoked when using data binding to fill the dependency property's value, as pointed out at MSDN:
The WPF XAML processor uses property system methods for dependency
properties when loading binary XAML and processing attributes that are
dependency properties. This effectively bypasses the property
wrappers. When you implement custom dependency properties, you must
account for this behavior and should avoid placing any other code in
your property wrapper other than the property system methods GetValue
and SetValue.
Instead, create a callback method that is invoked whenever InputValue's content changes, and call UpdateDisplay from there:
public static readonly DependencyProperty InputValueProperty = DependencyProperty.Register (
"InputValue",
typeof(int),
typeof(ConverterDisplay),
new PropertyMetadata (DEFAULT_INPUT_VALUE, InputValueChangedCallback));
private static void InputValueChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var userControl = dependencyObject as ConverterDisplay;
if (userControl != null)
userControl.UpdateDisplay();
}
To simply illustrate my dilemma, let say that I have the following code:
class A
{
// May be set by a code or by an user.
public string Property
{
set { PropertyChanged(this, EventArgs.Empty); }
}
public EventHandler PropertyChanged;
}
class B
{
private A _a;
public B(A a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Who changed the Property?
}
public void MakeProblem()
{
_a.Property = "make a problem";
}
}
In order to perform its duty, class B have to react on A's PropertyChanged event but also is capable of alternating that property by itself in certain circumstances. Unfortunately, also other objects can interact with the Property.
I need a solution for a sequential flow. Maybe I could just use a variable in order to disable an action:
bool _dontDoThis;
void Handler(object s, EventArgs e)
{
if (_dontDoThis)
return;
// Do this!
}
public void MakeProblem()
{
_dontDoThis = true;
_a.Property = "make a problem";
_dontDoThis = false;
}
Are there a better approaches?
Additional considerations
We are unable to change A.
A is sealed.
There are also other parties connected to the PropertyChanged event and I don't know who their are. But when I update the Property from B, they shouldn't be also notified. But I'm unable to disconnect them from the event because I don't know them.
What if also more threads can interact with the Property in the mean time?
The more bullets solved, the better.
Original problem
My original problem is a TextBox (WPF) that I want to complement depending on its content and focus. So I need to react on TextChanged event and I also need to omit that event if its origin is derived from my complements. In some cases, other listeners of a TextChanged event shouldn't be notified. Some strings in certain state and style are invisible to others.
If it is so important not to handle events you initiated, maybe you should change the way you set Property to include the initiator of the change?
public class MyEventArgs : EventArgs
{
public object Changer;
}
public void SetProperty(string p_newValue, object p_changer)
{
MyEventArgs eventArgs = new MyEventArgs { Changer = p_changer };
PropertyChanged(this, eventArgs);
}
And then in your handler - simply check your are not the initiator.
I find all these changes in registration and members very problematic in terms on multi threading and extensibility.
Well essentially you are trying to break the event delegation mechanism and any "solution" to that is going to be brittle since updates to the BCL might break your code. You could set the backing field using reflection. This of course would require that you do have permissions to do this and seeing the generic framing of the question it might not always be that you have the needed permissions
public void MakeProblem()
{
if (_backingField == null) {
_backingField = = _a.GetType().GetField(fieldname)
}
_backingField.SetValue(_a,"make a problem");
}
but as I started out, you are trying to break the event delegation mechanism. The idea is that the receivers of the event are independent. Disabling might lead to so very hard to find bugs because looking at any given piece of code it looks correct but only when you realize that some devious developer has hack the delegation mechanism do you realize why the information that is shown on screen seems to be a cached version of the actual value. The debugger shows the expected value of the property but because the event was hidden the handler responsible for updating the display was never fired and hence an old version is displayed (or the log shows incorrect information so when you are trying to recreate a problem a user has reported based on the content of the log you will not be able to because the information in the log is incorrect because it was based on no one hacking the event delegation mechanism
To my opinion your solution is possible, though I would have created a nested IDisposable class inside B that does the same thing with 'using', or put the '_dontDoThis = false' inside a 'finally' clause.
class A
{
// May be set by a code or by an user.
public string Property
{
set { if (!_dontDoThis) PropertyChanged(this, EventArgs.Empty); }
}
public EventHandler PropertyChanged;
bool _dontDoThis;
}
class B
{
private class ACallWrapper : IDisposable
{
private B _parent;
public ACallWrapper(B parent)
{
_parent = parent;
_parent._a._dontDoThis = true;
}
public void Dispose()
{
_parent._a._dontDoThis = false;
}
}
private A _a;
public B(A a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Who changed the Property?
}
public void MakeProblem()
{
using (new ACallWrapper(this))
_a.Property = "make a problem";
}
}
On the other hand, I would've used the 'internal' modifier for these things if those two classes are inside the same assembly.
internal bool _dontDoThis;
That way, you keep a better OOP design.
Moreover, if both classes are on the same assembly, I would've written the following code inside A:
// May be set by a code or by an user.
public string Property
{
set
{
internalSetProperty(value);
PropertyChanged(this, EventArgs.Empty);
}
}
internal internalSetProperty(string value)
{
// Code of set.
}
In this case, B could access internalSetProperty without triggering to PropertyChanged event.
Thread Sync:
NOTE: The next section applies to WinForms - I don't know if it applies to WPF as well.
For thread synchronizations, because we're referring to a control. you could use the GUI thread mechanism for synchronization:
class A : Control
{
public string Property
{
set
{
if (this.InvokeRequired)
{
this.Invoke((Action<string>)setProperty, value);
reutrn;
}
setProperty(value);
}
}
private void setProperty string()
{
PropertyChanged(this, EventArgs.Empty);
}
}
Great question.
As a general case, you can not mess around with event handlers of sealed classes. Normally you could override A's hypothetical OnPropertyChanged and based on some flag either raise the event or not. Alternatively you could provide a setter method that does not raise event, as suggested by #Vadim. However, if A is sealed your best option is to add flag to a lister, just as you did. That will enable you to recognize PropertyChanged event raised by B, but you won't be able to suppress the event for other listeners.
Now, since you provided context... There is a way of doing exactly this in WPF. All that needs to be done is B's handler for TextBox.TextChanged needs to set e.Handled = _dontDoThis. That will supress notifications for all other listeners, provided B's one was added as the first one. How to make sure this happens? Reflection!
UIElement exposes only AddHandler and RemoveHandler methods, there is no InsertHandler that would allow to manually specifiy the priority for the handler. However, a quick peek into .NET source code (either download the whole thing or query what you need) reveals that AddHandler forwards arguments to an interal method EventHandlersStore.AddRoutedEventHandler, which does this:
// Create a new RoutedEventHandler
RoutedEventHandlerInfo routedEventHandlerInfo =
new RoutedEventHandlerInfo(handler, handledEventsToo);
// Get the entry corresponding to the given RoutedEvent
FrugalObjectList<RoutedEventHandlerInfo> handlers = (FrugalObjectList<RoutedEventHandlerInfo>)this[routedEvent];
if (handlers == null)
{
_entries[routedEvent.GlobalIndex] = handlers = new FrugalObjectList<RoutedEventHandlerInfo>(1);
}
// Add the RoutedEventHandlerInfo to the list
handlers.Add(routedEventHandlerInfo);
All this stuff is internal, but can be recreated using reflection:
public static class UIElementExtensions
{
public static void InsertEventHandler(this UIElement element, int index, RoutedEvent routedEvent, Delegate handler)
{
// get EventHandlerStore
var prop = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.NonPublic | BindingFlags.Instance);
var eventHandlerStoreType = prop.PropertyType;
var eventHandlerStore = prop.GetValue(element, new object[0]);
// get indexing operator
PropertyInfo indexingProperty = eventHandlerStoreType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(x => x.Name == "Item" && x.GetIndexParameters().Length == 1 && x.GetIndexParameters()[0].ParameterType == typeof(RoutedEvent));
object handlers = indexingProperty.GetValue(eventHandlerStore, new object[] { routedEvent });
if (handlers == null)
{
// just add the handler as there are none at the moment so it is going to be the first one
if (index != 0)
{
throw new ArgumentOutOfRangeException("index");
}
element.AddHandler(routedEvent, handler);
}
else
{
// create routed event handler info
var constructor = typeof(RoutedEventHandlerInfo).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();
var handlerInfo = constructor.Invoke(new object[] { handler, false });
var insertMethod = handlers.GetType().GetMethod("Insert");
insertMethod.Invoke(handlers, new object[] { index, handlerInfo });
}
}
}
Now calling InsertEventHandler(0, textBox, TextBox.TextChangedEvent, new TextChangedEventHandler(textBox_TextChanged)) will make sure your handler will be the first one on the list, enabling you to suppress notifications for other listeners!
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var textBox = new TextBox();
textBox.TextChanged += (o, e) => Console.WriteLine("External handler");
var b = new B(textBox);
textBox.Text = "foo";
b.MakeProblem();
}
}
class B
{
private TextBox _a;
bool _dontDoThis;
public B(TextBox a)
{
_a = a;
a.InsertEventHandler(0, TextBox.TextChangedEvent, new TextChangedEventHandler(Handler));
}
void Handler(object sender, TextChangedEventArgs e)
{
Console.WriteLine("B.Handler");
e.Handled = _dontDoThis;
if (_dontDoThis)
{
e.Handled = true;
return;
}
// do this!
}
public void MakeProblem()
{
try
{
_dontDoThis = true;
_a.Text = "make a problem";
}
finally
{
_dontDoThis = false;
}
}
}
Output:
B.Handler
External handler
B.Handler
I found one solution with regard to third parties, that are connected to the property and we don't want to nofify them when that property changed.
There are though the requirements:
We are capable of override the A.
The A has a virtual method that is invoked when property changed and allows to suspend the event to be raised.
The event is raised immediately when property is being changed.
The solution is to replace the A by MyA, as follows:
class A
{
// May be set by a code or by an user.
public string Property
{
set { OnPropertyChanged(EventArgs.Empty); }
}
// This is required
protected virtual void OnPropertyChanged(EventArgs e)
{
PropertyChanged(this, e);
}
public EventHandler PropertyChanged;
}
// Inject MyA instead of A
class MyA : A
{
private bool _dontDoThis;
public string MyProperty
{
set
{
try
{
_dontDoThis = true;
Property = value;
}
finally
{
_dontDoThis = false;
}
}
}
protected override void OnPropertyChanged(EventArgs e)
{
// Also third parties will be not notified
if (_dontDoThis)
return;
base.OnPropertyChanged(e);
}
}
class B
{
private MyA _a;
public B(MyA a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Now we know, that the event is not raised by us.
}
public void MakeProblem()
{
_a.MyProperty = "no problem";
}
}
Unfortunately we still use back bool field and we assume a single thread. To rid of the first, we could use a refactored solution suggest by EZSlaver (here). First, create a disposable wrapper:
class Scope
{
public bool IsLocked { get; set; }
public static implicit operator bool(Scope scope)
{
return scope.IsLocked;
}
}
class ScopeGuard : IDisposable
{
private Scope _scope;
public ScopeGuard(Scope scope)
{
_scope = scope;
_scope.IsLocked = true;
}
public void Dispose()
{
_scope.IsLocked = false;
}
}
Then the MyProperty might be refactored to:
private readonly Scope _dontDoThisScope = new Scope();
public string MyProperty
{
set
{
using (new ScopeGuard (_dontDoThisScope))
Property = value;
}
}
In one of my WPF projects, I was creating an animation storyboard in XAML which had timing properties that could be dynamically changed before kicking off the animation. Since I needed a way to change the values in code, I bound them to properties of the class.
The basic idea was that there are two phases to the animation, and in the storyboard I use ObjectAnimationUsingKeyFrames which takes the total animation time, so I have properties like so:
public TimeSpan RaiseTime { get; set }
public TimeSpan FallTime { get; set; }
public TimeSpan TotalTime
{
get { return RaiseTime + FallTime; }
}
When the animation is first created, it gets the values from these properties correctly, but since they can be changed dynamically I need a way to notify the XAML that the values have changed.
It's easy enough to turn RaiseTime and FallTime into DependencyPropertys so that their changes will be reflected in the XAML bindings, but what about TotalTime? It doesn't have a value itself so I can't turn it into a DP.
Yesturday I spent some hours searching/trying random stuff to try and get this to work and eventually got something to work using a MultiBinding to both RaiseTime and FallTime and a IMultiValueConverter, thanks to a few SO questions and a blog post:
Bind an element to two sources
http://blog.wpfwonderland.com/2010/04/15/simplify-your-binding-converter-with-a-custom-markup-extension/
My question is: Is this really the best way to do it? It seems (to me atleast) such a simple task, yet it requires so much (mostly boilerplate) code to get working. I thought there must be a simpler, less verbose way of binding TotalTime and pushing updates to XAML, but I have yet to find one. Is there, or am I just dreaming?
You can definitely make it work using INotifyPropertyChanged, then simply binding to the class.
The code will look something like this: (untested code)
public class PleaseChangeTheNameOfThisClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private TimeSpan _raiseTime;
public TimeSpan RaiseTime
{
get { return _raiseTime; }
set
{
if (_raiseTime != value)
{
_fallTime = value;
RaisePropertyChanged("RaiseTime");
RaisePropertyChanged("TotalTime");
}
}
}
private TimeSpan _fallTime;
public TimeSpan FallTime
{
get { return _fallTime; }
set
{
if (_fallTime != value)
{
_fallTime = value;
RaisePropertyChanged("FallTime");
RaisePropertyChanged("TotalTime");
}
}
}
public TimeSpan TotalTime
{
get { return RaiseTime + FallTime; }
}
}
It may still be quite verbose, but you can in fact do this with DependencyProperties:
Attach "OnChanged" callbacks to the RaiseTime and FallTime DPs wich update the TotalTime DP, and make TotalTime read-only (different DP registration syntax, and only a private setter):
public TimeSpan RaiseTime
{
get { return (TimeSpan)GetValue(RaiseTimeProperty); }
set { SetValue(RaiseTimeProperty, value); }
}
public static readonly DependencyProperty RaiseTimeProperty =
DependencyProperty.Register("RaiseTime", typeof(TimeSpan), typeof(MainWindow),
new PropertyMetadata(TimeSpan.Zero, OnRaiseTimeChanged));
private static void OnRaiseTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var owner = sender as MainWindow;
owner.TotalTime = owner.RaiseTime + owner.FallTime;
}
public TimeSpan FallTime
{
get { return (TimeSpan)GetValue(FallTimeProperty); }
set { SetValue(FallTimeProperty, value); }
}
public static readonly DependencyProperty FallTimeProperty =
DependencyProperty.Register("FallTime", typeof(TimeSpan), typeof(MainWindow),
new PropertyMetadata(TimeSpan.Zero, OnFallTimeChanged));
private static void OnFallTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var owner = sender as MainWindow;
owner.TotalTime = owner.RaiseTime + owner.FallTime;
}
/// <summary>
/// Read-only DP:
/// http://msdn.microsoft.com/en-us/library/ms754044.aspx
/// http://www.wpftutorial.net/dependencyproperties.html
/// </summary>
public TimeSpan TotalTime
{
get { return (TimeSpan)GetValue(TotalTimeProperty); }
private set { SetValue(TotalTimePropertyKey, value); }
}
public static readonly DependencyPropertyKey TotalTimePropertyKey =
DependencyProperty.RegisterReadOnly("TotalTime", typeof(TimeSpan), typeof(MainWindow),
new PropertyMetadata(TimeSpan.Zero));
public static readonly DependencyProperty TotalTimeProperty = TotalTimePropertyKey.DependencyProperty;
The default values need to add up (here: 0 + 0 = 0). After that, OnRaiseTimeChanged and OnFallTimeChanged will keep TotalTime updated.
As your link suggests, MultiBinding is the standard way for this.
If you want obscure solution, you can create custom markup extension.
I think this is a bit more concise again. See "CallerMemberName" which is available in the latest C#.
public sealed class ViewModel : INotifyPropertyChanged
{
private TimeSpan _raiseTime;
private TimeSpan _fallTime;
public TimeSpan RaiseTime
{
get { return _raiseTime; }
private set
{
if (SetProperty(ref _raiseTime, value))
{
OnPropertyChanged("TotalTime");
}
}
}
public TimeSpan FallTime
{
get { return _fallTime; }
private set
{
if (SetProperty(ref _fallTime, value))
{
OnPropertyChanged("TotalTime");
}
}
}
public TimeSpan TotalTime
{
get { return RaiseTime + FallTime; }
}
#region Put these in a base class...
private bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I have the following WPF code. You can see in the comments there that I have a problem with my OnValueChanged handler. I need the code there to differentiate between a Value set from the UI (through various bindings) and one set from the manager class. I had hoped that DependencyPropertyChangedEventArgs would have some kind of source that I could use to differentiate this, but I don't see anything like that. Ideas? Is there some way to set a WPF DependencyProperty without triggering its PropertyChanged handler? Thanks for your time.
public class GaugeBaseControl : UserControl
{
protected readonly AssetModelManager Manager;
public GaugeBaseControl(AssetModelManager mgr)
{
Manager = mgr;
if(mgr != null)
mgr.TelemetryValueChanged += MgrOnTelemetryValueChanged; // coming on background thread
}
private void MgrOnTelemetryValueChanged(KeyValuePair<string, object> keyValuePair)
{
if(_localTelemetryId != keyValuePair.Key)
return;
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
if (!Equals(Value, keyValuePair.Value))
Value = keyValuePair.Value;
}));
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var gbc = (GaugeBaseControl) d;
var id = gbc.TelemetryId;
if (!string.IsNullOrEmpty(id))
{
// this is the problem:
// I need to always set gbc.Manager[id] if this event was triggered from the UI (even when equal)
// however, if it was triggered by TelemetryValueChanged then we don't want to go around in circles
if (!Equals(gbc.Manager[id], e.NewValue))
gbc.Manager[id] = e.NewValue;
}
}
private string _localTelemetryId; // to save us a cross-thread check
private static void OnTelemetryIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var gbc = (GaugeBaseControl)d;
var tid = gbc.TelemetryId;
gbc._localTelemetryId = tid;
gbc.Value = string.IsNullOrEmpty(tid) ? null : gbc.Manager[tid];
}
public static readonly DependencyProperty TelmetryIdProperty = DependencyProperty.Register("TelemetryId", typeof(string), typeof(GaugeBaseControl), new PropertyMetadata(OnTelemetryIdChanged));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(GaugeBaseControl), new PropertyMetadata(OnValueChanged));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value);}
}
public string TelemetryId
{
get { return (string)GetValue(TelmetryIdProperty); }
set { SetValue(TelmetryIdProperty, value); }
}
}
It seems a bit hackish, but it is the best shot i could come up with without changing the architecture. You could stop listening on the TelemetryValueChanged event while doing your internal update to stop the roundtrip like so:
internal void SetManagerIdInternal(string id, object value)
{
if(mgr != null)
{
mgr.TelemetryValueChanged -= MgrOnTelemetryValueChanged;
mgr[id] = value;
mgr.TelemetryValueChanged += MgrOnTelemetryValueChanged;
}
}
And use it like this:
if (!Equals(gbc.Manager[id], e.NewValue))
SetManagerIdInternal(id, e.NewValue);
You could also use a private field to just skip doing work without unregistering/reregistering the event in MgrOnTelemetryValueChanged wich might be better performance wise, but i haven't tested it.