1

I have a WPF application with a TabControl and several TabItems containing one UserControl each. Users can change entries in the usercontrol contained in the TabItem, for example configuring the application.

<TabControl>
  <TabItem Header="Configuration">
    <views:ConfigurationView x:Name="ConfigurationView_Object" />
  </TabItem>
  <TabItem Header="Artist">
    ...
  </TabItem>
</TabControl>

I have a function that checks, wheter there are unsaved changes in the UserControl. Before the user changes the tab or closes the application, I want to give him the option to either save, discard or stay on the tab.

Is that possible and if yes, how? If it needs some other controls/structures than the TabControl, that wouls also work, cause I'm currently in the planning stage ...

Thanks in advance,
Frank

Aaginor
  • 4,516
  • 11
  • 51
  • 75

4 Answers4

2

The TabControl doesn't have a TabChanging event. However, you can use the .Items.CurrentChanging event. This only works if you set IsSynchronizedWithCurrentItem="True" on the TabControl

XAML**

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height='1*' />
      <RowDefinition Height='Auto' />
    </Grid.RowDefinitions>
    <TabControl x:Name='MainTab'
                IsSynchronizedWithCurrentItem='True'
                Grid.Row='0'>
      <TabItem x:Name='TabTickets'
               Header='Tickets'>
        
          <StackPanel Orientation='Horizontal' >
            <TextBlock Text='Provide some text:'
                       Margin='10,0' />
            <TextBox x:Name='ExampleTextBox'
                     VerticalAlignment='Top' MinWidth='90' />
          </StackPanel>
       
      </TabItem>
      <TabItem x:Name='TabCalendar'
               Header='Calendar' />
      <TabItem x:Name='TabAbout'
               Header='About' />
    </TabControl>

    <TextBlock x:Name='MessageTextBox'
               Grid.Row='1' />

  </Grid>

Code

   public TabChangingWindow() {
      InitializeComponent();

      MainTab.Items.CurrentChanging += Items_CurrentChanging;
    }

    void Items_CurrentChanging(object sender,
                               System.ComponentModel.CurrentChangingEventArgs e) {
      if (e.IsCancelable)
      {
        var fromElement = ((ICollectionView)sender).CurrentItem as FrameworkElement;
        var toElement = MainTab.SelectedItem as FrameworkElement;
        if (fromElement!= null && toElement!= null)
        {
          if (ExampleTextBox.Text.Length == 0)
          {
            e.Cancel = true;
            MessageTextBox.Text = "Example Text cannot be blank.";
            MainTab.SelectedItem = fromElement;
          }
          else
          {
            MessageTextBox.Text = 
             String.Format("Changing from {0} to {1}", fromElement.Name, toElement.Name);
          }
         
        }
        
      }

    }

Screenshots

Prevent move to another tab

Prevent move to another tab when data is incomplete.


Allow move to another tab

Allow move to another tab when data is complete.

Community
  • 1
  • 1
Walt Ritscher
  • 6,977
  • 1
  • 28
  • 35
  • Thanks for the reply! The problem is, that this solution works as long as there is no user interaction during the change. (see http://stackoverflow.com/questions/30706758/how-to-cancel-tab-change-in-wpf-tabcontrol) But I wanted to ask the user, what he wants to do (save/dicard/stay) – Aaginor Apr 12 '17 at 12:04
  • Did you try the code? The user can't interact with any other parts of the UI, they get sent back to the original tab. If this isn't enough, you can use a messagebox to prompt the user and you can have buttons on the dialog to get information from the user. It's modal, the user won't be able to interact with the app until the messagebox is dismissed. – Walt Ritscher Apr 12 '17 at 21:04
  • How to display a dialog to the user and get results is a different question from the one you ask. See http://stackoverflow.com/questions/3830228/is-there-a-messagebox-equivalent-in-wpf – Walt Ritscher Apr 12 '17 at 21:18
0

You can use interactivity with any event.This is MVVM solution.

<TabControl>
   <TabItem Header="Configuration">
   <views:ConfigurationView x:Name="ConfigurationView_Object" />
    <intr:Interaction.Triggers>
         <intr:EventTrigger EventName="MouseUp">
               <intr:InvokeCommandAction Command="{Binding Yourcommand}" CommandParameter="YourCommandParameter"/>
               </intr:EventTrigger>
         </intr:Interaction.Triggers>

leapold
  • 133
  • 1
  • 10
  • xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" – leapold Apr 11 '17 at 17:28
  • Interesting possibility. I'm not sure if the mouseup event would be enough to cover all possible ways of changing the tab, and then I'd still need to prevent the tab from changing ... – Aaginor Apr 12 '17 at 12:07
0

If you want to do it via events, you can use SelectionChanged event for the TabControl and the Closing event for the Window. Something like this:

XAML:

<Window x:Class="Namespace.View"
        .....
        Closing="Window_Closing">
......

C#:

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    if (UnsavedChanges())
    {
        //save changes            
    }        
}

XAML:

<TabControl SelectionChanged="TabControl_SelectionChanged">
    <TabItem Header="Configuration">
    <views:ConfigurationView x:Name="ConfigurationView_Object" />
 </TabItem>
 <TabItem Header="Artist">
  ...
 </TabItem>
</TabControl>

C#:

private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (UnsavedChanges())
    {
        //save changes
    }
}
Daniel Marques
  • 683
  • 8
  • 17
  • Thanks for your reply! The point is, that I'd like to also enable the user to cancel the change, staying on the selected tab. From what I read on Stackoverflow about it (http://stackoverflow.com/questions/30706758/how-to-cancel-tab-change-in-wpf-tabcontrol) this seems to be rather complicated ... – Aaginor Apr 12 '17 at 11:56
0

I finally resourt to implement a simple tabcontrol myself, because then I have control over everything.

<WrapPanel x:Name="WrapPanel_Main"> <!-- This is the TabControl -->
  <Border x:Name="Border_Configuration" Margin="5,5,0,0" BorderThickness="4,4,4,0"> <!-- This is the first tab -->
    <TextBlock x:Name="TextBlock_Configuration" Text="Configuration" Padding="5" MouseLeftButtonUp="TextBlock_Step_MouseLeftButtonUp"/>
  </Border>
  <Border Margin="5,5,0,0" BorderThickness="4,4,4,0"> <!-- This is the second tab -->
    <TextBlock x:Name="TextBlock_Artists" Text="Artists" Padding="5" MouseLeftButtonUp="TextBlock_Step_MouseLeftButtonUp" />
  </Border>
  <Border Margin="5,5,0,0" BorderThickness="4,4,4,0"> <!-- This is the third tab -->
    <TextBlock x:Name="TextBlock_ReleaseGroups" Text="Release Groups" Padding="5" MouseLeftButtonUp="TextBlock_Step_MouseLeftButtonUp"/>
  </Border>
</WrapPanel>

<Border x:Name="Border_Placeholder" Grid.Row="1" Margin="5,0,5,5"> <!-- placeholder for the content of each tab -->
  <ContentControl x:Name="ContentControl_Placeholder" Grid.Row="1" Padding="5" />
</Border>

And here the handler that takes care of the Mouse-Up-Event of the "Tabs". I created an interface, each usercontrol that is used as a content for the tab has to implement. This allows the user control to inform the "Tab Control" about unsaved changes and to take appropriate (user choice) action. After that, it loads the new content and changes the appearance of the "Tab-Headers". The amount of more code this causes is in my opinion acceptable to the full control regarding tab changes.

private void TextBlock_Step_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  ICancelUnloading currentElement = ContentControl_Placeholder.Content as ICancelUnloading;
  if (currentElement != null)
  {
    if (currentElement.UnsavedChanges)
    {
      MessageBoxResult result = MessageBox.Show("Yes: Save, No: Discard, Cancel: Stay", "Unsaved Changes", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
      if (result == MessageBoxResult.Cancel)
        return;
      if (result == MessageBoxResult.Yes)
        currentElement.Save();
    }
  }

  TextBlock textBlock = sender as TextBlock;
  if (textBlock != null)
  {
    switch (textBlock.Name)
    {
      case "TextBlock_Configuration":
        ContentControl_Placeholder.Content = new ConfigurationView();
        break;
      case "TextBlock_Artists":
        ContentControl_Placeholder.Content = new ArtistsView();
        break;
      case "TextBlock_ReleaseGroups":
        ContentControl_Placeholder.Content = new ReleaseGroupsView();
        break;
    }

    ActivateTab(textBlock);
  }
}
Aaginor
  • 4,516
  • 11
  • 51
  • 75