WPF - Dynamically access a specific item of a collection in XAML - wpf

I have a data source ('SampleAppearanceDefinitions'), which holds a single collection ('Definitions'). Each item in the collection has several properties, including Color, which is what I'm interested in here.
I want, in XAML, to display the Color of a particular item in the collection as text. I can do this just fine using this code below...
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[0].Color}"
The only problem is, this requires me to hard-code the index of the item in the Definitions collection (I've used 0 in the example above). What I want to do in fact is to get that value from a property in my current DataContext ('AppearanceID'). One might imagine the correct code to look like this....
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[{Binding AppearanceID}].Color}"
...but of course, this is wrong.
Can anyone tell me what the correct way to do this is? Is it possible in XAML only? It feels like it ought to be, but I can't work out or find how to do it.
Any help would be greatly appreciated!
Thanks!
AT

MultiBinding is your friend here:
Assuming you have a TextBlock:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AppearanceIDConverter}">
<Binding Source="{StaticResource SampleAppearanceDefinitions}" />
<Binding Path="AppearanceID" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And define a MultiValueConverter to return what you wish to see:
public class AppearanceIDConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<item> items = (List<item>)values[0]; //Assuming its items in a List
int id = (int)values[1]; //Assuming AppearanceID is an integer
return items.First(i => i.ID == id).Color; //Select your item based on the appearanceID.. I used LINQ, but a foreach will work just fine as well
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
#endregion
}
Of course, you will need to set the converter as a resource in your Resource dictionary, like you did SampleAppearanceDefinitions. You can also ditch the multibinding and use a regular binding to AppearanceID with a IValueConverter, if you can get to the SampleAppearanceDefinitions collection through code ;).
Hope this helps

Even if it could be possible you'd better not do that this way, but instead use a dedicated property in your view model or in the code behind of your view if it has only a pure graphical meaning.
This property, say "CurrentAppearance", would expose a Color property you could bind from your Xaml :
Text="{Binding CurrentAppearance.Color}"
which is more understandable.
As a general advice : avoid to spoil your Xaml with plumbing code : Xaml should be as readable as possible,
particularly if you work with a team of designers that have no coding skills and do not want to be concerned with the way you are managing the data.
Moreover, if later you decide to change the way data are managed you would not have to change your Xaml.

MultiBinding might actually work if your list is on a viewmodel instead of a staticresource. I was suprised myself to see that the object passed on to the view is actually a pointer to the object on the model, so changing the object in the view (eg. typing in new test in the textbox) directly affects the model object.
This worked for me. The ConvertBack method is never useed.
public class PropertyIdToPropertyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2)
{
var properties = values[0] as ObservableCollection<PropertyModel>;
if (properties != null)
{
var id = (int)values[1];
return properties.Where(model => model.Id == id).FirstOrDefault();
}
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Related

XPath : Bind to last item of collection

Can I Bind TextBox.Text to last item of an ObservableCollection<string> ?
I tried this:
<TextBox Text={Binding XPath="Model/CollectionOfString[last()]"/>
But it doesn't bind.
Thank you.
Please try the method following,
1, use IValueConverter.
class DataSourceToLastItemConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IEnumerable<object> items = value as IEnumerable<object>;
if (items != null)
{
return items.LastOrDefault();
}
else return Binding.DoNothing;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
Then binding like this:
<Grid>
<Grid.Resources>
<local:DataSourceToLastItemConverter x:Key="DataSourceToLastItemConverter" />
</Grid.Resources>
<TextBox Text="{Binding Path=Model.CollectionOfString,Converter={StaticResource DataSourceToLastItemConverter}}"/>
</Grid>
It doesn't bind because you cannot use the XPath property on a-non XML data source; you have to use Path instead, and that property doesn't offer similar syntax. So you cannot directly bind to the last element of the collection unless you know the index of the last value. However there are a couple workarounds available:
Bind using a value converter
It's not difficult to write custom value converter that takes the collection and "converts" it to its last element. Howard's answer gives a barebones converter that does this.
Bind to the current item in the collection view
This is even easier to do, but it involves code-behind.
You can bind using Path=Model.CollectionOfString/ (note the slash at the end) if you have set the "current" item in the default collection view to be the last item in the collection. Do this inside your model:
// get a reference to the default collection view for this.CollectionOfString
var collectionView = CollectionViewSource.GetDefault(this.CollectionOfString);
// set the "current" item to the last, enabling direct binding to it with a /
collectionView.MoveCurrentToLast();
Be aware that if items are added to or removed from the collection, the current item pointer will not necessarily be adjusted automatically.

Writing conditional statements in XAML code

I have this listBox that gets populated, each item can be either male or female depending on the 'SEX' property that is binded to the listBox. (Could be either 'M' for male and 'F' for female)...
For each item i would like to display either a male or female symbol based on the list items SEX property.
for instance if "{Binding SEX}" equals 'M':
<Image Source="../Images/male48.png" Visibility="Visible" />
and if "{Binding SEX}" equals 'F':
<Image Source="../Images/female48.png" Visibility="Visible" />
How exactly would I go about getting this to work?
A common approach to this problem is to create a value converter, this converts the value returned by a binding into some other value that relates to the property of a UI control.
You can create a converter that takes the sex and maps it to an image source:
public class SexToSourceConverter : IValueConverter
{
public object Convert(object value, string typeName, object parameter, string language)
{
string url = ((string)value == "M") ? "../Images/male48.png" : "../Images/female48.png";
return new BitmapImage(new Uri(url , UriKind.Relative));
}
public object ConvertBack(object value, string typeName, object parameter, string language)
{
throw new NotImplementedException();
}
}
Using it in your XAML as follows:
<Image Source="{Binding Path=Sex, Converter={StaticResource SexToSourceConverter }" />
If someone is interested in how this could work, I've made a solution based on ColinE's answer. First, you've to create a new class which contain the conditions you'll like to add to the XAML code:
public class MyNiceConverterName : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
// Your conditions here!
return value_you_want_to_return; // E.g., a string, an integer and so on
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException(); // Leave this like here, don't worry
}
}
Call the class whatever you want (right now it's called MyNiceConverterName) and implement the Convert() method with the conditions you'd like to add to the XAML file. Remember to cast the object value to the type you're using (e.g., (int)value if it's an integer).
This is almost done! But not yet, first declare the converter in your XAML as a resource. You can paste this code below the namespaces declaration:
<Control.Resources>
<converter:MyNiceConverterName xmlns:converter="clr-namespace:My_Namespace" x:Key="MyNiceConverterName" />
</Control.Resources>
You've to declare the namespace where you defined the class (i.e., remove My_Namespace with yours') and also rename MyNiceConverterName to your class name. The key will be the name defined to reference the converter within the XAML document, here I've used the same class name but you can freely change it.
Finally, it's time to use the converter. Put this and you're done:
{Binding variable_with_value, Converter={StaticResource MyNiceConverterName}}
Remember to change variable_with_value with the one you'd like to use within your binding.
I hope it helps!
Either use a binding converter or use two triggers.
For Siverlight this is the correct IValueConverter link, I am not sure if triggers are supported.

How to resolve/control property name collision in XAML binding?

Interview Question
Phrased as:
If you have a property name collision, how would you specify the exact property to bind to in a Binding path expression (in XAML)?
I never faced this (property name collision) problem in any binding so far. With some reading I realized that this is possible in case I am binding to a overridden property because then I have two instances of this property (virtual in base, and overriden in derived) as far as resolution using Reflection is concerned. Which is what used by XAML.
Could there be any other case where XAML might face a property name collision?
Is there some support in API to handle/control that? (Instead of of course avoiding a collision)
Thanks for your interest.
Sounds like a complete nonsense to me. Unless they wanted to talk about bindings, using 'disjointed' sources like PriorityBinding and MultiBinding.
Frankly speaking I don't think overwritten properties can be involved into the matter as this is so much out of scope, you could equaly point out explicit interface implementations and many other things, which are clearly outside of WPF domain.
The best way I can think would be to use a ValueConverter. I don't think this really answers the question though since they're asking in a binding path expression, which I haven't seen to be possible. I'm not particularly fond of doing it this way because it feels like a hack, but it works at least for one way binding. Here's an example of how you might do it:
XAML:
<StackPanel Name="stack">
<StackPanel.Resources>
<loc:OverriddenMyPropertyConverter x:Key="BaseMyProperty"/>
</StackPanel.Resources>
<TextBox Text="{Binding Path=MyProperty}"/>
<TextBox Text="{Binding Mode=OneWay, Converter={StaticResource BaseMyProperty}}"/>
</StackPanel>
The DataContext of the StackPanel is an instance of MyClass. The first TextBox is bound to the MyClass.MyProperty property, and the second TextBox will be bound to the MyBaseClass.MyProperty property. Two way binding would be a bit more complex since the object actually being bound to the second TextBox is the MyClass object and not the MyProperty object.
Code:
class MyClass : MyBaseClass
{
string myProperty = "overridden";
public new string MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
class MyBaseClass
{
string baseProperty = "base";
public string MyProperty
{
get { return baseProperty; }
set { baseProperty = value; }
}
}
class OverriddenMyPropertyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value as MyBaseClass).MyProperty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Using resources as conversion results in a bind converter

When I try to bind a valueconverter from a defined enum Status to brush, I get an error in my XAML designer:
'OKStatus' resource not found.
The application works fine runtime, but I'm not able to see my GUI in the designer.
My resources are defined in the color.xaml file, which is read at run time.
All code is within the same namespace
My XAML:
xmlns:config="clr-namespace:App.MyNamespace"
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="c:\Skins\Colors.xaml" />
<ResourceDictionary Source="c:\Skins\Common.xaml" />
</ResourceDictionary.MergedDictionaries>
<config:StatusConverter x:Key="StateConverter" />
<config:BoolConverter x:Key="BoolConverter" />
<config:BooleanConverter x:Key="BooleanConverter" />
</ResourceDictionary>
</UserControl.Resources>
and
Status
My converter:
[ValueConversion(typeof(bool), typeof(Brush))]
public class BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool state = (bool)value;
FrameworkElement FrameElem = new FrameworkElement();
if (state == true)
return (FrameElem.FindResource("OKStatus") as Brush);
else
return (FrameElem.FindResource("ErrorStatus") as Brush);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
In this code the frameElem wont have any knowledge of the resources I have defined I guess, so I need a way to get access to my resources during design.
Is this possible?
Yes, it is possible, and your guess is correct. Resource finding starts with the logical tree, and creating a new FrameworkElement() doesn't satisfy this. It's completely disconnected.
What you can do (and what you may have to do if N8's suggestion doesn't work), is to hand your converter a reference to the UserControl as the FrameworkElement to call FindResource() on.
The reason N8's suggestion probably won't work is that Application.Current.FindResource() probably starts at application-level resources and then goes to system resources, but the resources you're after are in the UserControl's resources. If they were placed in App.xaml's resources, it would work. However, I think Application.Current may be null at design-time.
The easiest way I can think of to do this is in your UserControl's constructor:
public MyUserControl(){
var boolconv = new BoolConverter();
boolconv.FrameworkElement = this;
this.Resources.Add( "BoolConverter", boolconv );
InitializeComponent();
}
I'm pretty sure it goes before InitializeComponent(), rather than after.
Doing this in XAML would be more complicated, as you probably have to add a DependencyProperty to your converter so that you could bind the UserControl to it. I think that would be going overboard.
Another way is to put TrueBrush and FalseBrush properties on your converter and assign them in XAML, which is what I tend to do so that my converters are vague and general-use. (NB: Names are slightly different.)
<config:BoolToBrushConverter x:Key="Bool2Brush"
TrueBrush="{StaticResource OKStatusBrush}"
FalseBrush="{StaticResource ErrorStatusBrush}" />
I think the issue is that you are trying to find the resource out of a framework element not in the visual tree. Could you try the following instead?
Application.Current.FindResource("OKStatus") as Brush;
As I have learned by TechNet Wiki, there is necessary to use MultiValue Converter and MultiValueBinding to get correct registred converter and correct FrameworkElement by the UserControl.
XAML Example:
<TextBlock x:Name="tb1" Margin="20">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}"/>
<Binding Path="MyValue"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Then the converter declaration can looks :
public class MyConverter : IMultiValueConverter
{
FrameworkElement myControl;
object theValue;
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
myControl = values[0] as FrameworkElement;
theValue = values[1];
return myControl.FindResource(">>resource u need<<");
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
.....
}
}
The detail explanation is:
https://social.technet.microsoft.com/wiki/contents/articles/12423.wpfhowto-pass-and-use-a-control-in-it-s-own-valueconverter-for-convertconvertback.aspx
I have run into this problem as well. I think that calling Application.Current is the best way to get at resources from an IValueConverter, since they are not defined on a per window, or page or control, level. This would require the resources to be at least application-level, as stated above.
However, since the Application.Current reference is set to null in the designer, this method will always break the designer. What you appear to have done is given something for the designer to display, while you have given your running application access to resources in the converter.
For all of you out there with this issue, you don't need to implement the Kludge that lewi implemented; this is only if you want the designer surface to load. It does not affect your application while running as the Application.Current call has something to do.
Actually what I ended up doing (for now) was to change from FindResource to TryFindResource, and put the statements in a try/catch block.
This seems to work so far.
try
{
if (state == true)
return (FrameElem.TryFindResource("OKStatus") as Brush);
else
return (FrameElem.TryFindResource("ErrorStatus") as Brush);
}
catch (ResourceReferenceKeyNotFoundException)
{
return new SolidColorBrush(Colors.LightGray);
}

wpf xaml calling a method on the current object

Im attempting to bind to the output of a method. Now I've seen examples of this using ObjectDataProvider However the problem with this is ObjectDataProvider creates a new instance of the object to call the method. Where I need the method called on the current object instance. I'm currently trying to get a converter to work.
Setup:
Class Entity
{
private Dictionary<String, Object> properties;
public object getProperty(string property)
{
//error checking and what not performed here
return this.properties[property];
}
}
My attempt at the XAML
<local:PropertyConverter x:Key="myPropertyConverter"/>
<TextBlock Name="textBox2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myPropertyConverter}"
ConverterParameter="Image" >
<Binding Path="RelativeSource.Self" /> <!--this doesnt work-->
</MultiBinding>
</TextBlock.Text>
</TextBlock>
my code behind
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string param = (string)parameter;
var methodInfo = values[0].GetType().GetMethod("getProperty", new Type[0]);
if (methodInfo == null)
return null;
return methodInfo.Invoke(values[0], new string[] { param });
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException("PropertyConverter can only be used for one way conversion.");
}
My problem is that I cant seem to pass the current Entity into the converter. So When i try to use reflection to get the getProperty method I have nothing to operate on
thanks, steph
Wrap the call to the method inside a get property and add this get property to whatever class that is your current DataContext.
Edit: Answering your updated question.
If you only pass one parameter to the valueconverter you don't need a multivalueconverter, just use a regular valueconverter (implementing IValueConverter). Also, why not cast the object in the valueconverter to a Distionary and use it directly instead of using reflection.
To pass current datacontext as a binding do this: <Binding . />. I'm guessing the datacontext of the textblock is entity.
Still, all this is not necessary if all you want to do is run some code before accessing a dictionary item. Just use an index property instead, you can databind to it directly:
public class Entity
{
private Dictionary<String, Object> properties;
public object this[string property]
{
get
{
//error checking and what not performed here
return properties[property];
}
}
}
<TextBlock Text="{Binding Path=[Image]}" />

Resources