Binding custom Dependency Property to ListView width not updating - wpf

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>

Related

Can WPF Databinding source include a parameter?

Can I pass an index number into this list (SomeList)?
FontSize="{Binding FontSize, Source={x:Static ut:ViewSetupData.SomeList}, FallbackValue=12}"
You can put a constant indexer in the Path:
{Binding Path=[(sys:Int32)0], Source={x:Static ut:ViewSetupData.SomeList}}
But you can't bind a property of a Binding, so there's no way to stuff a parameter in there. However, you can combine multiple bindings in a MultiBinding, so you could use one of those with a multi-value converter:
C#:
public class IListIndexerConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// You might want a little more error-checking than this...
return ((IList)values[0])[(int)values[1]];
}
public virtual object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
XAML:
<TextBlock>
<TextBlock.Resources>
<local:IListIndexerConverter x:Key="ListIndexer" />
</TextBlock.Resources>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ListIndexer}">
<Binding Source="{x:Static ut:ViewSetupData.SomeList}" />
<Binding
ElementName="MyComboBox"
Path="SelectedIndex"
/>
</MultiBinding>
</TextBlock.Test>
</TextBlock>
Update
While you were marking this as the solution, I was writing a more complete solution that addressed your need to grab a property from the list item:
C#:
public class ListItemPropertyGetter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
var list = values[0] as IList;
var index = (int)(values[1] ?? 0);
var propname = values[2] as String;
object item = list[index];
var prop = item.GetType().GetProperty(propname);
var propvalue = prop.GetValue(item);
return propvalue;
}
catch
{
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
// Gotta put these somewhere
public static List<FontSizeThing> FontSizeThings { get; } =
new List<FontSizeThing>
{
new FontSizeThing(10),
new FontSizeThing(10.5),
new FontSizeThing(11),
new FontSizeThing(12),
new FontSizeThing(14),
new FontSizeThing(15),
};
}
public class FontSizeThing {
public FontSizeThing(double n) { FontSize = n; }
public double FontSize { get; set; }
}
XAML:
<ComboBox x:Name="FontSizeOptionCombo">
<sys:Int32>0</sys:Int32>
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
<sys:Int32>3</sys:Int32>
<sys:Int32>4</sys:Int32>
</ComboBox>
<TextBlock Text="Testing">
<TextBlock.Resources>
<hconv:ListItemPropertyGetter x:Key="ListItemPropertyGetter" />
</TextBlock.Resources>
<TextBlock.FontSize>
<MultiBinding Converter="{StaticResource ListItemPropertyGetter}" StringFormat="{}{0}">
<Binding Source="{x:Static hconv:ListItemPropertyGetter.FontSizeThings}" />
<Binding ElementName="FontSizeOptionCombo" Path="SelectedItem" />
<Binding Source="FontSize" />
</MultiBinding>
</TextBlock.FontSize>
</TextBlock>
FINAL UPDATE
Note that if I had merely populated FontSizeOptionCombo with the FontThings themselves, I could very simply have bound like this:
<ComboBox
x:Name="OtherCombo"
ItemsSource="{x:Static hconv:ListItemPropertyGetter.FontSizeThings}"
DisplayMemberPath="FontSize"
FontSize="{Binding SelectedItem.FontSize, ElementName=OtherCombo, FallbackValue=20}"
/>
If that fits in with what you're doing, it's by far the nicest way.

Binding-driven Indexed Property Doesn't Return

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>

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.

Why MenuItem doesn't send specified parameters but Button send

<MenuItem Command="local:CommandLibrary.RegisterServiceCommand">
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource TrayWindowViewModelConverterResource}">
<MultiBinding.Bindings>
<Binding ElementName="Me" />
<Binding FallbackValue="Parser" />
</MultiBinding.Bindings>
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
public class TrayWindowViewModelConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
var viewModel = new Window1ViewModel();
foreach (var obj in values) {
if (obj is Window)
viewModel.Caller = obj as Window;
else if (obj is string)
viewModel.ServiceName = obj.ToString();
}
return viewModel;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Button cammand is exactly same as MenuItem. when i debug Converter for MenuItem, values parameter contains two object: DependencyProperty.UnsetValue (I'm not aware what's this) and MyContextMenu object.
And also how can i pass SomeType as parameter?
Thanks
MenuItems exist in popups that are outside the main visual tree and so don't have the same name scope as surrounding elements, like your Button. When trying to bind, the ElementName binding can't resolve because the "Me" element is outside the MenuItem's name scope.

Changing the appearance of collection of items according to a single property in XAML

I have 4 buttons in grid which datacontext is set to an object which has property that indicates what button should be enabled (it's enumerable).
Currenty I have done this in code-behind so that when that specific property changes, it disables all but one depending on the value. It works, but I really don't like to put stuff like this to code-behind. There must be a way to do this in xaml?
I could make own style for all four buttons and then do this with data triggers, but I would prefer more generic approach: use same style for all buttons that somehow applies differently depending on, for example, a button name and value of the property.
Thanks in advance.
You could use a MultiBinding to bind the IsEnabled property to a combination of the control's name and the property from your DataContext, and create a Style to apply it to all buttons in the Grid:
<Grid.Resources>
<local:EqualsConverter x:Key="EqualsConverter"/>
<Style TargetType="Button">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Name"/>
<Binding Path="EnabledButtonName"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
And in code:
public class EqualsConverter
: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values.Length == 2 && object.Equals(values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
You can create an association between the enum you've created and the buttons by having integer references to your enum values and giving those enum values as ConverterParameters for corresponding buttons.
For Eg:
The enum:
public enum myOptions
{
value1 = 1,
value2 = 2,
value3 = 3,
value4 = 4
}
The Binding:
<Button IsEnabled = {Binding Path=myProperty,
Converter = {StaticResource EnumToBoolConverter},
ConverterParameter = 1} />
<Button IsEnabled = {Binding Path=myProperty,
Converter = {StaticResource EnumToBoolConverter},
ConverterParameter = 2} />
And the Converter:
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value == (int)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}

Resources