I've been researching for many hours the following situation: I have a xaml defined window that makes use of a usercontrol (ToggleButton) with some dependency properties.
The underlying viewmodel of the window contains some boolean objects that represent the state of devices (on/off) and others represent a request to toggle a device with a true/false flank (a PLC is connected to these and communication works fine).
Hence there are 2 DP's on the usercontrol:
The one to toggle the devices (binding mode OneWayToSource with an UpdateSourceTrigger.Explicit work fine (indicating to me that basics like shared DataContext is fine and not "disrupted" anywhere).
However the binding indicating the other DP (device state with binding mode OneWay) shows the following symptoms:
The (PLC-)device is off (false) before starting the program
Result: The DeviceState property is at the default value of false.
Set is called the first time when the device is switched on
(underlying viewmodel object changes to true, reports this via
PropertyChanged notification) and the DependencyPropertyChanged is
being called correctly. Further switches to off/on (false/true)
again don't result in "set" being called again (although
PropertyChanged on the underlying object is again called).
The device is on (true) before starting the program
Result: The DP Handler is triggered at
the start of the program and no change to
false or true lets it be called again.
What I've tried already for tracking this down is:
Implemented a DummyDebugConverter.
Result: I see that it's fired also only once. So giving me no further clue
Analyzed the Output Window and found the following message:
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=bLightState.Value; DataItem='ControlPanelModel' (HashCode=45596481); target element is 'AdsButton' (Name='btnLight'); target property is 'DeviceState' (type 'Boolean')
Debugging this didn't give me a clue. My breakpoints e.g. in the debugging converter or the set method never showed me a null-value anywhere. All values in the viewmodel constructor are initialized with default values. But I see the message always just one single time and I assume it relates to the problem somehow.
Used the same binding expression for testing purposes on some other elements (a label and a toggle button) besides my usercontrol. They work nicely and are updating their values as expected as soon as the object in the viewmodel changes (desired behaviour). The message in 2 disappers if I remove my usercontrol.
So I come to the conclusion that the error is in my definition of the DP's.
Here are the relevant code snippets:
AdsButton.xaml.cs
[Description("When set to true the device is shown as on"), Category("Default")]
public bool DeviceState
{
get { return (bool)GetValue(DeviceStateProperty); }
set { SetValue(DeviceStateProperty, value); }
}
public static readonly DependencyProperty DeviceStateProperty =
DependencyProperty.Register(
"DeviceState", typeof(bool),
typeof(AdsButton),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.None,
DeviceStateChanged,
CoerceDeviceStateProperty,
true,
UpdateSourceTrigger.Explicit));
private static void DeviceStateChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
(d as AdsButton).DeviceState = (bool) e.NewValue;
}
private static object CoerceDeviceStateProperty(DependencyObject d, object value)
{
return value ?? false;
}
ControlPanel.xaml
<src:AdsButton x:Name="btnLight"
Value="{Binding Path=bLight.Value, Mode=OneWayToSource}"
DeviceState="{Binding Path=bLightState.Value, Mode=OneWay}" />
<Label Content="{Binding bLightState.Value, Mode=OneWay}" />
<ToggleButton Content="Button" IsChecked="{Binding bLightState.Value, Mode=OneWay}" />
So does anybody know: Why is my own DP reacting differently from the ones in standard controls?
Thanks to the initial comment by Roger... the answer is obvious:
Setting the DP itself in the setter method overwrites the binding with a fixed value (which effectively removes it), but only if the new value is different from the old one.
Related
I'm fairly new to WPF. I have the following radio button in my application
<Viewbox Height="30">
<RadioButton Content="B1" GroupName="InputBelt" IsChecked="{Binding RBChecked, Mode=TwoWay, FallbackValue=True}" VerticalAlignment="Center"/>
</Viewbox>
<Viewbox Height="30">
<RadioButton Content="B2" GroupName="InputBelt" IsChecked="{Binding RBChecked, Converter={StaticResource boolconverter}, Mode=TwoWay}" VerticalAlignment="Center"/>
</Viewbox>
I have defined datacontext in xaml file
<Window.DataContext>
<vm:TestViewModel />
</Window.DataContext>
The issue is when the page is loaded for the 1st time, everything is fine. But when I go to some other page in the application and comes back to this page, the application crashes due to stackoverflow exception.
I even tried adding datacontext locally in radiobutton tag but it isn't working either.
Property structure given below.
private bool _bRBChecked;
public bool RBChecked
{
get { return _bRBChecked; }
set
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Upon investigating further, I found out that the RaisePropertyChanged of the binded property is being called too many times. This issue occurs only with the property binded to radio button. I have other controls which has two-way bind with other properties in the same page and it seems to work fine.
Now I have tried the below fix suggested in another stackoverflow question and it seems to be working.
set
{
if (_bRBChecked != value)
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
But I would like to know the root cause of this issue and why the property is being set so many times and find any alternate fix if possible. Please let me know if I am missing anything.
Any insight is highly appreciable.
Your change notification is not protected from recursion. Property A changing Property B, whose change changes Property A...
A simple solution is this:
set
{
if(value != _bRBChecked){
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Simply check if the value is actually a change, before you go tell everyone about it. This pattern is explicitly used in the Examples. I am 90% sure the Depdency Properties have a similar recursion protection, but it would not be the first time I was wrong.
I think it is fairly easy to figure this out, based on the fix you shared.
What happens in steps:
You set the new value in one of the radio buttons
The event is raised
Since it's two way binding, the binding of the second radio button sets the value again to the other radio button
The event is raised again due to 3
Go back to 1 as now the value is set again for the first radio button.
With your fix the value is not set (the setter it's not called) so the event is not triggered again.
We are converting an app from Silverlight to WPF. It's a fairly complex app, but the code sharing is about 95% +. The XAML is pretty much all the same except for XML namespace definitions etc. About 90% of the app now works but there are a few glaring issues that are puzzling me. One is this binding issue.
We have a model object called TaskInfo. It has a property called TaskNo. in Silverlight and WPF we bind to this property like this
<TextBox IsReadOnly="True" Grid.Column="0" Grid.Row="0" Margin="1" Text="{Binding Path=TaskNo}" Height="28" Background="#CAECF4" VerticalAlignment="Center" VerticalContentAlignment="Center" />
In both WPF and Silverlight the TaskNo is correctly displayed when the TaskInfo model is first set as the DataContext. In Silverlight, if we create a new TaskInfo, send it to the server for saving, and return the model with a new TaskNo, the TaskNo is successfully displayed. But, in WPF, it just displays 0 when the saved TaskInfo is returned from the server. There is some issue with binding. This is the binding error I see in the output window:
System.Windows.Data Information: 10 : Cannot retrieve value using the
binding and no valid fallback value exists; using default instead.
BindingExpression:Path=TaskNo; DataItem=null; target element is
'TextBlock' (Name=''); target property is 'Text' (type 'String')
I inspected the visual tree and the TextBox's DataContext is set to the TaskInfo as expected.
So, I turned off binding and tried this code. It's the event handler for the DataContextChanging on the TextBox. This code works fine. When a new task is saved and returned, the TaskNo successfully displays here:
private void TaskNoBox_DataContextChanging(object sender, DependencyPropertyChangedEventArgs e)
{
var task = TaskNoBox.DataContext as TaskInfo;
if (task == null)
{
throw new Exception("Ouch!");
}
TaskNoBox.Text = task.TaskNo.ToString();
}
To further debug this problem, I added this event handler for the GotFocus event on the text box. So, after the task has been saved on the server side and has been returned and set as the DataContext, I click inside the control to fire this event handler. When I step through this code, I can see that the DataContext is correct, and has the correct TaskNo. Calling this code still doesn't cause the binding to occur.
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
var be = textBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
be.UpdateTarget();
}
TextBox Text binding property:
DataContext of TextBox's properties:
How do I make sense of this binding error? What are the binding gotchas between Silverlight and WPF? Do I need some kind of workaround? Why is binding not working?
Binding in WPF never updates if the previous DataContext is equivalent to the new DataContext according to the Equals method.
The difference between Silverlight and WPF seems to be that when the DataContext changes, WPF seems to use the Equals method to evaluate difference between objects while Silverlight uses the reference. That means that WPF is the same as Xamarin.Forms.
I tried this code, and it causes the TaskNo to display correctly. I think what is happening is that because the previous DataContext was equivalent to the new DataContext when Equals is called. So, this works around the problem.
private async void TaskPageHeader_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TaskNoBox.DataContext = new object();
TaskNoBox.DataContext = CurrentTask;
}
I have a dependency property RecordContainerGenerationMode defined for XamDatagrid , irrespective of what the user sets in the XAML i need it to default to a specific value PreLoad .
How do i accomplish this ? The Xamdatagrid is a infragistics grid which really allow me to hide the dependency implementation.
I tried setting the value in the constructor of the xamdatagrid but the XAML defined value is overwritten onto it.
<Controls:XamDataGrid Grid.Row="1"
HorizontalAlignment="Stretch"
x:Name="gridTrdDetail"
DataSource="{Binding Items}"
SelectedRecords="{Binding SelectedObjects, Mode=TwoWay}"
IncludeDefaultCommands="True"
VerticalAlignment="Stretch"
ScrollingMode="Immediate"
CellContainerGenerationMode="Recycle"
GroupByAreaMode="MultipleFieldLayoutsCompact"
RecordContainerGenerationMode="PreLoad" SelectedSum="{Binding Sum,Mode=OneWayToSource}"
IsSynchronizedWithCurrentItem="True">
and this is how i set it in constructor : -
public XamDataGrid()
{
this.SetValue(XamDataGrid.RecordContainerGenerationModeProperty,ItemContainerGenerationMode.PreLoad);
}
Has anyone run into this kinda issue anywhere?
I'm not sure why you would want to do something like that. Like Anatolii Gabuza said, definite code smell there. It's something you'll need to really rethink.
The way everything gets generated is the control gets constructed (calling default constructor), then the properties in XAML get assigned, then if there are data bindings, the value gets updated at run time.
If you don't want the value to change, the easiest way is just don't expose it! If you need to read the value, maybe you can try a read only dependency property.
If you REALLY want it to expose the Dependency Property, you can set it in the property changed event. But, that kind of defeats the purpose of having a dependency property in the first place.
public static readonly DependencyProperty RecordContainerGenerationModeProperty = DependencyProperty.Register(
"RecordContainerGenerationMode",
typeof(ItemContainerGenerationMode),
typeof(XamDataGrid),
new PropertyMetadata(ItemContainerGenerationMode.PreLoad, OnRecordContainerGenerationModeChanged));
private static void OnRecordContainerGenerationModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
XamDataGrid control = obj as XamDataGrid;
if (control != null)
{
ItemContainerGenerationMode newMode = (ItemContainerGenerationMode)args.NewValue;
if (newMode != ItemContainerGenerationMode.PreLoad)
{
control.RecordContainerGenerationMode = ItemContainerGenerationMode.PreLoad;
}
}
}
I am trying to set an entire set of controls within a panel to read-only (e.g. if a user has no permission to edit) through data-binding and an attached property.
(I am aware of the fact that setting a panel to disabled also disables its children, but this is too much, since it would also disable hyperlinks, lists, etc.)
Basically, the property changed event handler iterates the visual tree and finds all TextBox children and then sets their IsReadOnly property to either true or false.
This works, but does not cover the case where the TextBox already has a IsReadOnly setting - either const or binding. For example if a TextBox should always be read-only, then the attached property should not change it to true. Also if the TextBox has a binding that restricts the TextBox to read-only in some cases, the attached property should not blindly set true or false, but rather combine the settings, i.e. if attached property AND textbox binding indicate no read-only, then it is editible, otherwise it is readonly.
How can this be done? This would require to somehow get the current IsReadOnly setting (binding, markup, constant value, ...) and replace it with a wrapper which does the AND-combination.
How do I get the current setting/value source for a dependency property? I looked at the following, but don't see how it would address my problem:
TextBox1.GetValue(TextBoxBase.IsReadOnlyProperty);
DependencyPropertyHelper.GetValueSource(TextBox1, TextBoxBase.IsReadOnlyProperty);
TextBox1.GetBindingExpression(TextBoxBase.IsReadOnlyProperty);
Any help would be appreciated.
J.-
EDIT: I am looking for something like
(pseudo-code)
TextBox1.IsReadOnly := OR(TextBox1.IsReadOnly, GlobalIsReadOnly)
which now sets the TextBox1.IsReadOnly to true if the GlobalIsReadOnly flag is set or if the TextBox1.IsReadOnly value indicates read-only, be it a binding, markup or const.
You could use a DependencyPropertyDescriptor on to hook your IsReadonly property changed handler (for all objects).
(beware: a handler added to DependencyPropertyDescriptor is a gcroot... keep that in mind to avoid memory leaks)
This hook would try to get your custom attached property, and if it's found and is set to 'readonly forced', re-set your IsReadOnly property to false if it's value is changed (but store a flag, maybe on another attached property, to know if it must be restored to read-only later).
However, your logic would override any binding on IsReadonly. But the same logic could be applied with binding expressions (and not only values of the property) using GetBindingExpression and storing/restoring binding expressions set on IsReadonly property.
pros: no further code required once this is implemented.
cons: DependencyPropertyDescriptor.AddValueChanged "hides" logic... since there will be no clue that this IsReadonly property will be bound to something in further xaml you will write.
* EDIT : other solution *
Using multibinding, this should work (not tested).
However, this has some requirements:
Bindings/values must1 no be modified
Bindings must be intialized before executing this
var readonlyGlobalBinding = new Binding
{
Source = myRoot, // to fill
Path = new PropertyPath(IsGlobalReadOnlyProperty)
};
var be = box.GetBindingExpression(TextBoxBase.IsReadOnlyProperty);
if (be != null)
{
var mb = new MultiBinding();
mb.Bindings.Add(be.ParentBinding);
mb.Bindings.Add(readonlyGlobalBinding);
mb.Converter = new OrConverter();
box.SetBinding(TextBoxBase.IsReadOnlyProperty, mb);
}else if(!box.IsReadOnly)
box.SetBinding(TextBoxBase.IsReadOnlyProperty, readonlyGlobalBinding);
using class
class OrConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.OfType<bool>().Aggregate(false, (a, b) => a || b);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
It's not exactly what you're after, but I'd approach this problem from a different angle. I'd basically add a bool IsReadOnly property to my view model, Bind it to the relevant IsReadOnly properties on the UI controls and then simply set it to true of false dependant upon some UI interaction:
public bool IsReadOnly { get; set; } // Implement INotifyPropertyChanged here
...
<TextBox Grid.Row="0" IsReadOnly="{Binding IsReadOnly}" ... />
...
<TextBox Grid.Row="3" IsReadOnly="{Binding IsReadOnly}" ... />
<TextBox Grid.Row="4" ... /> <!--Never readonly-->
...
IsReadOnly = true;
What I have used in similar scenarios is SetCurrentValue and InvalidateProperty:
This method is used by a component that programmatically sets the
value of one of its own properties without disabling an application's
declared use of the property. The SetCurrentValue method changes the
effective value of the property, but existing triggers, data bindings,
and styles will continue to work.
So, you can set IsReadOnly to true using SetCurrentValue, then later call InvalidateProperty to reset it to the "declared use" (which may be true or false).
However I'm not sure this is what you want. It sounds like you want the attached property to have precedence the entire time it's active. Using this method, if a binding updates sometime between SetCurrent and Invalidate, it would still be applied overriding the attached property.
Another half-solution is to add triggers. If this trigger was declared at the end of the TextBox style's Triggers collection, it would take precedence:
<DataTrigger Binding="{Binding GlobalIsReadonly}" Value="True">
<Setter Property="IsReadOnly" Value="True" />
</DataTrigger>
The problem is that it only takes precedence over other setters (styles or triggers), but not direct setting of the property. Also styles cannot be modified after they are assigned, so to add this programmatically you would need to copy the old style and reassign it.
Unfortunately I think the only complete solution is to listen to property changes on all the textboxes and force another change.
I'm using the MVVM pattern and am receiving the following when i run my app
InvalidOperationException
A TwoWay or OneWayToSource binding cannot work on the read-only property 'Options' of type 'ViewModel.SynergyViewModel'.
I have commented all my source out in my view model and have traced this back to a check box. If i comment out the the checkbox or the properity in my view model the app runs, minus the functionality. Below i have listed the code for my checkbox and the property within the viewmodel.
<CheckBox Grid.Column="4" HorizontalAlignment="Right" Margin="5,0,5,5" IsChecked="{Binding Options}" Content="Options"/>
private bool _Options;
public bool Options
{
get
{
return _Options;
}
private set
{
if (_Options == value)
return;
_Options = value;
OnPropertyChanged("Options");
}
}
System.InvalidOperationException occurred
Message=A TwoWay or OneWayToSource binding cannot work on the read-only property 'Options' of type 'ViewModel.MyViewModel'.
Source=PresentationFramework
StackTrace:
at MS.Internal.Data.PropertyPathWorker.CheckReadOnly(Object item, Object info)
InnerException:
Any ideas on what i'm what i'm missing here?
Either make your setter public or explicitly set the Binding.Mode to OneWay.
Your setter is private, either specify the binding to be mode OneWay or remove the private from the setter
In my absolutely stupid case, I have forgotten to define a setter for a property, making it, well, read-only. Just my 2 cents for those who work too late.
For those who find this without using PropertyChanged
Regardless of whether PropertyChanged is used, this exception is also thrown when you have a calculated property (without setter) and the user tries to edit the column. Setting the whole DataGrid to IsReadOnly="True" or just the column to ReadOnly is enough then.