Lets say I have 2 columns in my data Grid: Column A: Selected, and Column B: Name. The Selected column is a checkbox. And Name column is text field. I want to set the color of the text in 'Name' column as Blue if Column A's check box is checked, and Red otherwise.
Essentially I don't know how to bind data between columns of the datagrid. And sample code/link providing example would be useful.
I haven't used the WPF Toolkit's DataGrid much, but from what I can gather, one method is to use DataGridTemplateColumn's and then set up your DataTriggers based on the binding.
Here is an example that uses DataTriggers to set the style of the Foreground color as well the entire row's background color. Of note, you'll need a boolean Property in your ItemsSource's binding to make this work with this method.
XAML
<Window.Resources>
<Style TargetType="{x:Type tk:DataGridRow}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="MyTextBlockStyle">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<tk:DataGrid x:Name="MyGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<tk:DataGrid.Columns>
<tk:DataGridTemplateColumn Header="Selected"
Width="75">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn Header="Name" Width="100" >
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"
Style="{StaticResource MyTextBlockStyle}" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>
</Grid>
Code Behind
public partial class DataGridDataTrigger : Window
{
public List<Person> People { get; set; }
public DataGridDataTrigger()
{
InitializeComponent();
var names = new List<string> { "Joe", "Bob", "Frank", "Scott", "Mike" };
People = new List<Person>();
names.ForEach( x => People.Add( new Person { Name = x } ) );
People.ForEach( x =>
{
if( x.Name.Contains( "o" ) )
x.IsSelected = true;
} );
MyGrid.DataContext = People;
}
}
public class Person
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
Related
I am starting to get involved with WPF MVVM.
Now in a grid I need to format the appearance of a cell based on the value in another cell.
Current Grid Example 1
The values in 'SAP No.' should appear red if the value 'DoppelSAPOrder' is true in the viewmodel. The value 'DoppelSAPOrder' is not displayed in the grid.
The grid is on a UserControl
<UserControl...
<UserControl.DataContext>
<model:MainVM />
</UserControl.DataContext>
<Grid>
<datagrid:ThemedDataGrid HorizontalAlignment="Stretch" Height="auto" VerticalAlignment="Stretch" Width="auto"
x:Name="DataGrid" ItemsSource="{Binding Tickets}" AutoGenerateColumns="False"
IsReadOnly="True"
ClipboardCopyMode="IncludeHeader" SelectedItem="{Binding SelectedTicket}"...
<datagrid:ThemedDataGrid.Columns>
<datagrid:ThemedDataGrid.Columns>
<DataGridTextColumn Binding="{Binding CrmTicketId}" Header="Ticket Nr." Width="90">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding SapAuftrag}" Header="SAP Nr." Width="85">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{Binding ???, Converter={StaticResource SomeBoolToBrushConverter-ToDo}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
The grid is bound to a List.
public class SimpleTicketDTO : BaseDTO
{
public string Kategorie { get; set; }
public string Status { get; set; }
public string PersonFirmaName { get; set; }
public bool HatSAPOrder { get; set; }
public string SapAuftrag { get; set; }
public bool DoppelSAPOrder { get; set; }
...
I cannot now access the property 'DoppelSAPOrder ' of the Row in the ElementStyle :-/
Can someone show me how to address this problem?
You want to bind TextBlock property in element style to its' parent row binded object and it can be achieved with the RelativeSource property of the binding like this:
<DataGridTextColumn Binding="{Binding SapAuftrag}" Header="SAP Nr." Width="85">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}, Path =Item.DoppelSAPOrder,Converter={StaticResource IsObsoleteToTextDecorationsConverter}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
I have a Datagrid in WPF application (Using MVVM {Caliburn Micro}) which is bounded to property of type ObservableCollection<Student> where Student class looks like this:
public class Student
{
public int ID { get; set; }
public String FullName { get; set; }
public bool Passed { get; set; }
}
Depending on the fact - student passed the exam or not - I want to change the Background/Foreground of the row of the corresponding student to red (if didn't passed).
Below is shown my DataGrid:
<DataGrid Grid.Column="1"
RowBackground="White"
Visibility="Visible"
Grid.Row="15"
ColumnWidth="auto"
IsReadOnly="False"
AutoGenerateColumns="False"
BorderBrush="{StaticResource GridBorder}"
VerticalScrollBarVisibility="Auto"
HorizontalGridLinesBrush="LightGray"
HorizontalScrollBarVisibility="Disabled"
VerticalGridLinesBrush="LightGray"
Name="Students"
CanUserAddRows="True"
BorderThickness="0.8"
SelectionUnit="FullRow"
cal:Message.Attach="[Event MouseDoubleClick] = [Action GetRow($dataContext)]"
SelectionMode="Single" Grid.ColumnSpan="4">
these are the column Definitions:
<DataGridTextColumn Binding="{Binding ID}" Header="PersonalNumber"/>
<DataGridTextColumn Binding="{Binding FullName}" Header="FullName"/>
To solve this, I tried something like this, but doesn't work:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Passed}" Value="false">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Passed}" Value="true">
<Setter Property="Background" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
What can I do?
Your DataGrid is setting RowBackground="White" which is overriding the RowStyle, remove that setting and the Style will behave as you're expecting it to.
I have two WPF datagrids. When a row is selected from first datagrid, dg1, a column called 'Notes' in second datagrid, dg2, should be shown as empty strings only and only if the content of the column 'Note Type' for the selected row in dg1 is equal to "I". Otherwise, content of 'Notes' column in dg2 should be the one from collection ItemsDg2 that comes from Notes.
My problem is that when value is "I", the content of 'Note Type' column in dg1 is shown in column 'Notes' of dg2 instead of showing an empty string.
<Window x:Name="MainWindow"
xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<my:DataGrid Name="dg1" ItemsSource="{Binding Path=ItemsDg1}">
<my:DataGrid.Columns>
<my:DataGridTextColumn x:Name="iType" Binding="{Binding Path=Type}" Header="Note Type"/>
</my:DataGrid.Columns>
</my:DataGrid>
<my:DataGrid Name="dg2" ItemsSource="{Binding Path=ItemsDg2}">
<my:DataGrid.Columns>
<my:DataGridTextColumn Binding="{Binding Path=Notes}" Header="Notes">
<my:DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Path=Notes}"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.Type, ElementName=dg1}" Value="I">
<Setter Property="Text" Value="{x:Static sys:String.Empty}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</my:DataGridTextColumn.ElementStyle>
</my:DataGridTextColumn>
</my:DataGrid.Columns>
</my:DataGrid>
</Window>
ItemsDg1 is a List of It1
ItemsDg2 is a List of It2
public Class It1
{
private string _type;
public string Type
{
get { return _type; }
set
{
if (!_type.Equals(value))
{
_type = value;
}
}
}
}
public Class It2
{
private string _notes;
public string Notes
{
get { return _notes; }
set
{
if (!_notes.Equals(value))
{
_notes = value;
}
}
}
}
Classes It1 and It2 have more properties but I only show here the minimum to understand the scenario. The same applies to dg1 and dg2, they have more datagrid columns.
If I understand you correctly, you should get the results you want by replacing the Notes column definition in dg2 as follows:
<DataGridTemplateColumn Header="Notes">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="CellText" Text="{Binding Notes}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding SelectedItem.Type, ElementName=dg1}" Value="I">
<Setter TargetName="CellText" Property="Text" Value="" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have ToggleButtons and a DataGrid, each row in DataGridColumn has a ColGroup AttachedProperty set to the name of the column group name.
Attached property:
public class DataGridColumnsGroupProperty {
public static readonly DependencyProperty ColGroupProperty =
DependencyProperty.RegisterAttached("ColGroup", typeof(object), typeof(DataGridColumnsGroupProperty), new FrameworkPropertyMetadata(null));
public static void SetColGroup(DependencyObject element, string value) {
element.SetValue(ColGroupProperty, value);
}
public static string GetColGroup(DependencyObject element) {
return (string)element.GetValue(ColGroupProperty);
}
}
The ToggleButtons has two jobs, on Check/UnCheck show/collapse all columns with the same group name.
and it has a ContextMenu which shows only the DataGridColumns with the same group name.
I've managed to bind all DataGridColumns to the ToggleButton, but couldn't find a way to Collapse the DataGridColumns with different group names.
How to fill context menu with only the columns with the givin group name inside the Style Trigger?
And how to hid all columns that has the group name when un-check toggle button?
XAML:
<ToggleButton.ContextMenu>
<ContextMenu x:Name="ContextMenu" ItemsSource="{Binding Columns, ElementName=ElementDataGrid, Converter={StaticResource TestConverter}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="HeaderTemplate" Value="{Binding HeaderTemplate}"/>
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="StaysOpenOnClick" Value="True" />
<Setter Property="AutomationProperties.Name" Value="{Binding Header}"/>
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Visibility, Mode=TwoWay, Converter={StaticResource VisibilityToBooleanConverter}}" />
<Style.Triggers>
<Trigger Property="attachedProperties:DataGridColumnsGroupProperty.ColGroup" Value="FirstGroup">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</ToggleButton.ContextMenu>
DataGridColumns:
<DataGridTextColumn x:Name="StoryCol" attachedProperties:DataGridColumnsGroupProperty.ColGroup="FirstGroup" Header="{x:Static p:Resources.Story}" IsReadOnly="True" Binding="{Binding Story}" Visibility="Visible" />
<DataGridTextColumn x:Name="CadIdCol" attachedProperties:DataGridColumnsGroupProperty.ColGroup="SecondGroup" Header="{x:Static p:Resources.CadId}" IsReadOnly="False" Binding="{Binding CadId}" Visibility="Visible" />
Using a DataTrigger should work as far as the binding to the attached property is concerned:
<DataTrigger Binding="{Binding Path=(attachedProperties:DataGridColumnsGroupProperty.ColGroup)}" Value="FirstGroup">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
I am working on an application that implements the MVVM design pattern with DataAnnotations. The application is a dynamically generated list of pages. On one of those pages, I have 10 required fields with 2 yes/no radio buttons. Those 10 fields are divided into two groups and each group is wwapped with a border tag. Each border's visibility is bound with the radio buttons for hidden/visible.
My question is if yes was selected and the related 5 required text boxes are displayed how can i set the ValidatesOnDataErrors to false/true and clear the text boxes values of the other hidden required TextBoxes?
Here is a code Snippet.
thanks
<Border>
<Border.Style>
<Style>
<Setter Property="Border.Visibility" Value="Hidden"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PresentlyEmployed_yes, Path=IsChecked}"
Value="True">
<Setter Property="Border.Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid Height="Auto" Width="Auto">
<Label Name="JobTitle"
Content="{x:Static properties:Resources.JobTitlelbl}" />
<TextBox Name="JobTitle" Text="{Binding JobTitle, Mode=TwoWay,
ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding PrimaryInsuredBusinessDuties, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged, IsAsync=True}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PresentlyEmployed_yes, Path=IsChecked}"
Value="True">
<Setter Property="Text" Value="{Binding JobTitle, Mode=TwoWay,
ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=PresentlyEmployed_yes, Path=IsChecked}"
Value="False">
<Setter Property="Text" Value="{Binding JobTitle, Mode=TwoWay,
ValidatesOnDataErrors=False, UpdateSourceTrigger=PropertyChanged}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</Border>
Try setting the Validation.Template to {x:Null} if it shouldn't show the Validation Error
<StackPanel>
<ListBox x:Name="MyListBox" SelectedIndex="0">
<ListBoxItem>Validate Value 1</ListBoxItem>
<ListBoxItem>Validate Value 2</ListBoxItem>
</ListBox>
<TextBox Text="{Binding Value1, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=MyListBox}" Value="1" >
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Text="{Binding Value2, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=MyListBox}" Value="0" >
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
Sure, here is how my validationbase class looks like (Simplified)
public class ValidationViewModelBase : ViewModelBase, IDataErrorInfo, IValidationExceptionHandler
{
private Dictionary<string, Func<ValidationViewModelBase, object>> _propertyGetters;
private Dictionary<string, ValidationAttribute[]> _validators;
/// <summary>
/// Gets the error message for the property with the given name.
/// </summary>
/// <param name="propertyName">Name of the property</param>
public string this[string propertyName]
{
IList<string> fieldsNames = new List<string>();
{
if (propertyName == "PresentlyEmployed")
{
//if its true then
fieldsNames.Add("JobTitle");
AddFieldsValidation(fieldsNames);
}else{
fieldsNames.Add("EmploymentAddress");
RemoveValidation(fieldsNames);
}
if (this.propertyGetters.ContainsKey(propertyName))
{
var propertyValue = this.propertyGetters[propertyName](this);
var errorMessages = this.validators[propertyName]
.Where(v => !v.IsValid(propertyValue))
.Select(v => v.ErrorMessage).ToArray();
return string.Join(Environment.NewLine, errorMessages);
}
return string.Empty;
}
/// <summary>
/// Gets an error message indicating what is wrong with this object.
/// </summary>
public string Error
{
get
{
var errors = from validator in this.validators
from attribute in validator.Value
where !attribute.IsValid(this.propertyGetters[validator.Key](this))
select attribute.ErrorMessage;
return string.Join(Environment.NewLine, errors.ToArray());
}
}
}
/// <summary>
/// Gets the number of properties which have a validation attribute and are currently valid
/// </summary>
public int ValidPropertiesCount
{
get
{
var query = from validator in this.validators
where validator.Value.All(attribute => attribute.IsValid(this.propertyGetters[validator.Key](this)))
select validator;
var count = query.Count() - this.validationExceptionCount;
return count;
}
}
}
/// <summary>
/// Gets the number of properties which have a validation attribute
/// </summary>
public int TotalPropertiesWithValidationCount
{
get
{
return this.validators.Count();
}
}
public ValidationViewModelBase()
{
this.validators = this.GetType()
.GetProperties()
.Where(p => this.GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, p => this.GetValidations(p));
this.propertyGetters = this.GetType()
.GetProperties()
.Where(p => this.GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, p => this.GetValueGetter(p));
}
private ValidationAttribute[] GetValidations(PropertyInfo property)
{
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
private Func<ValidationViewModelBase, object> GetValueGetter(PropertyInfo property)
{
return new Func<ValidationViewModelBase, object>(viewmodel => property.GetValue(viewmodel, null));
}
private int validationExceptionCount;
public void ValidationExceptionsChanged(int count)
{
this.validationExceptionCount = count;
this.OnPropertyChanged("ValidPropertiesCount");
}