WPF Empty TabControl Content - wpf

I am using a TabControl to host workspaces, with the method outlined in This amazing article by John Smith. I was wondering if there is a way to add content, like an image, to the tab control when it has no tabs. Sort of a default or empty behavior. I would like to have the application logo, or maybe some helpful arrows a-la Chrome's first time use tab.
Edit: This might be a little more complicated. I tried Chad's solution below on a standard tabcontrol at it displayed. However, the tabcontrol I am using for the workspaces is being rendered by a content control using a datatemplate, and I couldn't get his solution working with it. HB's solution worked with some changes.
<DataTemplate x:Key="WorkspacesTemplate">
<Grid>
<Image Name="image1" Stretch="Uniform" Source="/Affinity;component/Images/affinity_logo.png" Margin="20"/>
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}" Margin="4">
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}"
Value="0">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<StackPanel x:Name="HeaderPanel"
Orientation="Horizontal"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost" Grid.Row="1"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</Grid>
</ControlTemplate>
</TabControl.Template>
</TabControl>
</Grid>
</DataTemplate>

You could overlay the TabControl on your Image and hide it if there are no items in it, e.g.
<Grid>
<Image />
<TabControl>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}"
Value="0">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
</TabControl>
</Grid>
Or you could swap the content of a ContentControl, also using Triggers, as in the above method both contols affect the layout. e.g.
<ContentControl>
<ContentControl.Resources>
<Image x:Key="Image"/>
<TabControl x:Key="TabControl" ItemsSource="{Binding Data}" />
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content" Value="{StaticResource TabControl}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}"
Value="0">
<Setter Property="Content" Value="{StaticResource Image}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Note that here the DataTrigger should bind directly to the same collection used in the TabControl. This is because if you bind to the TabControl.Items.Count this binding will break the moment the trigger fires as the TabControl will be unloaded.

You can add a background to the tab control. The XAML looks something like this:
<TabControl Height="290" HorizontalAlignment="Left" Margin="12,350,0,0" Name="TabControl" VerticalAlignment="Top" Width="481" TabIndex="200">
<TabControl.Background>
<ImageBrush ImageSource="/EffectsViewer;component/res/someimage.png" />
</TabControl.Background>
</TabControl>
Just be sure the image you use is in your resources. Its not to hard to add using the GUI in visual studio. As for adding controls, that might be a little more complicated. I think to do that you would need to either hide the control behind something while its empty, OR derive from it and implement it yourself.

Related

WPF Style ListBox with ItemStyle and DataTemplate

I'm using WPF 4.5.2 and .Net 4.7.2
My base style looks like this
<Style x:Key="MyListBoxItem" TargetType="{x:Type ListBoxItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="Black" BorderThickness="1,0,1,1">
<TextBox Text="{Binding MyText}" />
</Border>
<Border Grid.Column="1" BorderBrush="Black" BorderThickness="0,0,1,1">
<ContentPresenter />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
furthermore, there are several DataTemplates looking something like this
<DataTemplate x:Key="SomeDataTemplate">
<TextBox Text="{Binding SomeString}" x:Name="txtContent" Style="{DynamicResource MyStyle}" />
</DataTemplate>
I'm using a DataTemplateSelector class. Everything is recognized correctly, so there isn't any issue in terms of setting the ItemContainerStyle or the DataTemplateSelector.
But the style of the TextBox in the ControlTemplate of the ListBoxItem should be changed as well as the style the TextBox in the DataTemplate.
Is that possible or do I have move the ControlTemplate entirely to the DataTemplate?
I recommend to move all data related controls, i.e. controls that bind to the item's DataContext to the DataTemplate. Then use a DataTrigger to switch between different styles that targets the TextBox.
<DataTemplate x:Key="SomeDataTemplate">
<StackPanel>
<TextBox x:Name="TxtContent"
Style="{StaticResource DefaultStyle}" />
<TextBox x:Name="OtherTxtContent"
Style="{StaticResource OtherDefaultStyle}" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="True" >
<Setter TargetName="TxtContent" Property="Style" Value="{StaticResource AlternativeTextBoxStyle}" />
<Setter TargetName="OtherTxtContent" Property="Style" Value="{StaticResource OtherAlternativeTextBoxStyle}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

How to dynamically change the ControlTemplate based on a property in a ViewModel?

I want to select dynamically a ControlTemplate based on a Property of a ViewModel. How do I achieve it.
I have 2 ControlTemplates in the View, and a boolean property on a ViewModel. Based on that property, I have to select and display one of my ControlTempale in the View.
<Window.Resources>
<ControlTemplate x:Key="simpleErrorTemplate">
<TextBox Margin="10,10,10,5" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Text="T1" />
</ControlTemplate>
<ControlTemplate x:Key="detailedErrorTemplate">
<StackPanel>
<TextBox Margin="10,10,10,5" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Text="T2" />
<TextBox Margin="10,10,10,5" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Text="T3" />
<TextBox Margin="10,10,10,5" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Text="T4" />
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" DataContext="{Binding Report}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource simpleErrorTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsTyping}" Value="True">
<Setter Property="Template" Value="{StaticResource detailedErrorTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<CheckBox Margin="10,0,0,0" Grid.Row="1" x:Name="ChkShowDetails" IsChecked="{Binding IsTyping, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Show Details</CheckBox>
</Grid>
So based on the value of IsTyping, I want to display my ControlTemplate.
If I directly bind the element to the Control template, it will work, but this is not my requirement.
Bind the data trigger to the correct DataContext, i.e. the same that the CheckBox is bound to:
<DataTrigger Binding="{Binding DataContext.IsTyping, RelativeSource={RelativeSource AncestorType=ContentControl}}" Value="True">
<Setter Property="Template" Value="{StaticResource detailedErrorTemplate}"/>
</DataTrigger>
I think you can do something much simpler
What you need it to use the ContentControl's ContentTemplateSelector to achieve what you want.
The ContentTemplateSelector is a custom class that will switch the template depending on your data.
This will give you the idea : http://www.wpftutorial.net/datatemplates.html

Cannot show image as content of WPF ToggleButton

I'm working at this Point of time with VS 2015 and WPF.
Some weeks ago i had defined in the App.xaml of my project a templated style for the ToggleButton, so that users can see, if the button is checked or not.
Originally the Content of the togglebutton was a TextBlock with a text.
Now i want to replace that textblock by an Image.
Further i defined in the App.xaml some resources for the Images.
Here is the App.xaml:
<Application x:Class="WPFDesignerPrototyp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFDesignerPrototyp"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Style for togglebuttons-->
<Style x:Key="toggleButtonStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border x:Name="outer"
BorderBrush="Black"
BorderThickness="1"
Margin="5,5,0,0"
Opacity=".9"
Background="Transparent">
<Border x:Name="inner"
Margin="5"
BorderThickness="0"
Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}">
<Grid x:Name="container">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--<TextBlock x:Name="display"
Grid.Row="1"
TextAlignment="Center"
Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
Margin="5,2,5,2"/>-->
<Image x:Name="displayimage"
Grid.Row="1"
HorizontalAlignment="Center"
Source="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
Margin="5,2,5,2"/>
</Grid>
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="outer" Property="Background" Value="LightBlue"/>
<Setter TargetName="outer" Property="BorderBrush" Value="Transparent"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Image x:Key="imageHorizontalAlignmentLeft" Source="C:/Utilities/VS 2015 ImageLibrary/2015_VSIcon/AlignLeft/AlignLeft_16x.png"/>
<Image x:Key="imageHorizontalAlignmentCenter" Source="C:/Utilities/VS 2015 ImageLibrary/2015_VSIcon/AlignStretchHorizontal/AlignStretchHorizontal_16x.png"/>
<Image x:Key="imageHorizontalAlignmentRight" Source="C:/Utilities/VS 2015 ImageLibrary/2015_VSIcon/AlignRight/AlignRight_16x.png"/>
</Application.Resources>
</Application>
Then - in my WPF window - i implemented the new Content into one of my togglebuttons.
<ToggleButton x:Name="setAlignLeft" Grid.Row="1" Grid.Column="1" Content="{StaticResource imageHorizontalAlignmentLeft}" Style="{StaticResource toggleButtonStyle}" Click="setAlignLeft_Click"/>
But unfortunately nothing happens.
The Content of the button is blank.
So i tried it with another togglebutton, without the style toggleButtonStyle and there it worked.
I think there must be a mistake in my template but i don't know where.
Can anybody help?
Thanks in advance,
Patrick
You will need to bind with Content.Source of the parent as Content is already Image.
<Image x:Name="displayimage"
Grid.Row="1"
HorizontalAlignment="Center"
Source="{Binding Content.Source, RelativeSource={RelativeSource TemplatedParent}}"
Margin="5,2,5,2"/>

How to use WPF ContentControl 's content with DataTemplate

I copied some resource about custom button using Content control. And I changed something to be <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=Content}"> For the dataTempalte
<DataTemplate x:Key="PriceDataTemplate" DataType="m:ClickTradeViewModel">
<Button Command="{Binding ExecuteCommand}" Cursor="Hand">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="DarkGray" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#FF345C8B" />
</Trigger>
<DataTrigger Binding="{Binding IsExecuting}" Value="True">
<Setter Property="Background" Value="DimGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<UserControl>
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=Content}"></TextBlock>
</ControlTemplate>
</UserControl.Template>
</UserControl>
</Button>
</DataTemplate>
And for the actual Button, it used
<ContentControl x:Name="AskContentControl" Grid.Column="2"
Margin="5,0,0,0"
Content="{Binding QQ.Bid}"
ContentTemplate="{StaticResource PriceDataTemplate}"/>
I expect the Content will use double Bid's tostring method to render the content, but it shows nothing inside (gray color). In the plot the left side shows the price did exists.
Update: I am not sure what's going on, but with some change <TextBlock Text="{Binding QQ.Ask}"></TextBlock> and set
<ContentControl x:Name="AskContentControl" Grid.Column="2"
Margin="5,0,0,0"
Content="{Binding}"
ContentTemplate="{StaticResource PriceDataTemplate}"/> makes it work.
The problem is then I had to explicitly set the PriceDataTemplate several times for different properties.
It does not work because you are using a Binding with RelativeSource finding a ContentControl but UserControl is also a ContentControl, so what it found is actually the UserControl, not the root ContentControl you thought. In this case you can specify some AncestorLevel as 2 (to find the second ContentControl):
<TextBlock Text="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContentControl, AncestorLevel=2},
Path=Content}"></TextBlock>
However it's not really safe and in this case the implicit DataContext is actually the Content you set for your ContentControl (this DataContext flows down from the DataTemplate through the UserControl's Template). So the Binding can be just simple like this:
<TextBlock Text="{Binding}"></TextBlock>
Note I supposed you keep setting the ContentControl's Content to {Binding QQ.Bid}.
This is a full working solution... I'm late but perhaps it could help others?
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ParametricStudyAnalysis.ScopeSelection.Special"
xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid" x:Class="ParametricStudyAnalysis.ScopeSelection.Special.UserControlAddSpecialSignal"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:UserControlAddSpecialSignalModel></local:UserControlAddSpecialSignalModel>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:UserControlSpecialSignalTtrModel}">
<local:UserControlSpecialSignalTtr/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<GroupBox Header="Signal type" Grid.Row="0" Padding="5">
<xcdg:DataGridControl Name="DataGrid" SelectionMode="Single" ItemsSource="{Binding SpecialSignalEntries}"
SelectedItem="{Binding SpecialSignalEntrySelected}" Height="200">
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="Name" Title="Type of special signal" ReadOnly="True"></xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</GroupBox>
<GroupBox Header="Parameters" Grid.Row="1" Margin="0,3,0,0" Padding="5">
<ContentControl Name="MyContentControl"
DataContext="{Binding SpecialSignalEntrySelected, Mode=OneWay}"
Content="{Binding SignalProviderSpecial}">
</ContentControl>
</GroupBox>
</Grid>
</UserControl>

WPF Resource Styles not showing first time

When I bind my collection to the following window and usercontrol, the styles do not work.
When I press a button on the window, the styles kick in.
What is stopping my styles from firing at initial bind?
<Grid>
<ItemsControl Name="LbItems" ItemsSource="{Binding MyData}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type viewModel:SomeViewModel}">
<control:SomeView Margin="5" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:AnotherViewModel}">
<control:AnotherView Margin="5" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Grid>
And I have a user control as follows:
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static
RelativeSource.Self}, Path=DataContext.Selected}" Value="False">
<Setter Property="Foreground">
<Setter.Value>
Red
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={x:Static
RelativeSource.Self}, Path=DataContext.Selected}" Value="True">
<Setter Property="Foreground">
<Setter.Value>
DarkSeaGreen
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding SelectCommand}" Content="+" HorizontalAlignment=
"Left" VerticalAlignment="Top" Width="25" Grid.Column="0"/>
<TextBlock HorizontalAlignment="Left" Margin="5" TextWrapping="Wrap" Text=
"{Binding Endorsement.Name}" VerticalAlignment="Top" Grid.Column="1" />
<Button Command="{Binding DeselectCommand}" Content="-" HorizontalAlignment=
"Right" VerticalAlignment="Top" Width="25" Grid.Column="2"/>
</Grid>
When the page loads and when the 'Selected' is set is two different times; hence selected is null when the page loads and nothing happens. Anticipate the null situation such as an added style
<DataTrigger Binding="{Binding RelativeSource={x:Static
RelativeSource.Self}, Path=DataContext.Selected}" Value="{x:Null}">
...
You shouldn't have to do this in your DataTrigger binding:
Binding="{Binding RelativeSource={x:Static
RelativeSource.Self}, Path=DataContext.Selected}"
A binding by default is referencing the DataContext so the equivalent simpler form is this:
Binding="{Binding Path=Selected}"
I don't think that will solve your problem though (but if it does that's great). One way around it is to define a default value in the style for your Foreground if neither trigger fires:
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
...
This assumes that all your items are deselected upon load. Hope this helps.
Cheers,
Eric

Resources