Is there a way to check in DataTrigger if the object is of a particular class?
In fact I would like that DataTrigger answer this question in C#:
if(MyObject is MyClass)
I want it to look something like this in XAML:
<Grid>
<Grid.Triggers>
<DataTrigger Binding="{Binding MyObject}" Value="MyClass?">
<Setter..../>
</DataTrigger>
</Grid.Triggers>
</Grid>
You can use a converter for this:
<Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding MyString, Converter={StaticResource OConv}, ConverterParameter=System.String}" Value="True">
<Setter Property="Grid.Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
Use the ConverterParameter to state the type of the object you expect to receive...
Converter will return true if it matches or false otherwise...
Example of converter:
public clas s ObjectTypeToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType().ToString() == (string)parameter)
{
return true;
}
return false;
}
Related
I have a normal Checkbox, where I want to set the IsChecked property to a Binding resource.
The resource is a self written class myClass, which can be null or referenced (means not null).
The Checkbox should be NOT checked, if the assigned object myObject (out of myClass) is null
and checked, if it is not null.
What do I have to write in the IsChecked="..." property in my xaml file?
You can create a style with a DataTrigger that sets the IsChecked property.
<CheckBox>
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="IsChecked" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyObject}" Value="{x:Null}">
<Setter Property="IsChecked" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
An alternative is to create a reusable value converter.
public class NotNullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Create an instance of the converter in any resource dictionary, e.g. application resources.
<local:NotNullToBooleanConverter x:Key="NotNullToBooleanConverter"/>
This converter can be used directly in the binding.
<CheckBox IsChecked="{Binding MyObject, Converter={StaticResource NotNullToBooleanConverter}}"/>
I have both DataGrid and Listbox binded to the same ObservableCollection:
public ObservableCollection<Contact> contacts = new ObservableCollection<Contact>();
CntGrid.ItemsSource = contacts;
CntListBox.ItemsSource = contacts;
<DataGrid x:Name="CntGrid"
IsReadOnly="False"
CanUserAddRows="True"
CanUserDeleteRows="True"/>
<ListBox x:Name="CntListBox"/>
The problem is DataGrid allowing adding items (I want to keep this functionality) causes ListBox to display an empty row aswell. I don't want my ListBox to display this empty row at the end.
Can I somehow modify my ListBox to fix this?
This will hide the {NewItemPlaceholder} item in your ListBox:
<ListBox x:Name="CntListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Static CollectionView.NewItemPlaceholder}">
<Setter Property="UIElement.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Empty row in DataGrid is a NewItemPlaceholder. It has different type from Contact. So I suggest to use a converter to hide it in ListBox:
public class ObjectTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var t = parameter as Type;
if (value == null || t == null)
return false;
return t.IsAssignableFrom(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
use that converter in ListBoxItem Style to check the type of item and hide them if type doesn't match:
<Window.Resources>
<local:ObjectTypeConverter x:Key="tc"/>
</Window.Resources>
<ListBox x:Name="CntListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource tc}, ConverterParameter={x:Type local:Contact}}"
Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I have the following piece of code.
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Standby}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Busy}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Offline}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.StartingStream}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Connecting}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.Disconnecting}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, Mode=OneWay}"
Value="{x:Static comm:DeviceStatus.DownloadingFiles}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
The button is hidden by default and is visible when a property in the viewmodel has one out of a few values. The property is of an enum type called DeviceStatus.
Basically it conducts an OR operation on the provided triggers.
So, the visibility of the button is determined by: Status == StandBy || Status == Busy || ...
How can I implement this without having to have 8 triggers?
I would like to have something like the following:
<DataTrigger Binding="{Binding Status, Mode=OneWay}">
<DataTrigger.AnyValue>
<AnyValueItem Value="{x:Static comm:DeviceStatus.Standby}" />
<AnyValueItem Value="{x:Static comm:DeviceStatus.Busy}" />
<AnyValueItem Value="{x:Static comm:DeviceStatus.Offline}" />
...
</DataTrigger.AnyValue>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
Where the Visibility of the Button is set to Visible if the binding gets ANY of the supplied values.
Thanks!
After reading the answers I got some ideas and found a satisfying extendable solution.
First I created the following converter.
public sealed class EnumOrConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DeviceStatus status = (DeviceStatus)value;
DeviceStatus[] statuses = parameter as DeviceStatus[];
if (statuses.Any(s => s == status))
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Second I simply added the following code to the XAML.
<Button Command="{Binding SomeCommand}">
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
TargetType="{x:Type Button}">
<Setter Property="Visibility">
<Setter.Value>
<Binding Path="Status" Converter="{StaticResource EnumOrConverter}">
<Binding.ConverterParameter>
<x:Array Type="{x:Type comm:DeviceStatus}">
<x:Static Member="comm:DeviceStatus.Standby" />
<x:Static Member="comm:DeviceStatus.Busy" />
<x:Static Member="comm:DeviceStatus.Offline" />
<x:Static Member="comm:DeviceStatus.StartingStream" />
<x:Static Member="comm:DeviceStatus.Connecting" />
<x:Static Member="comm:DeviceStatus.Disconnecting" />
<x:Static Member="comm:DeviceStatus.DownloadingFiles" />
</x:Array>
</Binding.ConverterParameter>
</Binding>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
This allows me to reuse the converter logic for many other buttons and no adding of variables or properties to the ViewModel. What also happened is that the converter encapsulates the logic of "OR:ing" the parameters and "defaulting to Visibility.Collapsed". And adding a new parameter simply requires one line of code in XAML which is where it belongs.
The DataTrigger has no support for some kind of list of possible values. It only has a single Value property.
The easiest way to work around this would be to add a property to the view model that returns a value that indicates whether to display the Button:
public bool IsVisible => Status == Standby || Status == Busy || ...;
XAML:
<DataTrigger Binding="{Binding IsVisible, Mode=OneWay}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
Another option may be to use a converter as suggested by #l33t. You would then simply move the logic out of the view model, e.g.:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ViewModel vm = value as ViewModel;
return (vm != null && (vm.Status == Standby || vm.Status == Busy || ...)) ? Vsibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Style ...>
<Style.Resources>
<local:Converter x:Key="conv" />
</Style>
<Setter Property="Visibility" Value="{Binding Path=., Converter={StaticResource converter}}" />
You could just make a property:
private DeviceStatus _Status;
public DeviceStatus Status
{
get { return _Status; }
set
{
this.Set(ref _Status, value);
RaisePropertyChanged(nameof(this.StatusVisibility));
}
}
public Visibility StatusVisibility
{
get
{
switch (_Status)
{
case DeviceStatus.Busy: //add other statuses here
return Visibility.Visible;
}
return Visibility.Collapsed;
}
}
and then in your button:
<Button Content="MyButton" Visibility="{Binding StatusVisibility}"></Button>
For others. An answer that's similar to Lugvig W's answer but keeps with DataTriggers and is more generalised
ValueConverter
public sealed class AnyMatchConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Array objects && value is not null)
{
return Array.BinarySearch(objects, value) != -1;
}
return DependencyProperty.UnsetValue;
}
...
XAML
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="Status" Converter="{StaticResource AnyMatchConverter}">
<Binding.ConverterParameter>
<x:Array Type="{x:Type comm:DeviceStatus}">
<x:Static Member="comm:DeviceStatus.Standby" />
<x:Static Member="comm:DeviceStatus.Busy" />
...
</x:Array>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
...
Say I have a DataGrid with the following data:
John, Male
Mary, Female
Tony, Male
Sally, Female
The grid is bound to an ObservableCollection of Person model objects that implements INofifyPropertyChanged for the properties Person.Name and Person.Gender. I now want to bind the DataGridTextColumn's background color to the person's gender so that rows containing males are blue, and rows containing females are pink. Is it possible to do this by adding another property to the Person model like so:
public class Person
{
public Color BackgroundColor
{
get
{
if (gender == "Male")
{
return Color.Blue;
}
else
{
return Color.Pink;
}
}
}
if so, how do I bind this to the row or column's background color? I already have bounded columns like this:
<DataGridColumn Header="Name" Binding={Binding Name} />
<DataGridColumn Header="Gender" Binding={Binding Gender} />
Assuming that BackgroundColor is of a System.Windows.Media.Color type, and not System.Drawing.Color, if you want to change background of the entire row you can alter DataGrid.RowStyle and bind Background property to BackgroundColor property
<DataGrid ...>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding Path=BackgroundColor}"/>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
</DataGrid>
You want to implement an IValueConverter to convert a String to a Brush. See http://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/
public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = (string)value;
return new SolidColorBrush(val == "male" ? Colors.Blue : Colors.Pink);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In XAML, you'll want <Window.Resources> like
<Window.Resources>
<local:StringToBrushConverter x:Key="stringToBrush" />
<Style x:Key="MaleFemaleStyle" TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding Path=Gender, Converter={StaticResource stringToBrush}}" />
</Style>
</Window.Resources>
Then apply the MaleFemaleStyle to your grid.
<DataGrid CellStyle="{StaticResource MaleFemaleStyle}">
...
</DataGrid>
This works for me
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Sex}" Value="Male">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding Sex}" Value="Female">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
So here's the idea.
If the textbox is empty, 'DataTrigger' should set the outline (BorderBrush) to red.
If the textbox is not empty / has text; then the dataTrigger should set the BorderBrush to Blue.
xmlns:conv="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- conv is referenced in the "clr-namespace:WpfApplication1" namespace. It's bassically a referal to a converter I'm using -->
<conv:IsNullConverter x:Key="isNullConverter"/>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<!-- if the textbox is empty, then the setter should set the border colour to red-->
<DataTrigger Binding="{Binding Words, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource isNullConverter}}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
<!-- If it has text inside it, setter should set the border colour to blue -->
<DataTrigger Binding="{Binding Words, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource isNullConverter}}" Value="False">
<Setter Property="BorderBrush" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox x:Name="first" FontSize="14" TabIndex="1" Background="Black" BorderThickness="5" Foreground="White" Margin="29,10,132,272" />
</Grid>
Because it's not possible for datatriggers to see if a value is NOT null indipendently, I had to add some code to help it do that.
using System.Windows.Data;
using System.Globalization;
using System.ComponentModel;
namespace WpfApplication1
{
public class IsNullConverter : IValueConverter, INotifyPropertyChanged
{
// The string that the 'conv:' checks against
private string FOO;
// The DataTriggrer is bound to 'Words'
public string Words
{
get
{
return FOO;
}
set
{
if (FOO != value)
{
FOO = value;
RaisePropertyChanged("Words");
}
}
}
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged == null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { return null; }
}
// This is the 'Convert' Parameter conv checks against. Here is the problem is
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//This checks against the FOO string at the top of this class. Not the FOO in 'Words' get & set string
if (FOO == null)
{
value = true;
}
// So even if 'Words' raises the property changed, The origional FOO string remains unaffected.
// So the Datatrigger is never fired
if (FOO != null)
{
value = false;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
}
The thing is if I append the string FOO like;
private string FOO = "Something";
The Datatrigger fires at runtime and changes the outline colour to blue.
But I need the colour to be based on the textbox content rather than what I directly declare the string as.
I tried binding the data Trigger to the 'Words' string but the outline colour remains red, empty or not.
And suggestions? I really don't mind if I have to completely throw this code upside down if there's a better way of doing it.
Here you go:
<TextBox x:Name="first" FontSize="14" TabIndex="1" Background="Black" BorderThickness="5" Foreground="White" Margin="29,10,132,272">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Bind to the TextBox.Text property by using RelativeSource:
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}, Converter={StaticResource isNullConverter}}" Value="True">
<!--apropriate setter-->
</DataTrigger>
This trigger sets the BorderBrush if Text is empty. To set the border brush when Text is not empty, just use normal Setter, without DataTrigger.
Also, note that within your ValueConverter you should use String.IsNullOrEmpty instead of plain NULL comparison.
You can simplify your converter to look something like
public class IsNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.IsNullOrEmpty((string) value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
and then in style bind TextBox.Text via converter
<Window.Resources>
<conv:IsNullConverter x:Key="isNullConverter"/>
<Style TargetType="{x:Type TextBox}">
<Setter Property="BorderBrush" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource isNullConverter}}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
this will work but only if TextBox is not focused as then default template will take over and change BorderBrush. You can make it work but then you'll need to change default template as well where simpliest template would be another Setter in your Style
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>