I'm a bit beginner in WPF, so I ask this..
Let's say I have a window, and inside the window I want to have something like container, could be just border or maybe panel (in winform terms). The content of container is binded to the selected option (e.g:button). So, for instance, when user selects OPTION 1, the container shows chart; when user selects OPTION 2, the container shows listview filled with data; when user selects OPTION 3, the container shows another things, and so on.
What is the best/nicest (or easiest maybe) approach to do this? I'm thinking about using user control for the content of the container, but don't know if this is nice solution neither the performance for using user control to show little bit complex things and maybe some calculations. Any other idea guys?
To elaborate on #Sheridan's answer, here is a simple TabControl XAML that does what you need:
<TabControl TabStripPlacement="Left">
<TabItem Header="Option 1">
<Grid Background="DarkGray">
<TextBlock Foreground="AliceBlue" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="View 1"/>
</Grid>
</TabItem>
<TabItem Header="Option 2">
<Grid Background="DarkBlue">
<TextBlock Foreground="AliceBlue" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="View 2"/>
</Grid>
</TabItem>
<TabItem Header="Option 3">
<Grid Background="DarkSlateBlue">
<TextBlock Foreground="AliceBlue" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="View 3"/>
</Grid>
</TabItem>
</TabControl>
Result:
You can customize it a little bit by adding this simple Style To your Window.Resources:
<Window.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<RadioButton Content="{TemplateBinding Header}" Margin="2"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Which then results in:
The "WPF Mentality" makes you think the UI controls in terms of their functionality, not their appearance, this is a TabControl =)
I solved this with a ContentControl
MainWindow:
(Define the views you wish to visualize as resources)
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:SystemPCViewModel}">
<controls:SystemPCControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ChipPCViewModel}">
<controls:ChipPCControl/>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentView}"/>
</Grid>
ViewModel: (can't get much simpler)
public ViewModelBase CurrentView
{
get { return currentView; }
set { Set(() => CurrentView, ref currentView, value); }
}
And there you go, you can change your views by setting the view model for the controls you defined in your MainWindow
private void OnCommandExecuted()
{
CurrentView = someViewModel;
}
private void OnAnotherCommandExecuted()
{
CurrentView = anotherViewModel;
}
HTH!
What you are describing sounds pretty close to a standard TabControl, but with a ControlTemplate that puts the tabs on the left side instead of above the content panel. Using this method would mean having a UserControl in each TabItem, eg. multiple controls. You can find out more about the TabControl from the TabControl Class page at MSDN.
Related
I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.
You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}
If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.
For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>
I have a ListBox that presents a databound list of objects via its ItemSource. Because each object has special display needs I’m defining an ItemTemplateSelector that returns the appropriate DataTemplate depending on the object. That all works without a hitch.
The DataTemplates for each object follow a common formula, but contains custom elements in the middle. For example:
<DataTemplate x:Key="collectibleTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border BorderBrush="LightGray" BorderThickness="1">
<Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
<StackPanel>
<TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />
<!-- This is the only custom part of each template -->
<StackPanel Margin="0,10,5,0" Orientation="Horizontal">
<Label Content="Type:" />
<ComboBox Height="22" HorizontalAlignment="Left" SelectedItem="{Binding Path=CollectibleType, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource collectibleTypeFromEnum}}" />
</StackPanel>
<!-- End custom part -->
<StackPanel Margin="0,0,0,5">
<Label Content="Available Actions:" >
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding EditActions.Count}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<ItemsControl ItemsSource="{Binding EditActions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding}" Content="{Binding Title}" ToolTip="{Binding ToolTip}" Margin="5,0,5,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Expander>
</Border>
</Grid>
</DataTemplate>
As you can see there’s lots of shared XAML, wrapping a small custom section in the middle.
Additional data templates will be written by other engineers (they’ll want to create one for each new object type that they add), so I’m interested in making the creation of a new DataTemplate as fool-proof and painless as possible. No copying of the entire DataTemplate with the custom “stuff” added in the middle, of course – but I’m also not partial to extracting parts of the template as reusable parts and referencing them in because it still leads to lots of duplicate code in each new DataTemplate, and that means possible errors and hard maintainability. I.e., this right here is a more maintainable approach but still feels suboptimal:
<DataTemplate x:Key="collectibleTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border BorderBrush="LightGray" BorderThickness="1">
<Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
<StackPanel>
<TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />
<!-- This is the only custom part of each template -->
[...]
<!-- End custom part -->
<ContentPresenter Content="{StaticResource AvailableActions}" />
</StackPanel>
</Expander>
</Border>
</Grid>
</DataTemplate>
<StackPanel Margin="0,0,0,5" x:Key="AvailableActions" x:Shared="false">
<Label Content="Available Actions:" >
<Label.Style>
<!--
[Bottom half of shared XAML from the first example, offloaded here]
-->
</StackPanel>
So: what is my best strategy to solve this? AFAIK I’m stuck with using DataTemplates because that’s the only element that a ListBox ItemTemplateSelector accepts. Is there a way to create a compound DataTemplate in the DataTemplateSelector? I'd provide the stock DataTemplate that is shared by all objects, and the DataTemplateSelector references in the bit of custom XAML needed for each object type. Other engineers would hook into that generalized code behavior.
Not sure, fumbling a bit in the dark here as whether there is a pattern that allows me to solve this elegantly.
And, just for reference: my current DataTemplateSelector is super straightforward. This is where I would expect to construct the final DataTemplate, rather than simply returning one that's hardcoded in XAML.
public class NodeComponentDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is CollectibleComponent)
return element.FindResource("collectibleTemplate") as DataTemplate;
// [...]
}
}
}
You could create the DataTemplate dynamically using the XamlReader.Parse or XamlReader.Load method, e.g.:
string template = "<DataTemplate xmlns =\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x =\"http://schemas.microsoft.com/winfx/2006/xaml\"><StackPanel>[PLACEHOLDER]</StackPanel></DataTemplate>".Replace("[PLACEHOLDER]", "...custom code...");
return System.Windows.Markup.XamlReader.Parse(template) as DataTemplate;
The custom parts could be defined as UserControls.
I am afraid there is no way to base a DataTemplate on another one in pure XAML though.
You could create a new CustomControl that fits your needs. It will apply the style by itself and you can give additional DepdendencyProperties to make it more convinient. In the end you can still put it in a DataTemplate to use it with your DataTemplateSelector.
I've a problem to connect.
I started to connect my tabs with a tabcontrol.ressources and it worked to show the text of each tabs.
Then I wanted to had a scroll for my TabItems and it doesn't work, nothing shows in tab... I can't even use tabcontrol.ressources anymore...
<DockPanel>
<Button Background="DarkGoldenrod" Height="Auto" Command="{Binding OpenFlyoutDataCommand}">
<StackPanel>
<materialDesign:PackIcon Kind="ArrowRightBoldCircleOutline" Width="30" Height="30"/>
</StackPanel>
</Button>
<TabControl ItemsSource="{Binding TabEDCWaferData, Mode=TwoWay}"
SelectedItem="{Binding SelectedTabEDCWaferData}">
<!-- Used to create a scroolbar for tabitems -->
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<TabPanel Grid.Column="0" Grid.Row="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter ContentSource="..."/>
</Grid>
</ControlTemplate>
</TabControl.Template>
<!-- Contains the text in the tab item ! -->
<TabControl.Resources>
<DataTemplate DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</DockPanel>
This is connected to a collection of TabItem, where I've a function to add Items binding to an other button.
private ObservableCollection<TabItem> _TabEDCWaferData;
public ObservableCollection<TabItem> TabEDCWaferData
{
get { return _TabEDCWaferData; }
set
{
_TabEDCWaferData = value;
RaisePropertyChanged("TabEDCWaferData");
}
}
public void AddTabItem(string name)
{
TabItem tab = new TabItem();
tab.Header = name;
tab.Content = "Temporary content";
TabEDCWaferData.Add(tab);
}
I read that I have to use the ContentPresenter, but I don't know how to bind it. I think this is not working with TabItems...
I just want to bind it as I did in the Ressources by using the ContentPresenter.
I hope that I'm clear enough ! Thanks
EDIT : I try to display in the ContentPresenter the selected item tab content that I add in the function `AddTabItem.
With ContentPresenter, most times, this does the job:
<ContentPresenter />
The default ContentSource is "Content". That means it'll look at the Content property of the templated parent and it'll take whatever it finds there for its own content.
But that doesn't help you at all, and you don't have to use ContentPresenter; it's just a convenience. In this case, the content you want to present is SelectedItem.Content, which isn't a valid ContentSource for ContentPresenter. But you can do the same thing with a binding on a ContentControl instead:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="0"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden"
>
<TabPanel
Grid.Column="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentControl
Grid.Row="1"
Content="{Binding SelectedItem.Content, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Grid>
</ControlTemplate>
</TabControl.Template>
TemplateBinding isn't going to work with a Path such as "SelectedItem.Content"; it only accepts names of properties on the templated parent. I fixed your Grid.Row attributes, too.
Also, you may as well delete that DataTemplate for TabItem that you put in TabControl.Resources. That's not what DataTemplate is for; you use DataTemplates to define visual presentations for your viewmodel classes, but TabItem is a control. It already knows how to display itself, and in fact that DataTemplate is being ignored, so it's best not to leave it there; you'll only waste time later on making changes to it and trying to figure out why it's not having any effect. Your TabItems will display correctly without it.
Try something like this ?
<ContentPresenter Content="{TemplateBinding Content}" />
Edit
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
I have searched a lot on SO and didn't find answer to my problems.
I want to use TabControl with MVVM. Here is how I add TabControl to MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type models:PartnersViewModel}">
<views:PartnersView />
</DataTemplate>
<DataTemplate DataType="{x:Type models:ProjectsViewModel}">
<views:ProjectsView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<models:ApplicationViewModel/>
</Window.DataContext>
<TabControl ItemsSource="{Binding PageViewModels}" TabStripPlacement="Left">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
PageViewModels is ObservableCollection<IPageViewModel>.
IPageViewModel is simple interface with one property Name. There are 2 implementation of this interface PartnersViewModel and ProjectsViewModel.
public class ProjectsViewModel : IPageViewModel
{
public String Name
{
get { return "Projects"; }
}
}
public class PartnersViewModel : IPageViewModel
{
public String Name
{
get { return "Partners"; }
}
}
I want each tab to be displayed as ContentControl. Header text is taken from Name property. My ProjectsView and PartnersView looks like this:
<UserControl x:Class="WANIRPartners.Views.ProjectsView"
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" >
<Grid>
<Label Content="Projects Content" />
</Grid>
</UserControl>
Using this code, header and content in TabControl is exactly the same. 'Projects Content'/'Partners Content' are shown in tab headers and also(this is ok) in tab content. When I change <Label/> to <DataGrid/> tab headers contains datagrid (sic!).
How can I get this working properly. I mean how can I display headers as value of property Name and tab content as properly rendered <views:PartnersView /> or <views:ProjectsView /> depending on what is in PageViewModels.
your code should work, dont have a IDE atm. you can also use Snoop to check your bindings at runtime. i changed the ContentTemplate to:
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding .}" />
</DataTemplate>
</TabControl.ContentTemplate>
and it works. but even your code works in my testapp.
I have discovered solution for Elysium. Adding fter adding code belowe everything work fine.
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="Header" Value="{Binding Name}"/>
</Style>
</TabControl.Resources>
The below is my code
<Window x:Class="ScrollTester.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>
Here I have set the datatemplate for the header of the expander I am using inside the stack Panel
<DataTemplate x:Key="titleText">
<Border Background="Green" Height="24">
<TextBlock Text="{Binding}"
Width="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type Expander}},
Path=ActualWidth}"/>
</Border>
</DataTemplate>
<Style TargetType="{x:Type Expander}">
<Setter Property="Margin" Value="1,3"/>
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="HeaderTemplate" Value="{StaticResource titleText}"/>
</Style>
</Window.Resources>
I have a scroll viewer for the stack panel
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Expander IsExpanded="True" x:Name="general" Header="General">
<StackPanel Orientation="Horizontal">
<GroupBox Header="Mode Control">
<StackPanel>
<CheckBox Content="RESET"/>
<CheckBox Content="MAKEUP" IsChecked="True"/>
<CheckBox Content="POWERDOWN"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Mode Control">
<StackPanel>
<CheckBox Content="RESET"/>
<CheckBox Content="MAKEUP" IsChecked="True"/>
<CheckBox Content="POWERDOWN"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Mode Control">
<StackPanel>
<CheckBox Content="RESET"/>
<CheckBox Content="MAKEUP" IsChecked="True"/>
<CheckBox Content="POWERDOWN"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Mode Control">
<StackPanel>
<CheckBox Content="RESET"/>
<CheckBox Content="MAKEUP" IsChecked="True"/>
<CheckBox Content="POWERDOWN"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Mode Control">
<StackPanel>
<CheckBox Content="RESET"/>
<CheckBox Content="MAKEUP" IsChecked="True"/>
<CheckBox Content="POWERDOWN"/>
</StackPanel>
</GroupBox>
</StackPanel>
</Expander>
</StackPanel>
</ScrollViewer>
The problem here is the scroll bar is not working even when the stack panel's width is more.
I can understand why it happens,because of setting the width of the expander to its parent,so scroll bar is not working
But I want both the scroll bar working and enabled as well as the datatemplate with style for the header
Any one can give solution?
What you are trying to do won't work because the width of the expander's header also includes the button used to expand and collapse the control.
Think of it this way: the header area of the control contains two elements side-by-side, the expand/collapse button and the header "content". You are binding the width of the header content part (your Border element) to the width of the expander control, but this doesn't subtract the width of the expand/collapse button. Hence your layout breaks.
If the header content part sat behind the expand/collapse button then your approach would work, but unfortunately that's not how the default Expander control template is designed.
I'm just guessing, but it looks to me like you are simply trying to set the background colour of the expander. Unfortunately, again due to the default control template, this isn't possible:
if you set the Background property of the expander, this will change the colour of the header, but it will also change the colour of the content area which probably isn't what you want
putting another element in the header (such as your Border) and attempting to get it to "stretch" to the width of the control (by setting the HorizontalContentAlignment property to Stretch) won't work either, as the element into which the header is placed is hard-coded in the control template to be left-aligned.
As far as I know, the only way of accomplishing what you want is to write your own control template for the expander and use that instead. You should be able to find some examples of this here on SO or by Googling.