I've got a form with a datagrid and a timer. I've create a resources CalculationSheet and translate in DUTCH - UK (default) - DUTCH
I start the application in DUTCH language. When I select a new record a messagebox pop-ups. It shows the correct language, dutch. I also set the timer.
When the timer elapse and shows the messagebox again the resources is shown in the default language.
Here is the main entry point for the application:
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
System.Threading.Thread.CurrentThread.CurrentUICulture =
new System.Globalization.CultureInfo("nl", true);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
And here is the callback code:
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// shows in UK
MessageBox.Show(Properties.Resources.CalculationSheet);
}
private void Form1_Load(object sender, EventArgs e)
{
List<CalculationSheet> calculationSheets = new List<CalculationSheet>();
calculationSheets.Add(new CalculationSheet("a"));
calculationSheets.Add(new CalculationSheet("b"));
calculationSheets.Add(new CalculationSheet("c"));
this.dataGridView1.DataSource = calculationSheets;
this.m_Timer = new System.Timers.Timer();
this.m_Timer.Enabled = false;
this.m_Timer.Interval = 5000;
this.m_Timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
this.dataGridView1.SelectionChanged += new System.EventHandler(this.dataGridView1_SelectionChanged);
}
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
// shows in DUTCH
MessageBox.Show(Properties.Resources.CalculationSheet);
this.m_Timer.Enabled = true;
}
The callback for the System.Timers.Timer
class executes the callback on a separate thread.
When setting the Application.CurrentCulture
property (or Thread.CurrentUICulture
property) manually, it doesn't flow the CultureInfo
to other threads that are created (CultureInfo
doesn't flow with ExecutionContext
across threads) which is why you're seeing this; the callback executes on another thread and the CultureInfo
is not set.
This test case shows that the CultureInfo.CurrentCulture
is not flowed to other threads (and therefore not in the callback of the Timer
):
[TestMethod]
public void TestApplicationCurrentCultureInOtherThreads()
{
// Create the timer.
using (var t = new System.Timers.Timer(1000))
{
// The task completion source.
var tcs = new TaskCompletionSource<object>();
// Captured name.
CultureInfo capturedCulture = null;
// Set the current culture.
// The default for the system needs to be something other
// than "en-GB", mine is "en-US", which is why this
// test passes.
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
// Copy t.
var tCopy = t;
// Event handler.
t.Elapsed += (s, e) => {
// Stop the timer.
tCopy.Stop();
// What's the captured name.
capturedCulture = CultureInfo.CurrentCulture;
// Complete the task.
tcs.SetResult(null);
};
// Start.
t.Start();
// Wait.
tcs.Task.Wait();
// Compare.
Assert.AreNotEqual(Thread.CurrentThread.CurrentUICulture,
capturedCulture);
}
}
The reason your callback doesn't fail in showing the MessageBox.Show
method is that it doesn't require a message loop (it provides one of its own) and can be called from any thread safely.
If possible, I'd store the CultureInfo
that you need in one spot in your application and then pass that through to whatever methods need it on other threads (methods such as String.Format
).
If it's not possible, you'll have to set Application.CurrentCulture
on every thread that needs it. Be careful though, if doing it on timer callback threads, these threads are from the thread pool, so you'll never know what the current culture on the thread pool threads is (because it is not reset).
That said, if you're doing UI work in these callbacks, then you should really marshal the call back to the UI thread (where the CultureInfo
is set) through a call to the Post
or Send
method on the SynchronizationContext
class (you can store the value from the Current
property called on the UI thread to call from other threads).
Your Form1_Load
method would store the SynchronizationContext
(it's set when the form is created):
private SynchronizationContext _synchronizationContext;
private void Form1_Load(object sender, EventArgs e)
{
// Capture the context.
_synchronizationContext = SynchronizationContext.Current;
// Rest of code.
...
}
And then your timer_Elapsed
callback would look like this:
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// Marshal back to the UI thread.
_synchronizationContext.Send(s => {
// Will now show in Dutch, as this call is taking place
// on the UI thread.
MessageBox.Show(Properties.Resources.CalculationSheet);
}, null);
}