WPF Reflection, Late Binding - wpf

I am trying to set properties on WPF controls (height, width, fontweight, margin and many others) from data that is read through an XML file. I am not going to know what properties are going to be set beforehand. I was wondering if anyone knows a way to do this through reflection?
At the moment I have managed to assign all of the primitive types and enum types using reflection but I am having a little bit of trouble with properties like FontWeight, Margin, Background and many others that require other objects in setting the property for instance: To set a FontWeight property of a button you have to do it like this.
button.FontWeight = Fontweights.Bold;
or a Margin
button.Margin = new Thickness(10, 10, 10, 10);
As there are a possible 150 + properties that could be set on the controls in WPF I just wanted to avoid this sort of code.
public void setProperties(String propertyName, string PropertyValue
{
if(propertyName = "Margin")
{
Set the margin.....
}
else if (propertyName = "FontWeight")
{
set the FontWeight....
}
}
and so on for each possible property that can be set on WPF controls.

Behind the scenes, XAML uses TypeConverters to convert from string to the specified type. You can use them yourself, since each of the types you mentioned has a default TypeConverter specified using the TypeConverterAttribute. You can use it like this (or alternatively, make the method generic):
object Convert(Type targetType, string value)
{
var converter = TypeDescriptor.GetConverter(targetType);
return converter.ConvertFromString(value);
}
Then each of the following works as expected:
Convert(typeof(Thickness), "0 5 0 0")
Convert(typeof(FontWeight), "Bold")
Convert(typeof(Brush), "Red")

It's actually really simple. You read your string values into properties on a ViewModel, set that view model to your DataContext, and in xaml bind up your properties. Binding uses TypeConverters automatically.

You can do something like this
typeof(Button).GetProperty("FontWeight").SetValue(button1,GetFontWeight("Bold"), null);
EDIT:
You can have a mapping function that convert string to property value
FontWeight GetFontWeight(string value)
{
swithc(value)
{
case "Bold" : return FontWeights.Bold; break;
...
}
}

Related

Why WPF user control properties must be public to use them from XAML?

I'm working on a WPF custom control. The control has a property that is set in code behind and used in XAML. This property must be public for it work on XAML via a Binding. Why is this, if there is just one class?
<TextBlock Text="{Binding ElementName=PolicyBoxName, Path=FileNames[0]}" />
private string[] _fileNames;
public string[] FileNames
{
get
{
return _fileNames;
}
set
{
if (value != _fileNames)
{
_fileNames = value;
OnPropertyChanged("FileNames");
}
}
}
The XAML parsers constructs objects based on the supplied XML and sets their properties. It is no different from any other class, from a different namespace, that might wish to create your user control and set its properties. Without reflection, the constraints of the C# language demand that these properties are public for them to be set.

Dynamic Setting of WPF Control Properties

I'd like to be able to set properties for various controls in my WPF application where I have the string name of a control and the string name of its type, but I don't know how to do it. So far I have this:
( (TabItem)this.FindName( "tabPatient" ) ).IsEnabled = false;
I can iterate through a list of control names and set properties with just the string name of the control, but what I want is to be able to do it without having to perform an explicit hard-coded cast of the object type.
Is there a way to do this?
Thanks.
The type does not really matter, right? All you need is a property, so you could do something like this:
var obj = FindName("name");
obj.GetType().GetProperty("IsEnabled").SetValue(obj, false);
Alternatively you could use dynamic, which does about the same thing:
dynamic dynObject = (dynamic)FindName("name");
dynObject.IsEnabled = false;
You can navigate WPF's VisualTree to find an element by name and set a property.
For example, using some helper classes found here you can say
foreach(var s in controlList)
{
var ctrl = VisualTreeHelpers.FindChild<UIElement>(this, s);
if (ctrl != null)
ctrl.IsEnabled = false;
}
You don't really need to know the control type. All controls with an IsEnabled property are based off of UIElement, so just cast the control as a UIElement to modify it's IsEnabled property

how do I find out themed WinForms control's theme-based property values? for DevExpress or for WinForms in general

I am dealing with this issue in DevExpress.XtraEditors context, but maybe the answer would also apply to other situations where themes are used for WinForms controls.
Basically, how do I find out what collection of property settings does a themed control have? Is there a way for me to go look at the theme definition? Also, can I look at these settings dynamically, i.e. from within the app during execution (much like I can print out unthemed Appearance.BackColor during execution)?
I'm not certain what you're looking for, but if you're interested in finding all of a control's (or control Type's) 'Appearance' properties you can use the TypeDescriptor.GetProperties method. This method returns a PropertyDescriptorCollection from which you can pick out the properties with the CategoryAttribute.Appearance property.
You can use this method on an instance of the control:
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(myButtonInstance);
Or, on a control Type:
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Button));
But once you get a PropertyDescriptorCollection you can test for the presence of CategoryAttribute.Appearance (which means the property appears in the control's 'Appearance' section - assuming Browsable == true) like this:
foreach (PropertyDescriptor property in properties) {
if (property.Attributes.Contains(CategoryAttribute.Appearance)) {
Console.WriteLine("{0} - {1}", property.Name, property.Description);
// Do whatever...
}
}

DependencyProperty Orientation problem

I am learning WPF and am trying to create my first UserControl. My UserControl consists of
StackPanel
StackPanel contains a Label and TextBox
I am trying to create two Dependency Properties
Text for the Label
Orientation for the StackPanel - The orientation will affect the position of the Label and TextBox effectively
I have successfully created a Text dependency property and bind it to my UserControls . But when I created the Orientation property, I seem to get following error in get property
The as operator must be used with a reference type or nullable type ('System.Windows.Controls.Orientation' is a non-nullable value type)
public static DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(System.Windows.Controls.Orientation), typeof(MyControl), new PropertyMetadata((System.Windows.Controls.Orientation)(Orientation.Horizontal)));
public Orientation Orientation
{
get { return GetValue(OrientationProperty) as System.Windows.Controls.Orientation; }
set { SetValue(OrientationProperty, value); }
}
Appreciate your help.
Edit:
I changed the code as below and it seem to work as expected. But is this the correct way to solve the problem?
public Orientation Orientation
{
get
{
Orientation? o = GetValue(OrientationProperty) as System.Windows.Controls.Orientation?;
if (o.HasValue)
{
return (System.Windows.Controls.Orientation)o.Value;
}
else
{
return Orientation.Horizontal;
}
}
set { SetValue(OrientationProperty, value); }
}
The error message says it all. The as operator can only be used with a Type that is nullable (reference type, or Nullable<T>), because it will return either the value cast, or null.
What you're trying to use it on is an enumeration.
Just use a regular cast:
get { return (System.Windows.Controls.Orientation) GetValue(OrientationProperty); }
Reasons why:
You define a default value in your DependencyProperty.Register call, eliminating any default null value
Your DependencyProperty is typeof(Orientation), which doesn't allow for nulls
Your class's property definition is Orientation, which doesn't allow for nulls
Any attempt to set an invalid value via direct calls to SetValue(OrientationProperty, null) will receive an exception, so your property getter won't ever see a null value even by a naughty user of it.

Why would putting a no-op Converter on a Binding change its behavior?

I'm in the midst of testing a user control I've built, and I'm encountering something that's inexplicable to me.
The control's an extension of the ComboBox that handles values of a specific custom type. It has a dependency property of that custom type that is the target property of a Binding.
I've got a trace statement in the setter, and I can see that the property is getting set. But it's not appearing in my user control.
Now, ordinarily I'd say, okay, I've got a bug in my user control. I probably do, though I'm baffled about it. But this question isn't about finding the bug in my control. Read on; here is where it gets weird.
I'm also using Bea Stollnitz's little value converter to help debug the Binding:
public class DebuggingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value; // Add the breakpoint here!!
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("This method should never be called");
}
}
The idea behind this is that I add this converter to my Binding and can set a breakpoint to see what value is being pushed out to the target. Okay, that works just fine. I can see that the value is being pushed out.
In fact, it works a little too fine. If the DebuggingConverter is attached to the Binding, the user control displays the value. If it's not, it doesn't.
How is that even possible? How could a value converter that does nothing affect the behavior of a bound control?
Edit:
Not that it's likely to help, but here's the XAML for the user control:
<a:CodeLookupBox
Grid.Column="1"
Grid.IsSharedSizeScope="True"
MinWidth="100"
Style="{Binding Style}">
<a:CodeLookupBox.CodeLookupTable>
<Binding Path="Codes" Mode="OneWay"/>
</a:CodeLookupBox.CodeLookupTable>
<a:CodeLookupBox.SelectedCode>
<Binding Path="Value" Mode="TwoWay" ValidatesOnDataErrors="True"/>
</a:CodeLookupBox.SelectedCode>
</a:CodeLookupBox>
Without the converter on the second binding, the control behaves as though I didn't set SelectedCode. Even though a trace statement in the OnSelectedCodePropertyChanged handler shows that e.Value does indeed contain the correct value. This happens irrespective of whether the converter's attached or not.
I've been trying to reverse-engineer this problem with a thought experiment: if you wanted to create a bound user control whose behavior changed if a no-op converter were attached to its binding, how would you do it? I don't know enough about binding to come up with an answer.
Well, the good news is, I know why SelectedCode isn't being set when I'm not using a value converter. The bad news is, I still have something of a mystery, but the problem's been pushed up the food chain a bit, and I have a workaround.
This control is essentially a strongly-typed combo box with a bunch of additional features that are made possible by the fact that it knows what kind of items are in it. The SelectedCode and CodeLookupTable properties are strongly typed, and they hide the underlying SelectedItem and ItemsSource properties, which aren't. (This, by the way, is why this is a user control and not a subclass of ComboBox; I don't want those properties to be visible because a lot of things can happen if they get set improperly, none of them good.)
Here's what's happening. This is my debugging output when the value converter is attached (the number is the hash code of the control, because I've got a bunch of them that all get drawn simultaneously when the program's initialized):
14626603: OnCodeLookupTablePropertyChanged
CodeLookupTable property set to Proceedings.Model.CodeLookupTable
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
14626603: OnSelectedCodePropertyChanged:
SelectedCode property set to Unlicensed Driver [VC12500(A)]
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
This is the expected behavior. The CodeLookupTable property is set, so setting SelectedCode to one of the items in that collection correctly sets SelectedItem on the underlying ComboBox.
But without the value converter, we get this:
16143157: OnSelectedCodePropertyChanged:
SelectedCode property set to Unlicensed Driver [VC12500(A)]
box.MainComboBox.ItemsSource =
16143157: OnCodeLookupTablePropertyChanged
CodeLookupTable property set to Proceedings.Model.CodeLookupTable
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
Here, the SelectedCode property is being set before the CodeLookupTable property is. So when the method tries to set SelectedItem on the underlying ComboBox, nothing happens, because the ItemsSource is null.
And here is the root of the problem. I've foolishly assumed that the order that bindings update their target in is the same as the order they're declared in the XAML. (One of the reasons I've expressed the bindings as elements instead of attributes is because the order of elements in an XML document is deterministic and the order of attributes isn't. It's not like I didn't think about this.) This is apparently not the case.
I've also assumed, maybe a little less foolishly, that the order in which bindings update their target isn't dependent on whether or not they have attached value converters. Well, it is. I wonder what else it depends on.
Mercifully, I have a way to work around this. Since my CodeLookup object contains a reference to the CodeLookupTable, I can make the SelectedCode setter set the CodeLookupTable (and thus the ItemsSource) property first, if it hasn't already been set. That'll make this problem go away without having to stick a fake value converter on the binding and hope that the way bindings behave never changes.
Edit
Here's what the property declarations look like:
#region SelectedCode
public static readonly DependencyProperty SelectedCodeProperty = DependencyProperty.Register(
"SelectedCode", typeof(CodeLookup), typeof(CodeLookupBox),
new FrameworkPropertyMetadata(OnSelectedCodePropertyChanged));
private static void OnSelectedCodePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
CodeLookupBox box = (CodeLookupBox)source;
CodeLookup code = e.NewValue as CodeLookup;
// this right here is the fix to the original problem:
if (box.CodeLookupTable == null && code != null)
{
box.CodeLookupTable = code.Table;
}
box.MainComboBox.SelectedItem = e.NewValue;
}
public CodeLookup SelectedCode
{
get { return GetValue(SelectedCodeProperty) as CodeLookup; }
set { SetValue(SelectedCodeProperty, value); }
}
#endregion
#region CodeLookupTable
public static readonly DependencyProperty CodeLookupTableProperty = DependencyProperty.Register(
"CodeLookupTable", typeof(CodeLookupTable), typeof(CodeLookupBox),
new FrameworkPropertyMetadata(OnCodeLookupTablePropertyChanged));
private static void OnCodeLookupTablePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
CodeLookupBox box = (CodeLookupBox)source;
CodeLookupTable table = (CodeLookupTable)e.NewValue;
box.ViewSource = new CollectionViewSource { Source = table.Codes };
box.View = box.ViewSource.View;
box.MainComboBox.ItemsSource = box.View;
}
public CodeLookupTable CodeLookupTable
{
get { return GetValue(CodeLookupTableProperty) as CodeLookupTable; }
set { SetValue(CodeLookupTableProperty, value); }
}
#endregion
try to implement the ConvertBack function too, you are using two-way binding so the problem may be that the exception will be ignored by the "xaml" but when you step in debug mode you maybe stop the "send value back"-operation?
Hmm...very strange. I've seen similar behavior when dealing with MultiBindings with multiple converters, but not on a simple binding. Out of curiosity, how are you defining your DPs on the control? (including the callbacks, metadata options, etc)
Some stuff to try (after removing the converter hook):
Default the mode (ie, remove the TwoWay)
Remove the ValidatesOnDataErrors
Add an AffectsRender to the SelectedCode DP
What does setting SelectedCode do? Can you post the code for the property changed handler?
You've suggested that debugging shows the property is being set to the correct value, so the most obvious suggestion is that the code providing your intended behaviour is incorrect.

Resources