I created a custom UserControl. Following a blog post my control code-behind looks like this:
public BasicGeoposition PinGeoposition
{
get { return (BasicGeoposition) GetValue(PropertyPinGeoposition); }
set { SetValueDp(PropertyPinGeoposition, value);}
}
public static readonly DependencyProperty PropertyPinGeoposition =
DependencyProperty.Register("PinGeoposition", typeof(BasicGeoposition), typeof(CustomMapControl), null);
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value, [System.Runtime.CompilerServices.CallerMemberName] String p = null)
{
ViewModel.SetMode(ECustomMapControlMode.Default);
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
Use of my control:
<customControls:CustomMapControl Mode="ForImage" PinGeoposition="{Binding Geoposition, Mode=TwoWay}" Grid.Row="1"/>
Finally in the pages' ViewModel where I use my control I have:
public BasicGeoposition Geoposition
{
get { return _geoposition; }
set
{
if (Set(ref _geoposition, value))
{
RaisePropertyChanged(() => Geoposition);
}
}
}
I expect that every change of Geoposition in ViewModel to be reflected in SetValueDp. Unfortunately, it doesn't work.
Not sure what Jerry Nixon was trying to do on his blog article, as he didn't assign his SetValueDp method anywhere.
If you want it to be called, you can do something like that:
public static readonly DependencyProperty PropertyPinGeoposition =
DependencyProperty.Register("PinGeoposition", typeof(BasicGeoposition), typeof(CustomMapControl), new PropertyMetadata(null, SetPosition));
public BasicGeoposition PinGeoposition
{
get { return (BasicGeoposition) GetValue(PropertyPinGeoposition); }
set { SetValue(PropertyPinGeoposition, value);}
}
private static void SetPosition(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = (CustomMapControl)sender;
var position = e.NewValue as BasicGeoposition;
// Do whatever
}
Edit: After reading and re-reading the blog article, I think I got it backwards (and so did you, probably). From what I now understand, SetValueDp is a helper method you're supposed to call whenever you want to change the value of the dependency property. This is not something called automatically. So if you want a method that is called whenever the DP is modified, check my solution instead.
Related
I want to create a simple Control to enter Text.
For this Control i want to create a Property.
This Property should be bind to the ViewModel.
I Created a Model for an Folder:
public class FolderModel : ModelBase
{
private string fullPath;
public string FullPath
{
get { return fullPath; }
set
{
if (fullPath == value)
return;
fullPath = value;
RaisePropertyChanged(nameof(FullPath));
}
}
}
In my View xaml i bound the Data to my ViewModel:
<UserControl.DataContext>
<Browser:FolderBrowserViewModel/>
</UserControl.DataContext>
<Grid>
<TextBox x:Name="TextBox_Folder" IsReadOnly="True" Text="{Binding Folder.FullPath}"/>
</Grid>
To Create the Property created this code in the View:
public string FullPath
{
get { return (string)GetValue(FolderFullPathProperty); }
set { SetValue(FolderFullPathProperty, value); }
}
public static readonly DependencyProperty FolderFullPathProperty = DependencyProperty.Register("FullPath", typeof(string), typeof(FolderBrowser), new PropertyMetadata("", new PropertyChangedCallback(OnSetTextChanged)));
private static void OnSetTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FolderBrowser UserControl1Control = d as FolderBrowser;
UserControl1Control.OnSetTextChanged(e);
}
private void OnSetTextChanged(DependencyPropertyChangedEventArgs e)
{
TextBox_Folder.Text = e.NewValue.ToString();
}
My ViewModel looks like this:
private ICommand _browseFolderCommand;
public ICommand BrowseFolderCommand
{
get
{
return _browseFolderCommand is null ? (_browseFolderCommand = new RelayCommand(() => BrowseFolder(), true)) : _browseFolderCommand;
}
}
private FolderModel folder = new FolderModel();
public FolderModel Folder
{
get { return folder; }
set
{
if (folder == value)
return;
folder = value;
}
}
private void BrowseFolder()
{
System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog();
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
SetField(ref folder, Folder, "FullPath");
}
}
and my ViewModelBase looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected void Dispatch(Action f) => Application.Current.Dispatcher.Invoke(f);
protected TResult Dispatch<TResult>(Func<TResult> f) => Application.Current.Dispatcher.Invoke(f);
}
if i use the Control i want to set the Property "FullPath"
when i set this Property the Value should be set to the ViewModels Folder.FullPath.
If this is set, the Binding of the TextBox and Folder.FullPath should show the correct path.
if its possible i want all Code in the ViewModel =)
Edit:
I tried to illustrate it a bit more pictorially:
On the left side you can see the control as it is placed on a window. There you can set MyProp to any value and the TextBox will receive it.
On the right side I have tried to show it in more detail.
The view has the label, a TextBox and a button.
The view also has the property "MyProp".
The textbox of the view is bound to the ViewModel, namely to the field "MyCoolFieldValue".
This means that if I do anything in the ViewModel with the field MyCoolFieldValue, I know that it will always have the value that is in the textbox.
If I now press the button, a command is called. This command changes the WErt of MyCoolFieldValue. When this happens, the value should be written directly back into the property and the textbox of the view.
However, I can't get this to work and have tried it with the code above.
I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...
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.
In my usercontrol.xaml.cs. I have this dependency proprerty as bleow.
public static readonly DependencyProperty MessageKeyProperty =
DependencyProperty.Register("MessageKey", typeof(String),
typeof(UC_MessageEntry),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MessageKeyPropertyChangedCallback)));
private static void MessageKeyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UC_MessageEntry)
{
UC_MessageEntryucMessageEntryAccessDenied = (UC_MessageEntry)d;
if (e.NewValue != null)
{
ResourceBundle resourceBundle = App.ResourceBundle;
if (e.NewValue.ToString().Equals("enable"))
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg.Text = "";
return;
}
String actualMessage = resourceBundle.GetString("Resources", e.NewValue.ToString());
if (actualMessage == null)
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg.Text = resourceBundle.GetString("Resources", "ContractSetup.ExchangeAccessDeniedMessage.OTHERS");
}
else
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg.Text = actualMessage;
}
}
else
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg = null;
}
}
}
public String MessageKey
{
get
{
return (String)this.GetValue(MessageKeyProperty);
}
set
{
this.SetValue(MessageKeyProperty,value);
}
}
In mainwindow.xaml , i bind this MessageKey as below.
<view_MessageEntry:UC_MessageEntry
x:Uid="local:UC_MessageEntry_1" x:Name="UC_OrderEntry" MessageKey="{Binding MsgAccessDenied}"
Style="{DynamicResource contentControlStyle}" SnapsToDevicePixels="True" Margin="0" />
And Behind MessageViewModle.cs,
private static readonly PropertyChangedEventArgs MsgAccessDeniedPropertyChangedEventArgs
= new PropertyChangedEventArgs("MsgAccessDenied");
private string _msgAccessDenied;
public string MsgAccessDenied
{
get
{
if (_selectedExchange != null)
{
return _msgAccessDenied;
}
else
{
return "enable";
}
}
set
{
_msgAccessDenied = value;
this.RaisePropertyChanged(this, MsgAccessDeniedPropertyChangedEventArgs);
}
}
public void FireMsg()
{
this.MsgAccessDenied="value";
}
When the combo box selection is changed, I called FireMsg() and it will update the MessageKeyPropertyChangedCallback function in usercontrol.xaml.cs. It's working fine. But If I call this FireMsg() from Other ViewModels, value of _msgAccessDenied is updated. But MessageKeyPropertyChangedCallback function is not firing. Any solution for this issue? Thanks.
Your code looks generally sound. But if your dependency property change callback is not being called, it is almost certainly because the value of the dependency property isn't changing. And if the dependency property is bound to a source, then it is probably because the source isn't changing.
From my quick review of the code, the only way that I can see that happening is:
the value of MsgAccessDenied is set
this causes the field _msgAccessDenied to be set
the RaisePropertyChanged method is called to trigger PropertyChanged
the dependency subsystem does its thing which forces a get of MsgAccessDenied
the MsgAccessDenied getter is called
the getter checks _selectedExchange and it is null
the getter returns the value "enable" instead of the new value of _msgAccessDenied
the previous value was also "enable"
the dependency subsystem says, OK, no change
the property change callback is not called
In summary, _selectedExchange can hide value changes in _msgAccessDenied, thereby preventing the upstream change callback from firing.
This is just a theory.