How can I bind this Textblock inside ControlTemplate in button style? - c#

I have a Button style and this style contains an icon and a text. I would like to bind the text.
How can I achieve that?
<Style TargetType="{x:Type Button}" x:Key="ConnectedButton">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FlowDirection" Value="LeftToRight"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Padding="5 0"
Width="80"
Height="30"
Margin="5">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding Connect}"
Margin="3 0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="{StaticResource ConnectIcon}"
Style="{StaticResource Icon_Text}" Margin="3 0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Use the style in MainWindow.xaml:
<Button Style="{StaticResource ConnectedButton}" Margin="10,15,0,10" x:Name="cnct_btn" Content="{StaticResource Connect}" Height="40" Width="80 " HorizontalAlignment="Left" VerticalAlignment="Center" Click="Cnct_Click"/>
MainWindow.xaml.cs
bool test = false;
//...
if (test)
{
cnct_btn.Content = "Connect";
}
else
{
cnct_btn.Content = "Not Connected";
}
I have tried Text={Binding Connect} but it doesn't work.

In a control template, you have to use a TemplateBinding to access a property on a templated control. In case of a Button, it is the Content property that you want to bind.
Implements a markup extension that supports the binding between the value of a property in a template and the value of some other exposed property on the templated control.
<TextBlock Text="{TemplateBinding Content}"
Margin="3 0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
A simple Binding will resolve a property on the current data context. You can also make this work, but you need to specify a RelativeSource using TemplatedParent.
TemplatedParent - Refers to the element to which the template (in which the data-bound element exists) is applied. This is similar to setting a TemplateBindingExtension and is only applicable if the Binding is within a template.
<TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
Margin="3 0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
Please note that in this scenario a TemplateBinding is sufficient, as it is only one way. In a two-way binding you would have to use the variant using RelativeSource and TemplatedParent.
A TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}. A TemplateBinding is always a one-way binding, even if properties involved default to two-way binding. Both properties involved must be dependency properties. In order to achieve two-way binding to a templated parent use the following binding statement instead {Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=MyDependencyProperty}.

Related

Textbox filter in WPF Datagrid columns headers

I have a WPF Datagrid defined like this in my XAML :
<DataGrid x:Name="DtgPoshResults" Style="{StaticResource DatagridResults}" ItemsSource="{Binding Path=PoshResults, UpdateSourceTrigger=PropertyChanged}" ClipboardCopyMode="{Binding ClipBoardCopyMode}" CellStyle="{StaticResource CenterDatagridCell}" SelectedValue="{Binding SelectedItemPoshResults, Mode=TwoWay}" ColumnHeaderStyle="{DynamicResource DataGridColumnHeaderStyle1}"/>
To get a textbox in each column header (in order to filter by column), I use a Template.
My Datagrid has a dynamic number of columns (that can vary each time I start a command) :
<Style x:Key="DataGridColumnHeaderStyle1" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<StackPanel Orientation="Vertical" Background="#FF444444">
<TextBlock x:Name="DtgResultsColumnName" VerticalAlignment="Center" Text="{TemplateBinding Content}" Foreground="White" Margin="5,5,5,0"/>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Margin="5" Text="{Binding DataContext.TextFilterColumn, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Margin="0,0,5,0" VerticalAlignment="Center">
<Hyperlink Foreground="White" Command="{Binding DataContext.FilterColumnName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, UpdateSourceTrigger=PropertyChanged}" CommandParameter="{Binding ElementName=DtgResultsColumnName, Path=Text}">
Filter
</Hyperlink>
</TextBlock>
</StackPanel>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
After starting my command, the Datagrid displays results and looks like this :
When typing "lo" in the first textbox and clicking on "Filter", it works and I only get the line which is "localhost" but when the Datagrid is refreshed, all the textboxes are filled with the word "lo" :
I can't define a different Text Binding for each textbox as I have a dynamic number of columns so I don't know how to avoid that all textboxes in the headers get the same text when filter is applied.
Do you know how can I fix this?
Thanks and regards.

WPF add Margin Binding using 'Validation.HasError' as Path at runtime

I have the following XAML code that represents a TextBox with validation, when an error occurs then the margin is increased so that the message is not overlapping other UI elements under this TextBox.
In XAML every part of this works like I intended for it to work.
Validation is implemented using the IDataErrorInfo interface
<TextBox
Grid.Column="1"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}"
Style="{StaticResource baseMargins}"
Margin="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.HasError), Converter={StaticResource MarginConv}}"
Padding="{StaticResource basePadding}">
<TextBox.Text>
<Binding
Path="Email"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True"/>
</TextBox.Text>
</TextBox>
Now I want the create the same but at runtime. The page has an ItemsControl that I fill with the generated TextBoxes.
The following code shows how I create this TextBox
private static void CreateForStackOverflow()
{
TextBox textBox = new TextBox();
Binding textBinding = new Binding();
textBinding.Path = new PropertyPath("Email");
textBinding.Mode = BindingMode.TwoWay;
textBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
textBinding.ValidatesOnDataErrors = true;
textBinding.NotifyOnValidationError = true;
textBox.SetBinding(TextBox.TextProperty, textBinding);
var valTemplate = (ControlTemplate)Application.Current.FindResource("validationErrorTemplate");
Validation.SetErrorTemplate(textBox, valTemplate);
Binding valBinding = new Binding();
valBinding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
valBinding.Path = new PropertyPath("Validation.HasError");
valBinding.Converter = new Converters.BoolMarginConverter();
textBox.SetBinding(FrameworkElement.MarginProperty, valBinding);
}
Now the validation works, errors are created but the only thing that does not work is the Binding for the MarginProperty. This is never triggered.
The following error is displayed in the Output window:
BindingExpression path error: 'Validation' property not found on 'object' ''TextBox' (Name='')'. BindingExpression:Path=Validation.HasError;
DataItem='TextBox' (Name='');
target element is 'TextBox' (Name='');
target property is 'Margin' (type 'Thickness')
I have found it could maybe have something to do with my `DataContext] but it is set in the XAML, and setting that Context was the solution for all those people but not for me.
Using Snoop (2.10.0.0) I can see that the TextBox has a Validation property.
Here is what Snoop shows on the Marginproperty.
This a what the overlapping errors look like.
And the code for the BoolMarginConverter is the following:
public class BoolMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool hasError = (bool)value;
return hasError ? new Thickness(2, 2, 2, 20) : new Thickness(2, 2, 2, 2);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
But this is never executed because the Validation.HasError could not be found.
What am I missing here?
EDIT: The error template
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<StackPanel
Orientation="Horizontal" DockPanel.
Dock="Bottom"
ClipToBounds="True">
<Grid
Width="12"
Height="12">
<Ellipse
Width="12"
Height="12"
Fill="Red"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock
Foreground="White"
FontWeight="Heavy"
FontSize="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center"
Text="X"
ToolTip="{Binding ElementName=ErrorAdorner, UpdateSourceTrigger=PropertyChanged,Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</Grid>
<TextBlock
Foreground="Red"
FontWeight="Bold"
TextWrapping="NoWrap"
TextTrimming="WordEllipsis"
Margin="2,0,0,0"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors), Converter=converters:ValidationErrorsToStringConverter}}"
/>
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner"/>
</DockPanel>
</ControlTemplate>
I was missing the brackets on (Validation.HasError) ...
Validation.HasError ==> (Validation.HasError)
valBinding.Path = new PropertyPath("(Validation.HasError)");
Maybe someday this will help someone.
Thanks all
I wouldn't use code to do all that and even then what you have seems over complicated.
The below markup works for me.
I have a public Int property in code behind just to test by typing a character.
Obviously, if you want to apply this by code then put an x:Key on your style and then you can go grab it out resources and apply it. Mine applies to all textboxes. I don't really follow why you wouldn't want that anyhow.
I would also suggest that moving ui around like this is limiting. I'd prefer a border and outline colour change and see all the errors in a tooltip. You can only fit one this way.
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<StackPanel
Orientation="Horizontal" DockPanel.Dock="Bottom"
ClipToBounds="True">
<Grid
Width="12"
Height="12">
<Ellipse
Width="12"
Height="12"
Fill="Red"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock
Foreground="White"
FontWeight="Heavy"
FontSize="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center"
Text="X"
ToolTip="{Binding ElementName=ErrorAdorner, UpdateSourceTrigger=PropertyChanged,Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</Grid>
<TextBlock
Foreground="Red"
FontWeight="Bold"
TextWrapping="NoWrap"
TextTrimming="WordEllipsis"
Margin="2,0,0,0"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)/ErrorContent}"
/>
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)/ErrorContent}"/>
<Setter Property="Margin" Value="0,0,0,20"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Text="{Binding MyNum, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Banana"/>
<Button Content="Some Button"/>
</StackPanel>
</Grid>

A style gives rise to a different rendering result if given a x:Key

A style gives rise to a different rendering result if given a x:Key
I have the following Style and ControlTemplate for ToggleButtons and they work as I want it to.
<Style TargetType="RadioButton" BasedOn="{StaticResource {x:Type ToggleButton}}"/>
<ControlTemplate TargetType="RadioButton" x:Key="RadioTemplate">
<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked, Mode=TwoWay}">
<RadioButton.Content>
<Viewbox StretchDirection="DownOnly" Stretch="Uniform">
<ContentControl Content="{TemplateBinding Content}"/>
</Viewbox>
</RadioButton.Content>
</RadioButton>
</ControlTemplate>
However, when I give an x:Key to the Style and the RadioButton in the ControlTemplate inherits the Style as shown in the following,
the rendering result is different from the one given by the code above.
<Style TargetType="RadioButton" BasedOn="{StaticResource {x:Type ToggleButton}}" x:Key="RadioStyle"/>
<ControlTemplate TargetType="RadioButton" x:Key="RadioTemplate">
<RadioButton Style="{StaticResource RadioStyle}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked, Mode=TwoWay}">
<RadioButton.Content>
<Viewbox StretchDirection="DownOnly" Stretch="Uniform">
<ContentControl Content="{TemplateBinding Content}"/>
</Viewbox>
</RadioButton.Content>
</RadioButton>
</ControlTemplate>
Could anyone tell me why this happens?
The MSDN documentation:
Setting the TargetType property to the RadioButton type without setting an x:Key implicitly sets the x:Key to {x:Type RadioButton }. This also means that if you give the above Style an x:Key value of anything other than {x:Type RadioButton}, the Style would not be applied to all RadioButton elements automatically. Instead, you need to apply the style to the RadioButton elements explicitly like "{StaticResource RadioStyle}".
You need to use the key when you specify one.
The Style with a key, will not get automatically reflected to the togglebuttons.

How do I bind opacity of specific GridView-Items with a double property of its items-objects Win8.1 App, C#

I have a scedule with subjects in it. When I turn on a specific mode I want to change the opacity of certain subjects.
Therefore I have declared the property:
private double _sceduleOpacity;
public virtual double SceduleOpacity
{
get
{
if (_sceduleOpacity == 0) return 0.9;
else return _sceduleOpacity;
}
set { _sceduleOpacity = value; }
}
inside the Subject-Object.
When the mode is set, the SceduleOpacity-Property of the specific Subject is changed to the desired value (I've checked that).
Now all I need is a binding to that Property so that it is visible on the UI. But that is where the problem is. I've tried several approaches:
Bind it to the opacity of the DataTemplate:
GridView:
ItemTemplate="{Binding Mode=TwoWay, Source={StaticResource SceduleGridViewTemplate}}"
Resourcedictionary:
<DataTemplate x:Key="SceduleGridViewTemplate">
<Grid Background="{Binding ColorHash, Converter={StaticResource HexToSolidColorBrushConverter}}"
[...]
Opacity="{Binding SceduleOpacity}">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center"
FontSize="28"
Text="{Binding Name}"/>
</Grid>
This does not have any effect at all. But since the following binding:
<Grid [...]>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center"
FontSize="28"
Text="{Binding Name}"
Opacity="{Binding SceduleOpacity}"/>
</Grid>
sets the opacity of the displayed text successfully, I went on looking what the problem might be.
Bind it to the opacity of the Style or ItemContainerStyle:
I found out that the opacity of each GridView-Item is set by either in the Styles- or in the
ItemContainerStyle-Templates. But still the bindings do not work at all:
GridView:
Style="{Binding Source={StaticResource SceduleGridViewStyle}, Mode=TwoWay}">
Resourcedictionary:
<Style x:Key="SceduleItemStyle" TargetType="GridViewItem">
[...]
<Setter Property="Opacity" Value="{Binding SceduleOpacity}"/>
[...]</Style>
GridView:
ItemContainerStyle="{Binding Source={StaticResource SceduleItemStyle}, Mode=TwoWay}">
Resourcedictionary:
<Style x:Key="SceduleGridViewStyle" TargetType="GridView">
[...]
<Setter Property="Opacity" Value="{Binding SceduleOpacity}"/>
[...]</Style>
Do you have any idea on how I could fix that? Thank you very much!
It worked now. I have had to set the Opacity of the DateTemplate-Item as follows:
<Grid.Background>
<SolidColorBrush Color="{Binding ColorHash,
Converter={StaticResource HexToSolidColorBrushConverter}}"
Opacity="{Binding SceduleOpacity}"/>
</Grid.Background>
Thanks for help!

ListBox , DataTemplate and Triggers

i got a DataTemplate for a listboxitem and i want to create a triger , so when a user click an item the background will change and also the label
my code:
<Window.Resources>
<Style x:Key="RoundedItem" TargetType="ListBoxItem">
<EventSetter Event="MouseDoubleClick" Handler="listViewItem_MouseDoubleClick" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="ItemBorder" CornerRadius="10" BorderBrush="Black" BorderThickness="1" Margin="1" Background="Transparent">
<Label Name="ItemLabel" Foreground="Red" >
<ContentPresenter />
</Label>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="DeepSkyBlue" />
<Setter TargetName="ItemLabel" Property="Foreground" Value="Orange" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="TitleTemplate" DataType="models:Title" >
<StackPanel>
<Image Source="{Binding ThumbFilePath}" Width="50" HorizontalAlignment="Center"/>
<Label Content="{Binding Name}" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Description}" HorizontalAlignment="Center" TextWrapping="Wrap" Padding="5,5,5,5"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
What happend is that the TextBlock change his color and not the label..
anyone know why ?
Thanks.
The TextBlock inherits the Foreground definition from its parents in the visual tree. The Label, on the other hand, defines the Foreground in its default style.
Your approach is "non-WPF-like" - you shouldn't wrap the ContentPresenter in a Label control.
The right approach depends on whether you want all text in the item to change its Foreground, or just the label?
[In both cases, there's no apparent benefit to using a Label in the data template - so I'll assume that the label is changed to TextBlock.]
If the answer to the above question is that all text should be changed: in the ControlTemplate of the ListBoxItem, in the trigger for IsSelected, from the seccond setter remove TargetName="ItemLabel" so the final setter is:
<Setter Property="Foreground" Value="Orange" />
This will change the foreground of the item that will affect the foreground of both TextBlocks in the data template.
If you want to affect just one of the TextBlocks:
1. remove the setter for the foreground from the control template
2. add a trigger to your data template:
<DataTemplate>
<StackPanel>
<Image .../>
<TextBlock x:Name="Text01" ..../>
<TextBlock x:Name="Text02" ..../>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter TargetName="Text01" Property="Foreground" Value="Orange"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Side note: if you have to use Label control in your data template, bind its Foreground property to the Foreground of the list box item, like so:
<Label Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"....../>
If this doesn't help, it means that your list box item inherits its foreground, so use:
<Label Foreground="{Binding TextElement.Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"....../>
I want to tack on to this that I was experiencing a similar problem where I'd added a ListBox.ItemTemplate to my ListBox, and the styling then did not apply to the text anymore.
What I was doing was trying to display a list of languages (CultureInfo) for the user to select from, however I wanted the native names to display, not the English name. For some reason, not all languages have their native names capitalized in their CultureInfo, and NativeName is the only instance of their name, so I needed to apply a function to the CultureInfo.NativeName to capitalize the names myself. To accomplish this, I added the ItemTemplate with a Data Template inside, on which I applied a converter.
<ListBox IsSynchronizedWithCurrentItem="True" VerticalAlignment="Center" MinHeight="200" x:Name="cbLanguages"
ItemsSource="{Binding Path=SupportedCultures, Mode=OneWay, Source={StaticResource CultureResourcesDS}}"
FontSize="24" HorizontalAlignment="Stretch" Width="300" Margin="10"
Style="{DynamicResource ListBoxTemplate}" ItemContainerStyle="{DynamicResource ListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Converter={StaticResource NativeNameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
After a while of searching, I came across XAMeLi's answer here, and changed the Label I'd put in the DataTemplate to a TextBox, and the ListBoxItemStyle I'd created worked again.
Basically, Labels and TextBoxes have different traits that can be exploited, or can cause annoying issues in this case. Here's a good explanation with some examples of the differences: http://joshsmithonwpf.wordpress.com/2007/07/04/differences-between-label-and-textblock/

Categories

Resources