Is there a way I could do this in a Style:
<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
<Setter.Value>
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=UserControl}"
Converter="{StaticResource AccessLevelToVisibilityConverter}"
ConverterParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}" />
</Setter.Value>
</Setter>
</Style>
I simply need to send the Tag of top-level parent and the Tag of control itself to my converter class.
The ConverterParameter property can not be bound because it is not a dependency property.
Since Binding is not derived from DependencyObject none of its properties can be dependency properties. As a consequence, a Binding can never be the target object of another Binding.
There is however an alternative solution. You could use a MultiBinding with a multi-value converter instead of a normal Binding:
<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource AccessLevelToVisibilityConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=UserControl}"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
The multi-value converter gets an array of source values as input:
public class AccessLevelToVisibilityConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.All(v => (v is bool && (bool)v))
? Visibility.Visible
: Visibility.Hidden;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
No, unfortunately this will not be possible because ConverterParameter is not a DependencyProperty so you won't be able to use bindings
But perhaps you could cheat and use a MultiBinding with IMultiValueConverter to pass in the 2 Tag properties.
There is also an alternative way to use MarkupExtension in order to use Binding for a ConverterParameter. With this solution you can still use the default IValueConverter instead of the IMultiValueConverter because the ConverterParameter is passed into the IValueConverter just like you expected in your first sample.
Here is my reusable MarkupExtension:
/// <summary>
/// <example>
/// <TextBox>
/// <TextBox.Text>
/// <wpfAdditions:ConverterBindableParameter Binding="{Binding FirstName}"
/// Converter="{StaticResource TestValueConverter}"
/// ConverterParameter="{Binding ConcatSign}" />
/// </TextBox.Text>
/// </TextBox>
/// </example>
/// </summary>
[ContentProperty(nameof(Binding))]
public class ConverterBindableParameter : MarkupExtension
{
#region Public Properties
public Binding Binding { get; set; }
public BindingMode Mode { get; set; }
public IValueConverter Converter { get; set; }
public Binding ConverterParameter { get; set; }
#endregion
public ConverterBindableParameter()
{ }
public ConverterBindableParameter(string path)
{
Binding = new Binding(path);
}
public ConverterBindableParameter(Binding binding)
{
Binding = binding;
}
#region Overridden Methods
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multiBinding = new MultiBinding();
Binding.Mode = Mode;
multiBinding.Bindings.Add(Binding);
if (ConverterParameter != null)
{
ConverterParameter.Mode = BindingMode.OneWay;
multiBinding.Bindings.Add(ConverterParameter);
}
var adapter = new MultiValueConverterAdapter
{
Converter = Converter
};
multiBinding.Converter = adapter;
return multiBinding.ProvideValue(serviceProvider);
}
#endregion
[ContentProperty(nameof(Converter))]
private class MultiValueConverterAdapter : IMultiValueConverter
{
public IValueConverter Converter { get; set; }
private object lastParameter;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (Converter == null) return values[0]; // Required for VS design-time
if (values.Length > 1) lastParameter = values[1];
return Converter.Convert(values[0], targetType, lastParameter, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (Converter == null) return new object[] { value }; // Required for VS design-time
return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
}
}
}
With this MarkupExtension in your code base you can simply bind the ConverterParameter the following way:
<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
<Setter.Value>
<wpfAdditions:ConverterBindableParameter Binding="{Binding Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}"
Converter="{StaticResource AccessLevelToVisibilityConverter}"
ConverterParameterBinding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}" />
</Setter.Value>
</Setter>
Which looks almost like your initial proposal.
Related
I have a DataGrid displaying a list of maps with some details. I'm trying to change the foreground colour of the map which is currently active but I'm struggling. I've written the following converter to check if the DataGrid item's value matches the current map's name, but I'm not sure how to pass the name of the current map from XAML.
Converter:
public class StringToBrushConverter : IValueConverter
{
public string CurrentMapName { get; set; }
public Color CurrentMapColor { get; set; }
public Color OtherMapColor { get; set; }
string _value = "";
public StringToBrushConverter(string currentMapName, Color currentMapColor, Color otherMapColor)
{
CurrentMapName = currentMapName;
CurrentMapColor = currentMapColor;
OtherMapColor = otherMapColor;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
_value = value as string;
return (string)value == CurrentMapName ? new SolidColorBrush(CurrentMapColor) : new SolidColorBrush(OtherMapColor);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return _value; //TODO: Will this work?
}
}
public sealed class RaceMapColorConverter : StringToBrushConverter
{
public RaceMapColorConverter() : base("", new Color { R = 255, G = 0, B = 0 }, new Color { R = 255, G = 255, B = 255 }) { }
}
XAML:
<DataGrid x:Name="MapHistory"
AutoGenerateColumns="False"
IsReadOnly="True"
HeadersVisibility="Column"
GridLinesVisibility="None"
CanUserSortColumns="False"
Style="{StaticResource MapTimerDataGrid}"
CellStyle="{StaticResource MapTimerControlCell}"
ItemsSource="{Binding Plan.Plan}">
<DataGrid.Resources>
<!-- The Binding in CurrentMapName is not valid. It says it can only be set on a dependency property but I'm not sure how to do that -->
<conv:RaceMapColorConverter x:Key="MapColorConverter" CurrentMapName="{Binding CurrentMap.MapName}" CurrentMapColor="Red" OtherMapColor="White" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Map" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Map.MapName}" Foreground="{Binding Map.MapName, Converter={StaticResource MapColorConverter}}" Padding="0" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Other columns removed to shorten -->
</DataGrid.Columns>
</DataGrid>
I'm also not sure how to implement 'ConvertBack()`. Will storing the original value in the converter work? or could a new instance be created for the same control? Am I even going about this the right way? Thought it's also worth mentioning that I'm using Caliburn.Micro just in case it has any tricks that may help. Any help would be appreciated.
As a general rule-of-thumb you should use converters when the data conversion needs to be two-way (i.e. view-model to view and back again) and DataTriggers when it's one-way only. That answers your question about ConvertBack (which in the example you've provided never gets called anyway, in which case you can just return Binding.DoNothing).
In this particular case, however, a converter is warranted because you need to compare two bindings. Instead of writing an application-aware converter for this one very specific task, I would instead opt for a more generic MultiConverter that simply compares two values that you provide it:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (values.Length==2) && Object.Equals(values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
Then back in your application you pass both of your bindings into a MultiBinding of this type and use a DataTrigger to change the map color when the result of the comparison is true:
<Label Content="{Binding Map.MapName}">
<Label.Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Foreground" Value="Red" /> <!-- normal map color -->
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}" Mode="OneWay">
<Binding Path="Map.MapName" />
<Binding Path="CurrentMapName" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="White" /> <!-- other map color -->
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Is there a way I could do this in a Style:
<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
<Setter.Value>
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=UserControl}"
Converter="{StaticResource AccessLevelToVisibilityConverter}"
ConverterParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}" />
</Setter.Value>
</Setter>
</Style>
I simply need to send the Tag of top-level parent and the Tag of control itself to my converter class.
The ConverterParameter property can not be bound because it is not a dependency property.
Since Binding is not derived from DependencyObject none of its properties can be dependency properties. As a consequence, a Binding can never be the target object of another Binding.
There is however an alternative solution. You could use a MultiBinding with a multi-value converter instead of a normal Binding:
<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource AccessLevelToVisibilityConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=UserControl}"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
The multi-value converter gets an array of source values as input:
public class AccessLevelToVisibilityConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.All(v => (v is bool && (bool)v))
? Visibility.Visible
: Visibility.Hidden;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
No, unfortunately this will not be possible because ConverterParameter is not a DependencyProperty so you won't be able to use bindings
But perhaps you could cheat and use a MultiBinding with IMultiValueConverter to pass in the 2 Tag properties.
There is also an alternative way to use MarkupExtension in order to use Binding for a ConverterParameter. With this solution you can still use the default IValueConverter instead of the IMultiValueConverter because the ConverterParameter is passed into the IValueConverter just like you expected in your first sample.
Here is my reusable MarkupExtension:
/// <summary>
/// <example>
/// <TextBox>
/// <TextBox.Text>
/// <wpfAdditions:ConverterBindableParameter Binding="{Binding FirstName}"
/// Converter="{StaticResource TestValueConverter}"
/// ConverterParameter="{Binding ConcatSign}" />
/// </TextBox.Text>
/// </TextBox>
/// </example>
/// </summary>
[ContentProperty(nameof(Binding))]
public class ConverterBindableParameter : MarkupExtension
{
#region Public Properties
public Binding Binding { get; set; }
public BindingMode Mode { get; set; }
public IValueConverter Converter { get; set; }
public Binding ConverterParameter { get; set; }
#endregion
public ConverterBindableParameter()
{ }
public ConverterBindableParameter(string path)
{
Binding = new Binding(path);
}
public ConverterBindableParameter(Binding binding)
{
Binding = binding;
}
#region Overridden Methods
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multiBinding = new MultiBinding();
Binding.Mode = Mode;
multiBinding.Bindings.Add(Binding);
if (ConverterParameter != null)
{
ConverterParameter.Mode = BindingMode.OneWay;
multiBinding.Bindings.Add(ConverterParameter);
}
var adapter = new MultiValueConverterAdapter
{
Converter = Converter
};
multiBinding.Converter = adapter;
return multiBinding.ProvideValue(serviceProvider);
}
#endregion
[ContentProperty(nameof(Converter))]
private class MultiValueConverterAdapter : IMultiValueConverter
{
public IValueConverter Converter { get; set; }
private object lastParameter;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (Converter == null) return values[0]; // Required for VS design-time
if (values.Length > 1) lastParameter = values[1];
return Converter.Convert(values[0], targetType, lastParameter, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (Converter == null) return new object[] { value }; // Required for VS design-time
return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
}
}
}
With this MarkupExtension in your code base you can simply bind the ConverterParameter the following way:
<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
<Setter.Value>
<wpfAdditions:ConverterBindableParameter Binding="{Binding Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}"
Converter="{StaticResource AccessLevelToVisibilityConverter}"
ConverterParameterBinding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}" />
</Setter.Value>
</Setter>
Which looks almost like your initial proposal.
My model have two independent properties:
enumType DataType
SomeSpec DataSpec
based on value of DataType I would like to interpretate the DataSpec accordantely
<ContentControl DataContext ="{Binding}">
<MultiBinding Converter ="{StaticResource DataContentConverter}">
<Binding Path ="DataType"/>
<Binding Path ="DataSpec"/>
</MultiBinding>
<ContentControl.Style>
<Style TargetType ="ContentControl">
<Style.Triggers>
<DataTrigger Binding ="{Binding DataType}" Value ="Image">
<Setter Property ="Template">
<Setter.Value>
<ControlTemplate>
<Image Source ="{Binding Image, Mode = OneWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I am using the following converter implementation
public class DataViewConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] as enumType == null) return null;
var selectedType = (enumType)values[0];
var selectedObject = (SomeSpec)values[1];
switch (selectedType )
{
case enumType.Thumbnail:
case enumType.DisplayImage:
{
return new DataContent
{
DataType = ReducedDataType.Image,
Image = SelectedObject.GetBitmapImage()
};
}
case ...
default:
{
return new DataContent
{
DataType = ReducedDataType.Unknown,
Text = "Content Viewer is not implemented!"
};
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] {};
}
}
Implementing this I expect to see an image in my window, but instaed of it I only see a text MyNameSpace.DataContent
What am I missing?
I think that your DataTrigger's binding is being based off the ContentControl's DataContext.
<DataTrigger Binding ="{Binding DataType}" Value ="Image">
So, it is looking at ContentControl.DataContext.DataType.
I think what you want is for it to look at the DataType property of the ContentControl's Content. Try changing the DataTrigger to this:
<DataTrigger Binding ="{Binding Content.DataType, RelativeSource={RelativeSource Self}}" Value ="Image">
So now it is looking at ContentControl.Content.DataType. The RelativeSource part directs the binding to the ContentControl instead of ContentControl.DataContext.
Is it possible to bind the same property of the control more than once?
To example:
<Popup IsOpen="{Binding Path=(local:ListViewBehavior.IsColumnHeaderClicked),
RelativeSource={RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}}" ...
As you can see Popup.IsOpen is bound to attached property. I'd like to bind it to ViewModel IsPopupOpened, but have no idea how.
Trying #Arhiman answer without much success:
<Popup.IsOpen>
<MultiBinding Converter="{local:MultiBindingConverter}">
<Binding Path="(local:ListViewBehavior.IsColumnHeaderClicked)"
RelativeSource="{RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}" />
<Binding Path="DataContext.IsPopupId"
RelativeSource="{RelativeSource FindAncestor, AncestorType=UserControl}" />
</MultiBinding>
</Popup.IsOpen>
Naive converter logic:
public class MultiBindingConverter : MarkupExtension, IMultiValueConverter
{
public MultiBindingConverter() { }
public override object ProvideValue(IServiceProvider serviceProvider) => this;
object[] _old;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (_old == null)
_old = values.ToArray();
// check if one of values is changed - that change is a new value
for (int i = 0; i < values.Length; i++)
if (values[i] != _old[i])
{
_old = values.ToArray();
return values[i];
}
return values[0];
}
// replicate current value
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
Enumerable.Repeat(value, targetTypes.Length).ToArray();
}
You could simply use a MultiBinding with a converter to implement the logic you'd like.
<Popup.IsOpen>
<MultiBinding Converter="{StaticResource openLogicConverter}">
<Binding Path="MyAttachedProperty" ... />
<Binding Path="IsPopupOpened" />
</MultiBinding>
</Popup.IsOpen>
I'd usually put this logic in the ViewModel, but as it is an AttachedProperty, something directly in the View seems more appropriate to me.
I have a multiBinding which works great,
I want to be able to sort a certain column, it displays w:width, h:heightAs far as I understand I need to build a custom IComparer class which will to the comparison.
Here is my XAML
<igWPF:UnboundField Label="Output
Width/Height" Width="auto">
<igWPF:Field.Settings>
<igWPF:FieldSettings SortComparer="{StaticResource SortWidthHeightComparer }">
<igWPF:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igWPF:CellValuePresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igWPF:CellValuePresenter}" >
<TextBlock Margin="3">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource settingsBdsToStringConverter}">
<Binding Path="DataItem.Key"/>
<Binding Path="DataContext.SelectedPipeMode" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type igWPF:XamDataGrid}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</igWPF:FieldSettings.CellValuePresenterStyle>
</igWPF:FieldSettings>
</igWPF:Field.Settings>
</igWPF:UnboundField>
Here is my multibinding converter
class SettingsOutputResToStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] is Mode && values[0] is ConfigurationKey)
{
var pMode = (Mode)values[1];
var key = values[0] as ConfigurationKey;
var res = key.GetOutput(pMode);
return String.Format("W: {0}, H: {1}", res.Width, res.Height);
}
return String.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
However my problem is how to I pass the multibind result into the Comparer class
class SortWidthHeightComparer : IComparer
{
public int Compare(object x, object y)
{
return 1;
}
}
object x and object y , are always null
Gilad,
I found this post:
http://www.infragistics.com/community/forums/t/17878.aspx
It seems like a simple solution.
I'm going to try the same approach for filtering records.