MVVM - hiding a control when bound property is not present - wpf

I was wondering if it is possible to hide a control on a view if the property to which the control is bound does not exist in the view model. For example, if I have the following:
<CheckBox Content="Quote"
IsChecked="{Binding Path=IsQuoted}" />
Can I detect in XAML that the IsQuoted property does not exist on the view model, and simply hide the control in that instance.
I am essentially creating a wizard dialog that moves through a collection of view models, displaying the associated view for each one. For some of the view models in the collection, the "IsQuoted" property will be present, and for some not.
I would like to have a check box outside of these views that displays when the current view model has the property, and hides when the view model does not. All of the view models are derived from a common base class, but I would rather not clutter the base by adding a "ShowQuoted" property, etc.
Thoughts? And, thanks in advance...

Handle the case where it the value is present by using a converter which always returns Visibility.Visible. Handle the case where the value isn't present by specifying a fallback value. When the property isn't present the binding fails and receives the fallback value.
<Page.DataContext>
<Samples:OptionalPropertyViewModel/>
</Page.DataContext>
<Grid>
<Grid.Resources>
<Samples:AlwaysVisibleConverter x:Key="AlwaysVisibleConverter" />
</Grid.Resources>
<CheckBox
Content="Is quoted"
IsChecked="{Binding IsQuoted}"
Visibility="{Binding IsQuoted,
Converter={StaticResource AlwaysVisibleConverter},
FallbackValue=Collapsed}"
/>
</Grid>
public class OptionalPropertyViewModel
{
public bool IsQuoted { get; set; }
}
public class AlwaysVisibleConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value,
Type targetType, object parameter, CultureInfo culture)
{
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

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 do I sort a WPF TreeView using the HierarchicalDataTemplate for EntityCollection objects?

My Webpage is an Entity Framework entity. These are bound to a WPF TreeView. I want to order all the Webpages shown in the TreeView on the Sort property.
Code
EDMX
Its Subordinates property returns a collection of zero or more Webpages.
XAML
<TreeView Name="TreeViewWebpages">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Webpage}"
ItemsSource="{Binding Subordinates}">
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
C#
TreeViewWebpages.ItemsSource = from Webpage root in db.Webpages.Include("Subordinates")
where root.Dominant == null
select root;
Result
Webpages are unordered within the TreeView.
Problem
How do I change this to order all the Webpages shown in the TreeView on the Sort property?
Update
This ValueConverter seems to work (Thank you #KP Adrian and #IVerzin). Is there a better way?
XAML
ItemsSource="{Binding Path=Subordinates, Converter={local:SortConverter}}"
C#
[ValueConversion(typeof(EntityCollection<Webpage>), typeof(EntityCollection<Webpage>))]
public class SortConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((EntityCollection<Webpage>)value).OrderBy(o => o.Sort);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Assuming your Sort property is a string or integer you are using to determine the order at runtime, you can add an orderby section to your expression.
TreeViewWebpages.ItemsSource = from Webpage root in db.Webpages.Include("Subordinates")
where root.Dominant == null
orderby root.Sort
select root;

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

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

Bind silverlight combobox to the result of another combobox

I want to do something like this:
<combobox x:Name="cboCustomers" ItemsSource="{Binding Path=Data.Customers}"/>
<combobox x:Name="cboInvoices"ItemsSource="{cboCustomers.SelectedItem.Invoices}"/>
Anyone know of a way to do something like this in Silverlight 3? I am sure there is some information about it out there, but I am having bad luck with Google in forming the question.
You need to specify ElementName on the second binding:
<combobox x:Name="cboCustomers" ItemsSource="{Binding Data.Customers}"/>
<combobox x:Name="cboInvoices"ItemsSource="{Binding SelectedItem.Invoices, ElementName=cboCustomers}"/>
If you also want the second combobox to be disabled until something has been selected in the first combobox you can bind the IsEnabled property of the second combobox to the SelectedItem property of the first combobox through a converter.
Add this class to your project:
public class NullToBooleanConverter : IValueConverter {
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
if (targetType == typeof(Boolean))
return value != null;
throw new NotSupportedException("Value converter can only convert to Boolean type.");
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
throw new NotSupportedException("Value converter cannot convert back.");
}
}
Add an instance of this class to the resource dictionary of your user control (local is the namespace tag for the namespace of the converter):
<UserControl.Resources>
<local:NullToBooleanConverter x:Key="NullToBooleanConverter"/>
</UserControl.Resources>
Then you can add this to the second combobox:
IsEnabled="{Binding SelectedItem, ElementName=cboCustomers, Converter={StaticResource NullToBooleanConverter}}"
You'd be looking at a Cascading Combobox
http://weblogs.asp.net/manishdalal/archive/2008/10/22/cascading-combobox.aspx

Resources