OneWay binding on WinForms? - c#

I have a control with a property public MyClass MyProperty{...} which value is shown on the screen as a graph. I want this property to be bindable to any other MyClass in the program by using the Binding class (MyProperty would be the propertyName parameter in this Binding constructor, and the other MyClass would be the dataMember parameter) .
MyClass implements INotifyPropertyChanged so on that side everything is all right. But it happens that if I don't implement a get accessor in MyProperty and try to bind something to it, I get a "Cannot bind to the property 'MyProperty' on the target control.
Parameter name: PropertyName" error.
Does this mean I have to implement a get accessor even if I know I will never need to read it's value and I want a OneWay (source to target) binding, and even if I just return null in the get accessor?
I'm guessing the Binding class uses this to compare the new value to the old one or to do some other internal stuff. I'm not sure, then, if it's a good idea to just return null, or it would be better to keep always a copy of whatever last object was assigned with the set accessor and return it in the get accessor. Maybe I really don't even need to write a get accessor and I'm doing something else wrong. It just happens that I get the error only when I comment out the get accessor and stop getting it when I put it back.
Edit: In case there is any confusion: When I say MyProperty's value is shown on the screen as a graph I don't mean it has a value that some other code reads and show in the screen. No one reads any value from MyProperty. MyProperty's set accessor is the one that draws stuff on the screen and that's the end of the cycle.

I'm not 100% sure I understand what you mean, but I think the exception you're encountering stems from the Binding class's CheckBinding function (reflectored):
if (descriptor.IsReadOnly && (this.controlUpdateMode != ControlUpdateMode.Never))
{
throw new ArgumentException(SR.GetString("ListBindingBindPropertyReadOnly", new object[] { this.propertyName }), "PropertyName");
}
Therefore, changing the Binding's ControlUpdateMode to ControlUpdateMode.Never may be what you're looking for

Related

WPF DataBinding: Using SetCurrentValue in CLR property?

I have a custom WPF DependencyObject which owns a property DependentValue. DepedentValue will only ever output its value, the object never reads it. The idea is that the DependentValue property is bound in XAML either as a TwoWay or OneWayToSource binding and updated by the object. To bugs when DependentValue is assigned before the binding has completed, I would like to use SetCurrentValue to update the property.
The conventional CLR property would look like this:
public object DependentValue
{
get { return GetValue(DependentValueProperty); }
set { SetValue(DependentValueProperty, value); }
}
Because of the issue with SetValue, this setter should never be used from inside the object. Therefore, I would like to implement the following CLR property:
public object DependentValue
{
set { SetCurrentValue(DependentValueProperty, value); }
}
This works as expected, but the convention seems to imply that only SetValue should ever be used inside a CLR property. What are the issues with this approach?
For context: I have built a WPF component which can be used to conditionally link two databindings: A 'driver' value is forwarded to a 'dependent' value only if a boolean property is true. This means that the 'dependent' value is 'write-only' from the perspective of this component.

PropertyChanged is always fired

I am using standard notification pattern with INotifyPropertyChanged. Here is my sample property:
private string fanart;
public string Fanart
{
get { return fanart; }
set
{
if (fanart != value)
{
fanart = value;
NotifyPropertyChanged("Fanart");
}
}
}
Here, I expect the setter to skip everything if new value is equal to the current value. However, when I check using debugger, fanart always equals null and consequently, the event is always fired. Any reason for the filed (and property) to be null?
Edit: This property is part of class called PlayerItem. PlayerItem is part of another class PlayerState. All these classes extend from NotifyBase. A method RefreshPlayerState periodically refreshes the PlayerState. I just noticed that new instance of PlayerItem is created every time player state is refreshed. What could be causing this?
Edit2: Creation of new instance is expected since I am deserializing the object from a JSON response. Now, how do I prevent setter from setting the value if older values are equal to values deserialized from the JSON? myObj != value will always return true since both are different objects although contained data is same.
This code is part of project here. Feel free to look in for more details.
"fanart always equals null" means most likely, you are recreating instances of the class that owns this field (which I assume is your View Model). You should keep the reference to your bound VM and work on that instance.
Edit: Currently I am working with a Windows 7 machine so I could not see your code. But you are removing those players somewhere; those that you create at PlayerHelper and Player classes.

How do I get a `BindingExpression` from a `Binding` object?

In short(?), I have a ListView (target) one-way bound to an XmlDataProvider (source) two-way bound to a TextBox (target) using standard XAML for the control bindings and custom XAML extensions for the bindings to the XmlDataProvider. This is a convenience for the application as the XmlDataProvider is dynamically loaded from user inputs after the application is running,...
Anyway, at run-time, after modifying the TextBox.Text property, the IMultiValueConverter.ConvertBack(...) method is called to propagate the update from this target back to the source. But, because the XmlDataProvider object is not a DependencyProperty, the update is not further propagated from the changed XmlDataProvider source to the other binding to the ListView target.
Without rearchitecting, which you could legitimately advise, I need to notify WPF that any target with this XmlDataProvider as a source needs to be updated. I am hoping to maintain a generic, reusable binding class and have, so far, enjoyed the low coding burden of my mostly XAML solution.
Currently, the only code behind access I have is from within the IMultiValueConverter.ConvertBack(...) method. From within this method I do have access to the Binding object for the XmlDataProvider <--> TextBox link. If I could get the BindingExpression for the Binding.Source object I could then make the call to BindingExpression.UpdateTarget() to complete the update propagation,...
But, I do not know how to get a BindingExpressionfrom a Binding.Source object, that is not associated with a DependencyProperty.
Thanks in advance for your advice and assistance.
You can create a custom MarkupExtension which accepts a Binding as a constructor argument. In XAML usage, yours will be an outer binding that wraps the WPF one:
<StackPanel>
<TextBox x:Name="tb" />
<TextBlock Text="{local:MyBinding {Binding ElementName=tb,Path=Text,Mode=OneWay}}" />
</StackPanel>
In the MyBinding constructor you will receive a WPF Binding object. Store a copy for later when your ProvideValue is called. At that time, you can call ProvideValue on the binding you saved--and pass it the IServiceProvider instance you now have. You'll get back a BindingExpression that you can then return from your own ProvideValue.
Here's a minimal example. For a simple demonstration, it just adds (or overwrites) a Binding.StringFormat property to the inner (wrapped) binding.
[MarkupExtensionReturnType(typeof(BindingExpression))]
public sealed class MyBindingExtension : MarkupExtension
{
public MyBindingExtension(Binding b) { this.m_b = b; }
Binding m_b;
public override Object ProvideValue(IServiceProvider sp)
{
m_b.StringFormat = "---{0}---"; // modify wrapped Binding first...
return m_b.ProvideValue(sp); // ...then obtain its BindingExpression
}
}
If you try it with the XAML above, you'll see that a live binding is indeed set on the target, and you didn't have to unpack the IProvideValueTarget at all.
This covers the basic insight, so if you know exactly what to do now, you probably won't need to read the rest of this answer...
More details
In most cases, digging into the IProvideValueTarget is actually the point of the whole exercise, because you can then modify the wrapped binding dynamically according to runtime conditions. The expanded MarkupExtension below shows the extraction of the relevant objects and properties, and there are obviously numerous possibilities for what you can do from there.
[MarkupExtensionReturnType(typeof(BindingExpression))]
[ContentProperty(nameof(SourceBinding))]
public sealed class MyBindingExtension : MarkupExtension
{
public MyBindingExtension() { }
public MyBindingExtension(Binding b) => this.b = b;
Binding b;
public Binding SourceBinding
{
get => b;
set => b = value;
}
public override Object ProvideValue(IServiceProvider sp)
{
if (b == null)
throw new ArgumentNullException(nameof(SourceBinding));
if (!(sp is IProvideValueTarget pvt))
return null; // prevents XAML Designer crashes
if (!(pvt.TargetObject is DependencyObject))
return pvt.TargetObject; // required for template re-binding
var dp = (DependencyProperty)pvt.TargetProperty;
/*** INSERT YOUR CODE HERE ***/
// finalize binding as a BindingExpression attached to target
return b.ProvideValue(sp);
}
};
For completeness, this version can also be used with XAML object tag syntax, where the wrapped Binding is set as a property, instead of in the constructor.
Insert your customization code for manipulating the binding where indicated. You can do pretty much anything you want here, such as:
Check or modify the runtime situation and/or state:
var x = dobj.GetValue(dp);
dobj.SetValue(dp, 12345);
dobj.CoerceValue(dp); // etc.
Reconfigure and/or customize the binding prior to sealing it into the BindingExpression:
b.Converter = new FooConverter(/* customized values here */);
b.ConverterParameter = Math.PI;
b.StringFormat = "---{0}---"; // ...as shown above
Perhaps decide binding is not needed in certain cases; do not proceed with binding:
if (binding_not_needed)
return null;
Lots more, limited by your imagination. When ready, call the binding's ProvideValue method and it will create its BindingExpression. Because you pass it your own IProvideValueTarget info (i.e. your IServiceProvider), the new binding will substitute itself for your markup extension. It gets attached to the target object/property where your MarkupExtension was authored in XAML, which is exactly what you want.
Bonus: You can also manipulate the returned BindingExpression
If pre-configuring the binding isn't enough, note that you also have access to the instantiated BindingExpression. Instead of tail-calling the ProvideValue result as shown, just store the result into a local. Prior to returning it, you can set up monitoring or interception of the binding traffic via the various notification options that are available on BindingExpression.
Final note: as discussed here, there are special considerations when WPF markup extensions are used inside templates. In particular, you will notice that your markup extension is initially probed with IProvideValueTarget.TargetObject set to an instance of System.Windows.SharedDp. Because loading templates is a naturally a deferred process, I believe the purpose here is early probing of your markup extension to determine its characteristics, i.e. long prior to the existence of any real data which could populating the template properly. As shown in the above code, you [must return 'this'] c̲a̲n̲ r̲e̲t̲u̲r̲n̲ t̲h̲e̲ p̲r̲o̲b̲e̲ o̲b̲j̲e̲c̲t̲ i̲t̲s̲e̲l̲f̲ for these cases; if you don't, your ProvideValue won't be called back again when the real TargetObject is available [see edit].
edit: WPF tries really hard to make XAML resources shareable, and this especially includes the BindingBase and derived classes. If using the technique I describe here in a reusable context (such as a Template), you need to make sure that the wrapped binding does not meet the criteria for shareability, otherwise the wrapped binding will become BindingBase.isSealed=true after the first time it generates a BindingExpression; subsequent attempts to modify the Binding will fail with:
InvalidOperationException: Binding cannot be changed after it has been used.
There are several workarounds to do this, which you can ascertain by studying the source code of the (non-public) WPF function TemplateContent.TrySharingValue. One method I found was to return the System.Windows.SharedDp object from your markup extension anytime it shows up. You can detect System.Windows.SharedDp either by looking for any non-DependencyObject value, or more specifically as follows:
if (pvt.TargetObject.GetType().Name == "SharedDp")
return pvt.TargetObject;
(Technically, checking for .GUID value {00b36157-dfb7-3372-8b08-ab9d74adc2fd} instead would the most correct). I've updated the code in my original post to reflect this, but I welcome further insight on how to preserve maximal resource sharing for both of the use cases, template vs. non-template.
edit: I'm thinking that, for the purposes of the sharability determination in template usage, the main difference between returning this (as I had originally suggested) and my revised suggestion to return pvt.TargetObject is that the former derives from MarkupExtension--versus the base class of System.Windows.SharedDp being Object--and it's clear that the probing code recurses into nested markup extensions.

Compare value member when setting property

I have a property, of a custom class, in C# that I have overridden the setter for. I want to compare a property of/in the custom class in the setter, like the following:
public DatabaseInfo CurrentDatabaseManagedSelection
{
get { return CurrentDatabaseManaged; }
set {
if (String.Equals(value.Name, CurrentDatabaseManaged.Name,StringComparison.OrdinalIgnoreCase))
return;
CurrentDatabaseManaged = DatabaseManagement.ReadDatabase(value.FileName);
}
}
Inside the DatabaseInfo class, there is a standard String property called Name.
However, when I run the program I get the following exception. Can anyone tell me why this happens and how to solve the issue please?
Exception has been thrown by the target of an invocation.
EDIT: I do set the value of the property which the setter above is for, in the constructor of the view model. I do this simply by setting CurrentDatabaseManagedSelection equal to an object of the DatabaseInfo class.
Think I might have found the problem... Well, I've solved it!
The issue was CurrentDatabaseManaged had not been initialized and so was equal to null when I tried setting the above property. I discovered this by adding a try.. catch in the setter method, and created a new String for CurrentDatabaseManaged.Name - the stack trace pointed to that line.
Hope that helps some one else in the future.

Fixing .NET code generation of properties for user controls

I have a property of type IEnumerable<SomeClassIWrote> in a user control. When I use this control in a GUI, the .Designer.cs file contains the line:
theObject.TheProperty = new SomeClassIWrote[0];
Which for some reason causes a compiler warning:
Object of type 'SomeClassIWrote[]' cannot be converted to type
'System.Collections.Generic.IEnumerable`1[SomeClassIWrote]'.
Which is a mystery to me because I pass arrays as IEnumerables all the time, and the compiler has never complained.
For what it's worth, I have a default value of null specified for the property, but I got the same error before I set a default value.
How can I fix this so Visual Studio doesn't complain and ask me to ignore and continue every time I pull up the designer?
Code for the property:
[DefaultValue(null)]
public IEnumerable<SomeClassIWrote> TheProperty {
get {
return _theProperty;
}
set {
if (value == null) {
_theProperty = new SomeClassIWrote[] { };
}
else {
_theProperty = value;
}
}
}
First up, do you WANT to be able to set it in the designer?
If not, add the following attributes:
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
If you DO want to set in the designer, I'd start out by trying your class as a SomeClassIWrote[], to see if that works.
That aside, is it that important to use an IEnumerable here? As you say, you can pass arrays as IEnumerables.
I suspect there's probably some restrictions inside the designer, which wiser people than me know about...
And if you really DO want an IEnumerable property, you can expose your array as an IEnumerable, but keep your array as a designer-friendly backing field.

Categories