WPF ListBox data validation - c#

I have a ListBox of string items where I want to validate the strings every time a string is added or removed.
Below is the code I've cobbled together, but the problem is that ValidateAddresses is never called when the ObservableCollection Addresses changes.
Intended behavior is that when an invalid string is found, a red border should be shown around the ListBox with a tooltip that displays the error message.
This INotifyDataErrorInfo setup works fine for TextBoxes, so I dunno what I am doing wrong here.
ViewModel
[CustomValidation(typeof(ItemViewModel), "ValidateAddresses")]
public ObservableCollection<string> Addresses
{
get
{
return item.Addresses;
}
set
{
item.Addresses = value;
NotifyPropertyChanged(nameof(Addresses));
}
}
XAML
<Grid>
<Grid.Resources>
<Style TargetType="ListBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="2"/>
</AdornedElementPlaceholder>
<TextBlock Foreground="Red"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox ItemsSource="{Binding Path=Item.Addresses, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" SelectedIndex="{Binding Path=SelectedAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Validation method (never called)
public static ValidationResult ValidateAddresses(object obj, ValidationContext context)
{
ItemViewModel item = (ItemViewModel)context.ObjectInstance;
if (item.Addresses.Count > 0)
{
foreach (string address in item.Addresses)
{
if (Regex.IsMatch(address, #"[^\w]"))
return new ValidationResult($"{address} is not a valid address.", new List<string> { "Addresses" });
}
}
return ValidationResult.Success;
}

I ended up adding following in class constructor for each ObservableCollection I had.
Addresses.CollectionChanged += (sender, eventArgs) => { NotifyPropertyChanged(nameof(Addresses)); };
I tried to look into circumstances upon which unsubscribing from events is required to prevent memory leaks, but it does not seem like this is one of these cases so cleanup shouldn't be required.
Null check is not required because ObservableCollections are all initialized in Model class constructor.
Thank you for your replies.
And this is the code for NotifyPropertyChanged.
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

WPF DataTrigger never fires to Expand TreeView

I have the following XAML which should expand my whole treeview when a specific property ExpandNodes is True, but it is never triggered.
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding ExpandNodes}" Value="True">
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ExpandNodes}" Value="False">
<Setter Property="IsExpanded" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
// some other code
</TreeView>
In my ViewModel I set ExpandNodes to True during a specific event, yet the treeview remains non expanded. It's not an issue with my DataContext as I have other properties from the same Viewmodel that are bound and work fine.
My Viewmodel:
private bool _expandnodes;
public bool ExpandNodes
{
get
{
return _expandnodes;
}
set
{
_expandnodes = value;
OnPropertyChanged("ExpandNodes");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
Too compliacted, you can simply bind IsExpanded property to your object and raise PropertyChanged event.
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding ExpandNodes,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
TargetNullValue=False}" />
</Style>
</TreeView.ItemContainerStyle>
// some other code
</TreeView>

WPF: DataTrigger not firing

In a WPF Project I have some restyled DataGridColumnHeaders of a DataGrid which show a ComboBox for each DataGridColumnHeader. When the user selects from the ComboBox's the SelectionChanged handler (in the code behind) updates an Array of ColumnOptionViewModel objects on the MainWindowViewModel with the newest selection.
At this point some code also works out if there are any duplicate selections in this array, and then sets an IsDuplicate Boolean property on the ColumnOptionViewModel that are duplicates. The idea is that a DataTrigger picks up the change in IsDuplicate and changes the Background of a TextBlock in the DataTemplate of the ItemTemplate for the duplicate ComboBox's to Red.
However, this trigger is not firing. The IsDuplicate properties are being set ok, and everything else works as expected. What am I doing wrong?
Here is the XAML for the Window:
<Window x:Class="TestDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestDataGrid"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid Grid.Row="1" x:Name="dataGrid" ItemsSource="{Binding Records}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<ComboBox x:Name="cbo"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.ColumnOptions}"
SelectionChanged="cbo_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="txt" Text="{Binding Name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=cbo, Path=SelectedItem.IsDuplicate}">
<Setter TargetName="txt" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
</Grid>
CODE BEHIND:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel(RecordProvider.GetRecords());
}
private void cbo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var vm = (MainWindowViewModel)DataContext;
var selectionChangedCombo = (ComboBox)e.Source;
var dataGridColumnHeader = selectionChangedCombo.TemplatedParent as DataGridColumnHeader;
vm.ColumnSelections[dataGridColumnHeader.DisplayIndex] = selectionChangedCombo.SelectedItem as ColumnOptionViewModel;
CheckForDuplicates();
}
private void CheckForDuplicates()
{
var vm = (MainWindowViewModel)DataContext;
var duplicates = vm.ColumnSelections.GroupBy(x => x.Name)
.Where(g => g.Skip(1).Any())
.SelectMany(g => g);
foreach (var option in duplicates)
{
option.IsDuplicate = true;
}
}
}
MainWindowViewModel :
public class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ColumnOptionViewModel> _columnOptions = new ObservableCollection<ColumnOptionViewModel>();
public ObservableCollection<RecordViewModel> _records = new ObservableCollection<RecordViewModel>();
ColumnOptionViewModel[] _columnSelections = new ColumnOptionViewModel[3];
public MainWindowViewModel(IEnumerable<Record> records)
{
foreach (var rec in records)
{
Records.Add(new RecordViewModel(rec));
}
ColumnOptions.Add(new ColumnOptionViewModel(TestDataGrid.ColumnOptions.ColumnOption1));
ColumnOptions.Add(new ColumnOptionViewModel(TestDataGrid.ColumnOptions.ColumnOption2));
ColumnOptions.Add(new ColumnOptionViewModel(TestDataGrid.ColumnOptions.ColumnOption3));
ColumnSelections[0] = ColumnOptions[0];
ColumnSelections[1] = ColumnOptions[1];
ColumnSelections[2] = ColumnOptions[2];
}
public ObservableCollection<ColumnOptionViewModel> ColumnOptions
{
get { return _columnOptions; }
set { _columnOptions = value; }
}
public ColumnOptionViewModel[] ColumnSelections
{
get { return _columnSelections; }
set { _columnSelections = value; }
}
public ObservableCollection<RecordViewModel> Records
{
get { return _records; }
set { _records = value; }
}
}
ColumnOptionViewModel :
public class ColumnOptionViewModel : ViewModelBase
{
ColumnOptions _colOption;
public ColumnOptionViewModel(ColumnOptions colOption )
{
_colOption = colOption;
}
public string Name
{
get { return _colOption.ToString(); }
}
public override string ToString()
{
return Name;
}
private bool _isDuplicate = false;
public bool IsDuplicate
{
get { return _isDuplicate; }
set
{ _isDuplicate = value;
OnPropertyChanged();
}
}
}
EDIT:
ViewModelBase :
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If you are trying to bind to the IsDuplicate property of the SelectedItem in the ComboBox you could use a RelativeSource.
You should also set the Value property of the DataTrigger to true/false depending on when you want the Background property of the TextBlock to be set to Red:
<ComboBox x:Name="cbo" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.ColumnOptions}"
SelectionChanged="cbo_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="txt" Text="{Binding Name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem.IsDuplicate, RelativeSource={RelativeSource AncestorType=ComboBox}}" Value="True">
<Setter TargetName="txt" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
As #mm8 said, the Value is not chosen in your DataTrigger.
If that doesn't work, you can try using Trigger directly on TextBlock instead of DataTemplate.Triggers:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="White"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDuplicate}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Also, background and foreground values in controls that have selectable items can be tricky. For example, you may want to disable default selection colors (unfortunately then you will have to manage selected/focused background yourself):
<ComboBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
</ComboBox.Resources>

Highlight one item in DataGrid

I'm displaying Cars in a DataGrid and would like to highlight one special car, the CurrentlySelectedCar.
When the user double clicks on a car, this car is saved as CurrentlySelectedCar in my MainViewModel. Now, whenever the user comes back to the DataGrid, I would like to highlight this car = row, e.g. by using a red background.
I have found out how to highlight rows in a DataGrid based on certain values, but in my case, all I have is the CurrentlySelectedCar.
My First try:
<Style TargetType="DataGridRow">
<Style.Triggers>
<!-- not working-->
<DataTrigger Binding="{Binding CurrentlySelectedCar}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
My second try:
<Style TargetType="DataGridRow">
<Style.Triggers>
<!-- not working either, "Binding can only be set on DependencyProperty of DependecyObject"-->
<DataTrigger Binding="{Binding Guid}" Value="{Binding CurrentlySelectedCar.Guid}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
How can I highlight the row with this information?
I think that you have to do something like this described in this answer: Using bindings in DataTrigger condition
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource someMultiConverter}">
<Binding Path="Guid"></Binding>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Datagrid}}" Path="CurrentlySelectedCar.Guid"></Binding>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
You have to write a multiconverter that return true if the two Guid are equals.
Try the following instead, since each datagrid row has an IsSelected property, you can bind to it directly.
<DataGrid EnableRowVirtualization="False">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
Updated response
The problem right now is your DataContext is the DataGridRow, and it doesn't have access to the MainViewModel. You can address this by passing the DataGridRow to a converter that is aware of the current MainViewModel. However, it is a lot of code, and is barely comprehensible.
<Window>
<Window.Resources>
<local:IsCurrentlySelectedCarConverter x:Key="IsCurrentlySelectedCarConverter" />
</Window.Resources>
...
<DataTrigger Binding="{Binding
Path=DataContext,
Converter={StaticResource IsCurrentlySelectedCarConverter}}" >
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Window>
Here's corresponding converter
public class IsCurrentlySelectedConverter : IValueConverter
{
public MainViewModel MainViewModel { get; set;}
public object Convert(object value, ....)
{
return (value == MainViewModel.CurrentlySelectedCar);
}
}
and you'll need to wire in the converter's MainViewModel manually in your view
this.Resources["IsCurrentlySelectedCarConverter"].MainViewModel = _mainViewModel;
and at this point you'd have to question the maintainability of the monster that has been created.
It may be better to replace each Car with a CarViewModel so that it has a property called IsSelected and you can maintain that in code. The following in my opinion is easier to follow what is actually going on.
public class CarViewModel : INotifyPropertyChanged
{
public MainViewModel { get; set; }
public bool IsSelected { get { return this == MainViewModel.CurrentlySelectedCar; } }
// Call RaisePropertyChanged("IsSelected") whenever
// CurrentlySelectedCar is changed
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}

Data trigger not firing off correct string value

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>

How can I use 3 different styles based on two unrelated triggers

I have a Gridview.Column where the style of the content is changed depending on the content of another column by a IMultiValueConverter. This part works as expected.
If the values of the two values are the same then the text of this column is LightGray otherwise black.
<GridViewColumn Header="New Name" Width="300">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding newFileName}">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Foreground" Value="Black"></Setter>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource FileNameColorConverter}">
<!--<Binding Path="Selected"/>-->
<Binding Path="newFileName"/>
<Binding Path="fileName"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="TextBlock.Foreground" Value="LightGray"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Now I have another property in this object that is bound to this GridView. If this is TRUE the text of this column should become red, instead of black, if it is false, the FileNameColorConverter should decide the style of the column, same as it is now.
How can I do this? I am at the moment lost, and have no idea where to put this logic, I am quite sure there is a way to have this also in XAML.
Edit
What I tried is adding another trigger after the first one, but it didn't worked for me, if this is the way to go, whats wrong with my code?
I added this after my first </DataTrigger> but it didn't had an effect.
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="FileNameError"/>
</DataTrigger.Binding>
<Setter Property="TextBlock.Foreground" Value="Red"></Setter>
</DataTrigger>
Multiple data triggers with exclusive conditions on same data work for me perfectly... Can you check if your FileNameError is True for atleast 1 item? And if are you changing it dynamically then are you raising PropertyChanged event?
You can try my code and see...
XAML:
<Page.Resources>
<local:MultiBindingConverter x:Key="MultiBindingConverter"/>
<coll:ArrayList x:Key="MyData">
<local:Student Id="1" local:Student.Name="Test1" Active="False"/>
<local:Student Id="2" local:Student.Name="Test2" Active="True"/>
<local:Student Id="3" local:Student.Name="Test3" Active="False"/>
<local:Student Id="4" local:Student.Name="Test4" Active="False"/>
</coll:ArrayList>
</Page.Resources>
<Grid>
<ListBox ItemsSource="{StaticResource MyData}"
DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Resources>
<SolidColorBrush
x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Yellow"/>
</Style.Resources>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource
MultiBindingConverter}">
<Binding Path="Id"/>
<Binding Path="Active"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Value="3">
<DataTrigger.Binding>
<Binding Path="Id"/>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
Code Behind:
public class MultiBindingConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] != DependencyProperty.UnsetValue
&& values[0] != null && values[1] != null)
{
var value1 = (int)values[0];
var value2 = (bool)values[1];
var result = (value1 > 1 && value2);
if (result)
{
return true;
}
return false;
}
return false;
}
public object[] ConvertBack
(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class Student : INotifyPropertyChanged
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public bool Active
{
get;
set;
}
public Student()
{ }
public Student(int id, string name)
{
this.Id = id;
this.Name = name;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Add another trigger after the existing one which triggers on the additional property with Value="True". This should then override the existing trigger if the property is true, allowing you to set the column to be red. However, if the property is false the trigger will do nothing and the existing trigger's effects will be visible.
Look into the GridViewColumn.CellTemplateSelector Property.
Depending on some logic, the DataTemplateSelector, you give the cell a different DataTemplate.

Categories

Resources