WPF: Binding Hierarchy - wpf

I have defined a default binding in my style.
For example I have configured the visibility binding of my button so that it must not be visible if the relative command can not execute.
This is my default binding behavior.
Apart from my default behavior, every view can customize the buttons it uses with another visibility binding.
I want to combine the two bindings so that if any of two say "it's not visible" it will be not visible!
In other words, is it possibile to create a binding behavior hierarchy? Thanks!

use a MultiBinding to wire up all the different bindings in XAML, and write your own IMultiValueConverter that prioritises / ands / ors each value as you like. You can't use a MultiBinding without an IMultiValueConverter (or a StringFormat, but that's no use to you)
Note that PriorityBinding is NOT what you are looking for here.
here's a valueConverter you can use:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleansAndToVisibilityMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
Func<bool, bool, bool> aggregator = (x, y) => x && y;
bool aggregate = values.Cast<bool>().Aggregate(aggregator);
return aggregate ? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

No, sorry, not possible in XAML.
You CAN do this combination in code, though. You can write a BindingConverter for this.

Related

Hiding columns from datagrid when autogenerate=false

I create columns in codebehind as such.. each column is bound to a property.
Dim column_selected As New DataGridCheckBoxColumn()
column_selected.Header = "Selected"
column_selected.Binding = New Binding("IsChecked")
dgvResults.Columns.Add(column_selected)
I want to be able to hide a column, based on a checkbox or something of that nature, where I can hide/show them at will.
I've heard about binding visibility to a property Boolean, but i'm not sure how to do that when the columns are created in code behind.
Any idea on how to accomplish this? Say I want to have a single checkbox that hides a specific column, if you unchecked it, it shows it.
If logic of setting column visibility doesn't contains any business logic and this is clear UI operation. Then I think you can just put it in the code-behind, what is wrong with that?
XAML
<CheckBox Checked="HideColumn" Unchecked="UnhideColumn"/>
Code-behind
Protected Sub HideColumn()
'your code
End Sub
Protected Sub UnhideColumn()
'your code
End Sub
You can set Binding normally from code-behind, but since System.Visibilty is enum, you have to use Converter (an instance of IValueConverter interface) to set Binding correctly. There are a lots of possible implementation, the following is an example:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool? vis = value as bool?;
return (vis.HasValue && vis.Value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
After that, the only thing you should do is to set the Converter property of your Binding to the new instance of BoolToVisibilityConverter as follows:
column_selected.Binding.Converter = new BoolToVisibilityConverter()
And that's all.

How do I bind a DataGrid Width to a converter?

I want to include the size of the VerticalScrollbar when i define the width of a DataGrid.
So far i wrote a Converter:
[ValueConversion(typeof(double), typeof(double))]
public class VerticalScrollbarConverter : IValueConverter
{
#region IValueConverter Member
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double)
return (double)value + SystemParameters.VerticalScrollBarWidth;
else
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
I included my converter in the xaml namespace with:
xmlns:Core="clr-namespace:Core;assembly=SMS_Core"
And I defined the converter as a (window) resource:
<Window.Resources>
<Core:VerticalScrollbarConverter x:Key="VerticalScrollbarConverter"/>
</Window.Resources>
Since all of my DataGrid.Columns have a fixed Value I know the value that i need to pass.
How do I tell my GridView Width property in xaml to use the converter?
I know that my question is pretty basic. As you can tell I am very new to WPF.
Thanks in advance for every hint. If you need some more info or context just ask away.
The comment from sa_ddam213 didn't exactly solve the problem. But it sure did push me into the right direction.
To pass the value automatically I had to do the following:
I created a property in the window class called TotalColumnWidth. Returning this:
myDataGrid.Columns.Sum(c => c.ActualWidth);
The xaml is the following:
Width="{Binding ElementName=_Root, Path=TotalColumnWidth, Converter={StaticResource ResourceKey=VerticalScrollbarConverter}}"
Using the converter mentioned above.

Dependency Property: Getting but not Setting

public static readonly DependencyProperty SingleGridLengthProperty = DependencyProperty.Register("SingleGridLength", typeof(double), typeof(MapConverter));
public class MapConverter : DependencyObject, INotifyPropertyChanged, IMultiValueConverter
{
public double SingleGridLength
{
get { return (double)GetValue(MapConverter.SingleGridLengthProperty); }
set
{
SetValue(MapConverter.SingleGridLengthProperty, value);
OnNotifyPropertyChanged("SingleGridLength");
}
}
<local:MapConverter x:Key="MapConverter"
SingleGridLength="{Binding SingleGridLength, RelativeSource={RelativeSource Self}}" />
I have a converter with a set of dependency properties bound in the .xaml
The problem I am having is each property is "getting" and returning the value but it never "sets" the value. Am I allowed to use dependency properties in converters? Or should I be approaching this a different way? Thanks in advance!
First, your binding is invalid. You are binding the SingleGridLength property to itself. You would need to bind it to another property/object.
Second, you shouldn't raise the OnNotifyPropertyChanged in the setter for your SingleGridLength property. You only need to do that for regular CLR properties. Dependency properties have a built in change notification system that Binding hooks into.
Take a look at the PropertyChangedCallback delegate that you can specify in the PropertyMetadata constructor. The callback will be invoked when the property value of your dependency property changes, and you can place your handling code within this callback method.
I suggest using a Converter basing on IValueConverter?
The converter then should only do the calculation from the input to the output format. The value returned by the converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
and
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
will be used by the property you did the binding on.
See: http://msdn.microsoft.com/de-de/library/system.windows.data.ivalueconverter.aspx

ComboBox of Types with a ValueConverter and Custom Attributes

I'm using MVVM, in case it makes a difference.
My MainWindowViewModel has two DependencyProperties, TheList, and TheSelectedItem. TheList is a List<Type>, TheSelectedItem is a Type.
The MainWindow has a ComboBox. When the MainWindowViewModel loads it grabs a list of all the classes in the assembly that implement IMyInterface and sets TheList to this.
Each of these classes has a custom attribute applied called DisplayName, which has one parameter, that will be used to show a user-friendly name for the class instead of the name the application knows about for the class.
I've also got a ValueConverter for the express purpose of converting these types into the display names.
public class TypeToDisplayName : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType.Name == "IEnumerable")
{
List<string> returnList = new List<string>();
if (value is List<Type>)
{
foreach (Type t in value as List<Type>)
{
returnList.Add(ReflectionHelper.GetDisplayName(t));
}
}
return returnList;
}
else
{
throw new NotSupportedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return typeof(BasicTemplate);
}
}
So, what I wind up with is a ComboBox with a list of names in it that the user should be able to understand. Awesome! This is just what I want!
Next step: I bind the SelectedItem property of my ComboBox to my TheSelectedItem property in my ViewModel.
Here's the problem: When I make a selection, I get a little red box around my ComboBox and the TheSelectedItem property on my ViewModel never gets set.
I'm pretty sure it's because of a type mismatch (the items in the ComboBox appear to be strings now, and TheSelectedItem is of type Type--also, when I change TheSelectedItem to a string instead of a Type, it works). But I don't know where I need to start coding to convert the (hopefully unique) DisplayName that's in the ComboBox back to a Type object.
Thanks in advance for any help. I'm pretty stumped on this one.
If I understand your question correctly then you use that Converter on the ItemsSource for the ComboBox? In that case I think you can let the ItemsSource be like it is and instead just Convert each type when they are presented like this.
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=typeName, Converter={StaticResource TypeToDisplayNameConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And then just convert each type in the Converter.
public class TypeToDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Type t = (Type)value;
return ReflectionHelper.GetDisplayName(t);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
Make sure you have IsSynchronizedWithCurrentItem set to true on the ComboBox. Check this out...

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

Resources