How to do a "New Tab" tab

Docking/MDI for WPF Forum

Posted 15 years ago by David Mullin
Avatar
I want to implement a behavior like IE7 - where there is an empty DocumentWindow tab that, when clicked, creates a new document. The way that I have current done this is to add an empty DocumentWindow to my DockSite, and to trap the "RequestBringIntoView" event on this DocumentWindow. When that event fires, I close that empty DocumentWindow, create a new actual DocumentWindow (a derived class), and then add another empty DocumentWindow (so that tab is always last). Kludgy as hell, but the best way I could think of to do it.

Issue that I am having:
1) Sometimes, when closing a DocumentWindow or moving a DocumentWindow to another tab group, the empty DocumentWindow will end up the next one to get displayed, which then fires my RequestBringIntoView event - which is not at all desired.
2) The little dropdown arrow listing the documents ends up with an empty entry.
3) If the user makes another tab group (via right-click "New Vertical Tab Group"), I'd like each group to have its own empty tab.

Is there some easier way to do this that I just have not thought of? If there isn't an eaiser way, is there a less kludgy way?

David Mullin

Comments (16)

Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi David,

It seems like to do it right, it would have to be done internally to the control so that the things you mention are handled. However there are some points to consider.

VS style tabs (and thus Docking/MDI's) are different than say IE's tabs. VS doesn't have chevron tabs that appear on overflow and support scrolling. Rather there is an active files list.

Does a "new tab" tab make sense in this scenario? Or would a new small button on the right next to active files make more sense? Just asking opinion.

Alternatively Google Chrome seems to keep their plus sign next to the last tab and then they move it over as tabs are added. Something like that may look a little better, something with a non-tab UI but that fits in well next to tabs.

What are your thoughts?


Actipro Software Support

Posted 15 years ago by David Mullin
Avatar
I agree that, in the event that the tabs overflow, my scheme won't work very well. We didn't think of that.

When discussing this with my team, I suggested putting the plus button over next to the chevron and delete buttons, but they thought that having something next to the last tab was more natural (i.e., more what the user would expect).

Chrome's behavior sounds like a good compromise. The "add" button is in a more accessible place, but never gets shoved off.

Looking at the template for TabbedMdiContainer, it looks like I should be able to add my button there and fiddle with the docking properties or order or some such and get my button to always be the right place.

Does this sound accurate?

I'll experiment...

David
Posted 15 years ago by David Mullin
Avatar
I've managed to modify the TabbedMdiContainer template to include my button - however, I can't figure out any clean way to get it over next to the tabs. The tab control appears to cheat and paint outside of its area, so if I put it in a Grid (for instance), the right side of the last tab is truncated. I've tried wrapping the TabbedMdiTabPanel and my new button in a Grid and a StackPanel, but both result in the TabbedMdiTabPanel having it's right side cut off. If I used

Is there something that I'm missing? Some other idea?
Posted 15 years ago by David Mullin
Avatar
Just pinging to avoid falling through the cracks. Any ideas on the problem with the tab drawing funny?
Posted 15 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
We do still have this in our inbox list to look at in more detail, but have been catching up on support emails from over the holiday and some high priority dev tasks.


Actipro Software Support

Posted 14 years ago by Jim Strav
Avatar
I did the following:

var panels  = VisualTreeHelperHelper.FindAllChildren<ReverseMeasureDockPanel>(tabbedMdiContainer);

ReverseMeasureDockPanel panel= panels.Where(r => r is FrameworkElement).First(r => (r.Parent as FrameworkElement).Name == "tabContainer");

AddNewTabButton addButton = new AddNewTabButton();
addButton.Content = "+";
panel.Children.Add(addButton);
ReverseMeasureDockPanel.SetDock(addButton, Dock.Left);
My question is, can I rely on Actipro not changing their element name "tabContainer"?

[Modified at 02/02/2010 01:05 PM]

[Modified at 02/02/2010 01:06 PM]
Posted 14 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Jimmy,

I can't guarantee it would never change but we don't have any plans at this time to change it. Just be sure if you update to a new version to check that your code still works.


Actipro Software Support

Posted 14 years ago by Doug Beck
Avatar
What is the data type of "ReverseMeasureDockPanel"? Where does this code live? Is it in the "Loaded" event?
Posted 14 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
It inherits the standard WPF DockPanel and is in the ActiproSoftware.Windows.Controls.Docking.Primitives namespace. You use it just like a normal DockPanel.


Actipro Software Support

Posted 14 years ago by Doug Beck
Avatar
Now I'm trying to figure out VisualTreeHelperHelper. I know there's a VisualTreeHelper (not HelperHelper) but it doesn't have a method called FindAllChildren. Any idea what this is?
Posted 14 years ago by Doug Beck
Avatar
I found some code that will do the trick. Use this instead of VisualTreeHelperHelper.FindAllChilden.

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}

foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
Posted 14 years ago by Jim Strav
Avatar
Yes, you figured it out. The VisualTreeHelperHelper is my class that wraps the actual VisualTreeHelper to perform operations like the code you posted. Actually, I may have stole it from somewhere online...I don't remember.
Posted 10 years ago by Roger
Avatar

I aslo want to add + button in DockSite like google chrome.

I could not reach the answer from this discussion.

There are no ReverseMeasureDockPanel under TabbedMdiContainer.

How can I acheive the + button?

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

Hi Roger,

I just verified that there is a ReverseMeasureDockPanel within TabbedMdiContainer's template.  So you'd have to follow along with the thread to see what the other customers did.  Unfortunately we don't have a sample for an "add" button at this time but I will log the request for one.


Actipro Software Support

Posted 10 years ago by Doug Beck
Avatar

Here is a sample application that shows the concept. Just create a new project in Visual Studio and paste in the following XAML and cs code. Both the Add button at the bottom and the + button at the top will add a new document.  

Here is the XAML first. 

<Window x:Class="ActiProAddTab.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking"
        Title="MainWindow" Height="350" Width="525">
      <Grid>
         <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
         </Grid.RowDefinitions>
         <DockPanel x:Name="mainDockPanel" LastChildFill="True" Grid.Row="0" Margin="0" >
            <docking:DockSite x:Name="dockSite" >
               <docking:Workspace >
                  <docking:TabbedMdiHost x:Name="tabbedMdiHost" Background="LightGray" IsCloseButtonOnTab="False">
                     <docking:TabbedMdiContainer x:Name="documentContainer" >
                     </docking:TabbedMdiContainer>
                  </docking:TabbedMdiHost>
               </docking:Workspace>
            </docking:DockSite>
         </DockPanel>
         <StackPanel Orientation="Horizontal" Grid.Row="1">
            <Button x:Name="btnAdd" Content="Add..." PreviewMouseDown="btnAdd_MouseDown" />
         </StackPanel>
      </Grid>
</Window>

 And now the code behind.

public partial class MainWindow : Window
   {
      public MainWindow()
      {
         InitializeComponent();
         this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
      }

      private void MainWindow_Loaded(object sender, RoutedEventArgs e)
      {
         TextBlock text = new TextBlock();
         text.Text = "Hello, this is document number " + dockSite.DocumentWindows.Count.ToString();
         AddDocWindow(text, dockSite, "Document" + dockSite.DocumentWindows.Count.ToString());

         AddPlusButton();
      }

      public Grid CreateHeaderGrid(string strName, string strToolTip = "")
      {
         // Create the header
         Grid headerGrid = new Grid();
         headerGrid.ColumnDefinitions.Add(new ColumnDefinition());
         TextBlock titleText = new TextBlock();
         titleText.Text = strName;
         headerGrid.Children.Add(titleText);
         titleText.SetValue(Grid.ColumnProperty, 0);

         if (strToolTip != "")
         {
            TextBlock txtToolTip = new TextBlock();
            txtToolTip.Text = strToolTip;
            headerGrid.ToolTip = txtToolTip;
         }
         return (headerGrid);
      }

      private DocumentWindow AddWindow(string name, DockSite dockSite, Grid headerGrid)
      {
         var newDocWindow = new DocumentWindow(dockSite, null, name, null, null);
         newDocWindow.Header = headerGrid;
         newDocWindow.CanRaft = true;
         newDocWindow.CanClose = false;
         newDocWindow.Name = name.Replace(" ", "");
         newDocWindow.Activate();
         return newDocWindow;
      }

      public void AddDocWindow(Object control, DockSite dockSite, string name)
      {
         Grid newHeaderGrid = CreateHeaderGrid(name, "My happy tooltip");
         DocumentWindow docWin = AddWindow(name, dockSite, newHeaderGrid);
         docWin.Content = control;
      }

      private void btnAdd_MouseDown(object sender, MouseButtonEventArgs e)
      {
         TextBlock text = new TextBlock();
         text.Text = "Hello, this is document number " + dockSite.DocumentWindows.Count.ToString();
         AddDocWindow(text, dockSite, "Document" + dockSite.DocumentWindows.Count.ToString());
      }

      private void AddPlusButton()
      {
         var panels = FindVisualChildren<ReverseMeasureDockPanel>(documentContainer);

         ReverseMeasureDockPanel panel = panels.Where(r => r is FrameworkElement).First(r => (r.Parent as FrameworkElement).Name == "tabContainer");

         Button addButton = new Button();
         addButton.PreviewMouseDown += new MouseButtonEventHandler(addButton_PreviewMouseDown);
         addButton.Content = "+";
         panel.Children.Add(addButton);
         ReverseMeasureDockPanel.SetDock(addButton, Dock.Left);
      }

      private void addButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
      {
         TextBlock text = new TextBlock();
         text.Text = "Hello, this is document number " + dockSite.DocumentWindows.Count.ToString();
         AddDocWindow(text, dockSite, "Document" + dockSite.DocumentWindows.Count.ToString());
      }

      public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
      {
         if (depObj != null)
         {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
               DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
               if (child != null && child is T)
               {
                  yield return (T)child;
               }

               foreach (T childOfChild in FindVisualChildren<T>(child))
               {
                  yield return childOfChild;
               }
            }
         }
      }
   }
Posted 10 years ago by Doug Beck
Avatar

I ran into another problem because the child of an MdiHost isn't always a TabbedMdiContainer. In this case, I search for a TabbedMdiContainer descendant. This code finds all TabbedMdiContainer descendants and I just use the first one I find.

 

UIElement element = mdiHost.Content as UIElement;
foreach (TabbedMdiContainer child in FindVisualChildren<TabbedMdiContainer>(element))
{

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.