Empty text box default text - wpf

In my Window.Resources I have the following style:
<Style TargetType="TextBox" x:Key="HintText" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label Content="{DynamicResource EmptyText}" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
If I use this for 1 TextBox with this,
<Label Content="Test" Foreground="LightGray" />
Test will show up in my TextBox if it's empty. When I try to use this style in different TextBoxes with this,
<Label Content="{DynamicResource EmptyText}" Foreground="LightGray" />
and
<TextBox.Resources>
<sys:String x:Key="EmptyText">Test</sys:String>
</TextBox.Resources>
it doesn't show anything. Is it possible to use this 1 style with a different string that is shown in the TextBox or do I have to make a different style for each TextBox?

You don't appear to be employing this style in any of the examples you give and it isn't at all clear what relationship your last XAML block has with the one before it.
However, yes you should be able to redefine EmptyText as often as you like. The Text property will be resolved in accordance with the Dependency Property value precedence rules.
So you can do something like this:
<DockPanel HorizontalAlignment="Stretch">
<DockPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Text"
Value="{DynamicResource EmptyText/>
</Style>
<sys:String x:Key="EmptyText">Defined in the Dockpanel resource</sys:String>
</DockPanel.Resources>
<TextBlock/>
<TextBlock>
<TextBlock.Resources>
<sys:String x:Key="EmptyText">Defined in the textbox resource</sys:String>
</TextBlock.Resources>
</TextBlock>
<TextBlock>
<TextBlock.Resources>
<sys:String x:Key="EmptyText">Also defined at the textbox</sys:String>
</TextBlock.Resources>
</TextBlock>
</DockPanel>

Related

HOWTO override properties from a custom WPF UserControl

I have below WPF UserControl:
<UserControl x:Class="myComponents.UI.TextBoxWithPlaceholder"
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:pEp.UI"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Loaded="UserControl_Loaded">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TextBoxWithPlaceholder}}"
Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="myCustomTextBox"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
Padding="5"
IsReadOnly="{Binding IsReadOnly}"
HorizontalAlignment="Stretch"
TextChanged="CustomTextBox_TextChanged"
GotFocus="CustomTextBox_GotFocus"
LostFocus="CustomTextBox_LostFocus"
Margin="5"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" />
<TextBlock Name="myPlaceholderTextBlock"
IsHitTestVisible="False"
Padding="5"
Text="{Binding Placeholder}"
HorizontalAlignment="Left"
Foreground="DarkGray"
Margin="5">
</TextBlock>
</Grid>
</UserControl>
Basically it is a TextBox with a placeholder.
Now from a WPF view I reuse this component by doing:
xmlns:ui="clr-namespace:myComponents.UI"
and then place it as a normal control:
<ui:TextBoxWithPlaceholder Name="myNewTextBox" IsReadOnly="{Binding IsReadOnly}"
Style="{StaticResource myTextBoxStyle}"
Placeholder="please, enter something here"/>
Now as you see above I set a custom style for it:
<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="Foreground" Value="{x:Null}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
Now in my "myNewTextBox" control I am trying to override some inherited properties for the controls named myCustomTextBox and myPlaceholderTextBlock such as Margin, Padding, Background, Foreground, BorderBrush, etc. but I have tried above style and it is not working. Also I have tried:
<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
<Setter Property="{Binding Path=Margin, ElementName=myCustomTextBox}" Value="0" />
<Setter Property="{Binding Path=Padding, ElementName=myCustomTextBox}" Value="0" />
<Setter Property="{Binding Path=Margin, ElementName=myPlaceholderTextBlock }" Value="0" />
<Setter Property="{Binding Path=Padding, ElementName=myPlaceholderTextBlock }" Value="0" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="{Binding Path=Background, ElementName=myCustomTextBox}" Value="{x:Null}"/>
<Setter Property="{Binding Path=Foreground, ElementName=myCustomTextBox}" Value="{x:Null}"/>
<Setter Property="{Binding Path=BorderBrush, ElementName=myCustomTextBox}" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
If you want to be able to set properties of the myCustomTextBox from view that consumes your TextBoxWithPlaceholder control, you should add dependency properties to the latter and bind to them in TextBoxWithPlaceholder.xaml and set them in the consuming view, e.g.:
<ui:TextBoxWithPlaceholder ....PlaceHolderMargin="10" />
TextBoxWithPlaceholder.xaml:
<TextBlock Name="myPlaceholderTextBlock"
...
Margin="{Binding PlaceHolderMargin,RelativeSource={RelativeSource AncestorType=UserControl}}">
I am afraid you cannot refer to ElementName=myPlaceholderTextBlock from a namescope outside the TextBoxWithPlaceholder control so trying to do this in a Style that's defined in a consuming view won't work.
This is a task that cries out for a Custom control and Visual States instead of a UserControl with Triggers. But if you must do this as a UserControl (and I don't blame you because that's a lot to learn at this stage) then here goes:
First of all, when you use ElementName it is supposed to refer to elements that the XAML processor has already seen, previously in the current UI being laid out. Not elements inside the control being styled. I don't see that approach working.
If you want the TextBox and TextBlock inside a TextBoxWithPlaceholder to use the properties of that outer control, you could bind them to it, inside your control's XAML. For example, to rewrite a small part of that binding the background.
<TextBox Name="myCustomTextBox"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
Background={Binding RelativeSource={RelativeSource.FindAncestor, AncestorType={x:Type ui:TextBoxWithPlaceholder}, Path=Background}}"
But if you truly want that nested TextBox ("myCustomTextBox") to use a style with triggers and its own dedicated property values, then what you might try is creating a Resources section inside your style that itself contains implicit styles for the TextBox and TextBlock Something like this
<Style x:Key="myTextBoxStyle" TargetType="{x:Type ui:TextBoxWithPlaceholder}">
<Style.Resources>
<!-- Implicit style for TextBox should only apply to TextBoxes inside a TextBoxWithPlaceholder -->
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Background Value="{x:Null}"/>
<Setter Property="Foreground" Value="{x:Null}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
</Trigger>
<Style.Triggers>
</Style>
</Style.Resources>
</Style>

WPF Styles override a Style.Resources property

I have got a style as follows.
<TextBox HorizontalContentAlignment="Center" Text="{Binding IpAddress, Mode=TwoWay}" ToolTip="Ip Address of camera">
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Center" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label Content="Camera Ip Address" Foreground="Gray" Opacity="0.5" FontStyle="Italic" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
And I had kept it as a resource dictionary in a Skin.xaml file to be re-used as explained in the answer here.
But now I want the Content="Camera Ip Address" (the 7th line in the style) to be different for each textbox that I apply the style to. I saw SO answers this and this. These SO answers are suggesting the BasedOn attribute, but I am not sure how to apply this to my case. My case seems to be many levels deep. Can someone please suggest me how to achieve this.
Basically what I want is, for one textbox that I apply the Content should be Camera Ip Address, while for another textbox I want this content to be Camera Login. How do I achieve this?
You can set the content of that inner Label to the Tag property of the TextBox and then display it in the Label.
Like so:
<TextBox Tag="Whatever you want">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush">
<VisualBrush.Visual>
<Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=TextBox},Path=Tag,Mode=OneWay" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
</Style>
</TextBox.Style>
</TextBox>

textbox styling for all the forms in the project

Here is the code for the Textbox in one of my form(window1.xaml) here i have applied Textbox.style for the textbox, My requirement is i want to apply this same style for all the textboxes in my project all windows(window2.xaml,window3.xaml,.........)
<TextBox Grid.Column="0" MaxLength="1000" x:Name="txtQuestionDesc" HorizontalAlignment="Stretch" TextWrapping="Wrap" AcceptsReturn="True"
Grid.Row="1"
Margin="5,0,0,5" Height="100"
Text="{Binding QuestionText, UpdateSourceTrigger=LostFocus}" >
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label Content="Question Description" Foreground="LightGray" HorizontalAlignment="Center" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Add the Style on your App.xaml, it should work

how to set text in background of control

I want to set some default text in background of control (e.g. TreeView, ListView, GridView etc). Text is shown when no item in items control and hides when items control is not empty.
I imagine it like this:
i use the following style for this.
<Style x:Key="{x:Type ItemsControl}" TargetType="{x:Type ItemsControl}">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="No Data"
FontFamily="Consolas"
FontSize="16"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Items, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="No Data"
FontFamily="Consolas"
FontSize="16"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Type DataGrid}" TargetType="{x:Type DataGrid}" BasedOn="{StaticResource {x:Type ItemsControl}}">
</Style>
<Style x:Key="{x:Type TreeView}" TargetType="{x:Type TreeView}" BasedOn="{StaticResource {x:Type ItemsControl}}">
</Style>
There is likely a .Background property on the class. You can make an element out of it like:
<Button.Background>
<!-- content -->
</Button.Background>
This will allow you to place content inside it.
Also you can use a VisualBrush if you need a more complex background. You can create a VisualBrush out of any control in WPF. If you want a Brush with Labels in a Grid it can be done.

How to parametrize WPF Style?

I'm looking for a simplest way to remove duplication in my WPF code.
Code below is a simple traffic light with 3 lights - Red, Amber, Green. It is bound to a ViewModel that has one enum property State taking one of those 3 values.
Code declaring 3 ellipses is very duplicative. Now I want to add animation so that each light fades in and out - styles will become even bigger and duplication will worsen.
Is it possible to parametrize style with State and Color arguments so that I can have a single style in resources describing behavior of a light and then use it 3 times - for 'Red', 'Amber' and 'Green' lights?
<UserControl.Resources>
<l:TrafficLightViewModel x:Key="ViewModel" />
</UserControl.Resources>
<StackPanel Orientation="Vertical" DataContext="{StaticResource ViewModel}">
<StackPanel.Resources>
<Style x:Key="singleLightStyle" TargetType="{x:Type Ellipse}">
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="Stroke" Value="Black" />
<Setter Property="Height" Value="{Binding Width, RelativeSource={RelativeSource Self}}" />
<Setter Property="Width" Value="60" />
<Setter Property="Fill" Value="LightGray" />
</Style>
</StackPanel.Resources>
<Ellipse>
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" BasedOn="{StaticResource singleLightStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="Red">
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Ellipse>
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" BasedOn="{StaticResource singleLightStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="Amber">
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Ellipse>
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" BasedOn="{StaticResource singleLightStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="Green">
<Setter Property="Fill" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</StackPanel>
As long as your "Traffic Light" is wrapped up inside a control, which it appears it is, I don't think this is horrible. Each ellipse is well defined and has different triggers, each indicating its own state. You've already factored the common parts out into the base style, which is good.
You could wrap the individual ellipses inside another user control (which wouldn't need a backing ViewModel) that had an ActiveState property and an ActiveFill property. Then your TrafficLight looks something like:
<StackPanel Orientation="Vertical" DataContext="{StaticResource ViewModel}">
<my:Indicator State="{Binding State}" ActiveState="Red" ActiveFill="Red" />
<my:Indicator State="{Binding State}" ActiveState="Amber" ActiveFill="Red" />
<my:Indicator State="{Binding State}" ActiveState="Green" ActiveFill="Green" />
</StackPanel>
This lets you wrap up all your Ellipse styling inside your Indicator control and the only thing that control needs to worry about is comparing the State to the ActiveState to determine if it should fill itself with the ActiveFill brush.
As to if this is worth the effort or not, that depends on how many of these you have floating around and if you use them outside of your Traffic Light user control. Remember: You Ain't Gonna Need It.

Resources