Hide a Tab in TabbedMdi?

Docking/MDI for WPF Forum

Posted 4 years ago by Brad Salmon
Version: 19.1.0684
Avatar

I have a TabbedMdiContainer with several DocumentWindows. I want to be able to "hide" one of the windows (and therefore its corresponding Tab). I first tried binding the Visibility property of the DocumentWindow and it caused the document window to hide but the tab was still there. Then I saw some documentation indicating we should never set the Visiblity property of a DocumentWindow and instead use the IsOpen property. When I do that, however, the tab/window never displays.

Here is very trivial example:

<Window xmlns:editors="http://schemas.actiprosoftware.com/winfx/xaml/editors" xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking" x:Class="UpgradeTesting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UpgradeTesting"
mc:Ignorable="d"
Title="Actipro Upgrade Testing" Height="200" Width="400">

<Window.Resources>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
</ResourceDictionary>
</Window.Resources>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0">
<Label Content="Show Tab 1?"/>
<CheckBox IsChecked="{Binding IsVisible, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/>
</StackPanel>

<docking:DockSite Grid.Row="1" Grid.Column="0" CanDocumentWindowsFloat="True">
<docking:Workspace>
<docking:TabbedMdiHost>
<docking:TabbedMdiContainer>
<docking:DocumentWindow Title="Tab One" IsOpen="{Binding IsTabOpen}">
<Label Content="Here is the content for Tab #1"/>
</docking:DocumentWindow>
<docking:DocumentWindow Title="Tab Two">
<Label Content="Here is the content for Tab #2"/>
</docking:DocumentWindow>
</docking:TabbedMdiContainer>
</docking:TabbedMdiHost>
</docking:Workspace>
</docking:DockSite>
</Grid>
</Window>

And the view model is:

public class MainViewModel : INotifyPropertyChanged
{
private bool _isVisible = true;
private bool _isTabOpen = true;

public event PropertyChangedEventHandler PropertyChanged;

public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsVisible"));
IsTabOpen = value;
}
}

public bool IsTabOpen
{
get { return _isTabOpen; }
set
{
_isTabOpen = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsTabOpen"));
}
}
}

[Modified 4 years ago]

Comments (5)

Posted 4 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Brad,

Correct, you should never modify the Visibility property and should bind to the IsOpen property instead.  Your example above won't work because you don't have any proper data context for the DocumentWindow to bind to.  You are statically defining the DocumentWindow in the example above instead of using MVVM.

If you look at our MVVM sample, you can see how we do bind to the IsOpen property in a Style.  We also bind to IsActive in the Style.  Then setting true to either of those properties on the related bound view-model property will open the document window.


Actipro Software Support

Posted 4 years ago by Brad Salmon
Avatar

So I made the change and my trivial example works. However, making the change in my actual application is not working.

I think I need to better understand how DataContext works for the DocumentWindow when "statically defined". We have 100's of views and many have statically defined DocumentWindows that worked fine and as expected in v2015. They seem to be working (i.e. displaying expected data) now in v2019 without me explicitly setting DataContext.

I took your advice to try and set the DataContext for one of these static DocumentWindows to the data context of the containing view but I then get binding errors at runtime indicating it can't find the view to get its DataContext.

It may be helpful to understand how we have structured our application. Is is complex and we have about 200 views. We have a single executable that essentially consists of a DockSite that binds to a collection for the DocumentWindows. Based on what the user wants to do we create the view/viewmodel and add to the collection so now a tab is shown in the DockSite. Each of our views are UserControls and use MVVM. Some of those views also have one or more DockSites and some of those have these static DocumentWindows defined.

In my example, I have set the x:Name of the view to "contractDetails". Then, in the static DocumentWindow I have:

DataContext="{Binding ElementName=contractDetails, Path=DataContext}"

Running in the debugger to see binding errors I get:

Cannot find source for binding with reference 'ElementName=contractDetails'. BindingExpression:Path=DataContext; DataItem=null

There is clearly some funky stuff happening with the document windows and timing of creation that I don't understand.

Posted 4 years ago by Brad Salmon
Avatar

P.S. - I replicated my Binding issue by making some simple changes to my sample app. I created a simple UserControl that contains a dockSite with static DocumentWindows. I then modified the DockSite in the main window to include a DocumentWindow that is an instance of the UserControl.

MainWindow.xaml:

<Window xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking"
           x:Class="UpgradeTesting.MainWindow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:local="clr-namespace:UpgradeTesting"
           mc:Ignorable="d"
           Title="Actipro Upgrade Testing" Height="400" Width="400">
<Grid>
      <docking:DockSite Grid.Row="0" Grid.Column="0" CanDocumentWindowsFloat="True" Margin="10">
            <docking:Workspace>
                  <docking:TabbedMdiHost>
                         <docking:TabbedMdiContainer >
                              <docking:DocumentWindow Title="Tab One" >
                                    <StackPanel Orientation="Horizontal">
                                           <Label Content="Here is the content for Tab #1" Target="{Binding ElementName=txtContent1}"/>
                                           <TextBox x:Name="txtContent1" />
                                    </StackPanel>
                               </docking:DocumentWindow>
                               <docking:DocumentWindow Title="Simple View Tab">
                                      <local:SimpleView/>
                               </docking:DocumentWindow>
                          </docking:TabbedMdiContainer>
                    </docking:TabbedMdiHost>
              </docking:Workspace>
         </docking:DockSite>
    </Grid>
</Window>

SimpleView.xaml:

<UserControl x:Class="UpgradeTesting.SimpleView"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking"
          mc:Ignorable="d"
          x:Name="simpleView"
         d:DesignHeight="250" d:DesignWidth="400">
<Grid>
       <docking:DockSite Grid.Row="0" Grid.Column="0" CanDocumentWindowsFloat="True" AreDocumentWindowsDestroyedOnClose="False" >
            <docking:Workspace>
                <docking:TabbedMdiHost>
                    <docking:TabbedMdiContainer >
                           <docking:DocumentWindow Title="Tab One" DataContext="{Binding ElementName=simpleView, Path=DataContext}">
                                 <StackPanel Orientation="Horizontal">
                                     <Label Content="Here is the content for Tab #1" />
                                     <TextBox x:Name="txtContent1" />
                                 </StackPanel>
                           </docking:DocumentWindow>
                           <docking:DocumentWindow Title="Tab Duece">
                                  <Label Content="Here is the content for Tab #2"/>
                           </docking:DocumentWindow>
                    </docking:TabbedMdiContainer>
               </docking:TabbedMdiHost>
          </docking:Workspace>
     </docking:DockSite>
    </Grid>
</UserControl>

When I run, I get the following message in my debug output:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=simpleView'. BindingExpression:Path=DataContext; DataItem=null; target element is 'DocumentWindow' (Name=''); target property is 'DataContext' (type 'Object')

NOTE:

I tried a few things and discovered that if I change the order of the DocumentWindows in the MainWindow the binding error doesn't happen. I don't know why the order of the DocumentWindows should matter...and the sure seems like a bug to me.

Posted 4 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Brad,

We tried to make DockSite work as closely to a normal ItemsControl as possible. 

Take a ListBox for example.  ListBox generate a "ListBoxItem" container (which inherits ContentControl) to wrap the items passed in via ItemSource.  The item becomes the ListBoxItem.Content and thus effectively the data context of the DataTemplate that is applied.  If you pass in a UIElement as an item instead, no container is generated, and the UIElement should inherit the data context of the parent. 

DockSite is the same where if you set DocumentItemsSource to a non-UI object like a view-model, it will generate a "DocumentWindow" container (which inherits ContainerControl) to wrap the items passed in.  The view-model item becomes the DocumentWindow.Content and thus effectively the data context of the DataTemplate that is applied.  If you pass in a UIElement as an item instead, no container is generated, and the UIElement should inherit the data context of the parent.

For proper MVVM implementation (same thing with any ItemsControl), you should be binding DockSite.DocumentItemsSource to your view-models.  Then use implicit DataTemplates or a DataTemplateSelector (via DockSite.DocumentItemTemplateSelector) to pick the proper DataTemplate for each view-model item.  Your individual DataTemplates could each pull in a proper UserControl for the view-model they target.

As for the binding error when statically defining DocumentWindows in XAML instead, we've been looking at your sample for a long time today and haven't narrowed down what is causing it.  We did notice that if you swap the outer DockSite to just a plain TabControl with two TabItems, we still see the same issue.  If we swap the inner DockSite for a TabControl, we don't see the issue.  We will have to play more with it tomorrow.


Actipro Software Support

Posted 4 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Brad,

If we use a regular TabControl in the outer Window (no DockSite) and have this XAML in the UserControl, we still get the issue:

<docking:DockSite>
	<docking:Workspace >
		<TextBlock Text="{Binding ElementName=simpleView, Path=DataContext, Mode=OneWay}" />
	</docking:Workspace>
</docking:DockSite>

After a bunch more testing to narrow down the problem area, the reason it's happening is that ElementName uses the logical tree to find named items.  The DockSite.Child (a Workspace in the simple example above) doesn't get injected into the visual tree until the DockSite's template is applied.  Since the DockSite is nested in a second tab and tab content only gets loaded into the visual tree when the tab is selected, the nested DockSite's template doesn't get applied until the containing tab is selected.  But in the meantime the DockSite.Child (a Workspace here) isn't in the logical tree to locate the ancestor named element.

What we've done is add a way to temporarily "park" the child content in the logical tree until it gets added in the visual tree at a later time.  That seems to resolve this ElementName issue.  Please write our support address and reference this thread if you'd like to help us test a preview build with the code update.


Actipro Software Support

The latest build of this product (v24.1.1) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.