Desktop list: change background color on condition

Hi All,

On desktop list, I would like to change the background color of the cell is the value is below margin. When I export to excel, I need to cells to be red as well.

I see that the "this.list" control is available but the color did not change at all.


foreach(Sage.Common.Controls.ListItem r in this.list.Items)
{
if ((decimal)r.SubItems[7].Value <= 10)
{
r.SubItems[7].BackColor = System.Drawing.Color.Red;
}
}

I try adding the snippet on GetColumnsProtected(), on ItemRefresh event, ItemAdd event and many more. But seems like it did not change anything. Even my MessageBox.Show("testing"); is not triggered.

Is there any way to achieve this? Thank you.

  • 0

    Couple of problems here.

    Firstly - users can modify their desktop lists to add and remove columns, and drag the columns around. So you can't just hard-code a subitem index because that can change. You'll need to work out what the column index is when the desktop list loads. And handle what happens if a user removes that column.

    Secondly - you need to override the OnListFormated (yes, it's spelt incorrectly) method to change the colours.

    Here's an example. This is my inherited version of the Sales orders list and I'm going to highlight the date when it's more than a week old. I'll grab the column index (assuming the column is present) when the desktop loads. I'll refresh that value if the user modifies the list to add/remove columns (in the Column_ListChanged handler). The logic to deal with whether or not to change the colour is all in the OnListFormated override.

    protected int columnIndex = Int32.MinValue;
    
    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        this.list.Columns.ListChanged += Columns_ListChanged;
        GetTargetColumnIndex();
    }
    
    private void GetTargetColumnIndex()
    {
        columnIndex = Int32.MinValue;
        int? tmp = base.list.Columns
               .Cast<Sage.ObjectStore.Controls.ListColumn>()
               .FirstOrDefault(c => c.DisplayMember == "DocumentDate")?.Index;
        if (tmp.HasValue) columnIndex = tmp.Value;
    }
    
    private void Columns_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
    {
        if(e.ListChangedType == System.ComponentModel.ListChangedType.ItemAdded 
            || e.ListChangedType == System.ComponentModel.ListChangedType.ItemDeleted)
        {
            GetTargetColumnIndex();
        }
    }
    
    protected override void OnListFormated(ListItemFormatEventArgs e)
    {
        base.OnListFormated(e);
        if (columnIndex > Int32.MinValue && e.SubItem == columnIndex)
        {
            if (e.Item > -1 && e.Item < base.List.Items.Count)
            {
                ListItem listItem = base.list.Items[e.Item];
                if (listItem.DataSource is Sage.MMS.View.Data.SOP.SalesOrderView sopView
                    && sopView.DocumentDate < DateTime.Today.AddDays(-7))
                {
                    ListSubItem cell = base.list.Items[e.Item].SubItems[e.SubItem];
                    cell.ForeColor = System.Drawing.Color.Black;
                    cell.BackColor = System.Drawing.Color.Red;
                }
            }
        }
    }

    As for exporting to Excel in a way which uses the desktop colours - that's not going to be easy. It's possible to write your own version of the exporter and get the list to use that (I've done it before) but it's a lot of effort. I don't have time to go into that right now, but I'll dig out some of my code which does this later on.

  • 0 in reply to Chris Burke

    There's a post on the developer forum about modifying the Excel output:  https://my.sage.co.uk/forums/default.aspx?g=posts&t=6888

  • 0 in reply to Geoff Turner

    Ok..let's join all of this up then.

    We'll start with a subclassed version of the ExcelIntegrationViaReflection object which the ExcelController uses internally. In here we can put the required business logic - so I'm sticking with my example of examining the DocumentDate. Because we won't know which column we're targeting until runtime we'll need a way to pass that in. This is my finished class:

     public class IntegrationViaReflection :
            Sage.Common.Integration.ExcelIntegrationViaReflection
    {
        private int target = Int32.MinValue;
        public void SetTargetColumn(int target)
        {
            this.target = target;
        }
    
        protected override void SetRowValues(object oSheet, int Row, int RowCount, object[,] Values)
        {
            if (this.target > Int32.MinValue)
            {
                for (int i = 0; i < RowCount; i++)
                {
                    var val = Values[i, target];
                    var dateTime = DateTime.ParseExact(val.ToString(),
                        "MM/dd/yyyy", System.Globalization.CultureInfo.InvariantCulture);
                    if (dateTime < DateTime.Today.AddDays(-7))
                    {
                        MakeCellRed(oSheet, (Row + i), target);
                    }                    
                }
            }
            base.SetRowValues(oSheet, Row, RowCount, Values);
        }
    
        private void MakeCellRed(object sheet, int row, int col)
        {
            if (col > Int32.MinValue)
            {
                object cell = this.GetProperty(sheet, "Cells", row, col + 1);
                object property = this.GetProperty(cell, "Interior");
                SetProperty(property, "ColorIndex", 3);
            }
        }
    
        private object GetProperty(object obj, string sProperty, object oValue1, object oValue2)
        {
            object[] args = new object[] { oValue1, oValue2 };
            return obj.GetType()
                .InvokeMember(sProperty, BindingFlags.GetProperty, null, obj, args);
        }
    
        private object GetProperty(object obj, string sProperty) =>
            obj.GetType()
                .InvokeMember(sProperty, BindingFlags.GetProperty, null, obj, null);
    
        private void SetProperty(object obj, string sProperty, object oValue)
        {
            object[] args = new object[] { oValue };
            obj.GetType()
                .InvokeMember(sProperty, BindingFlags.SetProperty, null, obj, args);
        }
    
    }

    Ok, now we'll create ourselves a new ExcelController to wrap that up in:

    public class ExcelController : Sage.ObjectStore.Controls.ListExcelController
    {
        private IntegrationViaReflection _excelIntegration = null;
        public ExcelController(List oList) : base(oList)
        {
            this._excelIntegration = new IntegrationViaReflection();
            this.GetType().BaseType.BaseType
                .GetField("_oExcel", BindingFlags.Instance | BindingFlags.NonPublic)
                .SetValue(this, this._excelIntegration);            
        }       
    
        public void SetTargetColumn(int target)
        {
            this._excelIntegration.SetTargetColumn(target);
        }
    }

    Then we can tell our desktop list that it's going to use our new ExcelController:

    protected int columnIndex = Int32.MinValue;
    protected ExcelController myExcelController = null;
    
    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        
        this.list.Columns.ListChanged += Columns_ListChanged;
    
        GetTargetColumnIndex();
    
        FieldInfo fi = this.list.GetType().BaseType.GetField("_oListExcelController", 
                BindingFlags.Instance | BindingFlags.NonPublic);
        if (fi != null)
        {
            myExcelController = new ExcelController(this.list);
            fi.SetValue(this.list, myExcelController);
            myExcelController.SetTargetColumn(columnIndex);
        }
    }
    
    private void GetTargetColumnIndex()
    {
        columnIndex = Int32.MinValue;
        int? tmp = base.list.Columns
           .Cast<Sage.ObjectStore.Controls.ListColumn>()
           .FirstOrDefault(c => c.DisplayMember == "DocumentDate")?.Index;
        if (tmp.HasValue) columnIndex = tmp.Value;
    }
    
    private void Columns_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
    {
        if (e.ListChangedType == System.ComponentModel.ListChangedType.ItemAdded
            || e.ListChangedType == System.ComponentModel.ListChangedType.ItemDeleted)
        {
            GetTargetColumnIndex();
            if(myExcelController != null)
            {
                myExcelController.SetTargetColumn(columnIndex);
            }
        }
    }
    
    protected override void OnListFormated(ListItemFormatEventArgs e)
    {
        if (columnIndex > Int32.MinValue && e.SubItem == columnIndex)
        {
            base.OnListFormated(e);
            if (e.Item > -1 && e.Item < base.List.Items.Count)
            {
                ListItem listItem = base.list.Items[e.Item];
                if (listItem.DataSource is Sage.MMS.View.Data.SOP.SalesOrderView sopView
                    && sopView.DocumentDate < DateTime.Today.AddDays(-7))
                {
                    ListSubItem cell = base.list.Items[e.Item].SubItems[e.SubItem];
                    cell.ForeColor = System.Drawing.Color.Black;
                    cell.BackColor = System.Drawing.Color.Red;
                }
            }
        }
    }

    And that works very nicely. Sledgehammer to crack a nut? Probably. Good academic exercise though.