How to access property attributes on a data bound property in Silverlight? - silverlight

For example, I have a simple textbox bound to a property:
<TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />
The property looks something like this:
[Display(Name="Last Name")]
public string LastName { ... }
So, given the textbox, I would like to get the Display Name property from the attribute. This will be used in a composite control that includes a fieldlabel and some other niceties.
Thanks in advance.

I am not able to attempt this at the moment so this may not be accurate or even possible. As soon as I get to a computer that I can try this I will...until then, this is just theory.
I'm guessing in your composite control you'll have something like this for each data bound field:
<TextBlock Text="{Binding FirstName, Mode=OneWay}" />
<TextBox Text="{Binding FirstName, Mode=TwoWay, ...}" />
What you'll probably need to do in order to create a converter that will look at the binding data for the Display attribute, and convert the value to the attribute value instead. This would cause the above block to look like this:
<TextBlock Text="{Binding FirstName, Mode=OneWay, Converter={StaticResource AttributeConverter}, ConverterParameter=Display}" />
<TextBox Text="{Binding FirstName, Mode=TwoWay, ...}" />
Here I passed in the Display as the parameter in case you wanted to access a different attribute.
Again this is just theory since I'm not able to currently test this and cannot recall if IValueConverter.Convert(object value, ...) passes the object in question or just the string value in this case. If it's just the string value, it probably isn't possible, though if it's the object instead, it will depend on how much access you have to the reflection namespace to evaluate the attributes.
As soon as I am able to, I'll throw the scenario together and try it out.
EDIT:
For some reason the sytax highlighter is giving me the finger when I try to paste code in this edit
Anyways, after trying this out in a little project, it don't think you can do this.
Based on my suggestion of making 2 data bound controls and using a converter for the one that consumes the attribute, I did the following:
Created the xaml for the databound control.
Create the Custom Attribute for testing
Created the Model with the decorated property for testing.
Created the converter to attempt to read the attribute from the property.
Here's where I got caught up. I wasn't able to obtain the data bound type from the IValueConverter.Convert(...) method. The value parameter came through as String as did the targetType parameter. While that was the primary hangup, the second was that I was unable to dynamically identify the property name that the control was data bound to. This could be remedied through a converter parameter possibly.
Now, I WAS able to read the attribute value if I supplied the type of my test Model with the decorated property so that much is possible but I wasn't able to dynamically identify the type on the fly.
The only other way I can think of is create some form of observer or converter prior to the data truly being bound to your custom control.
Good Luck

Related

Binding Source vs x:Static

in WPF one can bind to static properties. Now I know 2 ways of doing this:
Content="{x:Static stat:Statics.CurrentUser}"
Or:
Content="{Binding Source={x:Static stat:Statics.CurrentUser}}"
Are there any differences between these 2 methods?
Main difference in this case is that x:Static does not perform additional conversion
From x:Static Markup Extension
Use caution when you make x:Static references that are not directly the type of a property's value. In the XAML processing sequence, provided values from a markup extension do not invoke additional value conversion. This is true even if your x:Static reference creates a text string, and a value conversion for attribute values based on text string typically occurs either for that specific member or for any member values of the return type.
So lets say you do
<TextBlock Text="{x:Static SystemColors.ActiveBorderBrush}"/>
this will cause runtime error:
'#FFB4B4B4' is not a valid value for property 'Text'.
because SolidColorBrush is not String whilst
<TextBlock Text="{Binding Source={x:Static SystemColors.ActiveBorderBrush}}"/>
will work fine and display #FFB4B4B4 because it will perform ToString() conversion. Also without Binding you are not able to access instance properties of static object so for example you would not be able to get Color property of that brush
<TextBlock Text="{Binding Source={x:Static SystemColors.ActiveBorderBrush}, Path=Color}"/>

WPF - expose binding methods for inherited column

A reoccurring issue I have is needing to create enhanced text columns for datagrids. By that I mean columns that act just like normal text columns, but with an additional graphic or feature, like an image displayed next to the text. So I'm using template columns, but apparently this means having to "start from scratch" in generating a lot of the features expected of a normal text column, such as the textbox editing template:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=[binded text], Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
I want to define a column that's inherited from DataGridTemplateColumn, then dump all this code into it, so I can reuse these columns with any datagrid I wish. But as shown above, I can't declare the binding in the class definition because that obviously depends upon usage.
How can I define an inherited datagrid column that makes use of child controls (specifically the cell editing textbox in this case), but still allows binding to be set for these controls when the column has been declared with xaml inside some actual datagrid?
So far I've tried to expose a method to do this, but it's not working:
Public Class MyTextColumn
Inherits DataGridTemplateColumn
....
Public Property EditorBinding As String
Get....
Set(value As String)
Dim b As New Binding(value)
b.Mode = BindingMode.TwoWay
b.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus
Dim tb = DirectCast(Me.CellEditingTemplate.LoadContent, TextBox)
tb.SetBinding(TextBox.TextProperty, b)
End Set
End Property
Not working, my best guess is I'm not setting the Binding.Source, but I have no idea what I should be setting it to. It's getting pretty frustrating.
So if I understand you correctly, you want to be able to bind the text property of the TextBox to something on the parent control which will hold this child control of yours. You can't do that using the normal property (I'm guessing you got the "Can't bind because it's not the dependency property" exception or something similar).
This is how I usually do it without any problems. First you need to define a dependency property in the code behind. This should show you how to do it in the VB.net (I really really suck at VB.net so I won't pretend to give you any advice on that). Check the first example in VB.net. What you need to change first is from Boolean to String, you will also probably want to change the property name. Be careful to leave the "Property" part of the name where it stands in the example. GetType(MyCode) should be changed to the name of the class where you are implementing the dependency property (the name of your MyTextColumn class)
In the MyTextColumn xaml, it should look something like this:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=IsSpinning, RelativeSource={RelativeSource AncestorType=DataGridTemplateColumn}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
I've put the original property name IsSpinning, you should put there your chosen name. Also, you might have to fix the relative source if the base class is not DataGridTemplateColumn. This should pick up anything comming to your custom control.
The final step is to use your control:
<controls:MyTextColumn IsSpinning="{binding PropName}"/>
You basically bind it to whatever string you want. Feel free to write up any problems that you might have with my explanation or code and I'll fix my answer accordingly.

How to set silverlight comboboxitem value in xaml

I create a datatemplate for a combobox as follows:
<DataTemplate x:Key="AircraftTypeTemplate">
<StackPanel Orientation="Horizontal" Width="340">
<ComboBox>
<ComboBoxItem>CJ1</ComboBoxItem>
<ComboBoxItem>CJ3</ComboBoxItem>
<ComboBoxItem>Bravo</ComboBoxItem>
<ComboBoxItem>Excel</ComboBoxItem>
<ComboBoxItem>Sovereign</ComboBoxItem>
</ComboBox>
</StackPanel>
</DataTemplate>
It renders fine, but I would like to be able to associate a value with each of the items without having to bind it to some data context. For example I would like the CJ1 comboboxitem to have a value of 5. How would I set those in XAML?
Like:
<ComboBoxItem Value="5">CJ1</ComboBoxItem>
Thanks!
You can set the Name property to be any arbitrary string and use that. For more flexibility, you can use the Tag property, which according to MSDN:
Gets or sets an arbitrary object value that can be used to store custom information about this object.
You can read more about Tag here. I'd say Tag is probably better as opposed to bending Name to your will, and you can stick a string into Tag just as easily as Name.

Databinding Not Updating When Using {Binding .} or {Binding}

I have an ObservableCollection of addresses that I am binding to a ListBox. Then in the ItemTemplate I am Binding to the current address record using {Binding .}. This results in my addresses displaying using their ToString method which I have setup to format the address. All is good, except if I update properties on an individual address record the list in the UI does not update. Adds/Deletes to the list do update the UI (using the ObservableCollection behavior). If I bind directly to properties on the address the UI does update (using the INotifyPropertyChanged behavior of the Address object).
My question is, is there a way to notify the UI of the change to the object as a whole so that I can still use this syntax or do I need to punt and put a DisplayText property on my address type that calls the ToString method and bind to that? FYI, this is an MVVM architecture so I don't have the luxury of calling Refresh on the ListBox directly.
Thanks for any help/ideas.
<ListBox x:Name="AddressList" ItemsSource="{Binding Addresses}" Background="Transparent" BorderBrush="Transparent"
Width="200" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding .}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When you bind to the Address object itself, the object itself -- that is, its identity -- doesn't change, even though its properties do. WPF therefore doesn't know to refresh the binding in this case.
So yes, you need to bind to a notifying property (or properties) rather than the whole object. As you say, one way to do this is to create a DisplayText property, and raise the PropertyChanged event for that property whenever something that affects the display text changes. Another is to use multiple TextBlocks in a horizontally oriented StackPanel, each bound to a particular property e.g.
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HouseNumber}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Street}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding City}" />
</StackPanel>
The advantage of the second approach is that it gives you flexibility in the UI to change how addresses are displayed, e.g. multiple lines, formatting, etc.; the downside is that it gets complicated if you have conditional logic e.g. an optional flat number or second address line.
I tried to reproduce the problem and succeeded.
I activated the step-into-.NET debugging options, and saw that WPF does not listen to INotifyPropertyChanged if the path in the binding is empty.
What worked to get a change to be reflected in the list box is to replace the whole object in the ObservableCollection. This triggers the INotifyCollectionChanged, with the Replace action.
But this may not be acceptable in your case. And it could be seen more like a hack than a solid solution.
I'd seriously consider having a DataTemplate for Address. There you should bind to the exact properties you need (which would create the listener for INotifyPropertyChanged). It is more flexible than ToString() and you may encounter cases where you have a need for ToString() to do something for non-UI stuff, which would create a conflict. And honestly, ToString is not really meant for UI stuff.

WPF databinding and converters

I'm trying to databind to a listbox like so:
<ListBox x:Name="MyListBox" Margin="0,0,0,65">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The reason I am binding to the whole object and not a property is because my converter will need multiple properties of the object to build the string that it returns.
This works and my string is returned. But then when I change the ObservableCollection that this is based on the value doesn't change on the screen. If I bind to just a single property and change it, then the value does change.
What can I do differently? I can't bind to a single property since I need the entire object in the converter... And the ConverterParameter is already being used.
Remember, if you bind to the "main" property and the value of the main property itself isn't changed, the binding will have no reason to refresh itself. It has no clue that your converter is actually based off of a sub-property. What you can do is use a MultiBinding where you bind not only the "main" property, but also a specific sub-property. This gives your IMultiValueConverter implementation access to the main data object, but because you're also binding to the sub-property that's changing, will also be refreshed when that sub-property's value changes.
You can try using a MultiBinding which I believe updates whenever any of its Bindings are triggered. You can also use an IMultiValueConverter or just take advantage of the StringFormat of the binding.

Resources