Click here to Skip to main content
15,918,976 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Here below is my code in which i try to update label controller which is directly placed inside a Form. I want it to be update after every single second. For that i suspend and resume the layout. But still it isn't updating any data on the screen.

What I have tried:

C#
private void Timer_Tick(object sender, EventArgs e, ParentPanel panel, int connector)
{
    GetSessionData(panel, TransactionId, connector);
    /*counter++;
    label1.Text = "Counter: " + counter.ToString();*/

}

public ChargingSession(ParentPanel panel, string ChargerId, int connector)
{

    this.Size = new Size(panel.Width, panel.Height);
    this.ConnectorId = connector;
    this.ChargerId = ChargerId;
    this.chargerData = ChargerInternalData.Instance;
    this.collection = CustomFont.GetInstance().pfc;
    this.TransactionId = getTransaction(connector);
    InitializeTimer(panel, connector);
    GetSessionData(panel, TransactionId, connector);
    DrawElements(panel, data);
    screensData.Attach(panel);

}

private async void GetSessionData(ParentPanel panel, int transactionId, int connectorId)
{
        if (data != null || data.ErrorCode == ErrorCodeEnum.NoError)
        {
            if (data.ChargerState == ChargerStateEnum.ChargingEV || data.ChargerState == ChargerStateEnum.Available || data.ChargerState == ChargerStateEnum.None)
            {
                // UpdateData(panel, data);
                SendMeterData(data, connectorId, transactionId);
                await Task.Delay(TimeSpan.FromSeconds(2));

                int voltage = Int32.Parse(sessionData.PresentVoltage);
                int current = Int32.Parse(sessionData.PresentCurrent);
                TimeSpan ts = TimeSpan.FromMilliseconds(Double.Parse(sessionData.ElapsedChargingTime));
                string timeDuration = ts.ToString("hh\\:mm");
                int power = (voltage * current) / 1000;
                double amount = Math.Round(power * 0.18, 2);

                int num = rnd.Next();

                PowerLabel.Text = num.ToString();
                TimerLabel.Text = DateTime.Now.ToString();
                PriceLabel.Text = amount.ToString();
                PowerLabel.Refresh();
                TimerLabel.Refresh();
                PriceLabel.Refresh();
                //  GetSessionData(panel, transactionId, connectorId);
            }

        }
        else
        {
            screensData.Notify(Event.ChargerEvents.ERROR, "Faulted Charger");
        }

}

private void DrawElements(ParentPanel pane, SessionDataParser sessionData)
{
    int voltage = Int32.Parse(sessionData.PresentVoltage);
    int current = Int32.Parse(sessionData.PresentCurrent);
    TimeSpan ts = TimeSpan.FromMilliseconds(Double.Parse(sessionData.ElapsedChargingTime));
    string timeDuration = ts.ToString("hh\\:mm");
    int power = (voltage * current) / 1000;
    double amount = Math.Round(power * 0.18, 2);
    Panel panel = new Panel();
    panel.Size = pane.Size;

    this.SuspendLayout();
    Image sparkCharge = Properties.Resources.Spark_Charge;
    pen = new Pen(_borderColor, penWidth);
    this.Size = new Size((int)(panel.Width * 0.82), (int)(panel.Height * 0.75));
    this.Location = new Point(25, 25);
    this.width = (int)((pane.Width) - panel.Width);
    this.height = (int)((pane.Height * 0.8) - panel.Height);
    this.Anchor = AnchorStyles.None;
    this.Top = (int)(panel.Width / 4.82);
    this.Left = (int)(panel.Width * 0.08);
    //  Attach(panel);


    var row3 = EnergyAndDurationPanel(mainPanel, panel, timeDuration, power);


    mainPanel.Controls.Add(row3);

    Controls.Add(mainPanel);
    this.ResumeLayout(false);
    this.PerformLayout();
}
Posted
Comments
Ralf Meier 22-Apr-24 18:43pm    
Have you ever checked with the Debugger if :
- GetSesionData is really called ?
- data is not Null ?
- what is data.Errorcode ?
- what is data.Chargerstate ?

Suspending the Layout isn't necessary - I suppose that one of those listed things is your problem ...

Just judging from what you've provided, it looks like you're doing all the work on the UI thread, and you're queuing up items via a timer. I suspect that one job isn't completing before another is started (how fast is your timer set to fire?). So the reason your form isn't updating is that the UI thread is too busy to update the controls because it's doing the other work you're requesting.

I don't know how familiar you are with windows forms and windows forms controls, so I'll explain this as if you don't know any of it.

In windows forms, all controls are created using one thread. We call this the UI (or user interface) thread. Your controls and your forms themselves can only be updated from the UI thread. Attempting to update them from a background thread will cause a "cross thread" exception. But that doesn't mean that we should do all our work on the UI thread, or that controls can't be updated with data created or pulled on a background thread. it just means we have to pass that data to the UI thread first, and that we always need to make sure that the UI thread is free to update the user interface when we ask it to.

So:

1.) Do as little work on the UI thread as possible, unless you are actively updating the user interface (your forms / controls). This will mean creating additional threads to handle the bulk of your work.

2.) When you have data that needs to be reflected in the UI, switch to the UI thread and update your controls. Here's an easy way to do that:

C#
// Set this to true in form_closing
private bool closing = false;

public void UI(Action action) {
    if (closing) return;

    if (InvokeRequired) {
        Invoke((MethodInvoker)delegate () { action(); });
    } else {
        action();
    }
}


You would use it like this:

C#
// We won't be able to use async here if we're running the code in another thread;
private void GetSessionData(ParentPanel panel, int transactionId, int connectorId)
{

        // Create a background thread to do the heavy lifting here:
        new Thread(()=>{
            if (data != null || data.ErrorCode == ErrorCodeEnum.NoError)
            {
                if (data.ChargerState == ChargerStateEnum.ChargingEV || data.ChargerState == ChargerStateEnum.Available || data.ChargerState == ChargerStateEnum.None)
                {
                     // UpdateData(panel, data);
                    SendMeterData(data, connectorId, transactionId);
                    
                    // Consider getting rid of this, and instead finding a better way to detect 
                    // when your operation is complete
                    Thread.Sleep(2000);
                    // again - not using async in this solution, we're using a background thread.
                    //await Task.Delay(TimeSpan.FromSeconds(2));

                    int voltage = Int32.Parse(sessionData.PresentVoltage);
                    int current = Int32.Parse(sessionData.PresentCurrent);
                    TimeSpan ts = TimeSpan.FromMilliseconds(Double.Parse(sessionData.ElapsedChargingTime));
                    string timeDuration = ts.ToString("hh\\:mm");
                    int power = (voltage * current) / 1000;
                    double amount = Math.Round(power * 0.18, 2);

                    int num = rnd.Next();

                    // now that we have some data to update a control with, pass that data to the UI thread:
                    UI(()=>{
                       PowerLabel.Text = num.ToString();
                       TimerLabel.Text = DateTime.Now.ToString();
                       PriceLabel.Text = amount.ToString();

                       // We won't need to do this anymore - it will just
                       // slow your application down.
                       //PowerLabel.Refresh();
                       //TimerLabel.Refresh();
                       //PriceLabel.Refresh();
                       //  GetSessionData(panel, transactionId, connectorId);
                    });
                }
            }
            else
            {
                // If this updates the UI, change it to:
                // UI(()=>screensData.Notify(Event.ChargerEvents.ERROR, "Faulted Charger"));
                screensData.Notify(Event.ChargerEvents.ERROR, "Faulted Charger");
            }

        }){ isBackground = true }.Start();

}


Handling it like this will shift all the work to background threads, leaving the UI free to update your controls.

- Pete
 
Share this answer
 
v3
Comments
Graeme_Grant 30-Apr-24 8:02am    
TAP[^] is the more modern way of doing the same thing.
pdoxtader 30-Apr-24 9:13am    
I'm aware. I was just trying to be as clear as possible, and it's easier to show how threading works this way then to explain TAP. And clearly, there is blocking happening on the UI thread. If there wasn't, the UI would be updating.
Graeme_Grant 30-Apr-24 9:18am    
Threads are not simple beyond the example given. TAP removes most of the complexity. You can still get TAP wrong if you do not understand it.
pdoxtader 30-Apr-24 11:00am    
I think we'll have to agree to disagree on TAP "removing most of the complexity". You still need to understand threading, locks, data synchronization, etc. Starting with Thread and learning about threading first is still the right thing to do - and that's what I was hoping would happen here.
Graeme_Grant 30-Apr-24 15:09pm    
I've done a lot of both, so I do talk from experience. You can write multi-threaded code like procedural code using TAP, Threads you can't. Your example above is a simple one, no different to Task.Run.

Look at the .Net Core source code. Look at the Microsoft documentation. They write all of the framework using TAP. Their docs also use TAP. When you create an app using the Microsoft templates, they use TAP. Show me the extensive use of Threads in the docs other than the Thread documentation itself. Show me a template from Microsoft that uses Threads. There is a reason that you can't.
After you set the Label controls' text, call Application.DoEvents();.  There's no need to call .Refresh() on the Label controls.

Warning:
The Form's message pump will respond to all events when executing Application.DoEvents();, including closing the Form!  So implement the Form.OnClosing() override to ensure the Form is closed only when it's OK to do so.

/ravi
 
Share this answer
 
v2

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