Binding-driven Indexed Property Doesn't Return - wpf

Public Class View
Public Property Items As String() = {"One", "Two", "Three"}
Public Property Index As Integer = 0
End Class
It's instance is set as DataContext of this XAML:
<Window>
<StackPanel>
<ListBox ItemsSource="{Binding Items}" SelectedIndex="{Binding Index}"/>
<Label Content="{Binding Items[Index]}"/>
</StackPanel>
</Window>
But this doesn't work.
<Label Content="{Binding Items[{Binding Index}]}"/>
This neither.
<Label Content="{Binding Items[0]}"/>
This works.
Is there any solution except making extra property in view? Something directly in XAML?

I'm afraid it's not possible without some code-behind, but using reflection and dynamic, you can create a converter that can do this (it would be possible without dynamic, but more complex):
public class IndexerConverter : IValueConverter
{
public string CollectionName { get; set; }
public string IndexName { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type type = value.GetType();
dynamic collection = type.GetProperty(CollectionName).GetValue(value, null);
dynamic index = type.GetProperty(IndexName).GetValue(value, null);
return collection[index];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Put following into resources:
<local:IndexerConverter x:Key="indexerConverter" CollectionName="Items" IndexName="Index" />
and use it like this:
<Label Content="{Binding Converter={StaticResource indexerConverter}}"/>
EDIT: The previous solution doesn't update properly when the values change, this one does:
public class IndexerConverter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
return ((dynamic)value[0])[(dynamic)value[1]];
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
In resources:
<local:IndexerConverter x:Key="indexerConverter"/>
Usage:
<Label>
<MultiBinding Converter="{StaticResource indexerConverter}">
<Binding Path="Items"/>
<Binding Path="Index"/>
</MultiBinding>
</Label>

What you write in the binding markup extension is assigned to the Path property by default, this property is a string so any dynamic content you refer to inside it will not be evaluated. There is no simple XAML-only method to do what you try to do.

Why don't use this:
<StackPanel>
<ListBox Name="lsbItems" ItemsSource="{Binding Items}" SelectedIndex="{Binding Index}"/>
<Label Content="{Binding ElementName=lsbItems, Path=SelectedItem}"/>
</StackPanel>

Related

Binding custom Dependency Property to ListView width not updating

I have an IValueConverter named CategoryToGridViewConverter which has a DependencyProperty named ListViewWidthProperty. I have also inherited my converter from DependencyObject so that it will work with DependencyProperties. Here is how my converter is defined:
internal class CategoryToGridViewConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty ListViewWidthProperty = DependencyProperty.Register("ListViewWidth", typeof (double),
typeof (CategoryToGridViewConverter), new PropertyMetadata(0d, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Debug.WriteLine("ListViewWidth: " + dependencyPropertyChangedEventArgs.NewValue);
}
public double ListViewWidth {
get { return (double)GetValue(ListViewWidthProperty); }
set { SetValue(ListViewWidthProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//Not relevant
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//Not relevant
}
}
}
Pretty simple. Then I am creating this converter in XAML (In ListView Resources section) like this:
<ListView Grid.Row="0" BorderThickness="0"
ItemsSource="{Binding SelectedCategory.Entries}"
x:Name="listView"
SizeChanged="ListView_OnSizeChanged">
<ListView.Resources>
<local:CategoryToGridViewConverter x:Key="CategoryToGridViewConverter" ListViewWidth="{Binding listView, Path=Width, Mode=OneWay}"/>
</ListView.Resources>
<ListView.View>
<Binding Path="SelectedCategory" Converter="{StaticResource CategoryToGridViewConverter}"/>
</ListView.View>
</ListView>
But the problem is that ListViewWidth never updates. It always stays at 0. But the ListView in question is obviously not having 0 width.
What am I doing wrong?
Edit: It also gives a binding error on output screen:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement
or FrameworkContentElement for target element.
BindingExpression:Path=Width; DataItem=null; target element is
'CategoryToGridViewConverter' (HashCode=33594544); target property is
'ListViewWidth' (type 'Double')
Does DependencyProperties only work for FrameworkElements? If so, how will my converter get to know about ListView's width?
I think you can just use a multi-value converter.
<local:CategoryToGridViewMultiConverter x:Key="CategoryToGridViewConverter" />
The converter:
public class CategoryToGridViewMultiConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var category = (Category)values[0]; // Assumption: you're using a class named Category
var listViewWidth = (double)values[1];
// same logic you had before
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage:
<ListView.View>
<MultiBinding Converter="{StaticResource CategoryToGridViewConverter}">
<Binding Path="SelectedCategory" />
<Binding ElementName="listView" Path="Width" Mode="OneWay" />
</MultiBinding>
</ListView.View>

WPF - binding complex objects to simple controls

I have created a class named "BoundProperty" which contains a property "Value".
Binding to a property which is an instance of that class looks this way
(Age is a BoundProperty):
<TextBox Text="{Binding MyModel.Age.Value, Mode=TwoWay}" />
Is there a way to make the binding look this way and additionally still retain it two-way?
<TextBox Text="{Binding MyModel.Age, Mode=TwoWay}" />
I can't use implicit/explicit conversion operators, for this "BoundProperty" initialization requires special parameters, which need to be copied from original object.
Thanks,
AD
If Value is public you can use ValueConverter:
public class BoundPropertyConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var age = value as BoundProperty;
if (age == null)
return string.Empty;
return age.Value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int age;
if (int.TryParse(value.ToString(), out age))
return new BoundProperty() {Value = age};
return null;
}
}
Then in xaml define namespace to this converter
xmlns:converters="clr-namespace:Your.Namespace"
And then in Resources area write something like this:
<converters:BoundPropertyConverter x:Key="BoundPropertyConverter"/>
And last but not least:
<TextBox Text="{Binding MyModel.Age, Mode=TwoWay, Converter={StaticResource BoundPropertyConverter}" />

Why is binding to "Self" not working?

I try to bind the control's Tag property to the control's Visibility property, but I receive the following error in VS:
object reference not set to an instance of an object
The XAML is like below:
<RadioButton GroupName="NavButtonSide"
Tag="7"
Visibility="{Binding Path=Tag,
Converter={StaticResource ControlTagVisibilityConverter},
RelativeSource={RelativeSource Mode=Self}}"/>
Can anyone tell me how to solve it?
Nothing wrong with your binding. This works fine for me;
<Window.Resources>
<local:ControlTagVisibilityConverter x:Key="ControlTagVisibilityConverter" />
</Window.Resources>
<RadioButton
Tag="7"
Visibility="{Binding Path=Tag,
Converter={StaticResource ControlTagVisibilityConverter},
RelativeSource={RelativeSource Mode=Self}}"/>
ControlTagVisibilityConverter.cs
public class ControlTagVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var valueAsString = (string) value;
var valueAsInt = int.Parse(valueAsString);
switch (valueAsInt)
{
case 7:
return Visibility.Visible;
default:
return Visibility.Hidden;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

textbox visibility/collapsed in silverlight mvvm

I Silverlight5 with mvvm pattern i have one doubt.
In xaml i have used one textblock and i bind some id in it.
If the textblock content value is 1 or 2 means
yet another textbox is visible or else that is collapsed.. how to acheive that..
my code:
<TextBlock Name="textBlock1" Text="{Binding id}" Loaded="textBlock1_Loaded" Visibility="Collapsed" />
<TextBox Text="{Binding name,Mode=TwoWay}" x:Name="t1" Visibility="{Binding IsVisible,Converter={StaticResource visibilityconverter}}" />
in view model i had created the property for id and raised the event and bind the value to textblock.
to convert the value to visible i have a visibilityconverter class in one separate folder named "Converters"
public class visibilityconverter:IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (id==1 && id==2)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
in the above visibleconverter class how i cna get the id value from viewmodel and check it..
If i got the value from viewmodel to visibilityconverter means i will proceed further.
tell me if u can..!
Hi i have found the solution..
In xaml give the following:
<TextBox Text="{Binding name,Mode=TwoWay}" x:Name="t1" Visibility="{Binding id,Converter={StaticResource visibilityconverter}}" />
In visibilityConverter class:
public class visibilityconverter:IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
string v = value.ToString();
if (v =="1" || v=="2")
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Problem Solved... !

how to bind a boolean to combobox in wpf

Well I was wondering how to bind a boolean property to a combobox.Combobox will be a yes/no combobox.
You could use a ValueConverter to convert the boolean value to a ComboBox index and back. Like this:
public class BoolToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value == true) ? 0 : 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value == 0) ? true : false;
}
}
}
Assuming Yes is on index 0 and No on index 1. Then you'd have to use that converter in binding to the SelectedIndex property. For this, you declare your converter in your resources section:
<Window.Resources>
<local:BoolToIndexConverter x:Key="boolToIndexConverter" />
</Window.Resources>
Then you use it in your binding:
<ComboBox SelectedIndex="{Binding YourBooleanProperty, Converter={StaticResource boolToIndexConverter}}"/>
I have found myself using the IsSelected property of the ComboBox items for this in the past. This method is entirely in xaml.
<ComboBox>
<ComboBoxItem Content="No" />
<ComboBoxItem Content="Yes" IsSelected="{Binding YourBooleanProperty, Mode=OneWayToSource}" />
</ComboBox>
First solution is to replace your 'Yes/No' combobox with a checkbox because, well, checkbox exists for a reason.
Second solution is to fill your combobox with true and false objects and then bind the 'SelectedItem' of your combobox to your Boolean property.
Here is an example (replace enabled/disabled with yes/no):
<ComboBox SelectedValue="{Binding IsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnabledDisabledToBooleanConverter.Instance}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Items>
<system:Boolean>True</system:Boolean>
<system:Boolean>False</system:Boolean>
</ComboBox.Items>
</ComboBox>
Here is Converter:
public class EnabledDisabledToBooleanConverter : IValueConverter
{
private const string EnabledText = "Enabled";
private const string DisabledText = "Disabled";
public static readonly EnabledDisabledToBooleanConverter Instance = new EnabledDisabledToBooleanConverter();
private EnabledDisabledToBooleanConverter()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Equals(true, value)
? EnabledText
: DisabledText;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//Actually won't be used, but in case you need that
return Equals(value, EnabledText);
}
}
And no need to play with indices.

Resources