Multibinding with MultiConverter yields DependencyProperty.UnsetValue - wpf

I have a list of MyItem. This object represents a shape with some properties, such as width, height and color. Using a converter, I'm trying to change the fill color from (255, r, g, b) to (Alpha, r, g, b) if my object is selected (I do not want to change the opacity). Alpha (double) lives inside my viewmodel.
My problem is that both values sent to the converter are set to DependencyProperty.UnsetValue, no matter what I give to it. If I specify only the Path I would expect it to bind to my viewmodel, but it doesn't. Obviously, I'm doing something wrong here.
Q: I need the Alpha of my viewmodel and the MyItemColor of the DataContext for myitem (which I would assume is still the DataContext when inside the MultiBinding block). What should my Binding tags look like?
XAML
<DataTemplate>
<Grid>
<Rectangle x:Name="myitem" Width="{Binding MyItemWidth}" Height="{Binding MyItemHeight}" />
</Grid>
<DataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="myitem" Property="Fill">
<Setter.Value>
<!-- Simple binding works. WHAT is the difference?
<SolidColorBrush>
<SolidColorBrush.Color>
<Binding Path="Color" />
</SolidColorBrush.Color>
</SolidColorBrush>
-->
<SolidColorBrush>
<SolidColorBrush.Color>
<!-- Change fill color if DataContext.Scale changes -->
<MultiBinding Converter="{StaticResource DoubleToColorConverter}">
<!-- wrong? -->
<Binding Path="Alpha" />
<!-- wrong? -->
<Binding ElementName="myitem" Path="MyItemColor" />
</MultiBinding>
</SolidColorBrush.Color>
</SolidColorBrush>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Converter
public class DoubleToColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
// values[0] == DependencyProperty.UnsetValue
// values[1] == DependencyProperty.UnsetValue
return null; // New color
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
UPDATE
I solved it. I had to look for the parent control (which contains the items) in order to get access to my viewmodel. A clarification of this, why it's good/bad, suffices as an answer! I don't fully understand what's going on :)
<MultiBinding Converter="{StaticResource DoubleToColorConverter}">
<Binding ElementName="myItemsControl" Path="DataContext.Alpha"/>
<Binding Path="Color" />
</MultiBinding>
And...
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue)
return Colors.Transparent;
...
}

Related

Change DataGridColumnheader background on click of DatagridCell or current cell changed

The background of the column header of the current cell is changed when the CurrentCellChanged event is raised using this code in code-behind:
private void DataGrid_CurrentCellChanged(object sender, EventArgs e)
{
foreach (var item in dataGrid.Columns)
{
if (item.HeaderStyle != null)
{
item.HeaderStyle = null;
}
}
Style selectedColumnStyle = new Style(typeof(DataGridColumnHeader));
selectedColumnStyle.Setters.Add(new Setter(DataGridColumnHeader.BackgroundProperty, Brushes.Gray));
dataGrid.Columns[Index].HeaderStyle = selectedColumnStyle;
}
How to achieve same functionality in XAML using a style and triggers ?
I do not think that you can achieve this purely in XAML...but almost. You could create a column header style that changes the background, which is triggered by determining if the current cell column matches the column of the header that the style is applied to.
<DataGrid ...>
<DataGrid.Resources>
<local:EqualityToBoolConverter x:Key="EqualityToBoolConverter"/>
</DataGrid.Resources>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityToBoolConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Column"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" Path="CurrentCell.Column"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Gray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.ColumnHeaderStyle>
<!-- ...your markup. -->
</DataGrid>
As you can see, there is a multi binding that binds the column of the header the style is applied to and the column of the current cell. What we still need in code is a converter that checks for equality.
public class EqualityToBoolConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values != null && values.Length > 0 && values.Skip(1).All(value => Equals(values[0], value));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("This conversion is one-way.");
}
}
The DataTrigger then receives a boolean indicating, whether to apply the setters or not.

TemplatedParent not working in MultiBinding: getting "UnsetValue" everytime

I am trying to bind visibility of a styled control to the condition that its tag is equal to the selected index of a TabControl.
I use RelativeSource TemplatedParent but it's not being set, and I suspect it's because I am not setting Template property in Style.
Here is my code:
<Grid x:Name="Telas" Grid.Column="1">
<Grid.Resources>
<vw:TagIsIndexBooleanConverter x:Key="TagIsIndexBooleanConverter"/>
<Style x:Key="SelectedIndexVisibleStyle" TargetType="UserControl">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TagIsIndexBooleanConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="SelectedIndex" ElementName="menu"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<vw:TelaColetaView
x:Name="telaColeta"
DataContext="{Binding TelaColetaVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="0"/>
<vw:TelaPacientesView
x:Name="telaPacientes"
DataContext="{Binding TelaPacientesVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="1"/>
<vw:TelaConfiguraçõesView
x:Name="telaConfigurações"
DataContext="{Binding TelaConfiguraçõesVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="4"/>
</Grid>
And converter:
public class TagIsIndexBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(v => v == null || v == DependencyProperty.UnsetValue))
return Binding.DoNothing;
var tag = System.Convert.ToInt32(values[0]);
var index = System.Convert.ToInt32(values[1]);
var result = tag == index;
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't use TemplatedParent in this case because there isn't one; that's meant to used inside a ControlTemplate to specify that the source for the binding is the control that you're applying the template to.
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}"/>
But this isn't inside a ControlTemplate. Instead, Tag is just a property of the thing you're styling. Ordinarily you'd do a trigger like this for Tag:
<Trigger Property="Tag" Value="0">
...but you need a multibinding to get the SelectedIndex from menu, and it's got to be a MultiDataBinding because you need to specify ElementName.
So you need a binding. To bind to one of your own properties instead of a property of your DataContext, you bind with a RelativeSource of Self:
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
OP also found he had to set the TargetType of the Style to "UserControl", for the Tag binding to work.

How to OR a local bool with a viewmodel bool and bind to visibility property

I have the following XAML in a usercontrol that loads a series of gauge usercontrols:
<ItemsControl ItemsSource="{Binding InstanceViper.Gauges}" Grid.Row="7" Grid.ColumnSpan="4">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Name="GaugePanel" VerticalAlignment="Bottom" HorizontalAlignment="Left" Orientation="Horizontal">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Gauge DataContext="{Binding}" AlwaysShowGauge="False"></local:Gauge>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am passing in a flag that determines if the gauge should be displayed always or only when in alarm. Now in gauge xaml I want to do something like the following to determine if it should be displayed:
<Border Name="MainBorder" Visibility="{Binding (ShowAlarm || AlwaysShowGauge) ,Converter={StaticResource BooleanToVisibilityConverter}}">
Problem is that the ShowAlarm property is from the gauge viewmodel and AlwaysShowGauge is a local property being passed in in the data template. The ShowAlarm is dynamic but AlwaysShowGauge won't change once set. How can I accomplish this?
Its hard to tell where your border comes from, and where the one of the bindings come from. But you can control the Binding flow, that's not a problem. The main problem is how do you multi trigger, below is just a start. Once you see how you can multi trigger, then, you can play with the binding, either control the flow from one view model or view properties, or play with paths from different sources, to do that is you might have to put the following sample in the DataTemplate and use SourceName and TargetName.. But again, it's hard to tell where the border is.
<converters:OrMultiConverter x:Key="Or" />
<Style x:Key="MainBorderStyle" TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource Or}">
<Binding Path="ShowAlarm"/>
<Binding Path="AlwaysShowGaug"/>
</MultiBinding>
</Condition.Binding>
</Condition>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
public class OrMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(value => value == null || value == DependencyProperty.UnsetValue))
return false;
var boolValues = values.Cast<bool>();
var result = boolValues.Any(b => b);
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

How to hide combobox toggle button if there is only one item?

I have a WPF application. In one window there is a combobox..and I want to hide the toggle button and disable the combo box if there is only one item.
How would I achieve this ?
I have tried the below code for hiding the toggle button. But of no luck
Any help would be appreciated. thanks
<ComboBox x:Name="CList" ItemsSource="{Binding Path=C}" >
<Style TargetType="{x:Type ToggleButton}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count, ElementName=CList}" Value="1">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox>
The better solution is to replace the template of combo box with a control template(which only contain textblock) when the item count is zero.
Here is the xaml for the same.
<ComboBox Name="CList" ItemsSource="{Binding Path=C}"
SelectedItem="{Binding Path=CC}" VerticalAlignment="Center" Margin="0,0,10,0" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count, ElementName=CList}" Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding Items[0], ElementName=CList}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
You would need to change the Template of the ComboBox and implement the trigger inside that. You have no access to the controls in the template from the outside.
(You could copy and modify the existing template, directly modifying a part of the template is practically impossible)
You can always use a Converter also:
(Sorry I didn't fully read your question)
Converters
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace WPFSandbox
{
public class ComboBoxItemCountToEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(Int32))
{
if ((int)value > 1)
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ComboBoxItemCountToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(Int32))
{
if ((int)value > 1)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML
<Window
...
...
xmlns:converters="clr-namespace:WPFSandbox">
<Window.Resources>
<converters:ComboBoxItemCountToVisibilityConverter x:Key="ComboBoxItemCountToVisibilityConverter"/>
<converters:ComboBoxItemCountToEnabledConverter x:Key="ComboBoxItemCountToEnabledConverter"/>
</Window.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding C}" IsEnabled="{Binding Path=C.Count, Converter={StaticResource ComboBoxItemCountToEnabledConverter}}"/>
<ToggleButton Visibility="{Binding Path=C.Count, Converter={StaticResource ComboBoxItemCountToVisibilityConverter}}"/>
</StackPanel>

WPF/XAML - compare the "SelectedIndex" of two comboboxes (DataTrigger?)

i've got two comboboxes with the same content. the user should not be allowed to choose the same item twice. therefore the comboboxes' contents (= selectedindex?) should never be equal.
my first attempt was to comapare the selectedindex with a datatrigger to show/hide a button:
<DataTrigger Binding="{Binding ElementName=comboBox1, Path=SelectedIndex}" Value="{Binding ElementName=comboBox2, Path=SelectedIndex}">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
it seems that it is not possible to use Value={Binding}. is there any other way (if possible without using a converter)? thanks in advance!
Option 1
You could use ValidationRules -- it can also be done in XAML, and will work for a one-off situation. It would be extremely localized and not something I would recommend since the rule would not be reusable. Maybe somebody else can come up with a generic rule to encompass different inputs. Try this out.
<ComboBox>
<ComboBox.SelectedValue>
<Binding Path="Whatever" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:ComparisonValidationRule />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedValue>
</ComboBox>
And maybe the ComparisonRule looks like this, and it would have to be in the code-behind for that rule to see the controls in the visual tree.
public class ComparisonValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (this.firstComboBox.SelectedIndex == this.secondComboBox.SelectedIndex)
return new ValidationResult(false, "These two comboboxes must supply different values.");
else return new ValidationResult(true, null);
}
}
OR you could definitely do it with a trigger if you wanted to set some interesting things outside of an error template.
Option 2
Use a trigger & converter. It's really not too difficult. Here's how I would do it.
<Style x:Key="{x:Type ComboBox}" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding ElementName="cbOne" Path="SelectedIndex"/>
<Binding ElementName="cbTwo" Path="SelectedIndex"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
and the converter in code-behind
public class EqualsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
if (values[0] is int && values[1] is int && values[0] == values[1])
return true;
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
If the ComboBoxes share the same itemsource, set a flag on the underlying data object when an item is selected via the first ComboBox.
In the datatemplate for the data object displayed in the second combobox write a datatrigger that binds to that property and does something appropriate.
Ensure your binding is TwoWay for the first ComboBox.

Resources