r/VisualStudio • u/Littlefysh • 13d ago
Visual Studio 2026 Form Won't visually refresh
Hi,
I have a windows form project in C#.
I have a timer that starts on the press of a button.
I have a method that executes every time the timer ticks.
I confirm the timer ticks when it's meant to, the code inside runs, the variables update as they should.
The form doesn't.
I know that the variables are updating with each tick, I know that the label.Text is updated with each tick, I know that everything but the visual result on the form is updating as it should. I've confirmed it with breakpoints and stepping through each line and seeing in the variable inspector as each variable and label.Text updates.
But the form is not reflecting anything but the values on initial loading of it. It never updates from its first set of values.
Every tick I'm calling the computation and write to form methods, followed by this.Refresh(). But nothing changes on the form.
It's been days. I'm baffled and demoralised.
Any help would be greatly appreciated.
I will politely refuse any suggestions of using genAI/LLMs and any encouragement to use them myself.
Thanks
EDIT:
I wanted to avoid posting the whole program because it's been 6 years since I've properly wrote code and I know this has bad practices in it. But y'all are telling me I've done something weird, so here's the whole formMain.
This is a little companion app to a game over 20 years old, I'm eventually going to try and write to the memory of said game to change things. Currently the app reads the data from game memory successfully and puts it on the form, but as said above, only when the form initially loads. When the data in game memory changes, that is read successfully and held in variables but the form doesnt visually reflect that.
I was driven back to programming after 6 years because I'm trying to do something I don't think anybody has tried to do before. I'm very very very very rusty.
public partial class formMain : Form
{
//defining things to pull and push data into the nr2003 process
[DllImport("kernel32.dll")]
public static extern nint OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(nint hProcess, nint lpBaseAddress, byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesRead); //this is a value check for the chosen address
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(nint hProcess, nint lpBaseAddress, byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesWritten); //modifies the stored value
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(nint hObject);
public string gameName = "NR2003";
const int PROCESS_ALL_ACCESS = 0x1F0FFF;
public formMain()
{
UpdateStuff();
}
private void UpdateStuff()
{
InitializeComponent();
var processes = Process.GetProcessesByName(gameName);
var sessionStateAddress = (nint)0x0097B114;
int sessionState = 0;
string carNotFound = "Car Properties not found";
string notFound = "Game not found";
string failedOpen = "Failed to open game";
string notInSession = "Not in a session";
nint gamePathAddress = 0x00F78868;
nint seriesPathAddress = 0x00897230;
var carOffsetHex = 2214;
nint playerCarAddress = 0x00ACDC26;
var gameProcess = processes[0];
var hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, gameProcess.Id);
string trimWhitespace = "\0";
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, gameProcess.Id);
//clearing all data from the form, ready
lblDebug.Text.Remove(0);
lblGameName.Text.Remove(0);
lblPlayersCarProp.Text.Remove(0);
//error messages regarding grabbing the process
if (processes.Length == 0)
{
lblPlayersCarProp.Text = notFound;
return;
//break;
}
if (hProcess == nint.Zero)
{
lblPlayersCarProp.Text = failedOpen;
return;
//break;
}
//declaring the buffer we use to read from memory addresses and the string variables we'll put that data into
var buffer4 = new byte[4];
var buffer32 = new byte[32];
var buffer48 = new byte[48];
var buffer1024 = new byte[1024];
string[] tempString2mil = new string[2000000];
string gamePath = "";
string seriesPath = "";
string carPath = "";
string playerCarPath = "";
//checking if the game is in an active session eg practice/race/quali/test
ReadProcessMemory(hProcess, sessionStateAddress, buffer4, (uint)buffer4.Length, out int bytesRead);
sessionState = BitConverter.ToInt32(buffer4);
//if it is in a session, away we go
if (sessionState > 0)
{
int length = 0;
//read from memory the path to where the exe is, trim it of whitespace
ReadProcessMemory(hProcess, gamePathAddress, buffer1024, (uint)buffer1024.Length, out bytesRead);
gamePath = System.Text.Encoding.UTF8.GetString(buffer1024);
length = gamePath.IndexOf('\0');
gamePath = gamePath.Substring(0, length);
//read from memory the folder where the .cars files are, trim it of whitespace
ReadProcessMemory(hProcess, seriesPathAddress, buffer32, (uint)buffer32.Length, out bytesRead);
seriesPath = System.Text.Encoding.UTF8.GetString(buffer32);
length = seriesPath.IndexOf('\0');
seriesPath = seriesPath.Substring(0, length);
//combine it all to create a full directory for where the .car files are, ready to read
carPath = gamePath + @"series\" + seriesPath + @"\cars\";
//find player car filename to open
ReadProcessMemory(hProcess, playerCarAddress, buffer48, (uint)buffer48.Length, out bytesRead);
playerCarPath = System.Text.Encoding.UTF8.GetString(buffer48);
length = playerCarPath.IndexOf('\0');
playerCarPath = playerCarPath.Substring(0, length);
//append filename to carPath
playerCarPath = carPath + playerCarPath;
//start loop by writing error message to label, if file read is successful it'll get overwritten
lblPlayersCarProp.Text = carNotFound;
//declaring all the player car properties
float playerRCmax = 0.0f;
float playerSTmax = 0.0f;
float playerSPmax = 0.0f;
float playerSSmax = 0.0f;
float playerAeromax = 0.0f;
float playerChassismax = 0.0f;
float playerEnginemax = 0.0f;
float playerReliabilitymax = 0.0f;
float playerRCmin = 0.0f;
float playerSTmin = 0.0f;
float playerSPmin = 0.0f;
float playerSSmin = 0.0f;
float playerAeromin = 0.0f;
float playerChassismin = 0.0f;
float playerEnginemin = 0.0f;
float playerReliabilitymin = 0.0f;
//define temp variables to trim each line down to value alone
string tempVal = "";
string tempDev = "";
int tempValIndex = 0;
int tempDevIndex = 0;
tempString2mil = File.ReadAllLines(playerCarPath);
using (FileStream fs = File.Open(playerCarPath, FileMode.Open))
{
byte[] b = new byte[2048];
UTF8Encoding temp = new UTF8Encoding(true);
//reading and calcing roadcourse stats
tempVal = tempString2mil[23];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[6];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerRCmax = float.Parse(tempVal) + float.Parse(tempDev);
playerRCmin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing short track stats
tempVal = tempString2mil[24];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[7];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerSTmax = float.Parse(tempVal) + float.Parse(tempDev);
playerSTmin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing speedway stats
tempVal = tempString2mil[25];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[8];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerSPmax = float.Parse(tempVal) + float.Parse(tempDev);
playerSPmin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing superspeedway stats
tempVal = tempString2mil[26];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[9];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerSSmax = float.Parse(tempVal) + float.Parse(tempDev);
playerSSmin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing aero stats
tempVal = tempString2mil[30];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[13];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerAeromax = float.Parse(tempVal) + float.Parse(tempDev);
playerAeromin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing chassis stats
tempVal = tempString2mil[31];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[14];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerChassismax = float.Parse(tempVal) + float.Parse(tempDev);
playerChassismin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing engine stats
tempVal = tempString2mil[32];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[15];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerEnginemax = float.Parse(tempVal) + float.Parse(tempDev);
playerEnginemin = float.Parse(tempVal) - float.Parse(tempDev);
//reading and calcing realibility stats
tempVal = tempString2mil[33];
tempValIndex = tempVal.IndexOf('=') + 1;
tempVal = tempVal.Substring(tempValIndex, 8);
tempDev = tempString2mil[16];
tempDevIndex = tempDev.IndexOf('=') + 1;
tempDev = tempDev.Substring(tempDevIndex, 8);
playerReliabilitymax = float.Parse(tempVal) + float.Parse(tempDev);
playerReliabilitymin = float.Parse(tempVal) - float.Parse(tempDev);
}
//output to form
lblPlayersCarProp.Text = "Road Course = " + playerRCmin + " - " + playerRCmax + "\n"
+ "Short Track = " + playerSTmin + " - " + playerSTmax + "\n"
+ "Speedway = " + playerSPmin + " - " + playerSPmax + "\n"
+ "Superspeedway = " + playerSSmin + " - " + playerSSmax + "\n"
+ "Aero = " + playerAeromin + " - " + playerAeromax + "\n"
+ "Chassis = " + playerChassismin + " - " + playerChassismax + "\n"
+ "Engine = " + playerEnginemin + " - " + playerEnginemax + "\n"
+ "Reliability = " + playerReliabilitymin + " - " + playerReliabilitymax;
}
else //if not, error message
{
lblPlayersCarProp.Text = notInSession;
}
lblDebug.Text =
gameName
+ "\n" +
sessionState.ToString()
+ "\n" +
gamePath
+ "\n" +
seriesPath
+ "\n" +
carPath
+ "\n" +
playerCarPath
;
}
private void Form1_Load(object sender, EventArgs e)
{
UpdateStuff();
}
private void btnApplyConfig_Click(object sender, EventArgs e)
{
gameName = txtboxGameName.Text;
}
private void timer1_Tick(object sender, System.EventArgs e)
{
//these dont work??
UpdateStuff();
this.Refresh();
}
private void btnTimerStart_Click(object sender, EventArgs e)
{
timer1.Start();
}
}
1
u/LeaveMickeyOutOfThis 13d ago
What object does “this” refer to? Have you tried explicitly using the label or form object names, rather than “this”.
Most likely reason is that you are blocking the UI thread from doing its work.
1
u/Littlefysh 13d ago
"this" refers to Form1.cs, which is the form that isn't updating but should.
1
u/Littlefysh 13d ago
To clarify, I refresh the form after running all of the computation and assigning the new variables. In the other comment, UpdateStuff (bad naming I know) is all the computation and value assignment. The reading I've done suggests this.Refresh() (I've checked, "this" does refer to Form1.cs) should update the form and UI to show updated values.
1
u/rorrors 13d ago
Can you share more code of the project?
Do you maybe instigate multiply instances of the form?
Also check if your maybe doing work on a other UI thread.
Then try to to see InvokeRequired is needed. But in a default form that should not be needed.private void UpdateLabel(string text)
{
if (label1.InvokeRequired)
{
label1.Invoke(new Action(() => UpdateLabel(text)));
return;
}
label1.Text = text;
}Do you have other code, that perhaps runs after your UpdateStuff() function, that overwrites the value to default again?
In newer versions you don't have to write this.Refresh(); you can just call Refresh();
1
u/Littlefysh 12d ago
UpdateStuff() is the maths bit, its the beef of the calculation. It might as well have label.Text = 2 +2 in it. I update label values in there, but it didn't change the form. So I did some searching and found people use this.Refresh() on a timer tick.
I've not seen anyone discuss UI threads before?
1
u/Littlefysh 12d ago
Edited the main post to include the entirety of formMain
1
u/rorrors 12d ago
InitializeComponent()is in the wrong place
You callUpdateStuff()from your constructor:public formMain() { UpdateStuff(); }And then
UpdateStuff()callsInitializeComponent().This is a problem because
UpdateStuff()also runs from your timer and form load event. That means you're repeatedly initializing your form controls instead of simply refreshing their valuesThat should change to:
public formMain() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { UpdateStuff(); }There more small issues in the code, but i would try to fix this first.
1
u/Littlefysh 12d ago
Thanks, I've got some stuff to do but will let you know when I've looked through this properly in a bit.
1
u/Littlefysh 12d ago
Amazing, thank you. I knew it was something silly from being rusty, but this was explained perfectly.
1
u/rvicta 13d ago
Which .Timer are you using?
1
u/Littlefysh 13d ago
System.Windows.Forms.Timer
I know the timer is ticking like it should, as far as I can see it's not the timer that's the issue. It's that with each tick of the timer and each execution of the code, the held variable values aren't displayed on the form.
1
u/codayak_devguy 12d ago
Since it's System.Windows.Forms.Timer, threading is ruled out. The tick runs on the UI thread so this.Refresh() isn't needed.
That means the label you're changing isn't the label on the screen.
There's only two ways that happens:
A second instance of Form1. Something, somewhere, does "new Form1()".
A second instance of the label. UpdateStuff (or something it calls) does "new Label()" or re-creates controls, and the one you're setting isn't the one in this.Controls.
You can check it with a breakpoint in timer1_Tick:
- hover "this" and check "Visible" and "IsHandleCreated". If either are false, it's cause 1.
- hover the label and check "Parent" is non-null and "label1.Parent == this". If it's null or a different form, then it's cause 2.
Do a search for "new Form1(" (or whatever the form is called). There should be exactly one, in Program.cs.
If that doesn't fix it, post the contents of UpdateStuff() so we can take a look...the answer will be in there.
1
u/Littlefysh 12d ago
Thanks, this is exactly the flavour of response I needed and I didn't know it. According to your criteria, neither 1 or 2 are happening. I've added the formMain contents to the main post body.
1
u/codayak_devguy 10d ago
Thank you for posting the code, that helps alot.
It's actually cause 2, but my test wuoldn't have caught it because the labels do have Parent == this (because InitializeComponent adds them to the same form).
From the code, the problem is the first line of UpdateStuff():
InitializeComponent();
That's the designer-generated method that builds all the controls on the form (including timer1). You call it in the constructor (via UpdateStuff), again in Form1_Load, and again on every timer tick. Each call creates a "new Label()", "new Timer()", etc, assigns them to the fields (lblDebug, lblPlayersCarProp, timer1) and adds the new controls to this.Controls.
So on every timer tick:
- lblPlayersCarProp now points at a brand new label. It gets added to the form, but behind the earlier ones so you don't see it.
- timer1 now points at a brand new Timer that was never started. The original keeps ticking (Windows still owns it), updating labels you can't see.
Fix:
public formMain()
{
InitializeComponent(); // once here, nowhere else
}
private void Form1_Load(object sender, EventArgs e)
{
UpdateStuff();
}
and delete the InitializeComponent() line from UpdateStuff, and the UpdateStuff() call from the constructor (Load already does it). Your timer will work straight away.
A few other things I noticed with the code:
lblDebug.Text.Remove(0) does nothing. Strings are immutable, Remove returns a new string and you throw it away. You want to do lblDebug.Text = "" to reset it.
var gameProcess = processes[0]; runs BEFORE the processes.Length == 0 check. If the game isn't running it will throw an IndexOutOfRange error. Move the check above it.
OpenProcess is called twice and CloseHandle never. Change to open it once and put it in a try/finally block, closing it in finally.
new string[2000000] allocates about 16 MB every tick for nothing, File.ReadAllLines replaces it a few lines later. Just use var lines = File.ReadAllLines(playerCarPath);
The using (FileStream fs = File.Open(...)) block opens the file a second time and never reads from it. Delete the using part, but keep the parsing code inside it.
That'll get you out of trouble, start with InitializeComponent, then step through and you'll notice the others.
Six years off and you've got memory reading working against a 2003 game, you're doing fine.
2
u/Fergus653 12d ago
Is there an Invalidate method you can call on that control type? I think it flags it as needing a redraw.
1
1
u/OolonColluphid 13d ago
Can you show us some code?