Search code examples
visual-studiowinformsc++-cli

Winforms: Change background colour of an element depending on value of a variable


I have a small project here in visual studio 2022, a Winforms c++/CLR project. It is a pretty simple project and form, but I have an issue with the following:

In the Winforms app I use a small panel to display the status of some input that I get. I want to change the background colour of the panel depending on the status of that variable. So for example if the variable is 0, the background colour should be red, if the value of the variable is 1, it should be green.

I can easily make this work, when this is coupled to a trigger event from the user, such as mouseclick, mousehover, .... But how do I make this work dynamically during runtime, such that even while the variable changes during runtime, the backgroundcolour of the panel changes with it too, without the need of a user input event such as a mouseclick, ... ?

What kind of class need to be made in order to make this work?


Solution

  • An example that shows how to bind the Property of a Control to the Property of a managed class object, implementing the INotifyPropertyChanged interface, used to trigger value change notifications.

    In the ColorTrigger class, when the value of an int Property is changed, this also changes the value returned by a Property of Type Color and triggers a PropertyChanged notification.
    This causes all Controls bound to the class object to read again the value of the Property specified in the binding:

    somePanel->DataBindings->Add(
        "BackColor", trigger, "ColorValue", false, DataSourceUpdateMode::OnPropertyChanged
    );
    

    Here, DataBindings.Add() binds the BackColor Property of a Panel Control to the ColorValue Property of the ColorTrigger class.

    #pragma once
    #include "ColorTrigger.h"
    
    public ref class SomeForm : public System::Windows::Forms::Form
    {
    // Initialize with default values
    private: ColorTrigger^ trigger = gcnew ColorTrigger();
    
    public: SomeForm(void) { 
        InitializeComponent(); 
        somePanel->DataBindings->Add("BackColor", trigger, "ColorValue", false, DataSourceUpdateMode::OnPropertyChanged);
    }
    

    Or pass different Colors to the Constructor of the ColorTrigger class:

    // [...]
    private: ColorTrigger^ trigger;
    
    public: SomeForm(void) { 
        InitializeComponent(); 
        trigger = gcnew ColorTrigger(gcnew array<Color> { 
            Color::Magenta, Color::Cyan, Color::Orange 
        });
        somePanel->DataBindings->Add("BackColor", trigger, "ColorValue", false, DataSourceUpdateMode::OnPropertyChanged);
    }
    

    Now, when you assign a new value to the Value Property of the ColorTrigger class, the Color returned by the ColorValue Property changes accordingly and a notification is sent, causing the Binding to update and with it the Property of Controls that share the binding.


    ColorTrigger managed class:

    Note that this line in the Property setter:

    colorIdx = Math::Max(Math::Min(value, colors->Length - 1), 0);
    

    trims the value set in the range (0, [ColorArray].Length - 1).
    You may want to throw instead, if value is outside the bounds of the array.

    #pragma once
    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Drawing;
    
    #define nameof(_propname) #_propname
    
    public ref class ColorTrigger : INotifyPropertyChanged
    {
    public:
        virtual event PropertyChangedEventHandler^ PropertyChanged;
    
    private:
        array<Color>^ colors;
        int colorIdx = 0;
    
    public:
        ColorTrigger(void) {
            this->ColorTrigger::ColorTrigger(
                gcnew array<Color> {Color::Green, Color::Orange, Color::Red}
            );
        }
        ColorTrigger(array<Color>^ colorArr) {colors = colorArr;}
    
    public : 
        property int Value {
            int get() { return colorIdx; }
            void set(int value) {
                if (value != colorIdx) {
                    colorIdx = Math::Max(Math::Min(value, colors->Length - 1), 0);
                    ColorValue = colors[colorIdx];
                }
            }
        }
    
        property Color ColorValue {
            Color get() { return colors[colorIdx]; }
            private : void set(Color value) {
                OnPropertyChanged(nameof(ColorValue));
            }
        }
    
        void OnPropertyChanged(String^ prop) {
            PropertyChanged(this, gcnew PropertyChangedEventArgs(prop));
        }
    };