Expander.Content only applied to one - wpf

I am trying to apply style to multiple expanders which will have similar look and functionality. But the problem is that if I style Expander.Content only one of many will have content and the rest are all empty.
Minimal example:
<Style TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="Header">
<Setter.Value>
Header
</Setter.Value>
</Setter>
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="1"/>
</Setter.Value>
</Setter>
</Style>
<!-- .... -->
<Expander Grid.Row="0"/>
<Expander Grid.Row="1"/>
<Expander Grid.Row="2"/>
as you see only the second row has content (1) during design time. During run time only the last expander has content (1).
If I click on the first expander the content (1) from the third expander actually moved to the first expander.
Why is this happening? And how to fix it?

Styles only create one instance, which for visual elements can only be the child of a single parent. Set the ContentTemplate instead of the Content directly. (Templates just describe what is to be created by the control using it, so it can be shared.)

Related

Stop child from inheriting parent Style in TabControls

In my WPF application i have a TabControl that i am binding to a style i created:
On my View:
<TabControl Grid.Row="6" Style="{DynamicResource SideBarTabControl}">
On a separate ResourceDictionary:
<Style x:Key="SideBarTabControl" TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}" >
<Setter Property="FontSize" Value="{DynamicResource TitleFontSize}"/>
</Style>
So far so good, things work as expected. The problem is that now all the children of this TabControl, such as a ListView inside a TabItem, is also getting the same FontSize as the TabControl, instead of the default.
I thought that by specifying TargetType="{x:Type TabControl}" i would stop the style from being applied to children of different types. What i'm looking for is to actually stop it from affecting EVERYTHING BUT the component that explicitly inherited the style. So how can this be done? I think i am missing something simple...
If i override the font size in my ListView it works, but this means i have to do it for every child, which might become very cumbersome.
I have read this and other questions but i can't find the answer i'm looking for:
Is it possible to set a style in XAML that selectively affects controls?
This is working for me. The part that's doing the work is TabControl.ItemContainerStyle. It applies a font size only to the header content.
<TabControl>
<TabControl.ItemContainerStyle>
<Style
TargetType="TabItem"
BasedOn="{StaticResource {x:Type TabItem}}"
>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl
TextElement.FontSize="20"
Content="{Binding Header, RelativeSource={RelativeSource AncestorType=TabItem}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Foo">
<Label Content="Bar" />
</TabItem>
<TabItem Header="Baz">
<Label Content="Bar" />
</TabItem>
</TabControl>
You cannot stop it, it's not the style causing this unwanted trickle-down effect you want rid of; it's just how WPF controls work.
What you will have to do to stop this is write another style for your tab items to intercept the one being inherited from the TabControl.
I suggest writing this style inside your existing TabControl style, inside the Style.Resources tag like so:
<Style x:Key="SideBarTabControl" TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}" >
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="FontSize" Value="9001"/>
<!-- Any other setters you want for TabItems -->
</Style>
</Style.Resources>
<Setter Property="FontSize" Value="{DynamicResource TitleFontSize}"/>
</Style>
By making a style inside your other style's resources, it will be carried with it, and by not specifying any x:Key for the TabItem style - it will apply it to any TabItem not ordered to have a specific style, becoming the default style for any TabItem you make inside the TabControl now.

Styling a Textblock autogenerated in a ContentPresenter

As I saw, a lot of people ran into this exact problem but I can't understand why my case is not working and it is starting to drive me crazy.
Context: I have a DataGrid which is to be colored according to the values of each cell. Hence, I have a dynamic style resolving the actual template to be used for each cell. Backgrounds now work accordingly.
New problem: when I have a dark background, I want the font color to be white and the font weight to be bold so the text is correctly readable. And... I can't style it correctly.
I read some Stackoverflow posts about that:
This one fits my problem but doesn't provide me any working solution
This one is also clear and detail but... duh
This is almost the same problem as me but... Solution does not work
Here is what I tried so far:
<!-- Green template-->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style BasedOn="{StaticResource BoldCellStyle}" TargetType="{x:Type TextBlock}" />
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</ControlTemplate>
Does not work. Background is green, but text stays in black & not bold.
BTW, the BoldCellStyle is as easy as it can be:
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White" />
</Style>
Okay. Second try (which is a real stupid one but well...)
<!-- Green template -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</ControlTemplate>
Doesn't work either.
Then, I tried to play with the ContentPresenter's properties:
<!-- Green template -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter TextElement.FontWeight="Bold" TextElement.Foreground="White" TextBlock.Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
And... As you can expect, this does not even work.
Intrigued, I used Snoop to browse all the components of my interface.
In the first two cases, Snoop actually shows me that each cell is a Grid with a ContentPresenter containing a TextBlock and the actual Style but... The TextBlock's properties do not apply and FontWeight is still normal.
Last case, even more shocking, I can see that snoop shows me that we actually have a ContentPresenter with the right properties (ie TextElement.FontWeight="Bold"), but the autogenerated TextBlock under is - still - not styled.
I can't get what am I missing here. I tried as you can see almost all I could possibly do here, and the TextBlocks keep being non-formatted.
Any idea here? Thanks again!
The DataGridColumns that derive from DataGridBoundColumn (all except DataGridTemplateColumn) has a property ElementStyle that is applied to the TextBlock when it is created. For e.g. DataGridTextColumn It looks like this
static DataGridTextColumn()
{
ElementStyleProperty.OverrideMetadata(typeof(DataGridTextColumn),
new FrameworkPropertyMetadata(DefaultElementStyle));
// ...
}
It overrides the metadata for ElementStyle and provides a new default value, DefaultElementStyle, which basically just sets the default margin for the TextBlock.
public static Style DefaultElementStyle
{
get
{
if (_defaultElementStyle == null)
{
Style style = new Style(typeof(TextBlock));
// Use the same margin used on the TextBox to provide space for the caret
style.Setters.Add(new Setter(TextBlock.MarginProperty, new Thickness(2.0, 0.0, 2.0, 0.0)));
style.Seal();
_defaultElementStyle = style;
}
return _defaultElementStyle;
}
}
This style is set in code everytime a new DataGridCell is created with element.Style = style; and this is overriding the Style you are trying to set, even if you try to set it implicitly.
As far as I know, you'll have to repeat this for your columns
<DataGridTextColumn Header="Column 1" ElementStyle="{StaticResource BoldCellStyle}" .../>
<DataGridTextColumn Header="Column 2" ElementStyle="{StaticResource BoldCellStyle}" .../>

WPF DataGrid Hyperlink Appearance and Behaviour

I am quite new to WPF, there is so much to learn and I think I'm getting there, slowly. I have a DataGrid which is used for display and user input, it's quite a tricky Grid as it is the main focus of the entire application. I have some columns that are read-only and I have used a CellStyle Setter to set KeyboardNavigation.IsTabStop to False to keep user input focused on the important columns and that works fine. I would like a couple of the read-only columns to be hyperlinks that show a tooltip and do not receive the focus, however I am struggling to write the XAML that will achieve all three requirements at the same time.
One of the columns is to indicate if the item on the row has any Notes. I have used the following XAML to display a HasNotes property in the cell in a DataGridTemplateColumn and on the Tooltip show the actual notes, in the Notes property:
<DataGridTemplateColumn x:Name="NotesColumn" Header="Notes">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding HasNotes, Mode=OneWay}">
<TextBlock.ToolTip>
<TextBlock Text="{Binding Notes}" MaxWidth="300" TextWrapping="Wrap" />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style>
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
That works fine but I would like to make it a Hyperlink instead so the user can do something with the Notes when they click on the cell contents.
I have another column which is a DataGridHyperlinkColumn used to display a Unit of Measurement on a hyperlink and when clicked the user can change the unit. (The reason I have done it like this, rather than a ComboBox for example, is because as much as I want the user to be able to change the unit I want to make the interface such that it is a very deliberate act to change the unit, not something that can be done accidentally). The following XAML puts the Hyperlink column on for Unit
<DataGridHyperlinkColumn x:Name="ResultUnitLink" Binding="{Binding Path=Unit.Code}" Header="Unit" Width="Auto" IsReadOnly="True">
<DataGridHyperlinkColumn.CellStyle>
<Style>
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
</Style>
</DataGridHyperlinkColumn.CellStyle>
<DataGridHyperlinkColumn.ElementStyle>
<Style>
<EventSetter Event="Hyperlink.Click" Handler="ChangeUnit" />
</Style>
</DataGridHyperlinkColumn.ElementStyle>
</DataGridHyperlinkColumn>
One problem with the XAML for the Hyperlink column is that the IsTabStop = False doesn't appear to work, when tabbing through the Grid my hyperlink column still receives the focus, unlike the other columns where I've used a setter to change IsTabStop. If push comes to shove I could live with that but I'd rather not.
What I actually want from both those columns is an amalgamation of the two appearances/behaviours i.e. Columns where the data is displayed on a hyperlink, where TabStop = False and which display a tooltip of a different property when hovered over.
Can anyone help advise me how to get a column that achieves the following:
Hyperlink displaying one property
Tooltip displaying a different property
IsTabStop = False that actually works when used with a hyperlink
Thanks in advance to anyone who can help.
I've had issues with the Hyperlink in the past, so have this Style which I use for Labels or Buttons to make them look like Hyperlinks. Try making your column Template into a Button or a Label and applying this style.
<Style x:Key="ButtonAsLinkStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ContentPresenter ContentStringFormat="{TemplateBinding ContentStringFormat}" />
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Cursor" Value="Hand" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

Wrapped Rows with Silverlight DataGrid

I'm trying to style a Silverlight DataGrid so that the content displayed in the grid wraps. I want each grid row (and the grid header), to consist of TWO lines of cells, instead of one. This makes each row taller, but keeps all of the content visible on screen simultaneously, instead of having to scroll horizontally to see all of the fields. Here is a picture to help illustrate what I am going for:
Screenshot
(I don't have enough rep to post an image directly but the link above will show you an example screen shot)
I see the templates that let me customize how the different cells are styled, but I'm not seeing anything that will let me to control how those cells are displayed beside each other on the screen.
Your best bet here is going to be to abandon the datagrid and use a ListView instead, and inside the ListView you will want to show a UserControl of your own design. I did something similar for an application I built.
In your XAML for your ListView you will want to set the ItemContainerStyle and inside that you'll want to display your custom UserControl (in which you can use a Grid to setup the rows/colums and their spans). It basically looks like this:
<ListView
Name="_listView"
Grid.Row="0" Grid.Column="0"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedAgent, Mode=TwoWay}"
ItemsSource="{Binding Agents, Mode=OneWay}"
GridViewColumnHeader.Click="ListView_Click"
DependencyProperties:ListBoxClickCommand.ClickCommand="{Binding ShowAgentDetailsCommand}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border
Name="_border"
Padding="2"
CornerRadius="5"
SnapsToDevicePixels="true"
Background="Transparent">
<Controls:AgentStateControl></Controls:AgentStateControl>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="_border" Property="Background" Value="CornflowerBlue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
The AgentStateControl is my custom control, and it has two rows, the second has a bigger span on the columns. You can create that control however you want.

Problem getting custom content in a TabItem visible

I have a custom ItemsControl (WorKArea) that marks all items it has into a WorkSheet instance.
I have a style for the ItemsControl that uses a TabControl to show the content. Every sheet creates a tab. The style is:
<Style TargetType="{x:Type local:WorkArea}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:WorkArea}">
<TabControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:WorkArea}}, Path=Items}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So far so good. The "sheets" show up, and the title is properly bound to the Header.
How can I get the tabs showing the content now? Whatever I try, nothing shows up at all for every WorkSheet - the content is always empty. Anyone the proper code?
Your work area should provide a ContentTemplate property and the TabControl should have a TemplateBinding which to it.

Resources