WPF Propagating GotFocus over the visualtree - c#

I Have this Style template
<Style x:Key="placeHolder" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<TextBox Text="{Binding Path=Text,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
x:Name="textSource"
Background="Transparent"
Focusable="True"
Panel.ZIndex="2">
</TextBox>
<TextBox Text="{TemplateBinding Tag}" Background="{TemplateBinding Background}" Panel.ZIndex="1">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text, Source={x:Reference textSource}}" Value="">
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And In a Custom Control have a fiel that uses this template:
<TextBox x:Name="DatoABuscar"
Height="Auto"
MinHeight="25"
Text="{Binding Filtro,UpdateSourceTrigger=PropertyChanged}"
Margin="1,1,1,0"
PreviewKeyDown="DatoABuscar_PreviewKeyDown"
VerticalAlignment="Center"
Style="{StaticResource placeHolder}"
Tag="{x:Static resources:Labels.CONTROLES_SearchPlaceHoderText}"
GotFocus="DatoABuscar_GotFocus"/>
As you cold see I have a GotFocus handler with this code:
private void DatoABuscar_GotFocus(object sender, RoutedEventArgs e)
{
((TextBox)sender).SelectAll();
}
This handle Works perfect and I pretend to select all the content of the textbox when GotFocus Happens, but If I understand the real textbox showed after GotFocus happen is textSource inner into the style template.
Now My Questions are?
How can I do to propagate the got focus received by DatoAbuscar to textSource?
or
How can i do to Access the visualtree under the DatoABuscar to get textSource?
or
How can i do to Select all text in textSource when DatoAbuscar recieves the GotFocus**?
Another aproach ?
EDIT: I'm totally wrong the problem isn't the TexBox where I'm selecting the text. My problema was the method to select the text. Finally I Implement This Post

Actually you only need to use FindName to find the "textSource".
private void DatoABuscar_GotFocus(object sender, RoutedEventArgs e)
{
var txt = sender as TextBox;
var innerTxt = txt.Template.FindName("textSource", txt) as TextBox;
Keyboard.Focus(innerTxt);
innerTxt.SelectAll();
}

Related

Text of the textbox is always empty on a customized styling in wpf

Hey I'm designing a new style for a textbox in my WPF application using XAML codes. The textbox is a combination of textbox and textblock, I used the textblock to show name of the textbox when the text is null, and disappears when the text is filled, but there is a problem when I run the app and fill something in the textbox it seems that it's working properly but in the backend when I want to access the textbox Text it's null even though it's filled!!!!
Am I doing something wrong from the base or I missed something to do.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type TextBox}"
x:Key="TextBoxTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border CornerRadius="10"
Background="#353340"
Width="200"
Height="40">
<Grid>
<Rectangle StrokeThickness="1"/>
<TextBox Margin="1"
Text="{TemplateBinding Property=Text}"
BorderThickness="0"
Background="Transparent"
VerticalAlignment="Center"
Padding="5"
Foreground="#CFCFCF"
x:Name="textBox"/>
<TextBlock IsHitTestVisible="False"
Text="{TemplateBinding Name}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10, 0, 0, 0"
FontSize="11"
Foreground="DarkGray">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=textBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Hidden"/>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Instead of giving the TextBox an area to display its contents, you have overlapped it on top with another TextBox.
To display its content, the TextBox looks in the Template for ScrollViewer x:Name="PART_ContentHost".
Try a Template like this:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Grid>
<TextBlock x:Name="PART_TextBlock" Text="{TemplateBinding Name}" Visibility="Collapsed"/>
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Text"
Value="{x:Static sys:String.Empty}">
<Setter TargetName="PART_TextBlock" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Found this on the YouTube video comments where this question code comes from. In the template xaml file do the following:
Repalce This:
Text="{TemplateBinding Text}"
With This:
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, UpdateSourceTrigger=PropertyChanged}"
I was able to capture text from the TextChanged event after this change.
If you check the documentation for TemplateBinding you'll see it's just syntactic sugar for a one-way RelativeSource binding to the templated parent, so all you need to do is change your TextBox binding to make it two-way:
Text="{Binding Path=Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Also, I disagree with the comment above about this not being a good idea, one of the whole purposes of WPF's templating system is to make it possible to template existing controls e.g. for redistribution of libraries etc. That said, I don't think Name is the best property to use for your hint text, if for no other reason than the fact that it doesn't allow spaces. A much better solution would be to use an attached property. For a quick-and-dirty solution there's also the Tag property, which you're free to use for whatever you want.

WPF How to bind a control which is defined in a style from the element which uses the style?

I'm new to wpf and xaml,
trying to understand the basic concepts by writing a simple app using MVVM.
One thing I can't wrap my head around is
How to bind a control eg. textBox which is defined in a style to a viewModel from the control which uses this style?
Style Example (I like to bind the textBox named "SearchBox"):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type TextBox}"
x:Key="FlatSearchBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border CornerRadius="10"
Background="#353340"
Width="200" Height="40">
<Grid>
<Rectangle StrokeThickness="1"/>
<TextBox Margin="1"
Text="{TemplateBinding Text}"
BorderThickness="0"
Background="Transparent"
VerticalContentAlignment="Center"
Padding="5"
Foreground="#CFCFCF"
x:Name="SearchBox"/>
<TextBlock Grid.Column="1"
IsHitTestVisible="False"
Text="Search"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10,0,0,0"
FontSize="11"
Foreground="DarkGray">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=SearchBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Hidden"/>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Part of XAML in which the style is applied to a textBox:
<TextBox Grid.Row="2" Grid.Column="2"
VerticalAlignment="Center"
FontSize="20"
Style="{StaticResource FlatSearchBox}"
Text="{Binding CurrentFinanceModel.Value, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource currencyConverter}}"/>
The text binding should apply to the textBox named "SearchBox" in the style.
Hope the question is clear and I did not make a mistake, because it's my first question here :)
Thanks!

WPF - can't access template elements for objects created at runtime

I'm trying to create a checkable variation of the ComboBox control using a custom ControlTemplate. The eventual aim is to add a click handler for the integrated CheckBox every time a new ComboBoxItem is created - but when I use (CheckBox)template.FindName("myCheckBox", item); it always returns null.
Here is the Style that sets the ControlTemplate:
<Style x:Key="CheckedComboBoxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Margin" Value="8"></Setter>
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<StackPanel x:Name="myStackPanel" Orientation="Horizontal">
<CheckBox x:Name="myCheckBox" Focusable="False" Width="50" Foreground="{StaticResource textBrush}"
IsChecked="{Binding Path=IsChecked, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter></ContentPresenter>
</CheckBox>
<TextBlock x:Name="myTextBlock" Text="{Binding Name}" Width="100" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
and here is the code that tries to get a reference to the CheckBox in the code behind whenever a new item is added to the ComboBox:
ComboBoxItem item = new ComboBoxItem();
item.Content = system.Name;
item.Tag = system.Id;
cbSystems.Items.Add(item);
item.Loaded += (sender, e) =>
{
var template = item.Template;
CheckBox chkBox = (CheckBox)template.FindName("myCheckBox", item);
MessageBox.Show(chkBox.Name);
};

ControlTemplate DataTrigger is not fired in ItemsControl ControlTemplate

I have a NavigationMenuControl with an ObservableCollection<HtNavigationMenuQuickLinkItem>. Everything is working fine, but the Style on my HtMenuIcon Control is not triggered. Where the Visibility is changed correctly. Can someone please give me a hint where I have a mistake? QuickLinkSymbol is a DependencyProperty of an Enum.
I also want to put the Visibility Behavior into the DataTrigger section.
Navigation Menu
<Style TargetType="Navigation:HtNavigationMenu">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Navigation:HtNavigationMenu">
<Grid>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=QuickLinkItems}"/>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
QuickLinkItem
<Style TargetType="Navigation:HtNavigationMenuQuickLinkItem">
<Style.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Navigation:HtNavigationMenuQuickLinkItem">
<Controls:MyButton Width="40" Height="40" Margin="10,10,10,0">
<Viewbox Margin="3">
<Controls:HtMenuIcon x:Name="icon" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsQuicklink, Converter={StaticResource BoolToVis}}"/>
</Viewbox>
</Controls:MyButton >
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding QuickLinkSymbol, RelativeSource={RelativeSource TemplatedParent}}" Value="Home">
<Setter TargetName="icon" Property="Style" Value="{StaticResource Home}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You need to reference Home as {x:Static EnumNAmeSpace:EnumType.Home}.
Oh, and if QuickLinkSymbol is a DepProp of HtNavigationMenuQuickLinkItem,
just use Trigger instead of DataTrigger.

TemplatedParent is null when used inside a ControlTemplate's DataTrigger

Consider this (edited-down) Style, designed for a Button whose Content is a String:
<Style x:Key="Test" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel>
<TextBlock x:Name="text" Text="{TemplateBinding Content}" />
<TextBlock x:Name="demo" Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
<DataTrigger.Value>
<system:String>Test</system:String>
</DataTrigger.Value>
<Setter TargetName="test" Property="Foreground" Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The intention in this example is to turn the button text red if it equals the word "Test"1. But it doesn't work, because the trigger's TemplatedParent binding resolves to null instead of to the Button the Style is applied to. However, the TextBlock named "demo" will have its Text set to "System.Windows.Controls.Button: [ButtonText]" as expected, which means TemplatedParent works correctly at that level. Why doesn't it work inside the DataTrigger?
1 I know there are other ways to achieve that, but I'm trying to understand why the binding doesn't work the way I expect it to.
TemplatedParent in your ControlTemplate.Triggers is not what you expect. Inside trigger it actually references Button.TemplatedParent. As such, it will only be non-null if your create that button inside template. You don't create button inside template, so it is null in your case. Now consider this xaml:
<Window.Resources>
<Style x:Key="Test"
TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel>
<TextBlock x:Name="text"
Text="dummy" />
<TextBlock x:Name="demo"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
<DataTrigger.Value>
<system:String>Test</system:String>
</DataTrigger.Value>
<Setter TargetName="text"
Property="Foreground"
Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Test2" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Button Style="{StaticResource Test}"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<!--<Button Content="Test" Style="{StaticResource Test}"/>-->
<ContentControl Style="{StaticResource Test2}" Content="Test" />
</Grid>
Here I retemplate ContentControl and inside template I use button with your template. If you run this code, you will see "dummy" text in red, because Button.TemplatedParent is now ContentControl, and it has it's Content equals "Test", which confirms what I said above.
Now back to your problem: just change RelativeSource TemplatedParent to RelativeSource Self (no need to change DataTrigger to Trigger) - this one would reference your Button.
I think it might be a similar issue in .NET Core WPF.
My DataTrigger was not firing with {Binding MyProp, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Convert}}, but instead when I changed the RelativeSource to Self the binding started to work. I'm not sure whether it's a hack or a solution, but it worked.
Maybe it's worth mentioning that my template was based on MyView (see below) and I was binding to a DependencyProperty on MyView.
So my final code looked like this:
<ControlTemplate x:Key="Template" TargetType="{x:Type ns:MyView}">
<!-- Some template -->
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding MyProp, RelativeSource={RelativeSource Self}, Converter={StaticResource Convert}}" Value="True">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I'm not quite sure, but I think the trigger equals by referenc, because Content returns an Object. So it will never be true with your string defined within the trigger.

Categories