TabControl inside a TabControl WPF - c#

I am trying to create horizontal tabs that load vertical tabs dynamically using WPF something like below. I want to create something similar, this is not a screenshot of my application.
It seems like I cannot create a tab control inside a tab item.
My XAML file has the following code where I am trying to add a TabControl inside the content of the tab:
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TabControl ItemsSource="{Binding steps}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
My C# code includes the following objects:
public ObservableCollection<TabItem> Tabs { get; set; }
public AddElement()
{
Steps step = new Steps();
step.display = "1";
Steps step2 = new Steps();
step.display = "2";
ObservableCollection<Steps> stepsList = new ObservableCollection<Steps>();
stepsList.Add(step);
stepsList.Add(step2);
Tabs = new ObservableCollection<TabItem>();
Tabs.Add(new TabItem { Header = "One", Content = "One's content", Steps = stepsList });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
public sealed class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
public ObservableCollection<Steps> Steps { get; set; }
}
public class Steps
{
public string display { get; set; }
}

There are multiple issues in your code, that you need to fix to make it work as intended.
There is a typo in the binding of the Steps property. It has to start with a capital letter.
<TabControl ItemsSource="{Binding Steps}">
Remove the last from the XAML or put it in a Grid like below. As it is, the code will not compile, as you can only set a single element as content in a DataTemplate.
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0" ItemsSource="{Binding steps}">
<!-- ...other code. -->
</TabControl>
<TextBlock Grid.Row="1" Text="{Binding Content}" />
</Grid>
</DataTemplate>
You bind to Header and Content properties on Steps items in XAML. This type does not have any of these properties, only display. You could introduce these properties and remove display.
public class Steps
{
public string Header { get; set; }
public string Content { get; set; }
}
In AddElement you only modify the step instance, not step2, so it will be empty. Initialize both, e.g. with the abovementioned additional properties.
Steps step = new Steps();
step.Header = "Header 1";
step.Content = "Content 1";
Steps step2 = new Steps();
step2.Header = "Header 2";
step2.Content = "Content 2";
If you want the tab strip of your inner TabControl to be displayed vertically on the left, you have to set the TabStripPlacement property accordingly.
<TabControl ItemsSource="{Binding Steps}" TabStripPlacement="Left">
The result will look like this with default WPF styles.

Related

WPF ListViewTemplate InvalidCastException: 'Unable to cast object of type 'System.Windows.Controls.ListView' to type 'System.Windows.DataTemplate'.'

I want to bind an ObservableCollection to a ListView.
class TestClass
{
public string attribute1 { get; set; }
public string attribute2 { get; set; }
public string attribute { get; set; }
}
static public ObservableCollection<TestClass> GetTestClassCollection()
{
SpecialObjects[] specialobject = Class.GetSpecialObjects();
ObservableCollection<TestClass> specialTestObjects = new ObservableCollection<TestClass>();
foreach (SpecialObject special in specialobject)
{
specialTestObjects.Add(new TestClass() { attribute1 = special.attribute1, attribute2 = special.attribute2, attribute3 = special.attribute3 });
}
return specialTestObjects;
}
My MainWindow.xaml
<!-- Data Template -->
<Window.Resources>
<ListView x:Key="ListViewTemplate">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding attribute1}" />
<TextBlock Text="{Binding attribute2}" />
<TextBlock Text="{Binding attribute3}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Window.Resources>
... and here the method content to create the ListView (it's called when you click a button)
ListView listView = new ListView();
listView.ItemsSource = TestClass.GetTestClassCollection();
listView.ItemTemplate = (DataTemplate)this.FindResource("ListViewTemplate");
mainGrid.Children.Add(listView);
As soon as I click the button the application crash:
Unable to cast object of type 'System.Windows.Controls.ListView' to type 'System.Windows.DataTemplate'.
I searched for some ListView templates reference, but they all seem pretty similar to mine. But obviously, something doesn't match.
The application crash on the line when I try to assign ItemTemplate.
Thanks!
The error message is pretty clear.
(DataTemplate)this.FindResource("ListViewTemplate")
requires that the resource is a DataTemplate, but actually it is a ListView.
The resource declaration should look like this:
<Window.Resources>
<DataTemplate x:Key="ListViewTemplate">
<StackPanel>
<TextBlock Text="{Binding attribute1}" />
<TextBlock Text="{Binding attribute2}" />
<TextBlock Text="{Binding attribute3}" />
</StackPanel>
</DataTemplate>
</Window.Resources>

Grid with usercontrols templates in TabControl

I'm new in WPF and try learn VMMV. I try to create TabControl with template for content in xaml.
I want tabitem with content of grid and in grid list of usercontrols. After adding a usercontrol the header of tabitem render right but nothing is in content. What is wrong?
This is my xaml:
<TabControl ItemsSource="{Binding Items}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Visibility="Hidden" Name="tcContent" Grid.Column="1" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Viewmodel:
public class Tab
{
public string Header { get; set; }
public ObservableCollection<UserControl> Content { get; set; }
}
public class MainWindowsViewModel
{
ObservableCollection<Tab> _items = new ObservableCollection<Tab>();
public ObservableCollection<Tab> Items
{
get
{
return _items;
}
}
}
Behind code for fill tabcontrol:
public MainWindow()
{
this.DataContext = new MainWindowsViewModel();
}
public void AddToTab(string header, UserControl c)
{
Tab tab = new Tab();
tab.Header = header;
tab.Content = new ObservableCollection<UserControl>();
tab.Content.Add(c);
((MainWindowsViewModel)this.DataContext).Items.Add(tab);
}
You are misunderstanding the MVVM principle.
In your view-model, you have a collection of the Tab objects, and each of them holds a collection of UserControls. In this way, your view-model contains some view elements (UserControls). In MVVM, you shouldn't do that.
Instead, you create the view-models for each tab item that describe a model of the representation (hence view-model); and in XAML, you describe how do these view-models should look like using DataTemplates.
But this is all required only if your views have to be dynamic. E.g. you don't know which data will be available because you're fetching them from a database.
If your TabItems display a set of UserControls that won't change, then just describe your view completely in XAML, without any DataTemplates.
Firstly, remove Visibility="Hidden" form the TabControl. Then change the ControlControl to ItemsControl which can hold Tab.Content, which is a collection. However, you should pay attention to the problems mentioned in #dymanoid's answer.
<TabControl.ContentTemplate>
<DataTemplate >
<ItemsControl ItemsSource="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>

WPF: Expander binding in Listview

I want to ListView with expander including some of information. So, I made this code. I'm not sure binding expander like that is correct. I just try to Binding like ListViewItem, But when I try to expander is not work at all. Here is my code.
XAML :
<Grid Grid.Row="2">
<ListView x:Name="lv">
<ListView.Template>
<ControlTemplate>
<HeaderedItemsControl>
<ItemsPresenter/>
</HeaderedItemsControl>
</ControlTemplate>
</ListView.Template>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:LogBase}">
<Expander Grid.Column="0" HorizontalAlignment="Center">
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"> <!-- why this code is not wokring...? -->
<TextBlock Text="{Binding No}"/>
<TextBlock Text="{Binding Timestamp}"/>
<TextBlock Text="{Binding Type}"/>
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
code behind :
public partial class MainWindow : Window
{
public List<LogBase> logs { get; set; }
public MainWindow()
{
InitializeComponent();
logs = new List<LogBase>();
logs.Add(new LogBase()
{
No = "1",
Timestamp = "123456789",
Type = "Tcp"
});
logs.Add(new LogBase()
{
No = "2",
Timestamp = "123456789",
Type = "Tcp"
});
logs.Add(new LogBase()
{
No = "3",
Timestamp = "123456789",
Type = "Tcp"
});
lv.ItemsSource = logs;
DataContext = this;
}
}
public class LogBase
{
public string No { get; set; }
public string Timestamp { get; set; }
public string Type { get; set; }
}
for better understanding I captured what I want to
Now my program's situation
If you have any of opinions please comment for me!
You also need to bind the header to set the DataContext of the HeaderTemplate correct. This is done by Header="{Binding HeaderSource}". In your case just use Header="{Binding}" to bind directly to the item:
<Expander Header="{Binding}>
After that your code works perfectly.

Cannot Select the whole surface of the tabHeader in WPF dynamically populated tabcontrol

In the above image:
You may note there is a little rectangle box around the tabHeader "title".
When I click inside the rectangle box, the tab does not get selected.
When I click outside the box, it does.
Code C#:
public class Lexicon : ObservableCollection<LexiconEntry>
{
public String leftLanguage { get; set; }
public String rightLanguage { get; set; }
public String name { get; set; }
public Lexicon(String name, String leftLanguage,String rightLanguage)
{
this.leftLanguage = leftLanguage;
this.rightLanguage = rightLanguage;
this.name = name;
}
}
public partial class MainWindow : System.Windows.Window
{
public List<Lexicon> lexicons;
public MainWindow()
{
InitializeComponent();
lexicons = new List<Lexicon>();
lexicons.Add(new Lexicon("foo_title","russian","french"));
lexicons.Add(new Lexicon("bar_title", "french", "english"));
lexicons.Add(new Lexicon("baz_title", "russian", "french"));
TheTabControl.ItemsSource = lexicons;
}
}
Xaml CODE:
<Window x:Class="InterpreterNotepad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:avalonDock="http://schemas.xceed.com/wpf/xaml/avalondock"
Title="InterpreterNotepad" WindowStartupLocation="CenterScreen" WindowState="Maximized" x:Name="mainWindow">
...
<DockPanel>
<Menu DockPanel.Dock="Top">
...
</Menu>
<TabControl x:Name="TheTabControl">
<TabControl.ItemTemplate>
<DataTemplate>
<TabItem Header="{Binding name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="bqr"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
Got it, Sorry for this newbie question, I'm new to WPF. Using snoop, I saw that my rectangle box was actually a tabItem inside a tabItem!
I thought the dataTemplate described the template to be repeated in the tabControl, while it describes the content of each tabItem.
I Need to put:
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
Instead of:
<TabControl.ItemTemplate>
<DataTemplate>
<TabItem Header="{Binding name}"/>
</DataTemplate>
</TabControl.ItemTemplate>

Data binding from multiple sources

I am trying to get data from two array lists into a window. The first array list contains images and the second array list contains names of movies.
The layout will show movie posters along with the names of the relevant movie underneath the poster. The code I am currently using doesn't seem to be working properly as I only get images showing in the window but no text.
This the current XAML code I have:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<WrapPanel Orientation="Vertical">
<Image Width="200" Height="300" Stretch="Fill" Source="{Binding}"/>
<TextBlock Text="{Binding movie_names}" HorizontalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="movie_grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ListView Grid.Row="1"
Name="MovieListView"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding Path = movie_posters_list}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<TextBlock Name="SampleTextBlock"
Text="{Binding Path=movie_names}"
DataContext="{StaticResource ItemTemplate}" />
</Grid>
</Window>
And here is the C# code behind:
public partial class MoviePanel : Window {
public MoviePanel() {
InitializeComponent();
}
List<ImageSource> movie_posters_list = new List<ImageSource>();
List<String> movie_names = new List<String>();
String regex_pattern = #"\\([\w ]+).(?:jpg|png)$";
public void LoadImages() {
//Image current_image;
String movie_poster_path = #"C:\Users\Vax\Desktop\movie_posters";
List<String> filenames = new List<String>(System.IO.Directory.EnumerateFiles(movie_poster_path, "*.jpg"));
foreach (String filename in filenames) {
this.movie_posters_list.Add(new BitmapImage(new Uri(filename)));
Match regex_match = Regex.Match(filename.Trim(), regex_pattern);
String matched_movie_name = regex_match.Groups[1].Value;
this.movie_names.Add(matched_movie_name);
}
MovieListView.ItemsSource = movie_posters_list;
MovieListView.DataContext = movie_names;
}
}
I think the issue stems from binding the data but I'm not sure what I've done wrong.
Create a model class and wrap your Movie Name and Image into it.
public class MovieDetail
{
public ImageSource Image { get; set; }
public string Name { get; set; }
}
When calling LoadImages, add instances of this model into ListView. And modify the datatemplate to bind the Name and Image.
List<MovieDetail> movies = new List<MovieDetail>();
foreach (String filename in filenames)
{
Match regex_match = Regex.Match(filename.Trim(), regex_pattern);
String matched_movie_name = regex_match.Groups[1].Value;
movies.Add(new MovieDetail { Image = new BitmapImage(new Uri(filename)), Name = matched_movie_name });
}
MovieListView.ItemsSource = movies;
The DataTemplate,
<DataTemplate x:Key="ItemTemplate">
<WrapPanel Orientation="Vertical">
<Image Width="200"
Height="300"
Stretch="Fill"
Source="{Binding Image}" />
<TextBlock Text="{Binding Name}"
HorizontalAlignment="Center" />
</WrapPanel>
</DataTemplate>

Categories

Resources