Search code examples
wpflistviewdatagridviewcolumn

Change GridViewColumn text color base on condition


This is my GridViewColumn:

<GridViewColumn Width="180" Header="Status">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock x:Name="Txt" Text="{Binding Status}" Foreground="Yellow" />
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>

The Status field is a property of my binding object and all i want to do is change this GridViewColumn color but this time base on condition:

I have another propgerty called StatusMessage which is simple enum:

public enum StatusMessage
{
    InProcess,
    Done,
    Wait
}

So this enum property is changing all the time and for every value of this enum i want to define different color. Is it possible ?

Edit

My View model class inherit from BaseObservableObject:

public class BaseObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
    {
        var propName = ((MemberExpression)raiser.Body).Member.Name;
        OnPropertyChanged(propName);
    }

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            OnPropertyChanged(name);
            return true;
        }
        return false;
    }
}

My properties:

public string Status
{
    get { return _status; }
    set
    {
        _status = value;
        OnPropertyChanged();
    }
}

public StatusMsg StatusMessage
{
    get { return _statusMsg; }
    set {
        _statusMsg = value;
        OnPropertyChanged();
    }
}

XAML:

<GridViewColumn Width="180" Header="Status" DisplayMemberBinding="{Binding Status}">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Foreground="{Binding StatusMsg,Converter={c:StatusMessageToColorConverter}}" />
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>

StatusMessageToColorConverter is the same as @grek40 suggested and still my TextBlock Foreground not changing.


Solution

  • I first focus on the value conversion, then I say something about the other requirement ("So this enum property is changing all the time and for every value of this enum i want to define different color.")

    Since you have an enum value but you want to have a color specification, you need to convert the value. This can be done with the help of an implementation of the IConverter interface. There are different ways to reference a converter in XAML, I additionally inherit from MarkupExtension so I am able to access the converter directly.

    public class StatusMessageToColorConverter : MarkupExtension, IValueConverter
    {
        // one way converter from enum StatusMessage to color
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is StatusMessage && targetType == typeof(Brush))
            {
                switch ((StatusMessage)value)
                {
                    case StatusMessage.InProcess:
                        return Brushes.Yellow;
                    case StatusMessage.Done:
                        return Brushes.Green;
                    case StatusMessage.Wait:
                        return Brushes.Red;
                }
            }
            return null;
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    
        public StatusMessageToColorConverter()
        {
        }
    
        // MarkupExtension implementation
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
    

    As you can see, I convert if the input value is of type StatusMessage and the target type is typeof(Brush), which is the Type of the Foreground property. I just chose some colors, you may use different colors or even more complex brushes if you like.

    In XAML, the namespace of the converter needs to be referenced. In my case its just WpfApplication2 associated to the XAML namespace name c

    <Window 
        [... other properties]
        xmlns:c="clr-namespace:WpfApplication2">
    

    Now, when binding to the foreground property, utilize the converter

    <TextBlock Text="{Binding Status}" Foreground="{Binding StatusMessage,Converter={c:StatusMessageToColorConverter}}" />
    

    This should do the trick.

    Now to the other part about dynamically changing the value. Your viewmodel class needs to implement INotifyPropertyChanged and raise the PropertyChanged event whenever a value is changed. As an example, see the following class that only contains the two properties of your example and the necessary notification logic.

    public class ItemModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        void NotifyPropertyChanged([CallerMemberName]string prop = null)
        {
            var tmp = PropertyChanged;
            if (tmp != null) tmp(this, new PropertyChangedEventArgs(prop));
        }
    
        private string _Status;
        public string Status
        {
            get { return _Status; }
            set { _Status = value; NotifyPropertyChanged(); }
        }
        private StatusMessage _StatusMessage;
        public StatusMessage StatusMessage
        {
            get { return _StatusMessage; }
            set { _StatusMessage = value; NotifyPropertyChanged(); }
        }
    }
    

    More complex viewmodels can follow the same approach. For less update overhead, compare the current value and the new value in the setter and only notify if the value actually changes.