I have created a FocusVisualStyle for my user control and have successfully implemented the override. My problem is I would like that to use some properties from the parent, but TemplateBinding doesn't seem to work.
A simplified version of the the Control is defined as below:
<Style TargetType="{x:Type local:Thought}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ThoughtFocusStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Thought}" >
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{StaticResource ThoughtBorderNormalBrush}">
<!-- other controls -->
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My custom FocusVisualStyle is defined as follows:
<Style x:Key="ThoughtFocusStyle" TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{StaticResource ThoughtBorderFocusBrush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If I hard code BorderThickness in ThoughtFocusStyle it works as expected (Tab into the control), but using TemplateBinding does not. I've played around with RelativeSource but can't seem to get the syntax right (still very new to WPF).
try :
<Border BorderThickness="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Control},
Path=BorderThickness}">
Related
I've noticed that TextBoxes are very slow and create performance issues when the Text is changed dynamically by code (I need to change the Text continuosly to 10-15 TextBoxes at the same time), so, as a workaround, I've created a custom control with a TextBlock and a TextBox:
The TextBlock is used in almost all time.
The TextBox is used only when I need to edit the Text inside the control with keyboard.
My solution is to change the template and use the TextBox when the control is focused:
(Value is a string Dependency Property)
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Value" Value="Val"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Value}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Text="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
But when I click on the control nothing happens.
I think that the problem is that the "focus state" is passed to the internal TextBox, and the control loses the "focus state".
There is a better way to create a custom "TextBox" control like this, or a way to resolve this problem?
You don't need a custom control for this, that's just adding unnecessary overhead. What you're trying to create is still a TextBox, with all the usual behavior of a TextBox (focus etc). All you need to do is change the template to a TextBlock when it's not in focus:
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<TextBlock Text="{TemplateBinding Text}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Text="Hello World" />
<TextBox Text="Goodbye World" />
</StackPanel>
I have the following style definitions:
<!-- Border -->
<Style x:Key="MyControlBorder" TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="DarkKhaki" />
<Setter Property="Background" Value="White" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="10" />
</Style>
<!-- TextBox -->
<Style x:Key="MyTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Height" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border Name="TextBoxBorder" Style="{StaticResource MyControlBorder}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- PasswordBox -->
<Style x:Key="MyPasswordBox" TargetType="{x:Type PasswordBox}">
<Setter Property="Height" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Border Name="Border" Style="{StaticResource MyControlBorder}">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the following XAML code:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Style="{StaticResource MyTextBox}" />
<PasswordBox Grid.Row="1" Style="{StaticResource MyPasswordBox}" />
</Grid>
Now I got this result:
The TextBox applies the style correctly, but why does the PasswordBox not apply the style?
If you wrap the Border in another Border everything just works as expected (I don't know why).
As a bonus, you can now have PasswordBoxes and TextBoxes "inherit" from the same Style, keeping things nice and DRY.
<!-- Border Style Definition -->
<Style x:Key="MyControlBorder" TargetType="Border">
<Setter Property="BorderBrush" Value="DarkKhaki" />
<Setter Property="Background" Value="White" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="10" />
</Style>
<!-- TextBox and PasswordBox Style -->
<Style x:Key="MyControlInputBox" TargetType="Control">
<Setter Property="Height" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Border>
<Border Name="Border" Style="{StaticResource MyControlBorder}">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TextBox -->
<Style x:Key="MyTextBox" TargetType="{x:Type TextBox}" BasedOn="{StaticResource MyControlInputBox}" />
<!-- PasswordBox -->
<Style x:Key="MyPasswordBox" TargetType="{x:Type PasswordBox}" BasedOn="{StaticResource MyControlInputBox}" />
Somehow Border within ControlTemplate of PasswordBox does not take MyControlBorder style.
When you modify MyPasswordBox style like this... then it will work.
<Style x:Key="MyPasswordBox" TargetType="{x:Type PasswordBox}">
<Setter Property="Height" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Border Name="Border" BorderBrush="DarkKhaki" Background="White" BorderThickness="1" CornerRadius="10">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
I know it's not the best solution... but I can't figure out why MyControlBorder is not applied. It doesn't even work when you get rid of MyTextBox style. Then you are left only with MyControlBorder and MyPasswordBox ...it does not work either.
The PasswordBox is strange in this regard, as it is explicitly implemented to show this behavior.
The default control template contains a Border, as you can see here:
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Border x:Name="border" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</Border>
<!-- ...other markup, triggers. -->
</ControlTemplate>
The code for PasswordBox (taken from reference source) does the following:
OnApplyTemplate on line 473 is called when the control template is applied.
AttachToVisualTree on line 476 is called which is defined on line 1176.
SetRenderScopeToContentHost on line 1181 is called which is defined on line 956.
From line 1014 on it searches a Border up the visual tree beginning from the password content host. Note that it searches until it does not find a Border anymore, which means it stores the last found Border within its _border field.
In line 1200, if a Border was found, its Style property is set to null.
Consequently, whenever a control template is applied, the Style of the outermost Border is reset. This is still the same in .NET Core and >.NET , as you can see from the code on GitHub. This is the reason why #David Murdoch's answer with a redundant nested Border works, when the style is applied to the inner Border.
Now, what can do? You can either insert a dummy Border as a workaround or you have to set the CornerRadius directly to the Border inside your custom control template.
<ControlTemplate TargetType="{x:Type Control}">
<Border Name="Border" CornerRadius="10" Style="{StaticResource MyControlBorder}">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
If you reoder the columns of a Datagrid by dragging a ColumnHeader you get an indicator that shows you where the column will be placed. How can you style this indicator?
This is what the Aero theme has for the indicator. I believe you are looking to style this.
<Style x:Key="{x:Static DataGridColumnHeader.ColumnHeaderDropSeparatorStyleKey}" TargetType="{x:Type Separator}">
<Setter Property="Background" Value="#FF000080" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Separator}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is the link to the msdn
Edit:
I think there is a simpler way for styling dragging and dropping header controls using DragIndicatorStyle and DropLocationIndicatorStyle properties of DataGrid. I tested the following code and datagrid picked up the style.
<Window.Resources>
<Style x:Key="DragHeaderStyle" TargetType="{x:Type Control}">
<Setter Property="BorderBrush" Value="Red"/>
</Style>
<Style x:Key="DropHeaderStyle" TargetType="{x:Type Separator}">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="5"/>
</Style>
</Window.Resources>
<DataGrid DragIndicatorStyle="{StaticResource DragHeaderStyle}"
DropLocationIndicatorStyle="{StaticResource DropHeaderStyle}"
...>
</DataGrid>
See if you can override the Control Template to suit your need.
<Style x:Key="Border"
TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border BorderThickness="1">
<ScrollViewer Margin="0"
x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Why I cannot change TextBox background after applying style?
<TextBox Style="{StaticResource Border}"
Background="Bisque"
Height="77"
Canvas.Left="184"
Canvas.Top="476"
Width="119">Text box</TextBox>
<Style x:Key="Border" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border Background="{TemplateBinding Background}" BorderThickness="1">
<ScrollViewer Margin="0" x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You need to add the following line:
Background="{TemplateBinding Background}"
You overwrite the original Control Template of textbox. Background is a child of Template. You need to bind it to the target textbox again.
By overwriting the template of the control, you are literally defining how it will be shown to the user. In your template, you don't take into account any 'settings' in the control, so it will be always drawn as Border with a ScrollViewer inside.
If you want to use the properties of the control to customize some parts of your template, you can bind properties of your template contents to properties in your control using TemplateBinding
Ex:
<Style x:Key="Border"
TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border BorderThickness="1"
Background="{TemplateBinding Background}">
<ScrollViewer Margin="0"
x:Name="PART_ContentHost" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In this case, you are binding the Background property of your Border (Background=) to the Background property of your TextBox ({TemplateBinding Background)
So, in summary, you use this notation in your bindings:
ThePropertyIWantToSet="{TemplateBinding PropertyInMyControl}"
I created a button. My basic requirements are rounded thicker border, with more than one color (i.e. for Buy/Sell buttons)
I was hoping that i could create the template once, and than just override the border brush like this:
<Style x:Key="BorderButton">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderThickness="2"
BorderBrush="Red"
CornerRadius="3"
Background="{x:Null}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="GreenBorderButton" BasedOn="{StaticResource BorderButton}" TargetType="{x:Type Button}">
<Setter Property="BorderBrush" Value="Green" />
</Style>
but they both produce the same style.
Do i need to write out the whole template every time? seems like unnecessary code repetition (especially if 3-4 colors are desired). Hoping there is some way to inherit a template.
Your code is very close to working; the issue is that GreenBorderButton is applying the BorderBrush to the button itself, not the Border in the overridden Template.
To fix this, simply change the Border's BorderBrush to use the parent Button's BorderBrush. You can do this using a TemplateBinding like so:
<Style x:Key="BorderButton">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border"
BorderThickness="2"
BorderBrush="{TemplateBinding Property=BorderBrush}"
CornerRadius="3"
Background="{x:Null}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then, you can either use the same overridden styles like you have, or you could simply do:
<Button Style="{StaticResource BorderButton}" BorderBrush="Blue" Content="Blue" />