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

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.

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.

Cancel binding depending on object type

In my WPF application I have a label which is bound to the length of a Text-property:
<Label Content="{Binding Editor.Text.Length}"/>
The Editor-object may be either a textbox or a checkbox. The textbox does have a Text-property whereas the checkbox does not.
When the Label is bound to a "checkbox-editor" it produces a warning in Visual Studio:
BindingExpression path error: 'Text' property not found on 'object'...
This is expected and I would like to know if there is any way to tell the binding engine not to try to bind this value unless the Editor-object is a textbox?
Is the Editor property of your viewmodel a control? I hope not, but anyway.
You could write a valueconverter that returns the type of the value, and then set the Label's Content via a series of triggers in a Style. If type of Editor is {x:Type TextBox}, set it to the binding you've got above. If it's {x:Type CheckBox}, make it `{Binding Editor.IsChecked}'.
XAML
<Label>
<Label.Style>
<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=Editor, Converter={local:GetTypeConverter}}"
Value="{x:Type TextBox}"
>
<Setter
Property="Content"
Value="{Binding Text.Length, ElementName=Editor}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding ElementName=Editor, Converter={local:GetTypeConverter}}"
Value="{x:Type CheckBox}"
>
<Setter
Property="Content"
Value="{Binding IsChecked, ElementName=Editor}"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
C#
public class GetTypeConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Alternatively, your viewmodel could have a readonly property which returns whatever should be in that label, since the viewmodel has Editor and knows what it is. Call it EditorLabelValue for the time being. Presumably Editor is bound to a string property or a bool property, depending on which editor it is. So both of those setters would raise PropertyChanged for "EditorLabelValue", which would return the appropriate value.
I tried to do this in pure XAML by making Editor the Content of a ContentControl and then playing with DataTemplates, but I couldn't find a way to make that work without getting an exception for reparenting Editor.

WPF - Use default value from style if property in view model is null

I have own custom control with dependency property called Maximum (double) and I have defined own style for this custom control. In custom control style is setter for Maximum (50). In application I have view model with property Maximum (double?).
<Style x:Key="MyCustomControlDefaultStyle" TargetType="controls:MyControl">
<Setter Property="Maximum" Value="50" />
</Style>
<controls:MyControl Maximum="{Binding Maximum}"
Style="{StaticResource MyCustomControlDefaultStyle}"
/>
If Maximum value in view model will be null I want to wpf automatically use default value defined in MyCustomControlDefaultStyle. Is it possible?
Thanks for advice
Can't check right now, but you could put your DefaultValue inside a static Property and then reference this value from the style and from the Fallbackvalue/TargetNullValue inside the concrete Binding like:
<Setter Property="Maximum" Value="{x:Static ns:MyStaticClass.MyStaticProp}" />
And inside the Binding like:
<controls:MyControl Maximum="{Binding Maximum, FallBackValue={x:Static ns:MyStaticClass.MyStaticProp}"
Style="{StaticResource MyCustomControlDefaultStyle}" />
Not sure if that works for FallBackValue. I will check a little later... :)
You can use a DataTrigger in your style along with a Converter:
<Style.Triggers>
<DataTrigger Binding="{Binding Maximum, Converter={StaticResource NullToBooleanConverter}}" Value="False">
<Setter Property="Text" Value="{Binding Maximum}" />
</DataTrigger>
</Style.Triggers>
The converter may look like this:
class NullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Edit: Can't manage the syntax highlight to work...

Multibinding with MultiConverter yields DependencyProperty.UnsetValue

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;
...
}

Select control style by binding value

I have an editor view that can be used for multiple edited objects. The view model for multiple objects provides a property like Field1Multiple of type bool for each field that needs to be handled. In this case, it's only ComboBox controls for now. Whenever multiple differing values shall be indicated for that field, a certain style should be applied to that control which is defined in App.xaml. That style changes the background of the control to visualise that there is no single value that can be displayed here.
I've tried with this XAML code:
<ComboBox
ItemsSource="{Binding Project.Field1Values}" DisplayMemberPath="DisplayName"
SelectedItem="{Binding Field1}">
<ComboBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Field1Multiple}" Value="true">
<Setter
Property="ComboBox.Style"
Value="{StaticResource MultiValueCombo}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
But it doesn't work because I cannot set the Style property from inside a Style. If I use triggers directly on the control, there may only be EventTriggers, no DataTriggers, the compiler says.
How can I set the control's style based on a binding value? Or, how can I set a certain style for a control if a binding value is true?
(EDIT to full solution)
You can use converter:
public class AnyIsMultipleToStyle : IValueConverter
{
public Style NormalStyle { get; set; }
public Style MultiStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
IList<SampleClass> list= value as IList<SampleClass>;
if (list!=null)
{
if (list.Any(i => i.Multi))
return MultiStyle;
}
}
return NormalStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your xaml:(You indicate normal style and multistyle to converter)
<Window.Resources>
<Style x:Key="MultiValueCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Olive" />
</Style>
<Style x:Key="NormalCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Red" />
</Style>
<my:AnyIsMultipleToStyle x:Key="AnyIsMultipleToStyle1" MultiStyle="{StaticResource MultiValueCombo}" NormalStyle="{StaticResource NormalCombo }" />
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Items, ElementName=root}" >
<ComboBox.Style>
<Binding Converter="{StaticResource AnyIsMultipleToStyle1}" Path="Items" ElementName="root" >
</Binding>
</ComboBox.Style>
</ComboBox>
</Grid>

Resources