I'm facing a strange issue with nested TabControls in WPF.
In my Home Window I need to put a TabControl inside another TabItem, in order to create different menu levels.
Since I need to have no default-selected TabItem, I'm using TabControl.Loaded event like so:
private void TabControl_Loaded(object sender, RoutedEventArgs e)
{
((TabControl)sender).SelectedItem = null;
}
The issue is: first TabItem of nested TabControl is not selectable, unless selecting the second one then clicking again on the first header.
I created a sample project to demonstrate the problem.
XAML:
<Window x:Class="TabControlTest.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:TabControlTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl Loaded="TabControl_Loaded">
<TabItem>
<TabItem.Header>
<Label Content="Menu 1" />
</TabItem.Header>
<Grid>
<TabControl Loaded="TabControl_Loaded">
<TabItem>
<TabItem.Header>
<Label Content="Sub-Menu 1" />
</TabItem.Header>
<Grid>
<Button Content="Button in subitem 1" Margin="82,131,553,89" />
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<Label Content="Sub-Menu 2" />
</TabItem.Header>
<Grid>
<Button Content="Button in subitem 2" Margin="429,69,206,151" />
</Grid>
</TabItem>
</TabControl>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<Label Content="Menu 2" />
</TabItem.Header>
<Grid>
<Button Content="Button in Hello 2" Height="NaN" Margin="274,65,54,237" Width="NaN" />
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
If I disable the Loaded event, there are no issues.
Any help would be appreciated.
EDIT: I uploaded the sample project here.
Instead of handling the Loaded event, I believe you can add an invisible TabItem to prevent the first "real" tab from being selected initially:
<TabControl>
<TabItem Visibility="Collapsed" />
<TabItem>
<TabItem.Header>
<Label Content="Menu 1" />
</TabItem.Header>
<Grid>
<TabControl>
<TabItem Header="" Visibility="Collapsed" />
<TabItem>
<TabItem.Header>
<Label Content="Sub-Menu 1" />
</TabItem.Header>
<Grid>
<Button Content="Button in subitem 1" Margin="82,131,553,89" />
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<Label Content="Sub-Menu 2" />
</TabItem.Header>
<Grid>
<Button Content="Button in subitem 2" Margin="429,69,206,151" />
</Grid>
</TabItem>
</TabControl>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<Label Content="Menu 2" />
</TabItem.Header>
<Grid>
<Button Content="Button in Hello 2" Height="NaN" Margin="274,65,54,237" Width="NaN" />
</Grid>
</TabItem>
</TabControl>
Another potential workaround would be to bind the TabControl to a source collection using the ItemsSource property.
You could handle window.contentrendered and set the selectedindex to -1 on the outer tabcontrol:
private void Window_ContentRendered(object sender, EventArgs e)
{
tc.SelectedIndex = -1;
}
I've named that tabcontrol tc, obviously:
<Grid>
<TabControl x:Name="tc">
<TabItem>
When I handle loaded on the outer tabcontrol, the same approach works.
private void tc_Loaded(object sender, RoutedEventArgs e)
{
tc.SelectedIndex = -1;
}
Related
I am trying to order a WPF form to dock in this order
Menu Strip
Tool Bar
Tab Control
StatusBar
So as you can see, StatusBar is at the bottom, Menu strip is at the top following with Tool Bar and Tab Control. Usually you could just dock to Fill with WinForms but its very complicated and I don't quite understand it when it comes to doing it in WPF, I was wondering if anyone could help me?
I've got them all to dock correctly, and strech horizontally but the Vertical is a bit messed up as the StatusBar is very high in height and the tabControl is a bit low in height, how cam I do it how WinForms does it?
<Window x:Class="Proxy_Scraper.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:Proxy_Scraper"
mc:Ignorable="d"
Title="MainWindow" Height="377.5" Width="811.578">
<Grid>
<DockPanel>
<Menu DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<MenuItem Header="MenuItem" Height="100" Width="100"/>
</Menu>
<ToolBar DockPanel.Dock="Top" HorizontalAlignment="Stretch"/>
<TabControl DockPanel.Dock="Top" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
</TabControl>
<StatusBar DockPanel.Dock="Bottom" HorizontalAlignment="Stretch">
<StatusBarItem>
<TextBlock>fff</TextBlock>
</StatusBarItem>
</StatusBar>
</DockPanel>
</Grid>
</Window>
If you set the LastChildFill attribute for the DockPanel to True, the child element you declare last will get all the remaining space.
So the right strategy would be to declare all the elements for which you want a fixed size first (the Menu, ToolBar & StatusBar) and the the last one (I presume the TabControl) will fill the remaining space:
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<MenuItem Header="MenuItem" Height="100" Width="100"/>
</Menu>
<ToolBar DockPanel.Dock="Top" HorizontalAlignment="Stretch"/>
<StatusBar DockPanel.Dock="Bottom" HorizontalAlignment="Stretch">
<StatusBarItem>
<TextBlock>fff</TextBlock>
</StatusBarItem>
</StatusBar>
<TabControl DockPanel.Dock="Top" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
</TabControl>
</DockPanel>
Note: for complex layout you can easily nest several DockPanels.
I have been looking for a way to implement a child tab control inside a tab control(sort of like multi level tab controls).
but haven't found any materials for that, i can implement dynamically that at run time in Xaml.cs though, but i want to define it into Window.Xaml only, so that i can embed my controls into it.
Figure Below is an sample of the ui.
Kindly Suggest me the way to do it.
This will work for you.
<TabControl>
<TabItem Header="Tab1">
<TabControl Margin="10">
<TabItem Header="Tab1">
<TabControl Margin="20">
<TabItem Header="Tab1">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<DockPanel >
<TextBlock Text="Name:" VerticalAlignment="Center" DockPanel.Dock="Left"></TextBlock>
<TextBox Text="Nitesh" Margin="10,0,0,0" DockPanel.Dock="Right"></TextBox>
</DockPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<Button Margin="5">OK</Button>
<Button Margin="5">Cancel</Button>
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Tab2"></TabItem>
</TabControl>
</TabItem>
<TabItem Header="Tab2"></TabItem>
</TabControl>
</TabItem>
<TabItem Header="Tab2"></TabItem>
</TabControl>
<Window x:Class="AutoMerge2013.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AutoMerge2013" Height="350" Width="525"
Style="{StaticResource VS2012WindowStyle}">
<TabControl x:Name="tbControl">
<TabItem Header="Main">
<Canvas>
<Button Name="btnOpen" Content="Open files" Canvas.Left="40" Canvas.Top="20" Width="98" Click="btnOpen_Click_1"/>
<Button Name="btnUpload" Content="Upload files" Canvas.Left="181" Canvas.Top="20" Width="98" Visibility="Hidden" Click="btnUpload_Click_1"/>
<Button Name="btnMerge" Content="AutoMerge" Canvas.Left="338" Canvas.Top="20" Width="98" Visibility="Hidden" Click="btnMerge_Click_1"/>
<StatusBar Canvas.Top="247" Width="499">
<TextBlock x:Name="txtInfo"></TextBlock>
<StatusBarItem HorizontalAlignment="Right">
<ResizeGrip />
</StatusBarItem>
</StatusBar>
</TabItem>
<TabItem Header="Original">
</TabItem>
</TabControl>
When I click the Original tab it throws an exeption:
An unhandled exception of type 'System.InvalidOperationException'
occurred in PresentationCore.dll
Additional information: The specified Visual is not a descendant of this Visual.
I use in my application Style="{StaticResource VS2012WindowStyle}"
Maybe it is an issue...
Can you give some advise how to avoid this exception and save my style ?
I am trying to add a context menu that has "Close" and "Close all but this" like in IE8.
This menu should be displayed when I click on the tab but not on tabitem.
How can i do this?
I believe you want the ContextMenu to appear only when user clicks on the Header of TabItem and not the content area of the TabControl.
If so, you can define a template for Header. See sample code below.
Note:
- The Context menu will appear only when you click on the text part (and not rest of the blank area) of the TabItem Header. If you want for the whole Tab Header area, you will need to modify the ControlTemplate for TabItem.
Sample Code:
<Window x:Class="WpfApplication4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="tabHeaderTemplate">
<ContentPresenter Width="Auto" Content="{TemplateBinding Content}">
<ContentPresenter.ContextMenu>
<ContextMenu>
<MenuItem Header="Close Tab" />
<MenuItem Header="Close Other Tabs" />
<Separator />
<MenuItem Header="New Tab" />
</ContextMenu>
</ContentPresenter.ContextMenu>
</ContentPresenter>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl>
<TabItem Header="Tab 1"
HeaderTemplate="{StaticResource tabHeaderTemplate}">
<Label>Data for first Tab goes here</Label>
</TabItem>
<TabItem Header="Tab 2"
HeaderTemplate="{StaticResource tabHeaderTemplate}">
<Label>Data for second Tab goes here</Label>
</TabItem>
<TabItem Header="Tab 3">
<Label>Data for third Tab goes here</Label>
</TabItem>
</TabControl>
</Grid>
</Window>
Is this what you need:
Code:
<TabControl Margin="28,25,57,38" Name="tabControl1">
<TabItem Header="tabItem1" Name="tabItem1">
<TabItem.ContextMenu>
<ContextMenu Name="ct1" >
<MenuItem Name="Item1" Header="Close"/>
<MenuItem Name="Item2" Header="CloseOtherThankThis" />
</ContextMenu>
</TabItem.ContextMenu>
<Grid>
<Label Margin="41,75,22,64" Name="label1">First Tab</Label>
</Grid>
</TabItem>
<TabItem Header="tabItem2" Name="tabItem2">
<TabItem.ContextMenu>
<ContextMenu Name="ct2">
<MenuItem Name="Item3" Header="Close"/>
<MenuItem Name="Item4" Header="CloseOtherThankThis" />
</ContextMenu>
</TabItem.ContextMenu>
</TabItem>
</TabControl>
Are you talking about the case in which there should be no duplicate context menu?
I'm trying to create a application that will be tabbed where each tab will have a button area and a view area.
Now each tab will essentially have the same layout just different things in the layout and I wanted to be able to reuse the same layout so that I won't have to change at many places ( it's just not good programming ). Can I accomplish this using resources or perhaps Styles.
Please supply a light code example if possible.
EDIT: I've decided to add an example of what I'm trying to do because I'm still not getting it.
Under each TabItem I'm trying to recreate this grid (It's a bit more complicated but you get the idea ):
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Margin="10"
BorderBrush="{StaticResource MediumColorBrush}"
CornerRadius="10"
BorderThickness="2"
Grid.Row="0">
<!-- First content goes here -->
</Border>
<Border Margin="10"
BorderBrush="{StaticResource MediumColorBrush}"
CornerRadius="10"
BorderThickness="2"
Grid.Row="1">
<!-- Second content goes here -->
</Border>
</Grid>
as you can see also the 2 borders are the same. Now I need to put some content placeholder where my comments are. I wan't to declare this Grid layout in a resource dictionary and then where I use it put seperate content into each border.
I may have alot of TabItems so repeating this code isn't a good idea and each Tab page will have different content in the 2 placeholders.
I'm able to use the
<ContentPresenter Content="{Binding}" />
thing but only for 1 content, what happens when there will be more.
Ingo,
Code is always available on MSDN. Check this: UserControl, Custom controls, DataTemplates.
Here are some examples of each approach. For sake of simplicity, let's assume the layout you want to replicate is one line of text with green foreground (in reality it may be really different, but you get the idea).
1. User Control
Xaml:
<UserControl x:Class="WpfApplication1.GreenTextUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock x:Name="txt" Foreground="Green"/>
</UserControl>
C#:
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class GreenTextUserControl : UserControl
{
public string Text
{
get { return txt.Text;}
set { txt.Text = value; }
}
public GreenTextUserControl()
{
InitializeComponent();
}
}
}
Tab control:
<TabControl>
<TabItem Header="Tab 1">
<loc:GreenTextUserControl Text="This is Tab 1"/>
</TabItem>
<TabItem Header="Tab 2">
<loc:GreenTextUserControl Text="This is Tab 2"/>
</TabItem>
<TabItem Header="Tab 3">
<loc:GreenTextUserControl Text="This is Tab 3"/>
</TabItem>
</TabControl>
2. Custom control
C#:
public class GreenTextBlock : TextBlock
{
public GreenTextBlock()
{
Foreground = Brushes.Green;
}
}
TabControl:
<TabControl>
<TabItem Header="Tab 1">
<loc:GreenTextBlock Text="This is Tab 1"/>
</TabItem>
<TabItem Header="Tab 2">
<loc:GreenTextBlock Text="This is Tab 2"/>
</TabItem>
<TabItem Header="Tab 3">
<loc:GreenTextBlock Text="This is Tab 3"/>
</TabItem>
</TabControl>
If your layout is more complex than textblock, custom controls also allows you to define it in XAML but it differs from UserControls.
3. DataTemplate
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<x:Array x:Key="GreenText" Type="{x:Type System:String}">
<System:String>This is Tab 1</System:String>
<System:String>This is Tab 2</System:String>
<System:String>This is Tab 3</System:String>
</x:Array>
<!--Tab item content data template-->
<DataTemplate x:Key="GreenTextTemplate">
<TextBlock Text="{Binding}" Foreground="Green"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{StaticResource GreenText}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="ContentTemplate" Value="{StaticResource GreenTextTemplate}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
That's it :). Hope it helps.
TabItem is a ContentControl which allows any child content, but also allows templating of the content, which is exactly what you're trying to do. You can use a DataTemplate like this to do your shared layout. ContentPresenter is the placeholder for the different content of each TabItem.
<DataTemplate x:Key="ButtonViewerTemplate">
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="OK"/>
<Button DockPanel.Dock="Bottom" Content="Cancel"/>
<Border Background="Aqua" BorderBrush="Red" BorderThickness="2" Padding="5">
<ContentPresenter Content="{Binding}" />
</Border>
</DockPanel>
</DataTemplate>
To use the template just set it to each TabItem's ContentTemplate. This works with anything derived from ContentControl.
<TabControl>
<TabItem ContentTemplate="{StaticResource ButtonViewerTemplate}" Header="Some Buttons">
<UniformGrid>
<Button Content="XXXXX"/>
<Button Content="XXXXX"/>
<Button Content="XXXXX"/>
<Button Content="XXXXX"/>
</UniformGrid>
</TabItem>
<TabItem ContentTemplate="{StaticResource ButtonViewerTemplate}" Header="All Blue">
<Border Background="Blue" MinHeight="50"/>
</TabItem>
<TabItem ContentTemplate="{StaticResource ButtonViewerTemplate}" Header="Image">
<Image Source="http://i.msdn.microsoft.com/Platform/Controls/StoMastheadMSDN/resources/logo_msdn.png"/>
</TabItem>
</TabControl>