After quite some search and reading other questions & posts, I was not able to find how to solve this. Note: I'm relatively new to WPF (not to binding in general).
Here's what I'm after:
I'd like to have all TextBlock controls in a window to be styled in some way.
The style should also apply a ValueConverter to make text all uppercase.
Finally, each TextBlock Text could come either from binding to a view-model property, or from binding to a resource string from a .resx file
Here's an excerpt of what I'm playing with:
<!-- in Window resources -->
<help:AllCapsStringConverter x:Key="AllCaps"/>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Brown" />
<Setter Property="Text">
<Setter.Value>
<Binding>
<Binding.Converter>
<help:AllCapsStringConverter />
</Binding.Converter>
<!-- also tried adding:
<Binding.RelativeSource>
<RelativeSource Mode="Self" />
</Binding.RelativeSource>
-->
</Binding>
<!-- also tried with:
<Binding Converter="{StaticResource AllCaps}"/>
<Binding Path="Text" Converter="{StaticResource AllCaps}"/>
-->
</Setter.Value>
</Setter>
</Style>
<!-- in Window content -->
<TextBlock Text="{x:Static resx:MyResources.MyTitle}" />
Here's the value converter, which on its own has proved to be working:
class AllCapsStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is string)
{
return ((string)value).ToUpper();
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
TextBlocks get the foreground color, but no conversion kicks in.
I was able to apply the converter locally to the single TextBlock, but I don't want to apply that to all TextBlocks around the window:
<TextBlock>
<TextBlock.Text>
<Binding Source="{x:Static resx:MyResources.MyTitle}"
Converter="{StaticResource AllCaps}"/>
</TextBlock.Text>
</TextBlock>
<!-- which is the same as -->
<TextBlock Style="{StaticResource CustomerInfoTitleStyle}"
Text="{Binding Source={x:Static resx:MyResources.MyTitle}, Converter={StaticResource AllCaps}}" />
Your converter is not working because your TextBlock is overriding the Text property of the Style, which includes the converter you have added to the binding.
For example:
<Grid.Resources>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Text" Value="You won't see this."></Setter>
</Style>
</Grid.Resources>
<TextBlock Text="You will see this." Style="{StaticResource MyTextBlockStyle}"/>
Hopefully from the above you can see why your approach is not working.
A better solution would just be to set the value of Text with the value converter on the TextBlock, rather than in the Style.
If you don't want to do that, one common cheat you could use to get around this could be to bind the TextBlock's Text property to the Tag property, like so:
<Grid.Resources>
<local:AllCapsConverter x:Key="AllCaps"/>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Text" Value="{Binding Tag, RelativeSource={RelativeSource Self}, Converter={StaticResource AllCaps}}"/>
</Style>
</Grid.Resources>
<TextBlock Tag="You will see this." Style="{StaticResource MyTextBlockStyle}"/>
I have a strong dislike of this approach, but it does get you what you want. I would prefer you just use the converter when you set the binding on the TextBlock.
lukegv's approach is another alternative. However, there is no need to use a Label, as you are overriding the template and (similar to my example above) you are just binding to the Content property of the Label. You could just as easily get what you need from a ContentControl.
<Grid.Resources>
<local:AllCapsConverter x:Key="AllCaps"/>
<Style x:Key="MyContentControl" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ContentControl Style="{StaticResource MyContentControl}" Content="You will see this too!"/>
I'm not a particularly huge fan of this idea either though, as you lose access to all the other TextBlock properties. For example, if I wanted to set the FontWeight property of my TextBlock then I would be stuffed.
Try to use a 'Label' and build a template, because a 'TextBlock' is not a control.
<Style x:Key="AllCapsLabel" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<TextBlock Foreground="Brown" Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And use it this way:
<Label Style="{StaticResource AllCapsLabel}" Content="whatever you want" />
If the content is plain text (aka a 'String'), the text should always be uppercase.
Related
I am trying to work with the MVVM principles within a small WPF project using C#.
I have a ListBox that is populated with CheckBoxes created through binding back to the ViewModel. I also have a command bound to the CheckBoxes and wish to pass the CheckBoxes Content as a CommandParameter. I was looking for something like this:
<Binding ElementName="" Path="Content"/>
Unfortunately, because the CheckBoxes are created through a binding I don’t have the element name.
The code for ListBox / ListBoxItem Style is this:
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Command="{Binding SelectedItemCommand, Mode=OneWay, Source={StaticResource comd}}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource cv}">
<Binding ElementName="" Path="Content"/>
<Binding ElementName="" Path="IsChecked"/>
</MultiBinding>
</CheckBox.CommandParameter>
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
And its implementation is:
<ListBox Grid.Row="1" Style="{StaticResource CheckBoxListStyle}" Name="lstProducts" ItemsSource="{Binding stampInfo, Mode=OneWay, Source={StaticResource vmStamp}}"
DisplayMemberPath="Country" >
</ListBox>
Ultimately my goal is to be able to display the text Contents (Countries in this case) of all the selected items in a text box were each country is separated by a comma. The only thing I am currently missing is the Country.
Do not create a ControlTemplate for ListBoxItem when you really want to display your data items differently, use a DataTemplate instead, that is exactly its purpose. See Data Templating Overview.
Remove the DisplayMemberPath from the ListBox, as you cannot use both use a path and a custom DataTemplate at the same time. You would only set this path, if there was no DataTemplate, but you wanted to specify a concrete property or property path to display.
<ListBox Grid.Row="1"
Style="{StaticResource CheckBoxListStyle}" Name="lstProducts"
ItemsSource="{Binding stampInfo, Mode=OneWay, Source={StaticResource vmStamp}}"/>
Replace the ControlTemplate with a DataTemplate as ItemTemplate. Then bind the Content and CommandParameter to the property Country. The data context is automatically set to the corresponding item in the bound collection of data items. The IsChecked property can be bound using a RelativeSource, which is the CheckBox itself.
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox Content="{Binding Country}"
Command="{Binding SelectedItemCommand, Mode=OneWay, Source={StaticResource comd}}">
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource cv}">
<Binding Path="Country"/>
<Binding Path="IsChecked" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
A different option would be to create data items with a property that can be bound to the IsChecked property of CheckBox. Then you could act in the setter of the data item or on e.g. a button click that executes a command, which filters the bound collection in your view model for checked items.
I have the following code. Please look at Radio Button Style, I want to bind the value of the enum to a property in the ViewModel when the radio button is checked. I have property called SelectedWellType and I want to bind to it. But it says a binding cannot be set on Property of Type Property in Setter. Please help.
<RadioButton
Name="IndividualWellsRadioButton"
Grid.Row="0"
KeyboardNavigation.IsTabStop="True"
KeyboardNavigation.TabIndex="1"
KeyboardNavigation.AcceptsReturn="True"
Content="{StaticResource IndividualWells}"
IsEnabled="{Binding IsIndividualWellAutoAnalysisPossible}">
<RadioButton.IsChecked>
<Binding
Converter="{StaticResource enumToBoolConverter}"
ConverterParameter="{x:Static dataDef:AutoAnalysisModes.IndividualWells}"
Path="AutoAnalysisMode" />
</RadioButton.IsChecked>
<RadioButton.Style>
<Style
TargetType="{x:Type RadioButton}">
<Style.Triggers>
<Trigger
Property="IsChecked"
Value="True">
<Setter
Property="{Binding SelectedWellType}"
Value="{x:Static dataDef:AutoAnalysisModes.IndividualWells}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
I have a custom control in WPF, which consists of a toggle button, a TextBlock and a TextBox. What I basically want to do is to show the TextBox when the toggle button is checked and the TextBlock otherwise. Furthermore I want allow defining to style properties on the control via dependency properties, which are applied to the TextBlock and the TextBox at runtime. The default template looks like this:
<Style TargetType="{x:Type views:EditableLabel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type views:EditableLabel}">
<Border Background="{TemplateBinding Background}">
<DockPanel Margin="0">
<telerik:RadToggleButton x:Name="PART_Toggle"
DockPanel.Dock="Right"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsInEditMode, Mode=TwoWay}">
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ToggleImage}" Height="14" />
</telerik:RadToggleButton>
<TextBlock x:Name="PART_TextBlock"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text}" >
</TextBlock>
<TextBox x:Name="PART_TextBox"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
</TextBox>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The custom control has two dependency properties for styles, one for the PART_TextBlock and one for PART_TextBox. The styles are assigned in the OnApplyTemplate method of the custom control and in the property change callbacks of the two dependency properties:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_textBlock = (TextBlock) GetTemplateChild("PART_TextBlock");
_textBox = (TextBox) GetTemplateChild("PART_TextBox");
_toggleButton = (RadToggleButton) GetTemplateChild("PART_Toggle");
ApplyStyles();
UpdateVisibilities();
}
private void ApplyStyles()
{
if (_textBlock != null) _textBlock.Style = TextBlockStyle;
if (_textBox != null) _textBox.Style = TextBoxStyle;
}
(The callbacks are not shown here, as they are trivial, just calling ApplyStyles().
I use the custom control like this:
<views:EditableLabel Text="{Binding SelectedToolbox.Description, Mode=TwoWay}"
CanEdit="{Binding SelectedToolbox.CanEdit}"
ToggleImage="../Resources/Images/edit-26.png">
<views:EditableLabel.TextBlockStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</views:EditableLabel.TextBlockStyle>
<views:EditableLabel.TextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
</Style>
</views:EditableLabel.TextBoxStyle>
</views:EditableLabel>
Everything works as expected, except from the AcceptsReturn setter is not applied, which I find very strange. I've debugged ApplyStyles(): The style is assigned correctly and both setters are contained within the style.
TextWrapping and VerticalScrollBarVisibility are both set correctly:
while AcceptsReturn is not:
Any ideas, what might be the issue here?
The screenshot you posted suggests AcceptsReturn has a local value, i.e., a value set by explicitly calling the property setter or SetValue. Do you have any code in EditableLabel which explicitly sets the AcceptsReturn property? If so, the local value you set will take precedence over any style setters. You can avoid this by using SetCurrentValue to change the value while leaving the value source unchanged.
Secondly, rather assigning the style in your code behind, it is generally easier and more reliable to simply bind the style within the template, e.g.:
<TextBox x:Name="PART_TextBox" Style="{TemplateBinding TextBoxStyle}" ... />
You might try this first and see if you get better results.
In my Window I have a series of six buttons that indicate the six possible states for one of the properties of my ViewModel. The one that's active needs to be highlighted. To do this, I've created the following ControlTemplate for the buttons:
<ControlTemplate x:Key="SnijRichtingTemplate" TargetType="Button">
<Border Name="toggleButton" BorderThickness="1" BorderBrush="{StaticResource KleurRadioCheckOuter}" Background="Transparent" Width="20" Height="20" Cursor="Hand">
<TextBlock Name="text" Foreground="{StaticResource KleurRadioCheckOuter}"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}"
ToolTip="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag.ToolTip}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityToBooleanConverter}">
<Binding Path="SnijRichting" />
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="toggleButton" Property="BorderBrush" Value="{StaticResource KleurTekstDonker}" />
<Setter TargetName="text" Property="Foreground" Value="{StaticResource KleurTekstDonker}" />
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="toggleButton" Property="BorderBrush" Value="{StaticResource Kleur2}" />
<Setter TargetName="text" Property="Foreground" Value="{StaticResource Kleur2}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
The template is then used like so:
<Button Grid.Column="0" Template="{StaticResource SnijRichtingTemplate}"
HorizontalAlignment="Right" Click="SnijRichting_Click"
Tag="{StaticResource XLinks}" />
Where the tag is just an instance defined in the XAML:
<wg:SnijRichting x:Key="XLinks" SnijAs="X" Negatief="True" />
The MultibindingConverter is nothing fancy:
public class EqualityToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Basically, each button has a Tag with the new value. In the click handler, the ViewModel's property is set to the button's Tag. The button state is updated by checking whether the button's Tag is equal to the ViewModel property.
The problem is that this doesn't work. When the EqualityToBooleanConverter is executed, the second value is null. By removing the Path="Tag" bit from the second binding I see that the TemplatedParent is a ContentPresenter rather than the Button I was expecting, which explains why Tag is null. Now of course I could write a ValueConverter to get the correct value using VisualTreeHelper.GetParent to get the ContentPresenter's parent (which returns the desired Button), but surely there must be a way to do this from XAML? The obvious Path="Parent.Tag" doesn't work, since the Parent of the ContentPresenter is apparently a Border.
Does anyone know how to access the button's Tag property from XAML?
Found the problem. Turns out you need {RelativeSource Mode=Self}, not {RelativeSource TemplatedParent}.
In the previous question of mine, I have asked how to hide an empty TextBlock, so that it doesn't take space in the panel. I have a new challenge now. How am I supposed to hide an empty Hyperlink:
<TextBlock>
<Hyperlink
NavigateUri="{Binding Path=Email}"
RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock Text="{Binding Path=Email}" />
</Hyperlink>
</TextBlock>
This is what made the hiding possible in the previous question:
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
Providing this style on first TextBlock won't work because the Text property is not set. Providing style on Hyperlink doesn't hide the parent TextBlock and same happens if I try to hide the TextBlock inside the Hyperlink.
I am certain that my style needs to be applied on the Hyperlink, but the trigger inside should target the Visibility property of the 'Hyperlink's parentTextBlock`. What is the style supposed to look like?
Just use DataTrigger on the top level TextBlock to check whether the bound property is an empty string:
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Email}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
EDIT:
Also you can try binding to the child hyperlink's NavigationUri property:
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Inlines
[0].NavigateUri}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
The solution provided by Foovanadil that solves the similar issue by implementing visibility converter works the best in my opinion. It is the easiest to implement and can be reused whenever needed.
The converter should be implemented like this:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (string.IsNullOrEmpty(value as string))
{
return Visibility.Collapsed;
}
else
{
return Visibility.Visible;
}
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
And used like this:
<Window.Resources>
<!-- Visibility converter -->
<converters:VisibilityConverter x:Key="visibleConv" />
</Window.Resources>
...
<TextBlock Visibility="{Binding Something, Converter={StaticResource visibleConv}}">
<Hyperlink NavigateUri="{Binding Something}">
<TextBlock Text="{Binding Something}" />
</Hyperlink>
</TextBlock>
All credits go to the original solution provider: Foovanadil