Get a DependencyProperties source (binding, const, ...) and replace with wrapper - c#

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.

Related

WPF PropertyChanged in UserControl DependencyProperty not fired every time

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.

WPF + DevExpress UpdateSourceTrigger

I would like to add to every control's binding the "UpdateSourceTrigger=PropertyChanged".
I could realize that by just writing for every control:
Text/EditValue/Stuff="{Binding x, UpdateSourceTrigger=PropertyChanged}"
But if possible, I would like to avoid this approach, since it is just redundant xaml code I would have to write over and over again, instead of reusing once piece of code.
A style is not an option, because the object bindings differ from xaml to xaml.
Since DevExpress is in use, we make use of the ExtensionMethod
DevExpress.Mvvm.POCO.POCOViewModelExtensions.RaisePropertiesChanged(this);
But the problem here: Imagine editing a TextEdit (Clicking into the TextEdit, typing stuff) and while the cursor is still in the TextEdit field, call the extension method mentioned above. The TextEdit's EditValue will be reset to the old value, since the PropertyChanged didn't call before.
Is there any way to modify application wide all binding behaviour at once?
Is there any way to modify application wide all binding behaviour at once?
No, different dependency properties have different default values for the UpdateSourceTrigger property, but you could create a custom binding markup extension:
public class PropertyChangedBinding : Binding
{
public PropertyChangedBinding()
:base()
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
}
public PropertyChangedBinding(string path)
: base(path)
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
}
}
...and replace {Binding} with your custom binding across all your XAML files:
<TextBox Text="{local:PropertyChangedBinding x}" />

How do i override a dependency property set in XAML to the value i need to in codebehind

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;
}
}
}

What is the proper way to bind to a value while showing a friendly name from a list of presets also defined on that item?

Got a tough one. Consider a ViewModel that is comprised of a list of objects, where each object defines an int value, and some of those objects also define a Presets dictionary of ints keyed on a 'friendly' string representing that value in the UI.
Here's an example...
List<SomeItem> AllItems;
public class SomeItem : INotifyPropertyChanged
{
public SomeItem(int initialValue, Dictionary<int,string> presets)
{
this.CurrentValue = initialValue;
this.Presets = presets;
}
public int CurrentValue{ get; set; } // Shortened for readability. Assume this property supports INPC
public Dictionary<int,string> Presets{ get; private set; }
}
The goal for the UI is if the item has no presets, the user can enter any int value they want. However, if there are presets, we want to limit them to those values and also display them in the UI as the Friendly names from the dictionary.
Our first attempt was to use a TextBox and a ComboBox, modifying their visibilities depending on if there were presets or not, like this...
<ComboBox ItemsSource="{Binding Presets}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding CurrentValue, Mode=TwoWay}"
Visibility={Binding HasPresets, Converter=...}">
<TextBox Text="{Binding CurrentValue}"
Visibility={Binding HasPresets, Converter...}" /> // Assume the inverse of the combo
...but when we're using this in a DataTemplate of a list that supports virtualization, the combo occasionally displays blanks. I believe is because when the item is reused and the DataContext changes, SelectedValue updates before ItemsSource meaning there's potentially no preset value to match on, thus the proposed SelectedValue value gets tossed by the control, then ItemsSource updates, but there's no selected value so it shows a blank.
My next thought (and what we preferred anyway) was to use only a TextBox that displayed the preset name but was actually bound to Value, then use a converter to work its magic, and let the user type either the friendly name or the actual value. If the user typed something that wasn't a valid value or a preset, we'd just throw an error. If there were no presets, it would simply act as a pass-through of the value.
However, there I'm not sure how to pass in the presets to the converter. You can't set a binding on a ConverterParameter to pass them in that way, and if you use a multi-binding, then I'm not sure how to structure the 'ConvertBack' call since there too I need them passed in, not sent back out.
I'm thinking the proper way is to implement UiValue in the ViewModel which we'd simply bind to like this...
<TextBox Text="{Binding UiValue}" />
...then move the code that would've been in the converter to that property's getter/setter implementation, or simply be a pass-through to Value if there are no presets. However, this seems like too much logic is going in the ViewModel where it should be in the View (ala a converter or similar.) Then again, maybe that's exactly the point of the ViewModel. I dunno. Thoughts welcome.
Personally, I would go for putting the 'converter code' into the property as you suggested... I don't see any problem with having the code in there. In fact, it's probably better than having it in a Converter because then you can easily test it too.
Sorry, this isn't much of an answer, but I felt that your question deserved at least one.
I like your question, because it illustrates the way of thinking that stands behind the existence of a ViewModel in WPF. Sometimes they seem inevitable.
Converters are designed to be stateless, which is why it's difficult to pass context variables like presets. ViewModel is a layer, of which responsibility is to prepare Model for binding purposes. The role of a "model" is to handle logic. Thus, a ViewModel may handle in detail the behaviour (logic) of a View. This is precisely what you want. Most of the time I find myself not needing Converters at all.
Sometimes it feels more natural that the view logic should be in the View, but then ViewModel seems superfluous. However, when that logic is located in the ViewModel it's usually easier to auto-test. I wouldn't be afraid of putting stuff like this in ViewModel at all. Often this is the easiest (and correct) way.
Have UiValue property in ViewModel and handle conversion there:
public string UiValue{ get{/*...*/} set{/*...*/} }
To rephrase, in WPF there is no clean way to replace the property you bind to. E.g. if you wanted to have
<TextBox Text="{Binding IntValue}" />
change at some point to:
<TextBox Text="{Binding PresetValue}" />
you're trapped. This is not how things are done. Better have a constant binding like
<TextBox Text="{Binding UiValue}" />
and deal with the logic behind the UiValue property.
Another possible approach (instead of playing with visibility of ComboBox and TextBox) is to have a DataTemplateSelector, which would decide whether a ComboBox or TextBox should be created for SomeItem. If presets are null or empty select TextBox-based DataTemplate, otherwise take ComboBox. If I'm not mistaken you'd have to investigate FrameworkElement.DataContext property from within the selector to find the context (presets).
Considering your doubt about ConvertBack method, most commonly value or Binding.DoNothing is returned in case you don't need conversion in any of the directions.

InvalidOperationException - A TwoWay or OneWayToSource binding cannot work on the read-only property

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.

Categories

Resources