views:

27

answers:

3

I have a .NET 1.1. application which provides string resources through a bespoke translation system that looks a little like this:

interface ITranslationProvider 
{
    string GetTranslation(string key);
    event LanguageChangedEvent LanguageChanged;
}

ie: language can change at runtime, and components need to respond by updating display strings.

A single translation provider lasts the lifetime of the application, whereas Windows Forms components that consume translation services get created dynamically. If I write forms components that use this, when is the correct time to unsubscribe from the LanguageChanged event?

for example, it seems like overriding Disposing() should work:

class MyPanel : System.Windows.Forms.Panel 
{
    public MyPanel(ITranslationProvider translator) 
    {
        this.translator = translator;
        translator.LanguageChanged += new LanguageChangedEvent(SetText);
        SetText();
    }

    protected override void Dispose(bool disposing) 
    {
        base.Dispose(disposing);

        // is this the correct place to unregister? will Dispose() get 
        // called on this panel, even though the translator's event has 
        // a reference to it?
        translator.LanguageChanged -= new LanguageChangedEvent(SetText);
    }

    private void SetText() 
    {
        this.Text = translator.GetTranslation("my.panel.text");
    }

    private ITranslationProvider translator;
}

... but I can't find a definitive answer to whether this is safe or not. Any ideas?

+2  A: 

Your control will be Disposed when its parent form is disposed.

If you show the form by calling Show(), .Net will automatically dispose it when it's closed.
If you call ShowDialog(), you are responsible for disposing the form, presumably in a using block. (You should dispose the form in any case, even if it doesn't add event handlers)

SLaks
A: 

Personally I prefer to unsubscribe from external events in the HandleDestroyed event (with the OnHandleDestroyed override since I loath self-subscription). This doesn't rely on the user of my componant doing the right thing--calling Dispose if they used ShowDialog.

I also subscribe in the HandleCreated event because ShowDialog can be invoked multiple times (where Show cannot).

Tergiver
+1  A: 

Yes, using Disposing() is fine. If the client code messes this up so it doesn't get called then it has much bigger problems due to the handle leak.

Note that these kind of "reverse events" are awkward. If you know that the event source always out-lives the consumer then a callback can be to more appropriate solution. An example interface declaration:

public interface ITranslatableControl {
    void SetText();
}

public MyPanel : Panel, ITranslatableControl {
    public MyPanel() {
       TranslationManager.RegisterControl(this);
    }
    void SetText() {
       this.Text = TranslationManager.GetText(this, "mumble");
    }
}

public static class TranslationManager {
    private List<ITranslatableControl> controls;
    public void RegisterControl(ITranslatableControl text) {
       Control ctl = (Control)text;
       ctl.Disposed += delegate { controls.Remove(text); }
       controls.Add(text);
       text.SetText();    // optional
    }
}

Note how listening to the Disposed event allows the manager to automatically remove the control from the registered controls list. The client control can no longer mess this up by forgetting to override Disposing. On a language change, simply iterate the list and call the SetText() method. Also note that you now can register multiple callbacks for the same control, in case a control has more than one translatable string. Which now also allows you to specify the key for the string in the Register method and supply the translation as an argument for SetText(). Etcetera.

Hans Passant