Time Monitor example
There was a request for a Time Monitor app that would show splits and the lap time including delta's. Currently
LFS, shows you your splits and laps, but only briefly and without any information about how it compares to the previous lap. But with the LFS button system, custom HUD elements can now be created relatively simply. Really,
"Button" is a misnomer. It's really a
label control that can be set to
clickable.
I figured the time monitor would be a good example to use the RaceTracker class I wrote a couple of days ago and exercise the button system to display the data gathered by the tracker. The resulting app, is a relatively simple winforms sample (the UI exists purely for configuration) that produces LFS stats like this (click image for full screenshot):
The UI for the app itself just lets the user specify how to connect to LFS
The app is not meant for LFS servers, but as a local Aid for clients. It connects to a client instance and displays the buttons locally, according to the currently viewed player Id. I.e. it switches the hud as you tab through the players. It won't start displaying anything until it hits a first split (i.e. if you just connected and the viewed player has already past the first split, no statistics for the player will show up until the next split 1 pass.
The code is a Visual Studio Solution, created in VS 2005, although it should work fine in Visual Studio Express. It can be downloaded from here. It's written in C# and uses the LFSLib.NET library for InSim connectivity. It's not meant to be an App, and you won't get any support from me for the binary. You can run it as is, but it's meant to show how LFSLib.NET can be used for tracking and creating buttons. As such, this is example code and completely free of any license (LFSLib.NET is still governed by the GPL, though). Use it however you wish.
Labels: LFS, LFSLib.NET, live for speed, winforms
OutGauge Monitor Windows App example
I had a request for an example of Win Forms with OutGauge. This is a very simple project that illustrate how to quickly wire up output from OutGauge to UI. The only tricky bit for novices is the whole Invoke business. I.e. updates from outgauge come from it's listener thread. So you cannot use the event handler for outgauge to directly update your UI, or you'll get Cross-Thread exceptions. The solution is the common Invoke Required boilerplate:
private void outGaugeHandler_Updated(object sender, Gauge gauge)
{
// this code is required for Windows Forms, since our OutGauge update
// comes from another thread and we can only perform form updates on
// the UI thread
if (this.InvokeRequired)
{
BeginInvoke(new OutGaugeHandler.GaugeEvent(outGaugeHandler_Updated), sender, gauge);
return;
}
// now we update the form with the new data
// text boxes
timeTextBox.Text = gauge.Time.ToString();
carTextBox.Text = gauge.Car;
[...]
}
The resulting App, has a single window that refreshes OutGauge updates at the interval specified in the Live For Speed cfg.txt.
The Solution for this project is for Visual Studio 2005, but should work just fine in Visual C# Express. The zip of the project can be found here
Update: LFSLib.NET 0.14b has a bug where an OutGauge ID 0 zero would cause an exception. You can avoid this by using an OutGauge ID higher than 0 or wait for 0.15b to be released. Same thing is true for OutSim.
Labels: LFS, LFSLib.NET, live for speed, winforms
Cross-thread data access
Continuing on my recent asynchronous UI adventures, my background object tried to access the SelectedItem of a ComboBox and I came crashing down with the dreaded Cross-Thread exception. At this point most of my code has the
if(InvokeRequired) boilerplate down....
And every time i cut-and-paste that snippet and tweak if for the current method, I again wonder why the UI framework can't do some Auto-Invoking magic. But I digress.
...The scenario this time was one of data access to the plain, call BeginInvoke and return approach didn't apply. This was a scenario I hadn't yet seen. Unlike normal delegate BeginInvoke, you don't provide a callback for the EndInvoke. Control.BeginInvoke works differently, but really, it also works simpler. It's just that it's not really pointed out on MSDN how the Begin and End fit together in the scope of a Control. The other issue was that I was accessing a property, so there wasn't a simple delegate for invoking it. Instead I created a simple wrapper Accessor Method for ComboBoxen:
public delegate object GetSelectedItemDelegate(ComboBox control);
public object GetSelectedItem(ComboBox control)
{
if (this.InvokeRequired)
{
IAsyncResult ar = BeginInvoke(new GetSelectedItemDelegate(GetSelectedItem), control);
return EndInvoke(ar);
}
return control.SelectedItem;
}
Labels: BeginInvoke, cross-thread exception, winforms