0

I'm using Prism 6 to create my application, and I have a need to color the background of DataGridRows based on the date. I started creating a Blend SDK Trigger to accept two parameters: MinDate and MaxDate. The action would then set the background color. However, I am hitting a roadblock when it comes to using the trigger. It isn't accepted in the collection and I can't seem to get the trigger to execute when I use a DataTemplate.

Here's the code for the trigger. It doesn't actually do anything other than invoke the action because I want to make sure it is executing before coding the logic to check the date.

public class AnniversaryTrigger: TriggerBase<DataGridRow>
{
    public DateTime MaxDate
    {
        get { return (DateTime)GetValue(MaxDateProperty); }
        set { SetValue(MaxDateProperty, value); }
    }

    public DateTime MinDate
    {
        get { return (DateTime)GetValue(MinDateProperty); }
        set { SetValue(MinDateProperty, value); }
    }

    protected override void OnAttached()
    {
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += OnUnloaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        AssociatedObject.Unloaded -= OnUnloaded;
    }

    private void OnLoaded(object sender, System.Windows.RoutedEventArgs e)
    {
        AssociatedObject.DataContextChanged += OnDataContextChanged;
        Refresh();
    }

    private void OnUnloaded(object sender, System.Windows.RoutedEventArgs e)
    {
        AssociatedObject.DataContextChanged -= OnDataContextChanged;
    }

    private void Refresh()
    {
        base.InvokeActions(null);
    }

    private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        Refresh();
    }

    #region Dependency Properties
    public static readonly DependencyProperty MaxDateProperty =
        DependencyProperty.Register("MaxDate", typeof(DateTime), typeof(AnniversaryTrigger), new PropertyMetadata(null));

    public static readonly DependencyProperty MinDateProperty =
        DependencyProperty.Register("MinDate", typeof(DateTime), typeof(AnniversaryTrigger), new PropertyMetadata(null));
    #endregion
}

The DataTemplate is as follows and was attached to the DataGrid ItemTemplate.

<UserControl.Resources>
    <DataTemplate x:Key="AnniversaryTemplate">
        <DataGridRow>
            <i:Interaction.Triggers>
                <b:AnniversaryTrigger MinDate="{Binding MinDate}" 
                                      MaxDate="{Binding MaxDate}" >
                    <ei:ChangePropertyAction PropertyName="Background">
                        <ei:ChangePropertyAction.Value>
                            <SolidColorBrush Color="Yellow"/>
                        </ei:ChangePropertyAction.Value>
                    </ei:ChangePropertyAction>
                </b:AnniversaryTrigger>
            </i:Interaction.Triggers>
        </DataGridRow>
    </DataTemplate>
</UserControl.Resources>

Here's the DataGrid:

<DataGrid ItemsSource="{Binding FalseAlarmHistory}" AutoGenerateColumns="False" IsReadOnly="True" ItemTemplate="{DynamicResource AnniversaryTemplate}" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Incident ID" 
                                Binding="{Binding IncidentID}" />
            <DataGridTextColumn Header="Incident Date" 
                                Binding="{Binding IncidentDate, StringFormat=d}" />
            <DataGridTextColumn Header="Incident Time" 
                                Binding="{Binding IncidentTime}" />
            <DataGridTextColumn Header="Notes" 
                                Binding="{Binding Notes}" />
        </DataGrid.Columns>
    </DataGrid>

Any guidance is appreciated.

Incident history view model:

public class FalseAlarmHistoryViewModel : ValidatingBindableBase, IConfirmNavigationRequest
{
    private IFalseAlarmService _service;
    private ICustomerService _custsvc;

    private string _title;
    private IEventAggregator _eventAggregator;

    public string Title { get => _title; set => SetProperty(ref _title, value); }

    public FalseAlarmHistoryViewModel(IFalseAlarmService service, ICustomerService custsvc, IEventAggregator eventAggregator)
    {
        _service = service;
        _custsvc = custsvc;
        _eventAggregator = eventAggregator;
        Title = "False Alarms History";

        _eventAggregator.GetEvent<PermitSelectedChangedEvent>().Subscribe(PermitIdChanged);
    }

    //todo Color DataGrid Rows based on the anniversary year the false alarm occurred.  
    // See https://stackoverflow.com/questions/14997250/datagrid-row-background-color-mvvm
    // Add unmapped item to the FalseAlarmHistory entity that returns the year based on the anniversary year. 0 = current year, 1 = 1 year ago, etc.
    // Translate the year number into a color that will be used on the DataGrid row.  Make the color configurable (in app.config at least).

    //todo Initial sort should be most recent first.
    private void PermitIdChanged(int obj)
    {
        FalseAlarmHistory = new ListCollectionView(_service.GetFalseAlarmHistoryByPermitId(_custsvc.CurrentPermitId).ToList());
        RaisePropertyChanged(nameof(FalseAlarmHistory));
    }

    public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
    {
        continuationCallback(true);
    }

    public void OnNavigatedTo(NavigationContext navigationContext)
    {
        FalseAlarmHistory = new ListCollectionView(_service.GetFalseAlarmHistoryByPermitId(_custsvc.CurrentPermitId).ToList());
        RaisePropertyChanged(nameof(FalseAlarmHistory));
    }

    public bool IsNavigationTarget(NavigationContext navigationContext)
    {
        return true;
    }

    public void OnNavigatedFrom(NavigationContext navigationContext)
    {
    }

    #region Commands
    #endregion
    #region Event Handlers
    #endregion
    #region Bound Controls
    public ICollectionView FalseAlarmHistory { get; private set; }


    #endregion
    #region Bound Commands
    #endregion
}
Ken
  • 57
  • 1
  • 11
  • I feel like what you are attempting is overkill for a simple background color change. Have you looked into [Style Triggers](https://msdn.microsoft.com/en-us/library/system.windows.style.triggers(v=vs.110).aspx)? With style triggers, you could set a property in the view model based on the dates, and set the color in the view based on that. – R. Richards Jul 30 '17 at 20:51
  • From what I know about style triggers, you can't set a range of dates. Am I wrong? – Ken Jul 30 '17 at 21:19
  • No, you are not wrong. But I suggest you deal with that in the view model, not in the view (XAML). – R. Richards Jul 30 '17 at 21:22
  • Can you give me more details? How would you deal with it in the view model and not in the XAML? – Ken Jul 31 '17 at 00:10
  • Be happy to. I can post something as an answer later today. Can you give me a rough idea of what you plan to do with the min and max dates as far as rules are concerned? I could use a bit more context. – R. Richards Jul 31 '17 at 13:01
  • The incident model for the grid has a date the incident occurred. The customer model has an anniversary date. I had anticipated using a min and max date for each year, up to three years, to color the rows for each of those years from the current date back three years. For example, if the anniversary date is 12/1/2017, the rows with an incident date from 12/1/2016 to the current date would have one color. The rows with an incident date between 12/1/2015 through 11/30/2016 would have a different color. – Ken Jul 31 '17 at 16:04

1 Answers1

0

So, what I am thinking is that you add a property to the incident model that would be calculated based on the incident and anniversary date. By the sounds of it, you would have to get the anniversary date into the incident model somehow. You could do that from your data, or pass it to the incident model via a property, constructor, a function, etc.

Say you call the property IncidentAgeBucket and make of type AgeBucket.

// Inside Incident Model
public AgeBucket IncidentAgeBucket { get {
    // AgeBucket calculation logic goes here
    // Check to make sure you have an anniversary date first...
    // There are a few ways to approach setting this value, use one that you are comfortable with 
    }
}

// New enumeration to use (name these something more to your liking)
public enum AgeBucket{
    OneYear,
    TwoYear,
    ThreeYear
}

So, if the date is within the year of the anniversary date, then make the value of IncidentAgeBucket return AgeBucket.OneYear, when two years then return AgeBucket.TwoYear, etc.

In the XAML, add a RowStyle based on the value in the property:

<DataGrid x:Name="IncidentList" ItemsSource="{Binding Incidents}" ...>
    <DataGrid.Columns>
        <DataGridTextColumn Header="Incident Number" Binding="{Binding IncidentNumber}"/>
        ...
    </DataGrid.Columns>
    <DataGrid.RowStyle>
        <Style TargetType="DataGridRow">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IncidentAgeBucket}" Value="{x:Static local:AgeBucket.OneYear}">
                    <Setter Property="Background" Value="LightGreen"></Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding IncidentAgeBucket}" Value="{x:Static local:AgeBucket.TwoYear}">
                    <Setter Property="Background" Value="LightBlue"></Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding IncidentAgeBucket}" Value="{x:Static local:AgeBucket.ThreeYear}">
                    <Setter Property="Background" Value="LightPink"></Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGrid.RowStyle>
</DataGrid>

You may have to play around with the {x:Static local:AgeBucket.OneYear} syntax a bit. The local namespace may not be what you use. If you end up nesting the enum, then there are other things you will have to do to get this to work. A seemingly good reference for that.

I am hoping that this will give you an idea of a direction to go with this other than the trigger route. This seems more straight forward to me.

If this is way off base, let me know and I will scrap this answer. Too much for a comment! Hope this helps.

R. Richards
  • 24,603
  • 10
  • 64
  • 64
  • Hmm...I was trying to stay away from modifying the model for view styling. That's why I was trying to create a trigger that could deal with the view model instead. I wanted to have properties in the view model to set the parameters for the trigger and use view styling to style the rows accordingly. This seems like I'm breaking the MVVM paradigm. I'm relatively new to this. Am I wrong with my thinking? – Ken Jul 31 '17 at 22:30
  • Admittedly, this isn't ideal. Does it break MVVM, maybe. But in a way that is acceptable to me (personally). Then again, maybe not. It does work. The view knows about the view model, but not the other way around, which is proper MVVM. It's not like you are directing the UI from the view model, the UI is simply looking at the view model for some state, and doing something. I would rather have a property return an enum value that can be used in several places rather that it actually return something like a Color object. That line I will not cross. – R. Richards Jul 31 '17 at 22:41
  • If you do not find this to be of any value, let me know, I will delete the answer. – R. Richards Jul 31 '17 at 22:44
  • It is of some value, and I do appreciate it. My concern is not with the view model but the model itself. If I could get the trigger to work, all the modifications would be in the view model. I may end up changing the model, but that's not my preference. I could also create a copy of the incident model and add a property to that just for the view model, but as a reuse-pattern that also isn't ideal. – Ken Jul 31 '17 at 22:52
  • Do you actually have an `IncidentViewModel`? Or, are you just exposing the incident collection via a `Customer.Incidents` property in the main `DataContext` for the view? – R. Richards Jul 31 '17 at 23:09
  • I have an incident View model. The view has buttons bound to the view model using DelegateCommand properties to further filter the data grid. – Ken Jul 31 '17 at 23:24
  • I can't think of any reason why you couldn't do this same thing in the view model then. Any chance you have your code on GitHub? – R. Richards Aug 01 '17 at 00:55
  • I don't have it on GitHub, and the view model isn't complete because I was trying to get the trigger to work. After that, I would complete the view model. What did you have in mind? – Ken Aug 01 '17 at 16:20
  • All I was thinking is that you could do what I have in my answer, but in the view model rather than the model. Not much changes, except where the code in written. If you had your code on GitHub, I was going to create a pull request with the change. – R. Richards Aug 01 '17 at 16:27
  • I don't currently use GitHub, but I edited my question and put the view model at the end of the post. – Ken Aug 02 '17 at 16:36