How can I create a style for a DataGridCell with DataTriggers linked to properties of the column value.
I am binding my DataGridTextColumn to an object - not just a text/numeric value. The object has properties that I want to use to display a ToolTip, set the color, etc...
I can do this by specifying the column property name within the style as follows:
<DataGridTextColumn Header="Payment Terms" Binding="{Binding PaymentTerms, StringFormat='{}{0:0.00}'}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock" BasedOn="{StaticResource RightDataGridColumnStyle}">
<Setter Property="ToolTip" Value="{Binding PaymentTerms.ToolTip}" />
<Style.Triggers>
<DataTrigger Binding="{Binding PaymentTerms.HasChanged}" Value="True" >
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
But I have several columns all looking for the same formatting approach, each based on the data within that column. So I am looking to do this within a Style that can pick up the bound item of that cell.
The PaymentsTerms property is a DataValue class as follows:
Public Class DataValue
Public Property Value As Double
Public Property HasChanged As Boolean
Public Property ToolTip As String
Public Overrides Function ToString()
Return Value
End Function
End Class
Related
I'm currently stuck with a problem. I want to declare a eventriggerbehavior for all my listviews. this is my code:
<Style TargetType="ListView">
<Setter Property="ItemTemplate" Value="{StaticResource itemShowTemplate}" />
<i:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="ItemClicked">
<Interactions:InvokeCommandAction Command="{Binding ShowItemClickedCommand}" />
</Interactions:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Style>
the problem I have now is that EventTriggerBehavior is looking for the event on Style and not the targettype of Listview. And the only property left to set on EventTriggerBehavior is SourceObject. But I don't want this behavior on 1 listview, I want it on al my listviews.
Is there a way todo this?
My first idea was that it could work this way:
<Style TargetType="Button">
<Setter Property="i:Interaction.Behaviors">
<Setter.Value>
<i:BehaviorCollection>
<core:EventTriggerBehavior EventName="Click">
<core:InvokeCommandAction Command="{Binding TestCommand}" />
</core:EventTriggerBehavior>
</i:BehaviorCollection>
</Setter.Value>
</Setter>
</Style>
But unfortunately this works only for the first control which gets the behavior attached. The reason is that the value is constructed just once and the AssociatedObject property of the BehaviorCollection is set to the first control.
I have then come over this solution - How to add a Blend Behavior in a Style Setter .
The idea is to create an attached property that manually assigns the behavior to the control.
Based on this I suggest you do it like this:
public static class ListViewBehaviorAttacher
{
public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached(
"IsAttached", typeof(bool), typeof(ListViewBehaviorAttacher), new PropertyMetadata(default(bool), IsAttachedChanged));
private static void IsAttachedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listView = (ListView)dependencyObject;
//create the binding
BehaviorCollection collection = new BehaviorCollection();
var eventTrigger = new EventTriggerBehavior() { EventName = "ItemClick" };
var invokeCommandAction = new InvokeCommandAction();
//binding to command
BindingOperations.SetBinding(
invokeCommandAction,
InvokeCommandAction.CommandProperty,
new Binding() { Path = new PropertyPath("ShowItemClickedCommand"), Source = listView.DataContext });
eventTrigger.Actions.Add(invokeCommandAction);
collection.Add(eventTrigger);
listView.SetValue(Interaction.BehaviorsProperty, collection);
}
public static void SetIsAttached(DependencyObject element, bool value)
{
element.SetValue(IsAttachedProperty, value);
}
public static bool GetIsAttached(DependencyObject element)
{
return (bool)element.GetValue(IsAttachedProperty);
}
}
And then in the style attach it like this:
<Style TargetType="ListView">
<Setter Property="SelectionMode" Value="None"></Setter>
<Setter Property="IsItemClickEnabled" Value="True" />
<Setter Property="local:ListViewBehaviorAttacher.IsAttached" Value="True" />
</Style>
<Button ToolTip="Duplicate" ToolTipService.ShowOnDisabled="True"
Command="{Binding Path=DuplicateEntityCommand }"
CommandParameter="{Binding ElementName=GridView, Path=SelectedItems}">
<Image Style="{StaticResource toolbarImageStyle}" Source="/OnePlanner;component/Resources/Icons/duplicate.png"></Image>
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=RadGridView, Path=SelectedItems.Count}" Value="1"/>
<Condition Binding="{Binding Path=IsItemListConstainsToyota}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
The button is tied with the GridView. I have to enable this button based on the selection in SelectedItems. If one selects only one items in the grid and that item does not has let say a string with value equal to Toyota, then the button should be enabled.
I have created a method in viewmodel that I use with commandParameter.
private bool IsItemListConstainsToyota(IList<object> itemList)
{
if (itemList.First() is CarList)
{
String carName= ((CarList)itemList.First()).carNameAsString;
if (carName.Equals("Toyota"))
{
return true;
}
}
return false;
}
The code so far I have does not work. Is there anyway to do in XAML or I have to do it in viewModel?
For the Duplicate Entity Command, you can pass Action<object> for CanExecuteAction. If this is false, then your button will be disabled. Try passing the following Action<object> to the CanExecuteParameter in ICommand.
itemList =>
{
return IsItemListConstainsToyota(itemList);
}
I have created a subclass of TextBox
public class MyAwesomeTextBox : TextBox { ... }
and have set the color of all TextBoxes to be red
<UserControl>
<UserControl.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="Red" />
</Style>
</UserControl.Resources>
<TextBox ... />
<xyz:MyAwesomeTextBox ... />
</UserControl>
It works for all TextBoxes but not for MyAwesomeTextBoxes.
Please tell me what is wrong.
I bet you set the DefaultStyleKey property to typeof(MyAwesomeTextBox) and now the framework will only apply a style with the specialized stylekey.
See the documentation, I extracted the following part:
If you do not set the DefaultStyleKey, the default style for the base class is used. For example, if a control called NewButton inherits from Button, to use a new default Style, set DefaultStyleKey to the type, NewButton. If you do not set DefaultStyleKey, the Style for Button is used.
So what can you do now? You can either remove the defaultStyleKey (but that means the TextBox style will be applied everywhere in your application and you cannot have you AwesomeControlTemplate applied to it by default, so I think this is not what you should do now) or you can add a derived style to your resources:
<UserControl.Resources>
<Style TargetType="TextBox" x:Key="BaseStyle">
<Setter Property="Background" Value="Red" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource BaseStyle}">
<Style TargetType="MyAwesomeTextBox" BasedOn="{StaticResource BaseStyle}">
</UserControl.Resources>
I have a ComboBox, bound to a list of objects. I can get the objects to fill the drop down just fine. I am trying to set the background color for each object in the items list of the dropdown. I can set any color for all of them easily in the below style code.
What I want to do is Bind the Background Color Value to the KeyColorValue field of my Key object.
Here is my XAML:
DisplayMemberPath="Name"
HorizontalAlignment="Left"
Margin="300,103,0,0"
VerticalAlignment="Top"
Width="186"
SelectionChanged="roleBoundSelector_SelectionChanged" >
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Background"
Value = "{Binding Path=KeyColorValue}" />
(If I put a color in here it works just fine...need to bind to the KeyColorValue of the MyKeys Object.)
Try this inside your style:
<Setter Property="Background">
<Setter.Value>
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext.KeyColorValue"/>
</Setter.Value>
</Setter>
The DataContext of each ComboBoxItem is an object contained in the List that is feeding the ItemsSource of the ComboBox.
Let me know if this was of any help, regards!
I have entity with property IsRemoved. When it is become true grid row should be Gray.
To do this I am using this code:
<dxg:TableView.RowStyle>
<Style TargetType="{x:Type dxg:GridRowContent}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsRemoved, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxg:TableView.RowStyle>
</dxg:TableView>
But It will run only when grid shows first time. I want to change color when value is changing. Property implement INotifyPropertyChange Event.
Note: this answer is legacy (see my other answer).
This answer is for DevExpress versions prior to v14.1, or DevExpress versions v14.1 and after with
UseLightweightTemplates="None".
You need to have an initial setter for the property you want to change. This is due to the order in which WPF uses styles.
Include this line after your style tag:
<Setter Property="Background" Value="Black" />
Full Example:
<dxg:TableView.RowStyle>
<Style TargetType="{x:Type dxg:GridRowContent}">
<Setter Property="Background" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsRemoved, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxg:TableView.RowStyle>
Starting with v14.1 of DevExpress, they introduced Optimized Mode which uses Lightweight Templates. This makes everything faster, but requires a change to how the styles and DataTriggers are specified.
Lightweight Templates are controlled by a the attached property UseLightweightTemplates="Row", which is on by default. It can be switched to None for backwards compatibility.
Here is a working MVVM example of how to color a row if the IsDirty property is set for any grid row.
<dxg:GridControl x:Name="MyGridControl"
ItemsSource ="{Binding MyViewModelList}"
SelectionMode="None"
VerticalAlignment="Stretch">
<dxg:GridControl.Resources>
<SolidColorBrush x:Key="GridRowIsDirty" Color="#FF602D2D" />
</dxg:GridControl.Resources>
<dxg:GridControl.View>
<dxg:TableView UseLightweightTemplates="Row" >
<dxg:TableView.RowStyle>
<Style TargetType="dxg:RowControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Row.IsDirty}" Value="True">
<Setter Property="Background" Value="{StaticResource GridRowIsDirty}" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxg:TableView.RowStyle>
</dxg:TableView>
</dxg:GridControl.View>
<dxg:GridControl.Columns>
<dxg:GridColumn x:Name="Included" FieldName="Included"/>
<dxg:GridColumn x:Name="ColumnB" Header="Column B" FieldName="ColumnB" ReadOnly="True"/>
<dxg:GridColumn x:Name="ColumnC" Header="Column C" FieldName="ColumnC" ReadOnly="True"/>ReadOnly="True"/>
</dxg:GridControl.Columns>
</dxg:GridControl>
In the ViewModel behind this grid:
public ObservableCollection<MyViewModel> MyViewModelList { get; set; }
Every row in the grid points to a class of type MyViewModel, which contains a custom IsDirty flag which we can set on demand:
public bool IsDirty
{
get { return _isDirty; }
set
{
_isDirty = value;
OnPropertyChanged();
}
}
Appendix A: Additional Links
See DevExpress: How to disable focused/selected row colors.
See DevExpress: Optimized Mode.
See DevExpress: DXGrid: DataTrigger does not seem to work with UseLightweightTemplates="All".
See DevExpress: Binding to the RowData.Row property is not updated when changing a specific data row property.
See DevExpress: DxGrid: Grid does not update until I scroll the row on off and one the screen.
Appendix B: Other solutions
This also works most of the time, but it will not work if the source of the event is via a context menu, so it is not recommended:
<DataTrigger Binding="{Binding DataContext.IsDirty}" Value="True">
<Setter Property="Background" Value="{StaticResource GridRowIsDirty}" />
</DataTrigger>
Appendix C: AllowLiveDataShaping
If the trigger is not firing, try switching on AllowLiveDataShaping="True" in <GridControl>. However, try to avoid this as it (theoretically) has an impact on the speed of large, complex grids (it has no discernable impact on most grids of a reasonable size).
Appendix D: If all else fails, use a custom ControlTemplate
With the introduction of "UseLightweightTemplates", DevExpress has been focusing on speed. However, the techniques used for speed involve switching off bindings that might slow things down.
This means that if we change something in a DxGrid cell, the value in the ViewModel does not change until the user shifts to the next cell or row. This means that the ViewModel lags behind what is actually in the grid.
To fix this, the only solution that I could find was to bypass DevExpress's templates entirely, and use my own. This means that the DxGrid has no choice but to display a custom template which updates the ViewModel instantaneously as soon as the user edits it, which means that the row color changes immediately:
<dxg:GridControl Grid.Row="3" x:Name="TrsGridControl"
ItemsSource ="{Binding MyObservableCollection}"
VerticalAlignment="Stretch"
AllowLiveDataShaping ="True">
<dxg:GridControl.Resources>
<converter:TestConverter x:Key="TestConverter" />
<ControlTemplate x:Key="DisplayedOnTicketTrs">
<dxe:CheckEdit x:Name="DisplayedOnTicketCheckEdit" HorizontalAlignment="Center" IsChecked="{Binding RowData.Row.DisplayedOnTicket, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ControlTemplate>
</dxg:GridControl.Resources>
<dxg:GridControl.View>
<dxg:TableView UseLightweightTemplates="All"/>
</dxg:GridControl.View>
<dxg:GridControl.Columns>
<dxg:GridColumn x:Name="DisplayedOnTicketTrs" DisplayTemplate="{StaticResource DisplayedOnTicketTrs}" Header="Displayed On Ticket?" HeaderToolTip="Displayed On Ticket?" AllowEditing="False"/>
Header ="Displayed On Ticket?"/>
<dxg:GridColumn x:Name="ColumnA" Header="ColumnA" FieldName="ColumnA" ReadOnly="True"/>
<dxg:GridColumn x:Name="ColumnB" Header="ColumnB" FieldName="ColumnB" ReadOnly="True"/>
</dxg:GridControl.Columns>
</dxg:GridControl>
After I made this change, everything started to work:
When the checkbox is clicked, the background color changes instantly (if we add the trigger to change the background color, above).
Editing the DxGrid changes the ViewModel instantaneously.
Changing the ViewModel updates the DxGrid instantaneously.
If a ContextMenu updates the ViewModel, then everything just works.
you should write just "Row" instead of "DataContext"