3

I am progmatically adding tabs to a TabControl in my program. The contents of each tab are populated using a UserControl:

private void process_addTab(string head, string type)
{
    TabItem tab = new TabItem();
    tab.Header = head;

    switch (type)
    {
        case "trophy2": tab.Content = new Scoreboard2(); break;
        case "trophy4": tab.Content = new Scoreboard4(); break;
        case "text": tab.Content = new TextFields(); break;
        case "talk": tab.Content = new LowerThirds(); break;
        case "image": tab.Content = new ImageSelect(); break;
        case "timer": tab.Content = new Timer(); break;
        case "hitbox": tab.Content = new Hitbox(); break;
        case "twitch": tab.Content = new Twitch(); break;
        case "twitter": tab.Content = new Twitter(); break;
        case "ustream": tab.Content = new Ustream(); break;
    }

    tabControl.Items.Add(tab);
}

This works great. However, the issue comes along when I remove tabs from the TabControl. Each tab has a button in it to remove that specific tab from the control:

public static void RemoveClick(TabControl tabControl)
{
    if (MessageBox.Show("Are you sure you wish to remove this tab?", "Remove Tab",
        MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
    {
        TabItem item = (TabItem)tabControl.SelectedItem;
        tabControl.Items.Remove(tabControl.SelectedItem);
    }
}

This also seems to work well in that the tab is removed. However, it's a bit deceiving. In a few of the controls, I have timed functions running off a DispatcherTimer. For instance, the Twitch control has a timer within the control that polls the Twitch API every 30 seconds to get channel information. If I remove the tab, the timer still continues to run; even though it shouldn't exist anymore.

Any idea how to fix this? Pretend I don't know much about C#; because I don't.

Jason Axelrod
  • 7,155
  • 10
  • 50
  • 78
  • 1
    Why should the timer not exist anymore? The dispatcher owns a reference to the timer, so it's not going to be garbage collected, and the framework doesn't know why you created it, so it's not going to arbitrarily deactivate it. You'll have to disable it yourself. – Mike Strobel Nov 10 '14 at 16:28
  • The timer is inside of a user control, which is inside of a tabitem... which was removed. Shouldn't removing the tabitem remove the user control which should remove the timer? – Jason Axelrod Nov 10 '14 at 16:30
  • Why would it? All you're doing is removing the `UserControl` from the visual tree. That does not inherently cause it to "go away"; it just removes it from display. It's only truly "gone" from memory if there are no outstanding references to it, and the garbage collector has removed it. That won't happen for the timer, because the timer still has an active reference (in the `Dispatcher`). – Mike Strobel Nov 10 '14 at 16:37
  • Okay, so how do I dispose of the usercontrol and thus the dispatchertimer? – Jason Axelrod Nov 10 '14 at 16:37
  • 1
    You can set `timer.IsEnabled = false` when you remove the tab. Alternatively, you can manage its state by handling the tab content's `Loaded` and `Unloaded` events, and setting `IsEnabled` to `true` or `false`, respectively. – Mike Strobel Nov 10 '14 at 16:40

1 Answers1

1

In general, WPF does not dispose of User Controls when they are removed from the viewport. This causes trouble in some applications (see Disposing WPF User Controls for a broader discussion).

To solve your more specific problem (e.g. stopping the timer), handle the Unloading event on the TabItem and disable the timer there (see https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.tabitem).

Here is a high-level example (subclassing TabItem is just done as an example to encapsulate the timer):

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        TimerTabItem item = new TimerTabItem();
        item.Unloaded += ItemOnUnloaded;
        tabControl.Items.Add(item);
    }

    private void ItemOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    {
        (sender as TimerTabItem).Dispose();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        tabControl.Items.Remove(tabControl.Items[0]);
    }
}

class TimerTabItem : TabItem, IDisposable
{
    private DispatcherTimer MyTimer { get; set; }

    public TimerTabItem() : base()
    {
        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 0, 0, 3);
        timer.Tick += TimerOnTick;
        timer.Start();
        MyTimer = timer;
    }

    private void TimerOnTick(object sender, EventArgs eventArgs)
    {
        MessageBox.Show("Hello!");
    }

    #region Implementation of IDisposable

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public void Dispose()
    {
        MyTimer.Stop();
        MyTimer.Tick -= TimerOnTick;
        MyTimer = null;
    }

    #endregion
}
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Michael
  • 1,306
  • 1
  • 12
  • 30
  • Hmm... I was using my DispatcherTimer as an example. But there are other elements in the UserControl which I need to get rid of. Is there a quick and easy way to dispose of the entire contents of the UserControl? Also, DispatcherTimer does not have a Dispose function. – Jason Axelrod Nov 10 '14 at 16:47
  • On DispatcherTimer you can call the Stop() method (http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer(v=vs.110).aspx). For the other contents, you could implement IDisposable, write the tear-down logic and then call Dispose from the event handler. – Michael Nov 10 '14 at 16:50
  • Do you mind giving me an example of how to do all that? Also, I read somewhere that WPF doesn't support IDisposable. – Jason Axelrod Nov 10 '14 at 16:54
  • Just updated the example. WPF does not support IDisposable, you would still need to handle the Unloaded event and call Dispose yourself. – Michael Nov 10 '14 at 16:56