wpf converter binding - dynamic binding in wrong order - wpf

I am trying to bind a combobox with the Tabitems using converter
My converter class is as follows
public class TabItemsCollection : IValueConverter
{
>public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ItemCollection collection = value as ItemCollection;
IList<string> names = new List<string>();
foreach (TabItem ti in collection.SourceCollection)
{
names.Add(ti.Header.ToString());
}
return names;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
My xaml is as follows
//combobox
<ComboBox Name="cmbModule"
ItemsSource="{Binding ElementName=mnuMain, Path=Items, Converter={StaticResource MenuItemsConverter}}" SelectedIndex="{Binding ElementName=mnuMain, Path=SelectedIndex}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
//TabControl
<local:MenuTab Name="mnuMain"></local:MenuTab>
I am binding 'mnuMain' with items which is a custom tabcontrol in codebehind, as i am doing so i am unable to popularate combobox with tabitems because the converter fires first and then the 'mnuMain'. If I create the Tabitems in xaml the combobox is populated with tabitems but my problem is with dynamic binding.

There is a way to force your binding to update again:
cmbModule.GetBindingExpression(ComboBox.ItemsSourceProperty).UpdateTarget();
Another option is to create a DependecyProperty that holds the collection of tabs and then bind the Combobox and MenuTab to the same property. the SelectedIndex can be done in the same way as you do now.
A third option is to create a property of type ObservableCollection that holds the information that is needed and then create 2 Converters, one to convert to tabitem and 1 to convert to Combobox item. If you add or remove an item from the collection will the binding be triggered automatically.

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.

MVVM - hiding a control when bound property is not present

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
}

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 bind IsEnabled on other controls if a listbox has a select item

I have a grid with 2 columns, a listbox in column 0 and a number of other controls in the a secondary grid in the main grids column 1.
I want this controls only to be enabled (or perhaps visible) if an items is selected in the listbox through binding. I tried on a combo box:
IsEnabled="{Binding myList.SelectedIndex}"
But that does not seem to work.
Am I missing something? Should something like this work?
thanks
You'll need a ValueConverter for this. This article describes it in detail, but the summary is you need a public class that implements IValueConverter. In the Convert() method, you could do something like this:
if(!(value is int)) return false;
if(value == -1) return false;
return true;
Now, in your XAML, you need to do:
<Window.Resources>
<local:YourValueConverter x:Key="MyValueConverter">
</Window.Resources>
And finally, modify your binding to:
IsEnabled="{Binding myList.SelectedIndex, Converter={StaticResource MyValueConverter}"
Are you sure you didn't mean
IsEnabled="{Binding ElementName=myList, Path=SelectedIndex, Converter={StaticResource MyValueConverter}"
though? You can't implicitly put the element's name in the path (unless the Window itself is the DataContext, I guess). It might also be easier to bind to SelectedItem and check for not null, but that's really just preference.
Oh, and if you're not familiar with alternate xmlns declarations, up at the top of your Window, add
xmlns:local=
and VS will prompt you for the various possibilities. You need to find the one that matches the namespace you put the valueconverter you made in.
Copy-paste solution:
Add this class to your code:
public class HasSelectedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is int && ((int) value != -1);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Add converter as StaticResource to App.xml in <Application.Resources> section:
<local:HasSelectedItemConverter x:Key="HasSelectedItemConverter" />
And now you can use it in your XAML:
<Button IsEnabled="{Binding ElementName=listView1, Path=SelectedIndex,
Converter={StaticResource HasSelectedItemConverter}"/>
Hmm, perhaps it works with a BindingConverter, which converts explicitly all indexes > 0 to true.

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