Bind header textblock isEnabled to parent Groupbox isEnabled - wpf

Referencing to this question: WPF Databinding: How do I access the "parent" data context?
I wanna do something similiar, but for the header of a Groupbox (because the header does not concern with the Box is being disabled and thus is always black while the rest is light gray. This looks a bit strange to me if all the content of the box is gray, the above is gray, but the box title itself stays black.
So I tried to use the approach mentioned in the linked question by flq to simply bind the isEnabled property of the header textblock to the isEnabled property of the groupbox but it seems that my binding at some point fails and I don't know where and why exactly.
heres my current code:
<GroupBox Header="Change Steps" Grid.Row="2" Grid.ColumnSpan="3" Name="gbChangeSteps">
<GroupBox.Style>
<Style TargetType="GroupBox">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold" Height="19" Foreground="Black" IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}, Path=isEnabled}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupBox.Style>
<!-- ... (some non relevant Content)-->
</GroupBox>

after additional research I found the post Disable groupBox including the groupBox name in WPF
that lead me, in combination with Properties->Create Databinding->Binding type->UIElement to the solution that fixed both problems, the one this question was about and the original one that lead to entire restyling, which was that letters like the small g got messed up in the header.
This is the code that fixed the issue:
<GroupBox.Style>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold" Height="19" IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UIElement}}}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="Control.IsEnabled" Value="False">
<Setter Property="Control.Foreground" Value ="#FF6D6D6D" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupBox.Style>

Related

Editing the property of an element within the ControlTemplate in WPF

I have some radio buttons that I'm building a custom control template for. Image of the buttons:
In the control template, each radio button will have a textblock with its name and another textblock below it to indicate if it's unavailable.
I want the "Unavailable" text to be visible ONLY when the button is NOT enabled. When the radio button is ENABLED, the "Unavailable" textblock should be collapsed.
Here is the simplified view.xaml for the buttons:
<RadioButton Name="one"
IsEnabled="{Binding One_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="two"
IsEnabled="{Binding Two_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="three"
IsEnabled="{Binding Three_isAvailable}"
Style="{StaticResource RadioButtonTheme}"/>
Here is the simplified version of the styling I have so far (RadioButtonTheme.xaml):
<ResourceDictionary>
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
So I've tried setting a couple things:
I set a visiblitity property on the radio button on the view.xaml. I then binded that visibility to the "UnavailableTextBlock" in the radiobuttontheme.xaml and set the rest of the template visiblity to "Visible." I thought that I can leave the template visible except for one element of it. I now don't think that's possible.
I tried directly binding the "UnavailableTextBlock" to the IsEnabled property of the radiobutton, and ran it through a BoolToVisiblityConverter.
<TextBlock Name="UnavailableTextBlock"
Visibility="{TemplateBinding Property=IsEnabled, Converter={StaticResource BoolToVisConverter}}">
However, I can't seem to get my converter to work inside of the ResourceDictionary. The program will crash with the error: "Cannot find resource named 'BoolToVisConverter'. Resource names are case sensitive"
I have this converter working across my other xaml files since I added it to my <Application.Resources> in the app.xaml. Do I need to link my Resource dictionary to the converter? How do I do that? <ResourceDictionary.Resources> didn't seem to work for me.
I tried adding a datatrigger to the "UnavailableTextBlock" as below:
<TextBlock Name="UnavailableTextBlock"....>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{TemplateBinding Property=IsEnabled}" Value="false">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
However, I get an error saying: '"IsEnabled" member is not valid because it does not have a qualifying type name.'
I'm guessing that it's referencing the IsEnabled property of the TextBlock and not of the radio button? Although I'm not too sure. I'm still learning WPF.
Thanks for all your help in advance!
If I understand correctly what you want to implement, then you need to use the control template trigger.
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="UnavailableTextBlock" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Excess border selection in WPF's Lisbox [duplicate]

I have a ListBox in which each item is a StackPanel. The StackPanel consist of an Image and a TextBlock below it:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Path=ImageFilePath}"/>
</Image.Source>
</Image>
<TextBlock Text="Title" TextAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
It looks like this:
When the user select an item, I get the default blue rectangle that surround the StackPanel:
Now, I want to make a different border for the selected-item, but I want it to surround only the image.
I know how to make a control template and put a custom border around the ContentPresenter, but this, of course, will surround the whole StackPanel, not only the Image.
I don’t know if making changes to the ContentPresenter is possible, and if it is a good idea at all. If there is other way to achieve the look I want, it will be fine as well.
Right, the ListBox's own ContentPresenter isn't helpful for what you're doing. You want to a) eliminate the ListBox's own selection visuals and b) replace them with something more suitable in the DataTemplate for your items.
The default selection visual is applied by the default template for ListBoxItem. So replace that template. Using a Style in the resources for your ListBox, apply your own control template to ListBoxItem. Not much to it, just present the content and don't provide a selection background. Then you handle the selection visuals with a trigger in your data template, where your image and your label are defined and you can apply changes to one and not the other. The below example works for me.
Note that there's some fiddling with the HorizontalAlignment on the Border element to make it cling to the Image element within it. Also, I wrote a quickie test viewmodel whose Items property is called Items; I assume this is not the name of the collection member you're using to populate your own ListBox.
<ListBox
Margin="8"
ItemsSource="{Binding Items}"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
x:Name="HighlightBorder"
BorderThickness="4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10"
>
<Border.Style>
<Style TargetType="Border">
<!-- MUST set default BorderBrush via a style, if you set it at all.
As an attribute on the Border tag, it would override the effects of
the trigger below.
-->
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Border.Style>
<Image Source="{Binding ImageFilePath}" />
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True">
<Setter TargetName="HighlightBorder" Property="BorderBrush" Value="Orange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

How to Bind ListViewItem's ToolTip to a ToolTip of TextBlock inside its ContentTemplate

I would like to bind my ListViewItem's ToolTip to its ContentTemplate's TextBlock's ToolTip.
I tried the following but it didnt work:
<ListView ItemsSource="{Binding DoestMatter}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip" Value="{Binding ElementName=Title, Path=ToolTip}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="Title" Text="{Binding Title}" ToolTip="Test"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The ToolTip's value is dynamically generated, here I just showed it as static string but actually it isn't, and that's why I need to bind it to the TextBlock's ToolTip.
How can I make it work?
Your code works just fine as it is... well, I had to change some Bindings to get it to work, but the main XAML is fine:
<ListView ItemsSource="{Binding Tests}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip" Value="{Binding ToolTip, ElementName=Title}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="Title" Text="{Binding Name}"
ToolTip="Test" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
However, one thing that I did notice was that it only works if you put your cursor directly over the TextBlock and that was not stretching across the width of the ListViewItem. To fix that, just set the HorizontalContentAlignment property to Stretch:
<ListView ItemsSource="{Binding Tests}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip" Value="{Binding ToolTip, ElementName=Title}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="Title" Text="{Binding Name}"
ToolTip="Test" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
UPDATE >>>
You're right... the ToolTip was still on the TextBlock. Then you just need to update your ToolTip.Binding to *the same value that works on the Text property:
<ListView ItemsSource="{Binding Tests}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip" Value="{Binding ToolTip}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock x:Name="Title" Text="{Binding Title}"
ToolTip="{Binding ToolTip}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Now you should see 2 tooltips, one when over the TextBlock and one when over the ListViewItem.
UPDATE 2 >>>
#Ron, I really cannot understand why you are taking such a negative reaction to my answer(s)... you really should watch your attitude, because I am trying to help you and I don't feel that I deserve your bad attitude. So, to address your second, even ruder comment:
I said that the HorizontalAlignment is set to Stretch by default
Really? Where did you say that? In fact, you didn't say that, you said The ListViewItem stretches on default, which is something entirely different. As mentioned in the comment, I was stretching the TextBlock inside the ListViewItem with the HorizontalContentAlignment property, which isn't set to Strecth by default.
Who said that I want the Title as my ToolTip?
Nobody said that, but you did say The ToolTip's value is dynamically generated... so I can only image that you are Binding your dynamically generated ToolTip. If this is so, then you can simply data Bind that same value to the ListViewItem.ToolTip property as well.
UPDATE 3 >>>
In response to your last comment:
I stick to my question from the beginning because I cant really explain the problem. I know whats the solution though I dont know the practical way. I want to bind to the TextBlock's ToolTip Property.
Well sorry, but you can't do that in XAML because the TextBlock is declared in a DataTemplate. You can only access DataTemplate generated elements in code because they don't just exist until runtime... see the How to: Find DataTemplate-Generated Elements page on MSDN to find out how to do that. So you'll have to find another way to achieve your goal and that's why I've been suggesting these other methods all along.

Binding the background colour of a control using a trigger in WPF/XAML

Okay, first off I have no experience of WPF whatsoever so please bear with me and apologies if my terminology is a little wayward... ;)
The following code snippet is part of a WPF application that I have inherited. The trigger governs whether mandatory fields on a particular form are highlighted or not. The code works but the highlighting seems to apply to the control and the border (??) which contains it.
<ItemsControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework"
x:Class="company.product.Jobs.JobParametersEditor"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel MinHeight="30">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"
DockPanel.Dock="Left"
VerticalAlignment="Center"
MinWidth="120"
Margin="6,0" />
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background"
Value="{x:Null}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}"
Value="False">
<Setter Property="Background"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentControl cal:View.Model="{Binding ValueEditor}"
ToolTip="{Binding ToolTip}"
IsTabStop="False"
MinHeight="19"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
</Border>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The result is a bit clunky so I would like to restrict the highlighting to the control only but I can't figure out how to do it. I've tried moving the trigger so that it applies to the ContentControl instead of the Border but that didn't work and fiddling about with border margins, padding and thickness hasn't had any effect either.
Could anybody enlighten me as to how to accomplish this?

WPF Bind to parent property from within nested element using style

I've been trying to build a text box with a hint that's displaying while it's empty.
I'm having trouble setting the hint text from within a style.
To be precise, this works (that is, it binds correctly):
<TextBox Tag="hint text">
<TextBox.Background>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=TextBox}}" FontStyle="Italic" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Background>
</TextBox>
but, when I move it to the Style, it doesn't:
<Style TargetType="TextBox" x:Key="stlHintbox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Tag="inner" Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=TextBox}}"
FontStyle="Italic" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBox Tag="hint text" Style="{StaticResource stlHintbox}" />
So what's the catch? How can I bind to ancestor property from within a style?
The problem is not with the RelativeSource but with the way you are using the VisualBrush. Recall that Styles are shared between the elements you apply them to. The reason that your example doesn't work is that, in effect you are trying to share a single textbox (the one you tagged "inner") with multiple parent textboxes.
To see why this is a problem, try a thought experiment: The inner textbox gets created once (roughly speaking, this will happen when the style is created). Which of the textboxes that the style gets applied to should be chosen as the ancestor of the inner text box when you use the RelativeSource binding?
This is why DataTemplates and ControlTemplates exist in WPF. Rather than actually instantiate visuals directly, they define a template that allow multiple copies of visuals to be created as needed.
Reativesource doesn't work as expected.
It is better to create watermark textbox using control template. But your version could work:
<Window.Resources>
<Style TargetType="TextBox" x:Key="stlHintbox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" Value="">
<Setter Property="TextBox.Background">
<Setter.Value>
<VisualBrush Stretch="None" Visual="{Binding ElementName=hintText}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Tag="hint text" x:Name="myTextBox" Style="{StaticResource stlHintbox}" />
<Border Visibility="Hidden">
<TextBlock x:Name="hintText" Text="{Binding Tag, ElementName=myTextBox}" FontStyle="Italic" Foreground="LightGray" />
</Border>
</StackPanel>

Resources