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.
Related
I have a dependency property in a user control
public List<Exclusion> SelectedExclusions
{
get { return (List<Exclusion>)GetValue(SelectedExclusionsProperty); }
set { SetValue(SelectedExclusionsProperty, value); }
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions), typeof(List<Exclusion>), typeof(TimeSeriesChart), new PropertyMetadata(new List<Exclusion>()));
The list is populated on delete key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.Exclusions[this.Index]);
}
}
}
In the view model I new up a list & public property. A delete command is invoked from the setter:
private IList<Exclusion> selectedExclusionsToDelete = new List<Exclusion>();
public IList<Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
//Delete the selected exclusion
ExecuteDeleteSelectedExclusionsCommand();
this.RaisePropertyChanged();
}
}
Finally, my xaml binding in the view:
SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
My problem is that at run time the SelectedExclusions dp getter returns null instead of a List<Exclusion> Is there something I'm missing here given that the PropertyMetadata value is of type List<Exclusion>?
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.
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();
}
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.
I have the following code, which is part of a work-around for binding null values to a ComboBox. (Let's assume I'm not supportive of changing this implementation).
SetBinding(ComboBox.SelectedValueProperty, _cachedBinding);
The question is: does this line of code lead to Source updating Target and vice-versa?
So if I have this property in the view model:
string MyProperty
{
get { return _myProperty; }
set
{
myProperty = value;
IsDirty = true;
}
}
then the setter will be called when I call SetBinding. That will be troublesome since in the setter I'm also managing the `IsDirty property to keep track of changes. I can code:
string MyProperty
{
get { return _myProperty; }
set
{
if (myProperty != value)
{
myProperty = value;
IsDirty = true;
}
}
}
but I feel that would be inelegant since I have a lot of properties out there.
So my second question is (assuming the answer to first question is yes), can I force Silverlight to not update Target or Source after SetBinding() (so they would update only when they are changed)?
How this workaraound works: http://surrealization.com/sample-code/fixing-combobox-selectedvalue-bug-with-expression-behaviors/. _cachedBinding thus contains the binding that was set before MyProperty became null and the binding disappeared (so it's the same binding that was there (not same as an object though, since it was created anew)).
Looking at the code once again, that might be even the same object, but it depends on how GetBindingExpression and ParentBinding work in .NET.
Here is the fully binded Combobox subclass:
public class ComboBoxFixedBinding : ComboBox
{
#region Events
private void ComboBoxEx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (SelectedValue == null)
{
if (_cachedBinding != null && GetExistingBinding() == null)
{
SetBinding(ComboBox.SelectedValueProperty, _cachedBinding);
}
}
else
{
CacheExistingBinding();
}
}
private void CacheExistingBinding()
{
Binding binding = GetExistingBinding();
if (binding != null)
{
_cachedBinding = binding;
}
}
private Binding GetExistingBinding()
{
BindingExpression bindingExpr = this.GetBindingExpression(ComboBox.SelectedValueProperty);
if (bindingExpr == null)
{
return null;
}
else
{
return bindingExpr.ParentBinding;
}
}
#endregion
public ComboBoxFixedBinding()
{
SelectionChanged += ComboBoxEx_SelectionChanged;
}
private Binding _cachedBinding;
}