Grid-like WPF control - c#

I'd like to implement a 'TabManager' control which can be instantiated much like a Grid in XAML. This looks like a fairly common task, but I couldn't seem to find a tutorial and I still don't want to reinvent the wheel, so I was wondering if anyone here had an example.
I'd want the XAML to generate the control to look something like this:
<TabManager>
<TabManager.TabDefinitions>
<TabDefinition Caption="Tab 1"/>
<TabDefinition Caption="Tab 2"/>
<TabDefinition Caption="Tab 3"/>
</TabManager.TabDefinitions>
<TabPanel TabManager.Tab="0">
<TextBlock Text="foo"/>
</TabPanel>
<TabPanel TabManager.Tab="1">
<TextBlock Text="bar"/>
</TabPanel>
<TabPanel TabManager.Tab="2">
<TextBlock Text="baz"/>
</TabPanel>
</TabManager>
The TabManager will render a few buttons and some elements to contain the TabPanels' children - e.g. place them into a DockPanel or whatever else I choose to do; shouldn't matter.

Have you tried <TabControl>?
For instance -
<TabControl>
<TabItem Header="Tab1" x:Name="Tab1">
<Grid>
<TextBlock Text="foo"/>
</Grid>
</TabItem>
<TabItem Header="Tab2" x:Name="Tab2">
<Grid>
<TextBlock Text="bar"/>
</Grid>
</TabItem>
<TabItem Header="Tab3" x:Name="Tab3">
<Grid>
<TextBlock Text="baz"/>
</Grid>
</TabItem>
</TabControl>

Related

Put tab headers in their own grid row?

I have a TabControl inside a Grid. Is there a way to assign the tab "headers area" to fill a particular grid row, and the tab content area to fill another?
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TabControl x:Name="tabControl">
<TabItem Header="TabItem">
</TabItem>
<TabItem Header="TabItem">
</TabItem>
</TabControl>
</Grid>
So like #xtreampb pointed out, you will need to make some modifications if you truly want to separate the functionality out.
HOWEVER, you do have multiple options to accomplish this if you don't want to dig through the control templates. For example, the last time I had to facilitate such a request I only had about 15 minutes to do it. So what I ended up doing was like the concept shown below and just made a RadioButton style to match the design of the "Tabs" they wanted.
Was quick and painless (under 5 minutes) and served the purpose at the time. Plus the app was really small and the owner's didn't care about much else than the design they wanted.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="20">
<RadioButton GroupName="Blah" Content="Blah1"
IsChecked="{Binding IsSelected, ElementName=Blah1}"/>
<RadioButton GroupName="Blah" Content="Blah2" Margin="10,0"
IsChecked="{Binding IsSelected, ElementName=Blah2}"/>
<RadioButton GroupName="Blah" Content="Blah3"
IsChecked="{Binding IsSelected, ElementName=Blah3}"/>
</StackPanel>
<TabControl Grid.Row="1">
<TabItem x:Name="Blah1" Visibility="Collapsed">
<TextBlock Text="Wow TAB 1 IsSelected!"/>
</TabItem>
<TabItem x:Name="Blah2" Visibility="Collapsed">
<TextBlock Text="Wow TAB 2 IsSelected!"/>
</TabItem>
<TabItem x:Name="Blah3" Visibility="Collapsed">
<TextBlock Text="Wow TAB 3 IsSelected!"/>
</TabItem>
</TabControl>
</Grid>
Hope this helps, cheers!

Reuse UserControl in TabControl with Various ViewModels

I have one user control that I would like to reuse with multiple ViewModels, all which implement the same interface. I would like to have these embedded in a TabControl.
Currently I can do this for a single instance but I am struggling to reuse my UserControl. For the single instance I can either bind the ViewModel in the UserContol's xaml or instantiate it in the code behind, however I can't figure out how to set this from a higher level.
Here is what I have,
<TabControl HorizontalAlignment="Left" Height="800" Margin="0,0,0,0" VerticalAlignment="Top" Width="600">
<TabItem Header="Tab1">
<Frame Source="SomeUserControl.xaml" BorderThickness="0" Margin="0" />
</TabItem>
</TabControl>
Here is pseudo code for what I would like to achieve,
<TabControl>
<TabItem Header="Tab1">
<Frame Source="{SomeUserControl.xaml, DataContext=ViewModel1}" />
</TabItem>
<TabItem Header="Tab2">
<Frame Source="{SomeUserControl.xaml, DataContext=ViewModel2}" />
</TabItem>
</TabControl>
Thanks!
Instead of hard coding the tabs, bind to an ObservableCollection of an "item" class. I.e. something like
class MyTabItems : INotifyPropertyChanged
{
public string Header...
public object DataContext...
}
create an:
ObservableCollection<MyTabItems>
Bind the TabControl ItemsSource to the ObservableCollection. In the item template for the TabControl bind the Header to the Header property and the DataContext to the DataContext property. The source can be hardcoded in the template or you can add another property to your item class and bind it to that if you want more flexibility.
Thanks to SledgeHammer's answer I was able to take a different approach and get things working.
<TabControl Name="TabControl" ItemsSource="{Binding TabItems}" HorizontalAlignment="Left" Height="800" Margin="0,0,0,0" VerticalAlignment="Top" Width="600">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl Content="{Binding DataContext}"/>
</DataTemplate>
</TabControl.ContentTemplate>
TabItems was populated in the code behind associated with the xaml above.
this.TabItems = new ObservableCollection<TabItem>
{
new TabItem("Main", new MainControl()),
new TabItem("Tab1", new GenericTabControl(new ViewModel1())),
new TabItem("Tab2", new GenericTabControl(new ViewModel2()))
};

WPF Expander overlay used in ItemsControl

I've been searching for some time but all solution I find only tackle parts of my issue.
I'd like to have an ItemsControl with each item containing an Expander. On expanding, the Expander's content should be shown as an overlay overlapping the other items in the ItemsControl and not moving them down.
The following XAML-Code does exactly as supposed to with one big issue: The Expander's content does not overlap the other items but is hidden behind them. I suppose this is due to the ZIndex as the following items in the ItemsControl are added after the Expander's content.
I managed to set one single Expander's ZIndex to 99 using style triggers but this seems to be a too complicated and error-prone solution. Any thoughts?
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<x:Array x:Key="items"
Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
<sys:String>Three</sys:String>
<sys:String>Four</sys:String>
</x:Array>
<DataTemplate x:Key="template">
<Grid Background="Red" Margin="0,0,0,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding}" />
<Canvas Height="25" Grid.Row="1">
<Expander ExpandDirection="Down" Header="Header" Grid.Row="1">
<Expander.Content>
<TextBlock Height="80" Text="Content" Background="Yellow" />
</Expander.Content>
</Expander>
</Canvas>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource items}"
ItemTemplate="{StaticResource template}"
HorizontalContentAlignment="Stretch">
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
It's going to be tricky to achieve this in the way you are attempting it. The problem is the nested structure -- since each Canvas is nested inside a Grid, you won't be able to control its z-index relative to other Canvas element. To illustrate this, here is a schematic of the visual tree created by your current markup:
<StackPanel> <!-- items panel -->
<ContentPresenter> <!-- item wrapper -->
<Grid>
<Canvas>
</Canvas>
</Grid>
</ContentPresenter>
<ContentPresenter> <!-- item wrapper -->
<Grid>
<Canvas>
</Canvas>
</Grid>
</ContentPresenter>
</StackPanel>
With reference to the above, your goal will be to have the Canvas elements appear in front of the siblings of its parent ContentPresenter. This is impossible in this hierarchy, because ZIndex only applies relative to siblings of the same parent element. Now, there might be ways you could massage the above into a flat structure so that you could then apply ZIndex your expanded content as needed.
However, I think an easier and more natural approach would be to use Popup elements for the expanded content. A Popup is a framework primitive that is located outside of the visual tree, and will always sit on top of your other content. You can use a ToggleButton or something similar to create the "expand" effect. For example:
<StackPanel Grid.Row="1">
<ToggleButton x:Name="PopupToggle" Content="Expand" />
<Popup IsOpen="{Binding IsChecked,ElementName=PopupToggle}">
<TextBlock Height="80" Text="Content" Background="Yellow" />
</Popup>
</StackPanel>

C# WPF Set Focus TabItem

I have a TabControl and i want to set the focus on the firsttab on a Textbox.
I tried to use in Code:
this.tabItemMerkmalAllgemein.Focus();
this.textBoxMerkmalBezeichnung.Focus();
If the second tab was selected the first Tab will be selected after this but no Cursor at the Textbox is shown, so the Textbox focus dosen't work.
XAML TabControle:
<TabControl Width="359" Grid.Column="1" Style="{StaticResource EditDialogTabControl}" Name="tabControlMerkmalDetails" Margin="4,0,0,0">
<TabItem Header="Allgemein" Name="tabItemMerkmalAllgemein" GotFocus="tabItemMerkmalAllgemein_GotFocus">
…
<Label Grid.Row="0" Style="{StaticResource EditDialogLabel}" Content="Bezeichnung"/>
<TextBox x:Name="textBoxMerkmalBezeichnung" Grid.Row="0" Grid.Column="1"
Style="{StaticResource EditDialogTextBox}" MaxWidth="300"
Text="{Binding Path=bezeichnung, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="textBoxMerkmalBezeichnung_TextChanged" LostFocus="textBoxMerkmalBezeichnung_LostFocus" />
…
</TabItem>
<TabItem Name="tabItemKostenpflichtig">
…
</TabItem>
</TabControl>
</Grid>
</TabItem>
You need to change your declaration of the TabItem to this:
<TabItem FocusManager.FocusedElement="{Binding ElementName=textBoxMerkmalBezeichnung}"
Header="Allgemein" Name="tabItemMerkmalAllgemein" GotFocus="tabItemMerkmalAllgemein_GotFocus">
The important part is the addition of the FocusManager.FocusedElement attached property. You can find out more from the FocusManager.FocusedElement Attached Property page at MSDN.

How to automatically size TabControl in a DockPanel - WPF

I have a simple WPF Forms app. I have a DockPanel as my root panel. The first child is a StackPanel that has some controls in it, then the second control is a TabControl. What I want, and the panel types can change all they want is for the TabControl to maintain the fill size of the window except for what the first StackPanel consumes. However no matter what I try the TabControl seems to change its size depending on whats inside it, not whats it is inside of.
<Window>
<DockPanel>
<StackPanel> </StackPanel>
<TabControl> </TabControl>
</DockPanel>
</Window>
Just set the HorizontalAlignment and VerticalAlignment properties of your TabControl to "Stretch":
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="5">
<TextBlock Text="Hello" />
<TextBlock Text="World" />
</StackPanel>
<TabControl HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabItem Header="Small">
<TextBlock Text="Just Some Small Stuff" />
</TabItem>
<TabItem Header="Bigger">
<StackPanel>
<TextBlock Text="One line" />
<TextBlock Text="The next line" />
</StackPanel>
</TabItem>
</TabControl>
</DockPanel>

Categories

Resources