Click here to Skip to main content
15,881,739 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have a application to run C# winform on Linux by mono. There is a problem with the Label control that is terribly flickering.

There are many controls ( 100 labels ) in a form. When I resize the form or when I try to update them, the labels will be update one by one. I tried some functions of double buffer to solve this problem, but the following function do not work on mono. Is there any other method to avoid the problem of label flickering?

.Net framework version : 4.7.2 Mono JIT compiler version : 5.12.0.226 Linux kernel version: 4.14.248
controlupdateslow.mp4 - Google Drive[^]

What I have tried:

Overriding the "CreateParam" function to set the WS_EX_COMPOSITED
The method can solve the problem on Windows. However, the flag is not implemented on the mono. so the method do not work on mono.

C#
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;

namespace LinuxTestTool
{

	public partial class Form1 : Form
	{
		bool isDoubleBuffer_CreateParam = false; //work at window, but not implemented on the mono
		bool isDoubleBuffer_SetStyle = false; // not work
		bool isDoubleBuffer_SetFormProperty = false; //not work
		bool isDoubleBuffer_SuspendLayout = true; // not work
		int m_Num = 1;

		public Form1()
		{
			InitializeComponent();

			// Set form property
			if ( isDoubleBuffer_SetFormProperty ) {
				this.DoubleBuffered = true;
			}

			// Set style
			if (isDoubleBuffer_SetStyle)
			{
				SetDoubleBuffer();
			}
			
		}

		// Button Rename
		private void button3_Click( object sender, EventArgs e )
		{
			if( isDoubleBuffer_SuspendLayout ) {
				this.SuspendLayout();
			}

			int i = 0;
			foreach( System.Windows.Forms.Control ctrl in this.Controls ) {
				if( ctrl is Label ) {
					ctrl.Text = ( i * m_Num ).ToString();
					i++;
				}
			}
			m_Num++;

			if( isDoubleBuffer_SuspendLayout ) {
				this.ResumeLayout();
			}
		}

		// Button Creater
		private void button1_Click( object sender, EventArgs e )
		{
			if( isDoubleBuffer_SuspendLayout ) {

				this.SuspendLayout();
			}

			int nLabelNum = 10;
			Control[] ctrl = new Control[ 100 ];
			for ( int j = 0; j < nLabelNum; j++ ) {
				for( int i = 0; i < nLabelNum; i++ ) {
					Label Label = new Label();
					Label.Height = 50;
					Label.Width = 50;
					Label.Text = String.Concat( i.ToString(), "///", j.ToString() );
					Label.Location = new Point( 50 * ( i ), 50 * ( j ) );
					Label.BackColor = Color.White;
					Label.BorderStyle = BorderStyle.FixedSingle;
					Label.Font = new Font( "Time New Romen", 11, FontStyle.Bold );
					ctrl[ i + 10 * j ] = Label;
					Label.BringToFront();
				}
			}
			this.Controls.AddRange( ctrl );

			if( isDoubleBuffer_SuspendLayout ) {
				this.ResumeLayout();
			}
		}

		// Button Hide
		private void button2_Click( object sender, EventArgs e )
		{
			if( isDoubleBuffer_SuspendLayout ) {
				this.SuspendLayout();
			}
            foreach (System.Windows.Forms.Control ctrl in this.Controls)
            {
                if (ctrl is Label)
                {
                    ctrl.Hide();
                }
            }

            if ( isDoubleBuffer_SuspendLayout ) {
				this.ResumeLayout();
			}
		}

		// Button Show
		private void button6_Click( object sender, EventArgs e )
		{
			if( isDoubleBuffer_SuspendLayout ) {
				this.SuspendLayout();
			}
            foreach (System.Windows.Forms.Control ctrl in this.Controls)
            {
                if (ctrl is Label)
                {
                    ctrl.Visible = true;
                }
            }
			if ( isDoubleBuffer_SuspendLayout ) {
				this.ResumeLayout();
			}
		}

		// Set style of double buffer --> not work
		void SetDoubleBuffer()
		{
			base.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true );
			base.UpdateStyles();
		}

		// Set the WS_EX_COMPOSITED --> work
		protected override CreateParams CreateParams
		{
			get
			{
				CreateParams cp = base.CreateParams;
				if( isDoubleBuffer_CreateParam == true ) {
					cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
				}
				else {
					cp.ExStyle &= ~0x02000000;  // Turn off WS_EX_COMPOSITED
				}
				return cp;
			}
		}
	}
}
Posted
Updated 7-Feb-23 19:17pm
v4

1 solution

No code posted, so not sure what you are doing exactly...'

I have not used Winform on Linux with mono.

You could try suspend/resume when updating the label:
C#
Label1.SuspendLayout();
Label1.Text = "new text here";
Label1.ResumeLayout();

or, this may or may not work on Linux:
C#
public static class ControlsExtension
{
    // ref: https://stackoverflow.com/a/42389596
    public static void SetDoubleBuffered(this Control control, bool doubleBuffered = true)
    {
        control
            .GetType()
            .GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance |
                                           System.Reflection.BindingFlags.NonPublic)
            ?.SetValue(control, doubleBuffered, null);
    }
}

UPDATE

As mentioned below, the alternative that I would use is a PictureBox control and draw directly on it.

Here is how I would do it:
C#
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        this.Resize += Form1_Resize;

        // Canvas is a PictureBox control
        Canvas.Paint += Canvas_Paint;
        Canvas.MouseClick += Canvas_MouseClick;
 
        Canvas.SetDoubleBuffered();
        Canvas.BackColor = Color.Gray;
        Canvas.BorderStyle = BorderStyle.FixedSingle;
        Canvas.Cursor = Cursors.Hand;
    }

    private bool showCells = true;

    private record Cell(int Row, int Column, string Text, bool Visible = true);

    private readonly List<Cell> cells = new();

    private readonly float cellWidth = 50f;
    private readonly float cellHeight = 50f;
    private readonly Font cellFont =  new Font( "Time New Roman", 8, FontStyle.Bold );

    private readonly StringFormat cellTextFormat = new StringFormat()
    {
        LineAlignment = StringAlignment.Center,
        Alignment = StringAlignment.Center
    };

    private readonly Color cellBackColor = Color.White;
    private readonly Color cellForeColor = Color.Black;
    private readonly Color cellBorderColor = Color.Black;

    private void Form1_Resize(object? sender, EventArgs e)
    {
        //Canvas.Invalidate();
    }

    private void Canvas_MouseClick(object? sender, MouseEventArgs e)
    {
        if (showCells)
            labSelected.Text = $@"{Math.Floor(e.X / cellWidth):N0},{Math.Floor(e.Y / cellHeight):N0}";
    }

    private void Canvas_Paint(object? sender, PaintEventArgs e)
    {
        Graphics gr = e.Graphics;

        foreach (Cell cell in cells)
        {
            RectangleF rect = new RectangleF(cell.Column * cellWidth,
                                             cell.Row * cellHeight,
                                             cellWidth,
                                             cellHeight);
        
            // background
            using SolidBrush backBrush = new SolidBrush(cellBackColor);
            gr.FillRectangle(backBrush, rect);

            // are we hiding the cells?
            if (!showCells)
                continue;

            // border
            using Pen pen = new Pen(cellBorderColor, 1);
            pen.Alignment = PenAlignment.Outset;
            gr.DrawRectangle(pen, rect);

            // text
            using SolidBrush foreBrush = new SolidBrush(cellForeColor);
            gr.DrawString(cell.Text, cellFont, foreBrush, rect,
                         cellTextFormat);
        }
    }

    private void OnAddClick(object sender, EventArgs e)
    {
        cells.Clear();
        
        FillCells();

        showCells = true;
        Canvas.Invalidate();
    }

    private void OnShowClick(object sender, EventArgs e)
    {
        if (showCells) return;

        showCells = true;
        Canvas.Invalidate();
    }

    private void OnHideClick(object sender, EventArgs e)
    {
        if (!showCells) return;

        showCells = false;
        Canvas.Invalidate();

        labSelected.Text = "None";
    }

    void FillCells()
    {
        int nLabelNum = 10;

        for (int j = 0; j < nLabelNum; j++)
        for (int i = 0; i < nLabelNum; i++)
            cells.Add(new Cell(j, i, $"{i} | {j}"));
    }
}

NOTES:
* Canvas is a PictureBox control.
* I am using the SetDoubleBuffered() extension method from above.
* I have added a Canvas_MouseClick to show how to get the selected cell.

Now the rendering is very performant and no flickering or rendering of cells is visible.
 
Share this answer
 
v2
Comments
yulin chang 7-Feb-23 19:46pm    
Hi Graeme_Grant,
I post my code, SuspendLayout and ResumeLayout doesn't work.
thanks for your reply.
Graeme_Grant 7-Feb-23 19:59pm    
You missed what I posted, don't set it on the form as it is not the form that you are modifying. Set it on the control itself that is being updated. Read the StackOverflow post where there is more information.
yulin chang 7-Feb-23 21:26pm    
I tried open DoubleBufferd for labels and form. but it doesn't work.
the test vedio as below,
https://drive.google.com/file/d/1ibu2VH8eh0L1FL9yiYoVhLyypgPkywfr/view
thanks.
Graeme_Grant 7-Feb-23 21:33pm    
That is not a flickering issue, that is a drawing/rendering issue, completely different.

What control is hosting those labels?
yulin chang 8-Feb-23 1:19am    
I modify my question, thanks!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900